mirror of
https://github.com/stenzek/duckstation.git
synced 2026-02-05 13:47:09 +00:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d15d1e817a |
110
.clang-format
110
.clang-format
@@ -10,115 +10,7 @@ AlignTrailingComments: true
|
||||
AllowAllParametersOfDeclarationOnNextLine: true
|
||||
AllowShortBlocksOnASingleLine: false
|
||||
AllowShortCaseLabelsOnASingleLine: false
|
||||
AllowShortFunctionsOnASingleLine: InlineOnly
|
||||
AllowShortIfStatementsOnASingleLine: false
|
||||
AllowShortLoopsOnASingleLine: false
|
||||
AlwaysBreakAfterDefinitionReturnType: None
|
||||
AlwaysBreakAfterReturnType: None
|
||||
AlwaysBreakBeforeMultilineStrings: false
|
||||
AlwaysBreakTemplateDeclarations: true
|
||||
BinPackArguments: true
|
||||
BinPackParameters: true
|
||||
BraceWrapping:
|
||||
AfterCaseLabel: true
|
||||
AfterClass: true
|
||||
AfterControlStatement: true
|
||||
AfterEnum: true
|
||||
AfterFunction: true
|
||||
AfterNamespace: false
|
||||
AfterObjCDeclaration: true
|
||||
AfterStruct: true
|
||||
AfterUnion: true
|
||||
BeforeCatch: true
|
||||
BeforeElse: true
|
||||
IndentBraces: false
|
||||
SplitEmptyFunction: true
|
||||
SplitEmptyRecord: true
|
||||
SplitEmptyNamespace: true
|
||||
BreakBeforeBinaryOperators: None
|
||||
BreakBeforeBraces: Custom
|
||||
BreakBeforeInheritanceComma: false
|
||||
BreakBeforeTernaryOperators: false
|
||||
BreakConstructorInitializersBeforeComma: false
|
||||
BreakConstructorInitializers: BeforeColon
|
||||
BreakAfterJavaFieldAnnotations: false
|
||||
BreakStringLiterals: true
|
||||
BreakAfterAttributes: Leave
|
||||
ColumnLimit: 120
|
||||
CommentPragmas: '^ IWYU pragma:'
|
||||
CompactNamespaces: false
|
||||
ConstructorInitializerAllOnOneLineOrOnePerLine: false
|
||||
ConstructorInitializerIndentWidth: 2
|
||||
ContinuationIndentWidth: 2
|
||||
Cpp11BracedListStyle: true
|
||||
DerivePointerAlignment: false
|
||||
DisableFormat: false
|
||||
ExperimentalAutoDetectBinPacking: false
|
||||
FixNamespaceComments: true
|
||||
ForEachMacros:
|
||||
- foreach
|
||||
- Q_FOREACH
|
||||
- BOOST_FOREACH
|
||||
IncludeCategories:
|
||||
- Regex: '^"(llvm|llvm-c|clang|clang-c)/'
|
||||
Priority: 2
|
||||
- Regex: '^(<|"(gtest|gmock|isl|json)/)'
|
||||
Priority: 3
|
||||
- Regex: '.*'
|
||||
Priority: 1
|
||||
IncludeIsMainRegex: '(Test)?$'
|
||||
IndentCaseLabels: true
|
||||
IndentWidth: 2
|
||||
IndentWrappedFunctionNames: false
|
||||
JavaScriptQuotes: Leave
|
||||
JavaScriptWrapImports: true
|
||||
KeepEmptyLinesAtTheStartOfBlocks: true
|
||||
MacroBlockBegin: ''
|
||||
MacroBlockEnd: ''
|
||||
MaxEmptyLinesToKeep: 1
|
||||
NamespaceIndentation: None
|
||||
ObjCBlockIndentWidth: 2
|
||||
ObjCSpaceAfterProperty: false
|
||||
ObjCSpaceBeforeProtocolList: true
|
||||
PenaltyBreakAssignment: 2
|
||||
PenaltyBreakBeforeFirstCallParameter: 19
|
||||
PenaltyBreakComment: 300
|
||||
PenaltyBreakFirstLessLess: 120
|
||||
PenaltyBreakString: 1000
|
||||
PenaltyExcessCharacter: 1000000
|
||||
PenaltyReturnTypeOnItsOwnLine: 60
|
||||
PointerAlignment: Left
|
||||
ReflowComments: true
|
||||
SortIncludes: true
|
||||
SortUsingDeclarations: true
|
||||
SpaceAfterCStyleCast: false
|
||||
SpaceAfterTemplateKeyword: false
|
||||
SpaceBeforeAssignmentOperators: true
|
||||
SpaceBeforeParens: ControlStatements
|
||||
SpaceInEmptyParentheses: false
|
||||
SpacesBeforeTrailingComments: 1
|
||||
SpacesInAngles: false
|
||||
SpacesInContainerLiterals: true
|
||||
SpacesInCStyleCastParentheses: false
|
||||
SpacesInParentheses: false
|
||||
SpacesInSquareBrackets: false
|
||||
Standard: Cpp11
|
||||
TabWidth: 2
|
||||
UseTab: Never
|
||||
...
|
||||
---
|
||||
Language: ObjC
|
||||
AccessModifierOffset: -2
|
||||
AlignAfterOpenBracket: Align
|
||||
AlignConsecutiveAssignments: false
|
||||
AlignConsecutiveDeclarations: false
|
||||
AlignEscapedNewlines: Right
|
||||
AlignOperands: true
|
||||
AlignTrailingComments: true
|
||||
AllowAllParametersOfDeclarationOnNextLine: true
|
||||
AllowShortBlocksOnASingleLine: false
|
||||
AllowShortCaseLabelsOnASingleLine: false
|
||||
AllowShortFunctionsOnASingleLine: InlineOnly
|
||||
AllowShortFunctionsOnASingleLine: Inline
|
||||
AllowShortIfStatementsOnASingleLine: false
|
||||
AllowShortLoopsOnASingleLine: false
|
||||
AlwaysBreakAfterDefinitionReturnType: None
|
||||
|
||||
45
.github/workflows/gamedb-lint.yml
vendored
45
.github/workflows/gamedb-lint.yml
vendored
@@ -1,45 +0,0 @@
|
||||
name: GameDB Lint
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- 'data/resources/gamedb.yaml'
|
||||
- 'data/resources/discsets.yaml'
|
||||
- 'data/resources/discdb.yaml'
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- dev
|
||||
paths:
|
||||
- 'data/resources/gamedb.yaml'
|
||||
- 'data/resources/discsets.yaml'
|
||||
- 'data/resources/discdb.yaml'
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
gamedb-lint:
|
||||
runs-on: ubuntu-slim
|
||||
timeout-minutes: 120
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Install Packages
|
||||
shell: bash
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get -y install yamllint
|
||||
|
||||
- name: Check GameDB
|
||||
shell: bash
|
||||
run: yamllint -c extras/yamllint-config.yaml -s -f github data/resources/gamedb.yaml
|
||||
|
||||
- name: Check Disc Sets
|
||||
shell: bash
|
||||
run: yamllint -c extras/yamllint-config.yaml -s -f github data/resources/discsets.yaml
|
||||
|
||||
- name: Check DiscDB
|
||||
shell: bash
|
||||
run: yamllint -c extras/yamllint-config.yaml -s -f github data/resources/discdb.yaml
|
||||
84
.github/workflows/linux-appimage-build.yml
vendored
84
.github/workflows/linux-appimage-build.yml
vendored
@@ -1,84 +0,0 @@
|
||||
name: 🐧 Linux AppImage
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
linux-appimage-build:
|
||||
name: "${{ matrix.name }}"
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 240
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- name: "x64"
|
||||
asset: "DuckStation-x64.AppImage"
|
||||
artifact: "linux-x64-appimage"
|
||||
cmakeoptions: ""
|
||||
- name: "x64 SSE2"
|
||||
asset: "DuckStation-x64-SSE2.AppImage"
|
||||
artifact: "linux-x64-sse2-appimage"
|
||||
cmakeoptions: "-DDISABLE_SSE4=ON"
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Install Packages
|
||||
run: scripts/packaging/appimage/install-packages.sh
|
||||
|
||||
- name: Cache Dependencies
|
||||
id: cache-deps
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: ~/deps
|
||||
key: deps ${{ hashFiles('scripts/deps/build-dependencies-linux.sh', 'scripts/deps/build-ffmpeg-linux.sh', 'scripts/deps/versions') }}
|
||||
|
||||
- name: Build Dependencies
|
||||
if: steps.cache-deps.outputs.cache-hit != 'true'
|
||||
run: scripts/deps/build-dependencies-linux.sh "$HOME/deps"
|
||||
|
||||
- name: Build FFmpeg
|
||||
if: steps.cache-deps.outputs.cache-hit != 'true'
|
||||
run: scripts/deps/build-ffmpeg-linux.sh "$HOME/deps"
|
||||
|
||||
- name: Tag as Preview Release
|
||||
if: github.ref == 'refs/heads/master'
|
||||
run: |
|
||||
echo '#pragma once' > src/scmversion/tag.h
|
||||
echo '#define UPDATER_RELEASE_CHANNEL "preview"' >> src/scmversion/tag.h
|
||||
echo '#define UPDATER_RELEASE_IS_OFFICIAL 1' >> src/scmversion/tag.h
|
||||
|
||||
- name: Tag as Stable Release
|
||||
if: github.ref == 'refs/heads/dev'
|
||||
run: |
|
||||
echo '#pragma once' > src/scmversion/tag.h
|
||||
echo '#define UPDATER_RELEASE_CHANNEL "latest"' >> src/scmversion/tag.h
|
||||
echo '#define UPDATER_RELEASE_IS_OFFICIAL 1' >> src/scmversion/tag.h
|
||||
|
||||
- name: Download Patch Archives
|
||||
shell: bash
|
||||
run: |
|
||||
cd data/resources
|
||||
curl --retry 5 --retry-all-errors -LO "https://github.com/duckstation/chtdb/releases/download/latest/cheats.zip"
|
||||
curl --retry 5 --retry-all-errors -LO "https://github.com/duckstation/chtdb/releases/download/latest/patches.zip"
|
||||
|
||||
- name: Compile Build
|
||||
shell: bash
|
||||
run: |
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -G Ninja -DCMAKE_BUILD_TYPE=Release ${{ matrix.cmakeoptions }} -DCMAKE_INTERPROCEDURAL_OPTIMIZATION=ON -DCMAKE_PREFIX_PATH="$HOME/deps" -DCMAKE_C_COMPILER=clang-19 -DCMAKE_CXX_COMPILER=clang++-19 -DCMAKE_EXE_LINKER_FLAGS_INIT="-fuse-ld=lld" -DCMAKE_MODULE_LINKER_FLAGS_INIT="-fuse-ld=lld" -DCMAKE_SHARED_LINKER_FLAGS_INIT="-fuse-ld=lld" ..
|
||||
cmake --build . --parallel
|
||||
cd ..
|
||||
scripts/packaging/appimage/make-appimage.sh $(realpath .) $(realpath ./build) $HOME/deps "${{ matrix.asset }}"
|
||||
|
||||
- name: Upload AppImage
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: "${{ matrix.artifact }}"
|
||||
path: "${{ matrix.asset }}"
|
||||
38
.github/workflows/linux-build.yml.disabled
vendored
Normal file
38
.github/workflows/linux-build.yml.disabled
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
name: Linux Build
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
|
||||
- name: Install packages
|
||||
shell: bash
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get -y install libsdl2-dev libgtk2.0-dev
|
||||
|
||||
- name: Compile debug build
|
||||
shell: bash
|
||||
run: |
|
||||
mkdir build-debug
|
||||
cd build-debug
|
||||
cmake -DCMAKE_BUILD_TYPE=Debug -DBUILD_QT_FRONTEND=OFF ..
|
||||
make
|
||||
|
||||
- name: Compile release build
|
||||
shell: bash
|
||||
run: |
|
||||
mkdir build-release
|
||||
cd build-release
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_QT_FRONTEND=OFF ..
|
||||
make
|
||||
|
||||
100
.github/workflows/linux-cross-appimage-build.yml
vendored
100
.github/workflows/linux-cross-appimage-build.yml
vendored
@@ -1,100 +0,0 @@
|
||||
name: 🐧 Linux Cross-Compiled AppImage
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: "${{ matrix.arch }}"
|
||||
strategy:
|
||||
fail-fast: true
|
||||
matrix:
|
||||
arch: ["arm64", "armhf"]
|
||||
|
||||
runs-on: ubuntu-22.04
|
||||
container:
|
||||
image: ghcr.io/duckstation/cross-build-${{ matrix.arch }}:latest
|
||||
|
||||
timeout-minutes: 240
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Cache Dependencies
|
||||
id: cache-deps
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: ~/deps
|
||||
key: deps-cross ${{ matrix.arch }} ${{ hashFiles('scripts/deps/build-dependencies-linux.sh', 'scripts/deps/build-dependencies-linux-cross.sh', 'scripts/deps/versions') }}
|
||||
|
||||
- name: Build Dependencies
|
||||
if: steps.cache-deps.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
scripts/deps/build-dependencies-linux.sh -skip-cleanup "$HOME/deps/host"
|
||||
scripts/deps/build-dependencies-linux-cross.sh -skip-download "$HOME/deps/host" "${{ matrix.arch }}" "/${{ matrix.arch }}-chroot" "$HOME/deps/cross"
|
||||
|
||||
# Work around container ownership issue
|
||||
- name: Set Safe Directory
|
||||
shell: bash
|
||||
run: git config --global --add safe.directory "*"
|
||||
|
||||
- name: Create Binary Aliases
|
||||
run: |
|
||||
ln -s llvm-strip-19 /usr/bin/llvm-strip
|
||||
|
||||
- name: Set Up Toolchain File
|
||||
run: |
|
||||
cp "$HOME/deps/cross/toolchain.cmake" "$HOME/toolchain.cmake"
|
||||
echo 'set(CMAKE_C_COMPILER clang-19)' >> "$HOME/toolchain.cmake"
|
||||
echo 'set(CMAKE_C_COMPILER_AR llvm-ar-19)' >> "$HOME/toolchain.cmake"
|
||||
echo 'set(CMAKE_C_COMPILER_RANLIB llvm-ranlib-19)' >> "$HOME/toolchain.cmake"
|
||||
echo 'set(CMAKE_CXX_COMPILER clang++-19)' >> "$HOME/toolchain.cmake"
|
||||
echo 'set(CMAKE_CXX_COMPILER_AR llvm-ar-19)' >> "$HOME/toolchain.cmake"
|
||||
echo 'set(CMAKE_CXX_COMPILER_RANLIB llvm-ranlib-19)' >> "$HOME/toolchain.cmake"
|
||||
echo 'set(CMAKE_EXE_LINKER_FLAGS_INIT "-fuse-ld=lld")' >> "$HOME/toolchain.cmake"
|
||||
echo 'set(CMAKE_MODULE_LINKER_FLAGS_INIT "-fuse-ld=lld")' >> "$HOME/toolchain.cmake"
|
||||
echo 'set(CMAKE_SHARED_LINKER_FLAGS_INIT "-fuse-ld=lld")' >> "$HOME/toolchain.cmake"
|
||||
|
||||
- name: Tag as Preview Release
|
||||
if: github.ref == 'refs/heads/master'
|
||||
run: |
|
||||
echo '#pragma once' > src/scmversion/tag.h
|
||||
echo '#define UPDATER_RELEASE_CHANNEL "preview"' >> src/scmversion/tag.h
|
||||
echo '#define UPDATER_RELEASE_IS_OFFICIAL 1' >> src/scmversion/tag.h
|
||||
|
||||
- name: Tag as Stable Release
|
||||
if: github.ref == 'refs/heads/dev'
|
||||
run: |
|
||||
echo '#pragma once' > src/scmversion/tag.h
|
||||
echo '#define UPDATER_RELEASE_CHANNEL "latest"' >> src/scmversion/tag.h
|
||||
echo '#define UPDATER_RELEASE_IS_OFFICIAL 1' >> src/scmversion/tag.h
|
||||
|
||||
- name: Download Patch Archives
|
||||
shell: bash
|
||||
run: |
|
||||
cd data/resources
|
||||
curl --retry 5 --retry-all-errors -LO "https://github.com/duckstation/chtdb/releases/download/latest/cheats.zip"
|
||||
curl --retry 5 --retry-all-errors -LO "https://github.com/duckstation/chtdb/releases/download/latest/patches.zip"
|
||||
|
||||
- name: Generate CMake
|
||||
shell: bash
|
||||
run: |
|
||||
cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_INTERPROCEDURAL_OPTIMIZATION=ON -DCMAKE_TOOLCHAIN_FILE="$HOME/toolchain.cmake" -DCMAKE_PREFIX_PATH="$HOME/deps/cross" -DLCONVERT_EXE="$HOME/deps/host/bin/lconvert" -DHOST_MIN_PAGE_SIZE=4096 -DHOST_MAX_PAGE_SIZE=16384 -DHOST_CACHE_LINE_SIZE=64 -DBUILD_QT_FRONTEND=ON -DBUILD_MINI_FRONTEND=ON
|
||||
|
||||
- name: Compile Build
|
||||
shell: bash
|
||||
run: |
|
||||
cmake --build build --parallel
|
||||
scripts/packaging/appimage/make-cross-appimage.sh duckstation-qt ${{ matrix.arch }} "$(realpath build)" "$HOME/deps/cross" "/${{ matrix.arch }}-chroot"
|
||||
scripts/packaging/appimage/make-cross-appimage.sh -inject-libc duckstation-mini ${{ matrix.arch }} "$(realpath build)" "$HOME/deps/cross" "/${{ matrix.arch }}-chroot"
|
||||
|
||||
- name: Upload AppImages
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: "linux-${{ matrix.arch }}-appimage"
|
||||
path: "DuckStation-*.AppImage"
|
||||
74
.github/workflows/macos-build.yml
vendored
74
.github/workflows/macos-build.yml
vendored
@@ -1,74 +0,0 @@
|
||||
name: 🍎 MacOS
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
macos-build:
|
||||
name: "Universal"
|
||||
runs-on: macos-15
|
||||
timeout-minutes: 240
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Use Xcode 26
|
||||
run: sudo xcode-select -s /Applications/Xcode_26.0.app
|
||||
|
||||
- name: Download Metal Toolchain
|
||||
run: xcodebuild -downloadComponent MetalToolchain
|
||||
|
||||
- name: Cache Dependencies
|
||||
id: cache-deps-mac
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: ~/deps
|
||||
key: deps-mac ${{ hashFiles('scripts/deps/build-dependencies-mac.sh', 'scripts/deps/versions') }}
|
||||
|
||||
- name: Build Dependencies
|
||||
if: steps.cache-deps-mac.outputs.cache-hit != 'true'
|
||||
run: scripts/deps/build-dependencies-mac.sh "$HOME/deps"
|
||||
|
||||
- name: Tag as Preview Release
|
||||
if: github.ref == 'refs/heads/master'
|
||||
run: |
|
||||
echo '#pragma once' > src/scmversion/tag.h
|
||||
echo '#define UPDATER_RELEASE_CHANNEL "preview"' >> src/scmversion/tag.h
|
||||
echo '#define UPDATER_RELEASE_IS_OFFICIAL 1' >> src/scmversion/tag.h
|
||||
|
||||
- name: Tag as Stable Release
|
||||
if: github.ref == 'refs/heads/dev'
|
||||
run: |
|
||||
echo '#pragma once' > src/scmversion/tag.h
|
||||
echo '#define UPDATER_RELEASE_CHANNEL "latest"' >> src/scmversion/tag.h
|
||||
echo '#define UPDATER_RELEASE_IS_OFFICIAL 1' >> src/scmversion/tag.h
|
||||
|
||||
- name: Download Patch Archives
|
||||
shell: bash
|
||||
run: |
|
||||
cd data/resources
|
||||
curl --retry 5 --retry-all-errors -LO "https://github.com/duckstation/chtdb/releases/download/latest/cheats.zip"
|
||||
curl --retry 5 --retry-all-errors -LO "https://github.com/duckstation/chtdb/releases/download/latest/patches.zip"
|
||||
|
||||
- name: Compile and Zip .app
|
||||
shell: bash
|
||||
run: |
|
||||
mkdir build
|
||||
cd build
|
||||
export MACOSX_DEPLOYMENT_TARGET=13.3
|
||||
cmake -DCMAKE_OSX_ARCHITECTURES="x86_64;arm64" -DCMAKE_BUILD_TYPE=Release -DENABLE_OPENGL=OFF -DCMAKE_PREFIX_PATH="$HOME/deps" -DCMAKE_INTERPROCEDURAL_OPTIMIZATION=ON -G Ninja ..
|
||||
cmake --build . --parallel
|
||||
mv bin/DuckStation.app .
|
||||
codesign -s - --deep -f -v DuckStation.app
|
||||
zip -9 -r duckstation-mac-release.zip DuckStation.app/
|
||||
|
||||
- name: Upload MacOS .app
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: "macos"
|
||||
path: "build/duckstation-mac-release.zip"
|
||||
103
.github/workflows/main.yml
vendored
103
.github/workflows/main.yml
vendored
@@ -1,103 +0,0 @@
|
||||
name: Automated Builds
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
pull_request:
|
||||
paths-ignore:
|
||||
- '**.md'
|
||||
- 'appveyor.yml'
|
||||
- 'extras/yamllint-config.yaml'
|
||||
- 'scripts/*'
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- dev
|
||||
paths-ignore:
|
||||
- '**.md'
|
||||
- 'appveyor.yml'
|
||||
- 'extras/yamllint-config.yaml'
|
||||
- 'scripts/*'
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
windows:
|
||||
name: 💻 Windows
|
||||
uses: "./.github/workflows/windows-build.yml"
|
||||
linux-appimage:
|
||||
name: 🐧 Linux AppImage
|
||||
uses: "./.github/workflows/linux-appimage-build.yml"
|
||||
linux-cross-appimage:
|
||||
name: 🐧 Linux Cross-Compiled AppImage
|
||||
uses: "./.github/workflows/linux-cross-appimage-build.yml"
|
||||
macos:
|
||||
name: 🍎 MacOS
|
||||
uses: "./.github/workflows/macos-build.yml"
|
||||
|
||||
create-release:
|
||||
name: 📤 Create Release
|
||||
needs: [windows, linux-appimage, linux-cross-appimage, macos]
|
||||
runs-on: ubuntu-22.04
|
||||
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/dev'
|
||||
steps:
|
||||
- name: Download Artifacts
|
||||
uses: actions/download-artifact@v7
|
||||
with:
|
||||
path: ./artifacts/
|
||||
merge-multiple: true
|
||||
|
||||
- name: Display Downloaded Artifacts
|
||||
run: find ./artifacts/ -type f | sort
|
||||
|
||||
- name: Create Preview Release
|
||||
if: github.ref == 'refs/heads/master'
|
||||
uses: marvinpinto/action-automatic-releases@d68defdd11f9dcc7f52f35c1b7c236ee7513bcc1
|
||||
with:
|
||||
repo_token: "${{ secrets.GITHUB_TOKEN }}"
|
||||
automatic_release_tag: "preview"
|
||||
prerelease: true
|
||||
title: "Latest Preview Build"
|
||||
files: |
|
||||
./artifacts/duckstation-windows-x64-installer.exe
|
||||
./artifacts/duckstation-windows-x64-release.zip
|
||||
./artifacts/duckstation-windows-x64-release-symbols.zip
|
||||
./artifacts/duckstation-windows-x64-sse2-installer.exe
|
||||
./artifacts/duckstation-windows-x64-sse2-release.zip
|
||||
./artifacts/duckstation-windows-x64-sse2-release-symbols.zip
|
||||
./artifacts/duckstation-windows-arm64-installer.exe
|
||||
./artifacts/duckstation-windows-arm64-release.zip
|
||||
./artifacts/duckstation-windows-arm64-release-symbols.zip
|
||||
./artifacts/DuckStation-x64.AppImage
|
||||
./artifacts/DuckStation-x64-SSE2.AppImage
|
||||
./artifacts/DuckStation-arm64.AppImage
|
||||
./artifacts/DuckStation-Mini-arm64.AppImage
|
||||
./artifacts/DuckStation-armhf.AppImage
|
||||
./artifacts/DuckStation-Mini-armhf.AppImage
|
||||
./artifacts/duckstation-mac-release.zip
|
||||
|
||||
- name: Create Rolling Release
|
||||
if: github.ref == 'refs/heads/dev'
|
||||
uses: marvinpinto/action-automatic-releases@d68defdd11f9dcc7f52f35c1b7c236ee7513bcc1
|
||||
with:
|
||||
repo_token: "${{ secrets.GITHUB_TOKEN }}"
|
||||
automatic_release_tag: "latest"
|
||||
prerelease: false
|
||||
title: "Latest Rolling Release"
|
||||
files: |
|
||||
./artifacts/duckstation-windows-x64-installer.exe
|
||||
./artifacts/duckstation-windows-x64-release.zip
|
||||
./artifacts/duckstation-windows-x64-release-symbols.zip
|
||||
./artifacts/duckstation-windows-x64-sse2-installer.exe
|
||||
./artifacts/duckstation-windows-x64-sse2-release.zip
|
||||
./artifacts/duckstation-windows-x64-sse2-release-symbols.zip
|
||||
./artifacts/duckstation-windows-arm64-installer.exe
|
||||
./artifacts/duckstation-windows-arm64-release.zip
|
||||
./artifacts/duckstation-windows-arm64-release-symbols.zip
|
||||
./artifacts/DuckStation-x64.AppImage
|
||||
./artifacts/DuckStation-x64-SSE2.AppImage
|
||||
./artifacts/DuckStation-arm64.AppImage
|
||||
./artifacts/DuckStation-Mini-arm64.AppImage
|
||||
./artifacts/DuckStation-armhf.AppImage
|
||||
./artifacts/DuckStation-Mini-armhf.AppImage
|
||||
./artifacts/duckstation-mac-release.zip
|
||||
354
.github/workflows/rolling-release.yml
vendored
Normal file
354
.github/workflows/rolling-release.yml
vendored
Normal file
@@ -0,0 +1,354 @@
|
||||
name: Create rolling release
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths-ignore:
|
||||
- '**.md'
|
||||
- 'appveyor.yml'
|
||||
- 'scripts/*'
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
paths-ignore:
|
||||
- '**.md'
|
||||
- 'appveyor.yml'
|
||||
- 'scripts/*'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
windows-build:
|
||||
runs-on: windows-2019
|
||||
steps:
|
||||
- uses: actions/checkout@v2.3.1
|
||||
with:
|
||||
fetch-depth: 0
|
||||
submodules: true
|
||||
|
||||
- name: Tag as release build
|
||||
shell: cmd
|
||||
run: |
|
||||
echo #pragma once > src/scmversion/tag.h
|
||||
echo #define SCM_RELEASE_TAG "latest" >> src/scmversion/tag.h
|
||||
echo #define SCM_RELEASE_ASSET "duckstation-windows-x64-release.zip" >> src/scmversion/tag.h
|
||||
|
||||
- name: Compile release build
|
||||
shell: cmd
|
||||
run: |
|
||||
call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Auxiliary\Build\vcvarsall.bat" x64
|
||||
msbuild duckstation.sln -t:Build -p:Platform=x64;Configuration=ReleaseLTCG
|
||||
|
||||
- name: Remove extra bloat before archiving
|
||||
shell: cmd
|
||||
run: |
|
||||
del /Q bin\x64\*.pdb
|
||||
del /Q bin\x64\*.exp
|
||||
del /Q bin\x64\*.lib
|
||||
del /Q bin\x64\*.iobj
|
||||
del /Q bin\x64\*.ipdb
|
||||
del /Q bin\x64\common-tests*
|
||||
del /Q bin\x64\duckstation-libretro-*
|
||||
rename bin\x64\updater-x64-ReleaseLTCG.exe updater.exe
|
||||
|
||||
- name: Create release archive
|
||||
shell: cmd
|
||||
run: |
|
||||
"C:\Program Files\7-Zip\7z.exe" a -r duckstation-windows-x64-release.zip ./bin/x64/*
|
||||
|
||||
- name: Upload release artifact
|
||||
uses: actions/upload-artifact@v1
|
||||
with:
|
||||
name: "windows-x64"
|
||||
path: "duckstation-windows-x64-release.zip"
|
||||
|
||||
|
||||
windows-libretro-build:
|
||||
runs-on: windows-2019
|
||||
steps:
|
||||
- uses: actions/checkout@v2.3.1
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Compile release build
|
||||
shell: cmd
|
||||
run: |
|
||||
call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Auxiliary\Build\vcvarsall.bat" x64
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -G Ninja -DCMAKE_BUILD_TYPE=Release -DBUILD_LIBRETRO_CORE=ON -DCMAKE_C_COMPILER:FILEPATH="%VCToolsInstallDir%\bin\HostX64\x64\cl.exe" -DCMAKE_CXX_COMPILER:FILEPATH="%VCToolsInstallDir%\bin\HostX64\x64\cl.exe" ..
|
||||
ninja
|
||||
|
||||
- name: Create libretro core archive
|
||||
shell: cmd
|
||||
run: |
|
||||
cd build
|
||||
"C:\Program Files\7-Zip\7z.exe" a -r duckstation_libretro.dll.zip ./duckstation_libretro.dll
|
||||
|
||||
- name: Upload release artifact
|
||||
uses: actions/upload-artifact@v1
|
||||
with:
|
||||
name: "windows-libretro-x64"
|
||||
path: "build/duckstation_libretro.dll.zip"
|
||||
|
||||
|
||||
linux-build:
|
||||
runs-on: ubuntu-18.04
|
||||
steps:
|
||||
- uses: actions/checkout@v2.3.1
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Install packages
|
||||
shell: bash
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get -y install cmake ninja-build ccache libsdl2-dev libgtk2.0-dev qtbase5-dev qtbase5-dev-tools qtbase5-private-dev qt5-default qttools5-dev
|
||||
|
||||
- name: Compile build
|
||||
shell: bash
|
||||
run: |
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_SDL_FRONTEND=ON -DBUILD_QT_FRONTEND=ON -DUSE_SDL2=ON -G Ninja ..
|
||||
ninja
|
||||
../appimage/generate_appimages.sh $(pwd)
|
||||
|
||||
- name: Upload SDL AppImage
|
||||
uses: actions/upload-artifact@v1
|
||||
with:
|
||||
name: "linux-x64-appimage-sdl"
|
||||
path: "build/duckstation-sdl-x64.AppImage"
|
||||
|
||||
- name: Upload SDL AppImage zsync
|
||||
uses: actions/upload-artifact@v1
|
||||
with:
|
||||
name: "linux-x64-appimage-sdl-zsync"
|
||||
path: "build/duckstation-sdl-x64.AppImage.zsync"
|
||||
|
||||
- name: Upload Qt AppImage
|
||||
uses: actions/upload-artifact@v1
|
||||
with:
|
||||
name: "linux-x64-appimage-qt"
|
||||
path: "build/duckstation-qt-x64.AppImage"
|
||||
|
||||
- name: Upload Qt AppImage zsync
|
||||
uses: actions/upload-artifact@v1
|
||||
with:
|
||||
name: "linux-x64-appimage-qt-zsync"
|
||||
path: "build/duckstation-qt-x64.AppImage.zsync"
|
||||
|
||||
|
||||
linux-libretro-build:
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@v2.3.1
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Install packages
|
||||
shell: bash
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get -y install gcc-aarch64-linux-gnu g++-aarch64-linux-gnu
|
||||
|
||||
- name: Compile and zip Linux x64 libretro core
|
||||
shell: bash
|
||||
run: |
|
||||
mkdir build-libretro-linux-x64
|
||||
cd build-libretro-linux-x64
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_LIBRETRO_CORE=ON ..
|
||||
cmake --build . --parallel 2
|
||||
zip -j duckstation_libretro_x64.so.zip duckstation_libretro.so
|
||||
|
||||
- name: Upload Linux x64 libretro core
|
||||
uses: actions/upload-artifact@v1
|
||||
with:
|
||||
name: "linux-libretro"
|
||||
path: "build-libretro-linux-x64/duckstation_libretro_x64.so.zip"
|
||||
|
||||
- name: Compile and zip Linux AArch64 libretro core
|
||||
shell: bash
|
||||
run: |
|
||||
mkdir build-libretro-linux-aarch64
|
||||
cd build-libretro-linux-aarch64
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_LIBRETRO_CORE=ON -DCMAKE_TOOLCHAIN_FILE=../CMakeModules/aarch64-cross-toolchain.cmake ..
|
||||
cmake --build . --parallel 2
|
||||
zip -j duckstation_libretro_linux_aarch64.so.zip duckstation_libretro.so
|
||||
|
||||
- name: Upload Linux AArch64 libretro core
|
||||
uses: actions/upload-artifact@v1
|
||||
with:
|
||||
name: "linux-libretro"
|
||||
path: "build-libretro-linux-aarch64/duckstation_libretro_linux_aarch64.so.zip"
|
||||
|
||||
- name: Compile and zip Android AArch64 libretro core
|
||||
shell: bash
|
||||
run: |
|
||||
mkdir build-libretro-android-aarch64
|
||||
cd build-libretro-android-aarch64
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_LIBRETRO_CORE=ON -DANDROID_ABI=arm64-v8a -DCMAKE_TOOLCHAIN_FILE=${ANDROID_SDK_ROOT}/ndk-bundle/build/cmake/android.toolchain.cmake ..
|
||||
cmake --build . --parallel 2
|
||||
zip -j duckstation_libretro_android_aarch64.so.zip duckstation_libretro_android.so
|
||||
|
||||
- name: Upload Android AArch64 libretro core
|
||||
uses: actions/upload-artifact@v1
|
||||
with:
|
||||
name: "linux-libretro"
|
||||
path: "build-libretro-android-aarch64/duckstation_libretro_android_aarch64.so.zip"
|
||||
|
||||
|
||||
android-build:
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@v2.3.1
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Compile with Gradle
|
||||
shell: bash
|
||||
run: |
|
||||
cd android
|
||||
./gradlew assembleRelease
|
||||
|
||||
- name: Sign APK
|
||||
if: github.ref == 'refs/heads/master'
|
||||
uses: r0adkll/sign-android-release@v1
|
||||
with:
|
||||
releaseDirectory: android/app/build/outputs/apk/release
|
||||
signingKeyBase64: ${{ secrets.APK_SIGNING_KEY }}
|
||||
alias: ${{ secrets.APK_KEY_ALIAS }}
|
||||
keyStorePassword: ${{ secrets.APK_KEY_STORE_PASSWORD }}
|
||||
keyPassword: ${{ secrets.APK_KEY_PASSWORD }}
|
||||
|
||||
- name: Rename APK
|
||||
if: github.ref == 'refs/heads/master'
|
||||
shell: bash
|
||||
run: |
|
||||
cd android
|
||||
mv app/build/outputs/apk/release/app-release-unsigned-signed.apk ../duckstation-android-aarch64.apk
|
||||
|
||||
- name: Upload APK
|
||||
if: github.ref == 'refs/heads/master'
|
||||
uses: actions/upload-artifact@v1
|
||||
with:
|
||||
name: "android"
|
||||
path: "duckstation-android-aarch64.apk"
|
||||
|
||||
macos-build:
|
||||
runs-on: macos-10.15
|
||||
steps:
|
||||
- uses: actions/checkout@v2.3.1
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Install packages
|
||||
shell: bash
|
||||
run: |
|
||||
brew install qt5 sdl2
|
||||
|
||||
- name: Clone mac externals
|
||||
shell: bash
|
||||
run: |
|
||||
git clone https://github.com/stenzek/duckstation-ext-mac.git dep/mac
|
||||
|
||||
- name: Compile and zip .app
|
||||
shell: bash
|
||||
run: |
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_SDL_FRONTEND=OFF -DBUILD_QT_FRONTEND=ON -DUSE_SDL2=ON -DQt5_DIR=/usr/local/opt/qt/lib/cmake/Qt5 ..
|
||||
cmake --build . --parallel 2
|
||||
cd bin
|
||||
zip -r duckstation-mac-release.zip DuckStation.app/
|
||||
|
||||
- name: Upload macOS .app
|
||||
uses: actions/upload-artifact@v1
|
||||
with:
|
||||
name: "macos-x64"
|
||||
path: "build/bin/duckstation-mac-release.zip"
|
||||
|
||||
- name: Compile libretro core and zip
|
||||
shell: bash
|
||||
run: |
|
||||
mkdir build-libretro
|
||||
cd build-libretro
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_BUILD_TYPE=Release -DBUILD_LIBRETRO_CORE=ON ..
|
||||
cmake --build . --parallel 2
|
||||
zip -j duckstation_libretro_mac.dylib.zip duckstation_libretro.dylib
|
||||
|
||||
- name: Upload macOS libretro core
|
||||
uses: actions/upload-artifact@v1
|
||||
with:
|
||||
name: "macos-x64"
|
||||
path: "build-libretro/duckstation_libretro_mac.dylib.zip"
|
||||
|
||||
|
||||
create-release:
|
||||
needs: [windows-build, windows-libretro-build, linux-build, linux-libretro-build, android-build, macos-build]
|
||||
runs-on: "ubuntu-latest"
|
||||
if: github.ref == 'refs/heads/master'
|
||||
steps:
|
||||
- name: Download Windows x64 Artifact
|
||||
uses: actions/download-artifact@v1
|
||||
with:
|
||||
name: "windows-x64"
|
||||
|
||||
- name: Download Windows libretro x64 Artifact
|
||||
uses: actions/download-artifact@v1
|
||||
with:
|
||||
name: "windows-libretro-x64"
|
||||
|
||||
- name: Download SDL AppImage Artifact
|
||||
uses: actions/download-artifact@v1
|
||||
with:
|
||||
name: "linux-x64-appimage-sdl"
|
||||
|
||||
- name: Download SDL AppImage zsync Artifact
|
||||
uses: actions/download-artifact@v1
|
||||
with:
|
||||
name: "linux-x64-appimage-sdl-zsync"
|
||||
|
||||
- name: Download Qt AppImage Artifact
|
||||
uses: actions/download-artifact@v1
|
||||
with:
|
||||
name: "linux-x64-appimage-qt"
|
||||
|
||||
- name: Download Qt AppImage zsync Artifact
|
||||
uses: actions/download-artifact@v1
|
||||
with:
|
||||
name: "linux-x64-appimage-qt-zsync"
|
||||
|
||||
- name: Download Linux libretro core
|
||||
uses: actions/download-artifact@v1
|
||||
with:
|
||||
name: "linux-libretro"
|
||||
|
||||
- name: Download Android APK
|
||||
uses: actions/download-artifact@v1
|
||||
with:
|
||||
name: "android"
|
||||
|
||||
- name: Download Mac App
|
||||
uses: actions/download-artifact@v1
|
||||
with:
|
||||
name: "macos-x64"
|
||||
|
||||
- name: Create release
|
||||
uses: "marvinpinto/action-automatic-releases@latest"
|
||||
with:
|
||||
repo_token: "${{ secrets.GITHUB_TOKEN }}"
|
||||
automatic_release_tag: "latest"
|
||||
title: "Latest Development Build"
|
||||
files: |
|
||||
windows-x64/duckstation-windows-x64-release.zip
|
||||
windows-libretro-x64/duckstation_libretro.dll.zip
|
||||
linux-x64-appimage-sdl/duckstation-sdl-x64.AppImage
|
||||
linux-x64-appimage-sdl-zsync/duckstation-sdl-x64.AppImage.zsync
|
||||
linux-x64-appimage-qt/duckstation-qt-x64.AppImage
|
||||
linux-x64-appimage-qt-zsync/duckstation-qt-x64.AppImage.zsync
|
||||
linux-libretro/duckstation_libretro_x64.so.zip
|
||||
linux-libretro/duckstation_libretro_linux_aarch64.so.zip
|
||||
linux-libretro/duckstation_libretro_android_aarch64.so.zip
|
||||
android/duckstation-android-aarch64.apk
|
||||
macos-x64/duckstation-mac-release.zip
|
||||
macos-x64/duckstation_libretro_mac.dylib.zip
|
||||
|
||||
31
.github/workflows/translation-lint.yml
vendored
31
.github/workflows/translation-lint.yml
vendored
@@ -1,31 +0,0 @@
|
||||
name: Translation Lint
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- 'src/duckstation-qt/translations/*.ts'
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- dev
|
||||
paths:
|
||||
- 'src/duckstation-qt/translations/*.ts'
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
translation-lint:
|
||||
runs-on: ubuntu-slim
|
||||
timeout-minutes: 120
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
# Meh, can't be bothered to work out exactly which one was modified, just check them all.
|
||||
- name: Check Translation Placeholders
|
||||
shell: bash
|
||||
run: |
|
||||
for i in src/duckstation-qt/translations/*.ts; do
|
||||
python scripts/verify_translation_placeholders.py "$i"
|
||||
done
|
||||
39
.github/workflows/upload-caches.yml
vendored
39
.github/workflows/upload-caches.yml
vendored
@@ -1,39 +0,0 @@
|
||||
name: Upload Caches
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
upload-windows-cache:
|
||||
runs-on: windows-2022
|
||||
timeout-minutes: 120
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Cache Dependencies
|
||||
id: cache-deps
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: |
|
||||
dep/msvc/deps-arm64
|
||||
dep/msvc/deps-x64
|
||||
key: deps ${{ hashFiles('scripts/deps/build-dependencies-windows-arm64.bat', 'scripts/deps/build-dependencies-windows-x64.bat', 'scripts/deps/versions') }}
|
||||
|
||||
- name: Zip Cache Files
|
||||
if: steps.cache-deps.outputs.cache-hit == 'true'
|
||||
shell: cmd
|
||||
run: |
|
||||
"C:\Program Files\7-Zip\7z.exe" a -r deps-x64.zip ./dep/msvc/deps-x64
|
||||
"C:\Program Files\7-Zip\7z.exe" a -r deps-arm64.zip ./dep/msvc/deps-arm64
|
||||
|
||||
- name: Upload Cache Files
|
||||
if: steps.cache-deps.outputs.cache-hit == 'true'
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: "windows"
|
||||
path: "deps-*.zip"
|
||||
147
.github/workflows/windows-build.yml
vendored
147
.github/workflows/windows-build.yml
vendored
@@ -1,147 +0,0 @@
|
||||
name: 💻 Windows
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
windows-build:
|
||||
name: "${{ matrix.name }}"
|
||||
runs-on: windows-2022
|
||||
timeout-minutes: 240
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- name: "x64"
|
||||
arch: "x64"
|
||||
vcvars: "x64"
|
||||
config: "ReleaseLTCG-Clang"
|
||||
platform: "x64"
|
||||
bindir: "x64"
|
||||
assetname: "duckstation-windows-x64-release.zip"
|
||||
installerassetname: "duckstation-windows-x64-installer.exe"
|
||||
updatername: "updater-x64-ReleaseLTCG.exe"
|
||||
installername: "installer-x64-ReleaseLTCG.exe"
|
||||
uninstallername: "uninstaller-x64-ReleaseLTCG.exe"
|
||||
- name: "x64 SSE2"
|
||||
arch: "x64-sse2"
|
||||
vcvars: "x64"
|
||||
config: "ReleaseLTCG-Clang-SSE2"
|
||||
platform: "x64"
|
||||
bindir: "x64"
|
||||
assetname: "duckstation-windows-x64-sse2-release.zip"
|
||||
installerassetname: "duckstation-windows-x64-sse2-installer.exe"
|
||||
updatername: "updater-x64-ReleaseLTCG-SSE2.exe"
|
||||
installername: "installer-x64-ReleaseLTCG-SSE2.exe"
|
||||
uninstallername: "uninstaller-x64-ReleaseLTCG-SSE2.exe"
|
||||
- name: "ARM64"
|
||||
arch: "arm64"
|
||||
vcvars: "amd64_arm64"
|
||||
config: "ReleaseLTCG-Clang"
|
||||
platform: "ARM64"
|
||||
bindir: "ARM64"
|
||||
assetname: "duckstation-windows-arm64-release.zip"
|
||||
installerassetname: "duckstation-windows-arm64-installer.exe"
|
||||
updatername: "updater-ARM64-ReleaseLTCG.exe"
|
||||
installername: "installer-ARM64-ReleaseLTCG.exe"
|
||||
uninstallername: "uninstaller-ARM64-ReleaseLTCG.exe"
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Cache Dependencies
|
||||
id: cache-deps
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: |
|
||||
dep/msvc/deps-arm64
|
||||
dep/msvc/deps-x64
|
||||
key: deps ${{ hashFiles('scripts/deps/build-dependencies-windows-arm64.bat', 'scripts/deps/build-dependencies-windows-x64.bat', 'scripts/deps/versions') }}
|
||||
|
||||
- name: Build x64 Dependencies
|
||||
if: steps.cache-deps.outputs.cache-hit != 'true'
|
||||
env:
|
||||
DEBUG: 0
|
||||
run: scripts/deps/build-dependencies-windows-x64.bat
|
||||
|
||||
- name: Build ARM64 Dependencies
|
||||
if: steps.cache-deps.outputs.cache-hit != 'true'
|
||||
env:
|
||||
DEBUG: 0
|
||||
run: scripts/deps/build-dependencies-windows-arm64.bat
|
||||
|
||||
- name: Tag as Preview Release
|
||||
if: github.ref == 'refs/heads/master'
|
||||
shell: cmd
|
||||
run: |
|
||||
echo #pragma once > src/scmversion/tag.h
|
||||
echo #define UPDATER_RELEASE_CHANNEL "preview" >> src/scmversion/tag.h
|
||||
echo #define UPDATER_RELEASE_IS_OFFICIAL 1 >> src/scmversion/tag.h
|
||||
|
||||
- name: Tag as Stable Build
|
||||
if: github.ref == 'refs/heads/dev'
|
||||
shell: cmd
|
||||
run: |
|
||||
echo #pragma once > src/scmversion/tag.h
|
||||
echo #define UPDATER_RELEASE_CHANNEL "latest" >> src/scmversion/tag.h
|
||||
echo #define UPDATER_RELEASE_IS_OFFICIAL 1 >> src/scmversion/tag.h
|
||||
|
||||
- name: Update RC Version Fields
|
||||
shell: cmd
|
||||
run: |
|
||||
cd src\scmversion
|
||||
call update_rc_version.bat
|
||||
cd ..\..
|
||||
git update-index --assume-unchanged src/duckstation-qt/duckstation-qt.rc
|
||||
|
||||
- name: Download Patch Archives
|
||||
shell: cmd
|
||||
run: |
|
||||
cd data/resources
|
||||
curl --retry 5 --retry-all-errors -LO "https://github.com/duckstation/chtdb/releases/download/latest/cheats.zip"
|
||||
curl --retry 5 --retry-all-errors -LO "https://github.com/duckstation/chtdb/releases/download/latest/patches.zip"
|
||||
|
||||
- name: Compile ${{ matrix.name }} Release Build
|
||||
shell: cmd
|
||||
run: |
|
||||
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvarsall.bat" ${{ matrix.vcvars }}
|
||||
msbuild duckstation.sln -t:Build -p:Platform=${{ matrix.platform }};Configuration=${{ matrix.config }}
|
||||
|
||||
- name: Create ${{ matrix.name }} Symbols Archive
|
||||
shell: cmd
|
||||
run: |
|
||||
"C:\Program Files\7-Zip\7z.exe" a -mx9 -r duckstation-windows-${{ matrix.arch }}-release-symbols.zip ./bin/${{ matrix.bindir }}/*.pdb
|
||||
|
||||
- name: Remove Extra Bloat Before Archiving
|
||||
shell: cmd
|
||||
run: |
|
||||
del /Q bin\${{ matrix.bindir }}\*.pdb bin\${{ matrix.bindir }}\*.exp bin\${{ matrix.bindir }}\*.lib bin\${{ matrix.bindir }}\*.iobj bin\${{ matrix.bindir }}\*.ipdb bin\${{ matrix.bindir }}\common-tests*
|
||||
|
||||
- name: Rename Updater and Installer Programs
|
||||
shell: cmd
|
||||
run: |
|
||||
rename bin\${{ matrix.bindir }}\${{ matrix.updatername }} updater.exe
|
||||
rename bin\${{ matrix.bindir }}\${{ matrix.uninstallername }} uninstaller.exe
|
||||
move bin\${{ matrix.bindir }}\${{ matrix.installername }} ${{ matrix.installerassetname }}.tmp
|
||||
|
||||
- name: Create ${{ matrix.name }} Release Archive
|
||||
shell: cmd
|
||||
run: |
|
||||
"C:\Program Files\7-Zip\7z.exe" a -mx9 -r ${{ matrix.assetname }} ./bin/${{ matrix.bindir }}/*
|
||||
|
||||
- name: Create ${{ matrix.name }} Installer Archive
|
||||
shell: cmd
|
||||
run: |
|
||||
"C:\Program Files\7-Zip\7z.exe" a -mx9 -r installer.7z ./bin/${{ matrix.bindir }}/*
|
||||
copy /B ${{ matrix.installerassetname }}.tmp + installer.7z ${{ matrix.installerassetname }}
|
||||
del /Q ${{ matrix.installerassetname }}.tmp
|
||||
|
||||
- name: Upload ${{ matrix.name }} Artifacts
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: "windows-${{ matrix.arch }}"
|
||||
path: "duckstation-windows-${{ matrix.arch }}-*.*"
|
||||
38
.github/workflows/windows-build.yml.disabled
vendored
Normal file
38
.github/workflows/windows-build.yml.disabled
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
name: Windows Build
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: windows-2019
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- name: Compile release build
|
||||
shell: cmd
|
||||
run: |
|
||||
call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Auxiliary\Build\vcvarsall.bat" x64
|
||||
msbuild duckstation.sln -t:Build -p:Platform=x64;Configuration=ReleaseLTCG
|
||||
|
||||
- name: Remove extra bloat before archiving
|
||||
shell: cmd
|
||||
run: |
|
||||
del /Q bin\x64\*.pdb
|
||||
del /Q bin\x64\*.exp
|
||||
del /Q bin\x64\*.lib
|
||||
del /Q bin\x64\*.iobj
|
||||
del /Q bin\x64\*.ipdb
|
||||
|
||||
- name: Upload release archive
|
||||
uses: actions/upload-artifact@v1
|
||||
with:
|
||||
name: "duckstation-windows-x64-release"
|
||||
path: ".\\bin\\x64"
|
||||
10
.gitignore
vendored
10
.gitignore
vendored
@@ -3,13 +3,10 @@
|
||||
|
||||
# binaries folder
|
||||
/bin/
|
||||
/Build/
|
||||
/build/
|
||||
/build-*/
|
||||
|
||||
# dependency build temp files
|
||||
deps-build/
|
||||
/deps/
|
||||
|
||||
# vs stuff
|
||||
.vs
|
||||
ipch
|
||||
@@ -20,7 +17,6 @@ ipch/*
|
||||
*.vcxproj.user
|
||||
*.VC.opendb
|
||||
*.VC.db
|
||||
/.vscode/
|
||||
|
||||
# cmake stuff
|
||||
CMakeCache.txt
|
||||
@@ -28,7 +24,6 @@ CMakeFiles
|
||||
Makefile
|
||||
cmake_install.cmake
|
||||
install_manifest.txt
|
||||
/.cache/
|
||||
|
||||
# unix intermediate files
|
||||
config.h
|
||||
@@ -44,5 +39,4 @@ CMakeLists.txt.user
|
||||
__pycache__
|
||||
|
||||
# other repos
|
||||
/android
|
||||
|
||||
/dep/mac
|
||||
|
||||
4
.gitmodules
vendored
Normal file
4
.gitmodules
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
[submodule "dep/msvc/qt"]
|
||||
path = dep/msvc/qt
|
||||
url = https://github.com/stenzek/duckstation-ext-qt-minimal.git
|
||||
shallow = true
|
||||
283
CMakeLists.txt
283
CMakeLists.txt
@@ -1,131 +1,226 @@
|
||||
# SPDX-FileCopyrightText: 2019-2026 Connor McLaughlin <stenzek@gmail.com>
|
||||
# SPDX-License-Identifier: CC-BY-NC-ND-4.0 + Packaging Restriction
|
||||
#
|
||||
# NOTE: In addition to the terms of CC-BY-NC-ND-4.0, you may not use this file to create
|
||||
# packages or build recipes without explicit permission from the copyright holder.
|
||||
#
|
||||
# Unless otherwise specified, other files supporting the build system are covered under
|
||||
# the same terms.
|
||||
#
|
||||
|
||||
cmake_minimum_required(VERSION 3.19)
|
||||
cmake_minimum_required(VERSION 3.8)
|
||||
project(duckstation C CXX)
|
||||
|
||||
# Policy settings.
|
||||
cmake_policy(SET CMP0069 NEW)
|
||||
set(CMAKE_POLICY_DEFAULT_CMP0069 NEW)
|
||||
|
||||
if(${CMAKE_SOURCE_DIR} STREQUAL ${CMAKE_BINARY_DIR})
|
||||
message(FATAL_ERROR "DuckStation does not support in-tree builds. Please make a build directory that is not the source"
|
||||
"directory and generate your CMake project there using either `cmake -B build_directory` or by "
|
||||
"running cmake from the build directory.")
|
||||
endif()
|
||||
|
||||
if(NOT CMAKE_BUILD_TYPE MATCHES "Debug|Devel|MinSizeRel|RelWithDebInfo|Release")
|
||||
message(FATAL_ERROR "CMAKE_BUILD_TYPE not set. Please set it first.")
|
||||
endif()
|
||||
message("CMake Version: ${CMAKE_VERSION}")
|
||||
|
||||
# Pull in modules.
|
||||
set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/CMakeModules/")
|
||||
include(DuckStationUtils)
|
||||
|
||||
# Detect system attributes.
|
||||
detect_operating_system()
|
||||
detect_compiler()
|
||||
detect_architecture()
|
||||
detect_page_size()
|
||||
detect_cache_line_size()
|
||||
|
||||
# Build options. Depends on system attributes.
|
||||
include(DuckStationBuildOptions)
|
||||
include(DuckStationDependencies)
|
||||
include(DuckStationCompilerRequirement)
|
||||
|
||||
# Enable PIC on Linux, otherwise the builds do not support ASLR.
|
||||
if(LINUX OR BSD)
|
||||
include(CheckPIESupported)
|
||||
check_pie_supported()
|
||||
set(CMAKE_POSITION_INDEPENDENT_CODE TRUE)
|
||||
# Platform detection.
|
||||
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
|
||||
set(LINUX TRUE)
|
||||
set(SUPPORTS_X11 TRUE)
|
||||
set(SUPPORTS_WAYLAND TRUE)
|
||||
endif()
|
||||
|
||||
|
||||
# Global options.
|
||||
if(NOT ANDROID)
|
||||
option(BUILD_SDL_FRONTEND "Build the SDL frontend" ON)
|
||||
option(BUILD_QT_FRONTEND "Build the Qt frontend" ON)
|
||||
option(BUILD_LIBRETRO_CORE "Build a libretro core" OFF)
|
||||
option(ENABLE_DISCORD_PRESENCE "Build with Discord Rich Presence support" ON)
|
||||
option(USE_SDL2 "Link with SDL2 for controller support" ON)
|
||||
endif()
|
||||
|
||||
|
||||
# OpenGL context creation methods.
|
||||
if(SUPPORTS_X11)
|
||||
option(USE_X11 "Support X11 window system" ON)
|
||||
endif()
|
||||
if(SUPPORTS_WAYLAND)
|
||||
option(USE_WAYLAND "Support Wayland window system" OFF)
|
||||
endif()
|
||||
if(LINUX OR ANDROID)
|
||||
option(USE_EGL "Support EGL OpenGL context creation" ON)
|
||||
endif()
|
||||
|
||||
# Force EGL when using Wayland
|
||||
if(USE_WAYLAND)
|
||||
set(USE_EGL ON)
|
||||
endif()
|
||||
|
||||
# When we're building for libretro, everything else is invalid because of PIC.
|
||||
if(ANDROID OR BUILD_LIBRETRO_CORE)
|
||||
if(BUILD_SDL_FRONTEND)
|
||||
message(WARNING "Building for Android or libretro core, disabling SDL frontend")
|
||||
set(BUILD_SDL_FRONTEND OFF)
|
||||
endif()
|
||||
if(BUILD_QT_FRONTEND)
|
||||
message(WARNING "Building for Android or libretro core, disabling Qt frontend")
|
||||
set(BUILD_QT_FRONTEND OFF)
|
||||
endif()
|
||||
if(ENABLE_DISCORD_PRESENCE)
|
||||
message("Building for Android or libretro core, disabling Discord Presence support")
|
||||
set(ENABLE_DISCORD_PRESENCE OFF)
|
||||
endif()
|
||||
if(USE_SDL2)
|
||||
message("Building for Android or libretro core, disabling SDL2 support")
|
||||
set(USE_SDL2 OFF)
|
||||
endif()
|
||||
if(USE_X11)
|
||||
set(USE_X11 OFF)
|
||||
endif()
|
||||
if(USE_WAYLAND)
|
||||
set(USE_WAYLAND OFF)
|
||||
endif()
|
||||
if(BUILD_LIBRETRO_CORE AND USE_EGL)
|
||||
set(USE_EGL OFF)
|
||||
endif()
|
||||
|
||||
# Force PIC when compiling a libretro core.
|
||||
if(BUILD_LIBRETRO_CORE)
|
||||
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
|
||||
# Common include/library directories on Windows.
|
||||
if(WIN32)
|
||||
set(SDL2_FOUND TRUE)
|
||||
set(SDL2_INCLUDE_DIRS "${CMAKE_CURRENT_SOURCE_DIR}/dep/msvc/sdl2/include")
|
||||
if(CMAKE_SIZEOF_VOID_P EQUAL 8)
|
||||
set(SDL2_LIBRARIES "${CMAKE_CURRENT_SOURCE_DIR}/dep/msvc/sdl2/lib64/SDL2.lib")
|
||||
set(SDL2MAIN_LIBRARIES "${CMAKE_CURRENT_SOURCE_DIR}/dep/msvc/sdl2/lib64/SDL2main.lib")
|
||||
set(SDL2_DLL_PATH "${CMAKE_CURRENT_SOURCE_DIR}/dep/msvc/sdl2/bin64/SDL2.dll")
|
||||
set(Qt5_DIR "${CMAKE_CURRENT_SOURCE_DIR}/dep/msvc/qt/5.15.0/msvc2017_64/lib/cmake/Qt5")
|
||||
else()
|
||||
set(SDL2_LIBRARIES "${CMAKE_CURRENT_SOURCE_DIR}/dep/msvc/sdl2/lib32/SDL2.lib")
|
||||
set(SDL2MAIN_LIBRARIES "${CMAKE_CURRENT_SOURCE_DIR}/dep/msvc/sdl2/lib32/SDL2main.lib")
|
||||
set(SDL2_DLL_PATH "${CMAKE_CURRENT_SOURCE_DIR}/dep/msvc/sdl2/bin32/SDL2.dll")
|
||||
set(Qt5_DIR "${CMAKE_CURRENT_SOURCE_DIR}/dep/msvc/qt/5.15.0/msvc2017_32/lib/cmake/Qt5")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
|
||||
# Required libraries.
|
||||
if(NOT ANDROID)
|
||||
if(NOT WIN32 AND (BUILD_SDL_FRONTEND OR USE_SDL2))
|
||||
find_package(SDL2 REQUIRED)
|
||||
endif()
|
||||
if(BUILD_QT_FRONTEND)
|
||||
find_package(Qt5 COMPONENTS Core Gui Widgets Network LinguistTools REQUIRED)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(USE_EGL)
|
||||
find_package(EGL REQUIRED)
|
||||
endif()
|
||||
if(USE_X11)
|
||||
find_package(X11 REQUIRED)
|
||||
endif()
|
||||
if(USE_WAYLAND)
|
||||
find_package(ECM REQUIRED NO_MODULE)
|
||||
list(APPEND CMAKE_MODULE_PATH "${ECM_MODULE_PATH}")
|
||||
find_package(Wayland REQUIRED Egl)
|
||||
message(STATUS "Wayland support enabled")
|
||||
endif()
|
||||
|
||||
# Set _DEBUG macro for Debug builds.
|
||||
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -D_DEBUG")
|
||||
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -D_DEBUG")
|
||||
|
||||
|
||||
# Release build optimizations for MSVC.
|
||||
if(MSVC)
|
||||
add_compile_definitions("_UNICODE" "UNICODE" "_CRT_NONSTDC_NO_DEPRECATE" "_CRT_SECURE_NO_WARNINGS")
|
||||
add_definitions("/D_CRT_SECURE_NO_WARNINGS")
|
||||
foreach(config CMAKE_C_FLAGS CMAKE_CXX_FLAGS)
|
||||
# Enable intrinsic functions, disable minimal rebuild, UTF-8 source, set __cplusplus version.
|
||||
set(${config} "${${config}} /Oi /Gm- /utf-8 /Zc:__cplusplus")
|
||||
# Set warning level 3 instead of 4.
|
||||
string(REPLACE "/W3" "/W4" ${config} "${${config}}")
|
||||
|
||||
# Enable intrinsic functions, disable minimal rebuild, UTF-8 source.
|
||||
set(${config} "${${config}} /Oi /Gm- /utf-8")
|
||||
endforeach()
|
||||
|
||||
# RelWithDebInfo is set to Ob1 instead of Ob2.
|
||||
string(REPLACE "/Ob1" "/Ob2" CMAKE_C_FLAGS_RELWITHDEBINFO "${CMAKE_C_FLAGS_RELWITHDEBINFO}")
|
||||
string(REPLACE "/Ob1" "/Ob2" CMAKE_C_FLAGS_DEVEL "${CMAKE_C_FLAGS_DEVEL}")
|
||||
string(REPLACE "/Ob1" "/Ob2" CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO}")
|
||||
string(REPLACE "/Ob1" "/Ob2" CMAKE_CXX_FLAGS_DEVEL "${CMAKE_CXX_FLAGS_DEVEL}")
|
||||
|
||||
# Disable incremental linking in RelWithDebInfo.
|
||||
string(REPLACE "/INCREMENTAL" "/INCREMENTAL:NO" CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO "${CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO}")
|
||||
string(REPLACE "/INCREMENTAL" "/INCREMENTAL:NO" CMAKE_EXE_LINKER_FLAGS_DEVEL "${CMAKE_EXE_LINKER_FLAGS_DEVEL}")
|
||||
|
||||
# COMDAT folding/remove unused functions.
|
||||
set(CMAKE_EXE_LINKER_FLAGS_RELEASE "${CMAKE_EXE_LINKER_FLAGS_RELEASE} /OPT:REF /OPT:ICF")
|
||||
set(CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO "${CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO} /OPT:REF /OPT:ICF")
|
||||
set(CMAKE_EXE_LINKER_FLAGS_DEVEL "${CMAKE_EXE_LINKER_FLAGS_DEVEL} /OPT:REF /OPT:ICF")
|
||||
else()
|
||||
# Force debug symbols for Linux builds.
|
||||
add_debug_symbol_flag(CMAKE_C_FLAGS_RELEASE)
|
||||
add_debug_symbol_flag(CMAKE_CXX_FLAGS_RELEASE)
|
||||
endif()
|
||||
|
||||
# Warning disables.
|
||||
if(COMPILER_CLANG OR COMPILER_CLANG_CL OR COMPILER_GCC)
|
||||
include(CheckCXXFlag)
|
||||
check_cxx_flag(-Wall COMPILER_SUPPORTS_WALL)
|
||||
check_cxx_flag(-Wno-class-memaccess COMPILER_SUPPORTS_MEMACCESS)
|
||||
check_cxx_flag(-Wno-invalid-offsetof COMPILER_SUPPORTS_OFFSETOF)
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wno-switch")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-switch")
|
||||
endif()
|
||||
|
||||
# We don't need exceptions, disable them to save a bit of code size.
|
||||
if(MSVC)
|
||||
string(REPLACE "/EHsc" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
|
||||
string(REPLACE "/GR" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
|
||||
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /D_HAS_EXCEPTIONS=0 /permissive-")
|
||||
if(COMPILER_CLANG_CL)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /clang:-fno-rtti")
|
||||
# Enable LTO/LTCG on Release builds.
|
||||
if(${CMAKE_BUILD_TYPE} STREQUAL "Release")
|
||||
if (${CMAKE_VERSION} VERSION_LESS "3.9.0")
|
||||
message(WARNING "CMake version is less than 3.9.0, we can't enable LTCG/IPO. This will make the build slightly slower, consider updating your CMake version.")
|
||||
else()
|
||||
cmake_policy(SET CMP0069 NEW)
|
||||
include(CheckIPOSupported)
|
||||
check_ipo_supported(RESULT IPO_IS_SUPPORTED)
|
||||
if(IPO_IS_SUPPORTED)
|
||||
message(STATUS "Enabling LTCG/IPO.")
|
||||
set(CMAKE_INTERPROCEDURAL_OPTIMIZATION ON)
|
||||
else()
|
||||
message(WARNING "LTCG/IPO is not supported, this will make the build slightly slower.")
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
else()
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-exceptions -fno-rtti")
|
||||
endif()
|
||||
|
||||
# Rewrite paths in macros to be relative to the source directory.
|
||||
# Helpful for reproducible builds.
|
||||
if("${CMAKE_BUILD_TYPE}" STREQUAL "Release" AND NOT CMAKE_GENERATOR MATCHES "Xcode" AND
|
||||
(COMPILER_CLANG OR COMPILER_CLANG_CL OR COMPILER_GCC))
|
||||
file(RELATIVE_PATH source_dir_remap "${CMAKE_BINARY_DIR}" "${CMAKE_SOURCE_DIR}")
|
||||
string(REGEX REPLACE "\/+$" "" source_dir_remap "${source_dir_remap}")
|
||||
set(source_dir_remap_str "\"${CMAKE_SOURCE_DIR}\"=\"${source_dir_remap}\"")
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -ffile-prefix-map=${source_dir_remap_str}")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -ffile-prefix-map=${source_dir_remap_str}")
|
||||
|
||||
# Detect C++ version support.
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
if(CMAKE_COMPILER_IS_GNUCC OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
|
||||
include(CheckCXXFlag)
|
||||
check_cxx_flag(-Wall COMPILER_SUPPORTS_WALL)
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wno-switch")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-switch")
|
||||
if(NOT ANDROID)
|
||||
check_cxx_flag(-Wno-class-memaccess COMPILER_SUPPORTS_MEMACCESS)
|
||||
check_cxx_flag(-Wno-invalid-offsetof COMPILER_SUPPORTS_OFFSETOF)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
|
||||
# Detect processor type.
|
||||
if(${CMAKE_SYSTEM_PROCESSOR} STREQUAL "x86_64" OR ${CMAKE_SYSTEM_PROCESSOR} STREQUAL "amd64")
|
||||
set(CPU_ARCH "x64")
|
||||
elseif(${CMAKE_SYSTEM_PROCESSOR} STREQUAL "AMD64")
|
||||
# MSVC x86/x64
|
||||
if(CMAKE_SIZEOF_VOID_P EQUAL 8)
|
||||
set(CPU_ARCH "x64")
|
||||
else()
|
||||
set(CPU_ARCH "x86")
|
||||
endif()
|
||||
elseif(${CMAKE_SYSTEM_PROCESSOR} STREQUAL "x86" OR ${CMAKE_SYSTEM_PROCESSOR} STREQUAL "i386" OR
|
||||
${CMAKE_SYSTEM_PROCESSOR} STREQUAL "i686")
|
||||
set(CPU_ARCH "x86")
|
||||
elseif(${CMAKE_SYSTEM_PROCESSOR} STREQUAL "aarch64")
|
||||
set(CPU_ARCH "aarch64")
|
||||
elseif(${CMAKE_SYSTEM_PROCESSOR} STREQUAL "arm" OR ${CMAKE_SYSTEM_PROCESSOR} STREQUAL "armv7-a")
|
||||
set(CPU_ARCH "arm")
|
||||
else()
|
||||
message(FATAL_ERROR "Unknown system processor: " ${CMAKE_SYSTEM_PROCESSOR})
|
||||
endif()
|
||||
|
||||
|
||||
# Write binaries to a seperate directory.
|
||||
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/bin")
|
||||
|
||||
# Enable large file support on Linux 32-bit platforms.
|
||||
if(CMAKE_SIZEOF_VOID_P EQUAL 4)
|
||||
add_definitions("-D_FILE_OFFSET_BITS=64")
|
||||
if(WIN32 AND NOT BUILD_LIBRETRO_CORE)
|
||||
# For Windows, use the source directory, except for libretro.
|
||||
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/bin/${CPU_ARCH}")
|
||||
else()
|
||||
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/bin")
|
||||
endif()
|
||||
|
||||
# Optional unit tests.
|
||||
if(BUILD_TESTS)
|
||||
enable_testing()
|
||||
endif()
|
||||
# Needed for Linux - put shared libraries in the binary directory.
|
||||
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}")
|
||||
|
||||
|
||||
# Enable threads everywhere.
|
||||
set(THREADS_PREFER_PTHREAD_FLAG ON)
|
||||
find_package(Threads REQUIRED)
|
||||
|
||||
|
||||
# Recursively include the source tree.
|
||||
enable_testing()
|
||||
add_subdirectory(dep)
|
||||
add_subdirectory(src)
|
||||
|
||||
# Output build summary.
|
||||
include(DuckStationBuildSummary)
|
||||
if(ANDROID AND NOT BUILD_LIBRETRO_CORE)
|
||||
add_subdirectory(android/app/src/cpp)
|
||||
endif()
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
function(copy_base_translations target)
|
||||
get_target_property(MOC_EXECUTABLE_LOCATION Qt6::moc IMPORTED_LOCATION)
|
||||
get_filename_component(QT_BINARY_DIRECTORY "${MOC_EXECUTABLE_LOCATION}" DIRECTORY)
|
||||
find_program(LCONVERT_EXE lconvert HINTS "${QT_BINARY_DIRECTORY}")
|
||||
set(BASE_TRANSLATIONS_DIR "${QT_BINARY_DIRECTORY}/../translations")
|
||||
|
||||
if(NOT APPLE)
|
||||
add_custom_command(TARGET ${target} POST_BUILD
|
||||
COMMAND "${CMAKE_COMMAND}" -E make_directory "$<TARGET_FILE_DIR:${target}>/translations")
|
||||
endif()
|
||||
|
||||
file(GLOB qmFiles "${BASE_TRANSLATIONS_DIR}/qt_*.qm")
|
||||
foreach(path IN LISTS qmFiles)
|
||||
get_filename_component(file ${path} NAME)
|
||||
|
||||
# qt_help_<lang> just has to ruin everything.
|
||||
if(file MATCHES "qt_help_" OR NOT file MATCHES "qt_([^.]+).qm")
|
||||
continue()
|
||||
endif()
|
||||
|
||||
# If qtbase_<lang>.qm exists, merge all qms for that language into a single qm.
|
||||
set(lang "${CMAKE_MATCH_1}")
|
||||
set(baseQmPath "${BASE_TRANSLATIONS_DIR}/qtbase_${lang}.qm")
|
||||
if(EXISTS "${baseQmPath}")
|
||||
set(outPath "${CMAKE_CURRENT_BINARY_DIR}/qt_${lang}.qm")
|
||||
set(srcQmFiles)
|
||||
file(GLOB langQmFiles "${BASE_TRANSLATIONS_DIR}/qt*${lang}.qm")
|
||||
foreach(qmFile IN LISTS langQmFiles)
|
||||
get_filename_component(file ${qmFile} NAME)
|
||||
if(file STREQUAL "qt_${lang}.qm")
|
||||
continue()
|
||||
endif()
|
||||
LIST(APPEND srcQmFiles "${qmFile}")
|
||||
endforeach()
|
||||
add_custom_command(OUTPUT ${outPath}
|
||||
COMMAND "${LCONVERT_EXE}" -verbose -of qm -o "${outPath}" ${srcQmFiles}
|
||||
DEPENDS ${srcQmFiles}
|
||||
)
|
||||
set(path "${outPath}")
|
||||
endif()
|
||||
|
||||
target_sources(${target} PRIVATE ${path})
|
||||
if(APPLE)
|
||||
set_source_files_properties(${path} PROPERTIES MACOSX_PACKAGE_LOCATION Resources/translations)
|
||||
else()
|
||||
add_custom_command(TARGET ${target} POST_BUILD
|
||||
COMMAND "${CMAKE_COMMAND}" -E copy_if_different "${path}" "$<TARGET_FILE_DIR:${target}>/translations")
|
||||
endif()
|
||||
endforeach()
|
||||
endfunction()
|
||||
46
CMakeModules/DolphinPostprocessBundle.cmake
Normal file
46
CMakeModules/DolphinPostprocessBundle.cmake
Normal file
@@ -0,0 +1,46 @@
|
||||
# This module can be used in two different ways.
|
||||
#
|
||||
# When invoked as `cmake -P DolphinPostprocessBundle.cmake`, it fixes up an
|
||||
# application folder to be standalone. It bundles all required libraries from
|
||||
# the system and fixes up library IDs. Any additional shared libraries, like
|
||||
# plugins, that are found under Contents/MacOS/ will be made standalone as well.
|
||||
#
|
||||
# When called with `include(DolphinPostprocessBundle)`, it defines a helper
|
||||
# function `dolphin_postprocess_bundle` that sets up the command form of the
|
||||
# module as a post-build step.
|
||||
|
||||
if(CMAKE_GENERATOR)
|
||||
# Being called as include(DolphinPostprocessBundle), so define a helper function.
|
||||
set(_DOLPHIN_POSTPROCESS_BUNDLE_MODULE_LOCATION "${CMAKE_CURRENT_LIST_FILE}")
|
||||
function(dolphin_postprocess_bundle target)
|
||||
add_custom_command(TARGET ${target} POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -DDOLPHIN_BUNDLE_PATH="$<TARGET_FILE_DIR:${target}>/../.."
|
||||
-P "${_DOLPHIN_POSTPROCESS_BUNDLE_MODULE_LOCATION}"
|
||||
)
|
||||
endfunction()
|
||||
return()
|
||||
endif()
|
||||
|
||||
get_filename_component(DOLPHIN_BUNDLE_PATH "${DOLPHIN_BUNDLE_PATH}" ABSOLUTE)
|
||||
message(STATUS "Fixing up application bundle: ${DOLPHIN_BUNDLE_PATH}")
|
||||
|
||||
# Make sure to fix up any additional shared libraries (like plugins) that are
|
||||
# needed.
|
||||
file(GLOB_RECURSE extra_libs "${DOLPHIN_BUNDLE_PATH}/Contents/MacOS/*.dylib")
|
||||
|
||||
# BundleUtilities doesn't support DYLD_FALLBACK_LIBRARY_PATH behavior, which
|
||||
# makes it sometimes break on libraries that do weird things with @rpath. Specify
|
||||
# equivalent search directories until https://gitlab.kitware.com/cmake/cmake/issues/16625
|
||||
# is fixed and in our minimum CMake version.
|
||||
set(extra_dirs "/usr/local/lib" "/lib" "/usr/lib")
|
||||
|
||||
# BundleUtilities is overly verbose, so disable most of its messages
|
||||
function(message)
|
||||
if(NOT ARGV MATCHES "^STATUS;")
|
||||
_message(${ARGV})
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
include(BundleUtilities)
|
||||
set(BU_CHMOD_BUNDLE_ITEMS ON)
|
||||
fixup_bundle("${DOLPHIN_BUNDLE_PATH}" "${extra_libs}" "${extra_dirs}")
|
||||
@@ -1,30 +0,0 @@
|
||||
# Renderer options.
|
||||
option(ENABLE_OPENGL "Build with OpenGL renderer" ON)
|
||||
option(ENABLE_VULKAN "Build with Vulkan renderer" ON)
|
||||
option(BUILD_QT_FRONTEND "Build the Qt frontend" ON)
|
||||
option(BUILD_MINI_FRONTEND "Build the Mini frontend" OFF)
|
||||
option(BUILD_REGTEST "Build regression test runner" OFF)
|
||||
option(BUILD_TESTS "Build unit tests" OFF)
|
||||
option(DISABLE_SSE4 "Build with SSE4 instructions disabled, reduces performance" OFF)
|
||||
|
||||
if(LINUX OR BSD)
|
||||
option(ENABLE_X11 "Support X11 window system" ON)
|
||||
option(ENABLE_WAYLAND "Support Wayland window system" ON)
|
||||
endif()
|
||||
if(APPLE)
|
||||
option(SKIP_POSTPROCESS_BUNDLE "Disable bundle post-processing, including Qt additions" OFF)
|
||||
endif()
|
||||
|
||||
# Set _DEBUG macro for Debug builds.
|
||||
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -D_DEBUG")
|
||||
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -D_DEBUG")
|
||||
|
||||
# Create the Devel build type based on RelWithDebInfo.
|
||||
set(CMAKE_C_FLAGS_DEVEL "${CMAKE_C_FLAGS_RELWITHDEBINFO} -D_DEVEL" CACHE STRING "Flags used by the C compiler during DEVEL builds." FORCE)
|
||||
set(CMAKE_CXX_FLAGS_DEVEL "${CMAKE_CXX_FLAGS_RELWITHDEBINFO} -D_DEVEL" CACHE STRING "Flags used by the CXX compiler during DEVEL builds." FORCE)
|
||||
set(CMAKE_EXE_LINKER_FLAGS_DEVEL "${CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO}" CACHE STRING "Flags used for the linker during DEVEL builds." FORCE)
|
||||
set(CMAKE_MODULE_LINKER_FLAGS_DEVEL "${CMAKE_MODULE_LINKER_FLAGS_RELWITHDEBINFO}" CACHE STRING "Flags used by the linker during the creation of modules during DEVEL builds." FORCE)
|
||||
set(CMAKE_SHARED_LINKER_FLAGS_DEVEL "${CMAKE_SHARED_LINKER_FLAGS_RELWITHDEBINFO}" CACHE STRING "Flags used by the linker during the creation of shared libraries during DEVEL builds." FORCE)
|
||||
set(CMAKE_STATIC_LINKER_FLAGS_DEVEL "${CMAKE_STATIC_LINKER_FLAGS_RELWITHDEBINFO}" CACHE STRING "Flags used by the linker during the creation of static libraries during DEVEL builds." FORCE)
|
||||
list(APPEND CMAKE_CONFIGURATION_TYPES "Devel")
|
||||
mark_as_advanced(CMAKE_C_FLAGS_DEVEL CMAKE_CXX_FLAGS_DEVEL CMAKE_EXE_LINKER_FLAGS_DEVEL CMAKE_MODULE_LINKER_FLAGS_DEVEL CMAKE_SHARED_LINKER_FLAGS_DEVEL CMAKE_STATIC_LINKER_FLAGS_DEVEL)
|
||||
@@ -1,80 +0,0 @@
|
||||
message(STATUS "Build Type: ${CMAKE_BUILD_TYPE}")
|
||||
|
||||
if(ENABLE_OPENGL)
|
||||
message(STATUS "Building with OpenGL support.")
|
||||
endif()
|
||||
if(ENABLE_VULKAN)
|
||||
message(STATUS "Building with Vulkan support.")
|
||||
endif()
|
||||
if(ENABLE_X11)
|
||||
message(STATUS "Building with X11 support.")
|
||||
endif()
|
||||
if(ENABLE_WAYLAND)
|
||||
message(STATUS "Building with Wayland support.")
|
||||
endif()
|
||||
|
||||
if(BUILD_QT_FRONTEND)
|
||||
message(STATUS "Building Qt frontend.")
|
||||
endif()
|
||||
if(BUILD_NOGUI_FRONTEND)
|
||||
message(STATUS "Building NoGUI frontend.")
|
||||
endif()
|
||||
if(BUILD_REGTEST)
|
||||
message(STATUS "Building RegTest frontend.")
|
||||
endif()
|
||||
if(BUILD_TESTS)
|
||||
message(STATUS "Building unit tests.")
|
||||
endif()
|
||||
|
||||
# Refuse to build in hostile package environments. The code and build script licenses do not allow for
|
||||
# packages, and I'm sick of dealing with people complaining about things broken by packagers, and then
|
||||
# being attacked by package maintainers who violate their distribution's codes of conduct. Attempts to
|
||||
# request removal of these packages have been unsuccessful, so we have to resort to this.
|
||||
# NOTE: You do NOT have permission to distribute build scripts or patches that modify the build system
|
||||
# without explicit permission from the copyright holder.
|
||||
# DuckStation's code is public so it can be audited and learned from. Not to repackage.
|
||||
# This is why we can't have nice things.
|
||||
if(EXISTS /etc/os-release)
|
||||
file(READ /etc/os-release OS_RELEASE_CONTENT)
|
||||
if(OS_RELEASE_CONTENT MATCHES "ID=arch" OR OS_RELEASE_CONTENT MATCHES "ID_LIKE=arch" OR OS_RELEASE_CONTENT MATCHES "ID=nixos")
|
||||
message(FATAL_ERROR "Unsupported environment.")
|
||||
endif()
|
||||
endif()
|
||||
if(DEFINED ENV{NIX_BUILD_TOP} OR DEFINED ENV{NIX_STORE} OR DEFINED ENV{IN_NIX_SHELL} OR EXISTS "/etc/NIXOS")
|
||||
message(FATAL_ERROR "Unsupported environment.")
|
||||
endif()
|
||||
|
||||
if(DEFINED HOST_MIN_PAGE_SIZE AND DEFINED HOST_MAX_PAGE_SIZE)
|
||||
message(STATUS "Building with a dynamic page size of ${HOST_MIN_PAGE_SIZE} - ${HOST_MAX_PAGE_SIZE} bytes.")
|
||||
elseif(DEFINED HOST_PAGE_SIZE)
|
||||
message(STATUS "Building with detected page size of ${HOST_PAGE_SIZE}")
|
||||
endif()
|
||||
if(DEFINED HOST_CACHE_LINE_SIZE)
|
||||
message(STATUS "Building with detected cache line size of ${HOST_CACHE_LINE_SIZE}")
|
||||
endif()
|
||||
|
||||
if(NOT IS_SUPPORTED_COMPILER)
|
||||
message(WARNING "*************** UNSUPPORTED CONFIGURATION ***************
|
||||
You are not compiling DuckStation with a supported compiler.
|
||||
It may not even build successfully.
|
||||
DuckStation only supports the Clang and MSVC compilers.
|
||||
No support will be provided, continue at your own risk.
|
||||
*********************************************************")
|
||||
endif()
|
||||
|
||||
if(WIN32)
|
||||
message(WARNING "*************** UNSUPPORTED CONFIGURATION ***************
|
||||
You are compiling DuckStation with CMake on Windows.
|
||||
It may not even build successfully.
|
||||
DuckStation only supports MSBuild on Windows.
|
||||
No support will be provided, continue at your own risk.
|
||||
*********************************************************")
|
||||
endif()
|
||||
|
||||
if(CPU_ARCH_X64 AND DISABLE_SSE4)
|
||||
message(WARNING "*********************** WARNING ***********************
|
||||
SSE4 instructions are disabled. This will result in
|
||||
reduced performance. You should not enable this option
|
||||
unless you have a pre-2008 CPU.
|
||||
*******************************************************")
|
||||
endif()
|
||||
@@ -1,16 +0,0 @@
|
||||
# Use C++20.
|
||||
set(CMAKE_CXX_STANDARD 20)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
# C++20 feature checks. Some Linux environments are incomplete.
|
||||
check_cpp20_feature("__cpp_structured_bindings" 201606)
|
||||
check_cpp20_feature("__cpp_constinit" 201907)
|
||||
check_cpp20_feature("__cpp_designated_initializers" 201707)
|
||||
check_cpp20_feature("__cpp_using_enum" 201907)
|
||||
check_cpp20_feature("__cpp_lib_bit_cast" 201806)
|
||||
check_cpp20_feature("__cpp_lib_bitops" 201907)
|
||||
check_cpp20_feature("__cpp_lib_int_pow2" 202002)
|
||||
check_cpp20_feature("__cpp_lib_starts_ends_with" 201711)
|
||||
check_cpp20_attribute("likely" 201803)
|
||||
check_cpp20_attribute("unlikely" 201803)
|
||||
check_cpp20_attribute("no_unique_address" 201803)
|
||||
@@ -1,122 +0,0 @@
|
||||
# Set prefix path to look for our bundled dependencies first on Windows.
|
||||
if(WIN32 AND CPU_ARCH_X64)
|
||||
list(APPEND CMAKE_PREFIX_PATH "${CMAKE_SOURCE_DIR}/dep/msvc/deps-x64")
|
||||
elseif(WIN32 AND CPU_ARCH_ARM64)
|
||||
list(APPEND CMAKE_PREFIX_PATH "${CMAKE_SOURCE_DIR}/dep/msvc/deps-arm64")
|
||||
endif()
|
||||
|
||||
# Enable threads everywhere.
|
||||
set(THREADS_PREFER_PTHREAD_FLAG ON)
|
||||
find_package(Threads REQUIRED)
|
||||
|
||||
# pkg-config gets pulled transitively on some platforms.
|
||||
if(NOT WIN32 AND NOT APPLE)
|
||||
find_package(PkgConfig REQUIRED)
|
||||
endif()
|
||||
|
||||
# libpng relies on zlib, which we need the system version for on Mac.
|
||||
if(APPLE OR CPU_ARCH_ARM32 OR CPU_ARCH_ARM64)
|
||||
find_package(ZLIB REQUIRED)
|
||||
endif()
|
||||
|
||||
# Enforce use of bundled dependencies to avoid conflicts with system libraries.
|
||||
set(FIND_ROOT_PATH_BACKUP ${CMAKE_FIND_ROOT_PATH})
|
||||
set(FIND_ROOT_PATH_MODE_INCLUDE_BACKUP ${CMAKE_FIND_ROOT_PATH_MODE_INCLUDE})
|
||||
set(FIND_ROOT_PATH_MODE_LIBRARY_BACKUP ${CMAKE_FIND_ROOT_PATH_MODE_LIBRARY})
|
||||
set(FIND_ROOT_PATH_MODE_PACKAGE_BACKUP ${CMAKE_FIND_ROOT_PATH_MODE_PACKAGE})
|
||||
set(FIND_ROOT_PATH_MODE_PROGRAM_BACKUP ${CMAKE_FIND_ROOT_PATH_MODE_PROGRAM})
|
||||
set(CMAKE_FIND_ROOT_PATH ${CMAKE_PREFIX_PATH})
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM ONLY)
|
||||
|
||||
# Search for local zlib version outside of Mac.
|
||||
if(NOT APPLE AND NOT CPU_ARCH_ARM32 AND NOT CPU_ARCH_ARM64)
|
||||
find_package(ZLIB 1.3.1 REQUIRED)
|
||||
endif()
|
||||
|
||||
# Bundled dependencies.
|
||||
find_package(zstd 1.5.7 REQUIRED)
|
||||
find_package(WebP REQUIRED) # v1.4.0, spews an error on Linux because no pkg-config.
|
||||
find_package(PNG 1.6.54 REQUIRED)
|
||||
find_package(JPEG REQUIRED)
|
||||
find_package(SDL3 3.4.0 REQUIRED)
|
||||
find_package(Freetype 2.14.1 REQUIRED)
|
||||
find_package(harfbuzz REQUIRED)
|
||||
find_package(plutosvg 0.0.6 REQUIRED)
|
||||
find_package(cpuinfo REQUIRED)
|
||||
find_package(DiscordRPC 3.4.0 REQUIRED)
|
||||
find_package(SoundTouch 2.3.3 REQUIRED)
|
||||
find_package(libzip 1.11.4 REQUIRED)
|
||||
find_package(Shaderc REQUIRED)
|
||||
find_package(spirv_cross_c_shared REQUIRED)
|
||||
|
||||
if(NOT WIN32 AND NOT APPLE)
|
||||
find_package(Libbacktrace REQUIRED)
|
||||
|
||||
# We need to add the rpath for shaderc to the executable.
|
||||
get_target_property(SHADERC_LIBRARY Shaderc::shaderc_shared IMPORTED_LOCATION)
|
||||
get_filename_component(SHADERC_LIBRARY_DIRECTORY ${SHADERC_LIBRARY} DIRECTORY)
|
||||
list(APPEND CMAKE_BUILD_RPATH ${SHADERC_LIBRARY_DIRECTORY})
|
||||
get_target_property(SPIRV_CROSS_LIBRARY spirv-cross-c-shared IMPORTED_LOCATION)
|
||||
get_filename_component(SPIRV_CROSS_LIBRARY_DIRECTORY ${SPIRV_CROSS_LIBRARY} DIRECTORY)
|
||||
list(APPEND CMAKE_BUILD_RPATH ${SPIRV_CROSS_LIBRARY_DIRECTORY})
|
||||
endif()
|
||||
|
||||
# Restore system package search path.
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ${FIND_ROOT_PATH_MODE_INCLUDE_BACKUP})
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ${FIND_ROOT_PATH_MODE_LIBRARY_BACKUP})
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ${FIND_ROOT_PATH_MODE_PACKAGE_BACKUP})
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM ${FIND_ROOT_PATH_MODE_PROGRAM_BACKUP})
|
||||
set(CMAKE_FIND_ROOT_PATH ${FIND_ROOT_PATH_BACKUP})
|
||||
|
||||
# Qt has transitive dependencies on system libs, so do it afterwards.
|
||||
if(BUILD_QT_FRONTEND)
|
||||
# All our builds include Qt, so this is not a problem.
|
||||
set(QT_NO_PRIVATE_MODULE_WARNING ON)
|
||||
|
||||
if(LINUX)
|
||||
find_package(Qt6 6.10.2 COMPONENTS Core Gui GuiPrivate Widgets LinguistTools DBus REQUIRED)
|
||||
else()
|
||||
find_package(Qt6 6.10.2 COMPONENTS Core Gui GuiPrivate Widgets LinguistTools REQUIRED)
|
||||
endif()
|
||||
|
||||
# Have to verify it down here, don't want users using unpatched Qt.
|
||||
if(NOT Qt6_DIR MATCHES "^${CMAKE_PREFIX_PATH}")
|
||||
message(FATAL_ERROR "Using incorrect Qt library. Check your dependencies.")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Libraries that are pulled in from host.
|
||||
if(NOT WIN32)
|
||||
find_package(CURL REQUIRED)
|
||||
if(LINUX)
|
||||
find_package(UDEV REQUIRED)
|
||||
endif()
|
||||
|
||||
if(NOT APPLE)
|
||||
if(ENABLE_X11)
|
||||
find_package(X11 REQUIRED)
|
||||
if (NOT X11_xcb_FOUND OR NOT X11_xcb_randr_FOUND OR NOT X11_X11_xcb_FOUND)
|
||||
message(FATAL_ERROR "XCB, XCB-randr and X11-xcb are required")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(ENABLE_WAYLAND)
|
||||
find_package(ECM REQUIRED NO_MODULE)
|
||||
list(APPEND CMAKE_MODULE_PATH "${ECM_MODULE_PATH}")
|
||||
find_package(Wayland REQUIRED Egl)
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(NOT WIN32)
|
||||
find_package(FFMPEG 8.0.1 COMPONENTS avcodec avformat avutil swresample swscale)
|
||||
if(NOT FFMPEG_FOUND)
|
||||
message(WARNING "FFmpeg not found, using bundled headers.")
|
||||
endif()
|
||||
endif()
|
||||
if(NOT FFMPEG_FOUND)
|
||||
set(FFMPEG_INCLUDE_DIRS "${CMAKE_SOURCE_DIR}/dep/ffmpeg/include")
|
||||
endif()
|
||||
@@ -1,377 +0,0 @@
|
||||
include(CheckSourceCompiles)
|
||||
|
||||
function(disable_compiler_warnings_for_target target)
|
||||
if(MSVC)
|
||||
target_compile_options(${target} PRIVATE "/W0")
|
||||
else()
|
||||
target_compile_options(${target} PRIVATE "-w")
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
function(detect_operating_system)
|
||||
message(STATUS "CMake Version: ${CMAKE_VERSION}")
|
||||
message(STATUS "CMake System Name: ${CMAKE_SYSTEM_NAME}")
|
||||
|
||||
# LINUX wasn't added until CMake 3.25.
|
||||
if (CMAKE_VERSION VERSION_LESS 3.25.0 AND CMAKE_SYSTEM_NAME MATCHES "Linux")
|
||||
# Have to make it visible in this scope as well for below.
|
||||
set(LINUX TRUE PARENT_SCOPE)
|
||||
set(LINUX TRUE)
|
||||
endif()
|
||||
|
||||
if(WIN32)
|
||||
message(STATUS "Building for Windows.")
|
||||
elseif(APPLE AND NOT IOS)
|
||||
message(STATUS "Building for MacOS.")
|
||||
elseif(LINUX)
|
||||
message(STATUS "Building for Linux.")
|
||||
elseif(BSD)
|
||||
message(STATUS "Building for *BSD.")
|
||||
else()
|
||||
message(FATAL_ERROR "Unsupported platform.")
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
function(detect_compiler)
|
||||
if(MSVC AND CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
|
||||
set(COMPILER_CLANG_CL TRUE PARENT_SCOPE)
|
||||
set(IS_SUPPORTED_COMPILER TRUE PARENT_SCOPE)
|
||||
message(STATUS "Building with Clang-CL.")
|
||||
elseif(CMAKE_CXX_COMPILER_ID STREQUAL "Clang" OR CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang")
|
||||
set(COMPILER_CLANG TRUE PARENT_SCOPE)
|
||||
set(IS_SUPPORTED_COMPILER TRUE PARENT_SCOPE)
|
||||
message(STATUS "Building with Clang/LLVM.")
|
||||
elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
|
||||
set(COMPILER_GCC TRUE PARENT_SCOPE)
|
||||
set(IS_SUPPORTED_COMPILER FALSE PARENT_SCOPE)
|
||||
message(STATUS "Building with GNU GCC.")
|
||||
elseif(MSVC)
|
||||
set(IS_SUPPORTED_COMPILER TRUE PARENT_SCOPE)
|
||||
message(STATUS "Building with MSVC.")
|
||||
else()
|
||||
message(FATAL_ERROR "Unknown compiler: ${CMAKE_CXX_COMPILER_ID}")
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
function(detect_architecture)
|
||||
if(APPLE AND NOT "${CMAKE_OSX_ARCHITECTURES}" STREQUAL "")
|
||||
# Universal binaries.
|
||||
if("x86_64" IN_LIST CMAKE_OSX_ARCHITECTURES)
|
||||
message(STATUS "Building x86_64 MacOS binaries.")
|
||||
set(CPU_ARCH_X64 TRUE PARENT_SCOPE)
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Xarch_x86_64 -msse4.1" PARENT_SCOPE)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Xarch_x86_64 -msse4.1" PARENT_SCOPE)
|
||||
endif()
|
||||
if("arm64" IN_LIST CMAKE_OSX_ARCHITECTURES)
|
||||
message(STATUS "Building ARM64 MacOS binaries.")
|
||||
set(CPU_ARCH_ARM64 TRUE PARENT_SCOPE)
|
||||
endif()
|
||||
elseif(("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "x86_64" OR "${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "amd64" OR
|
||||
"${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "AMD64" OR "${CMAKE_OSX_ARCHITECTURES}" STREQUAL "x86_64") AND
|
||||
CMAKE_SIZEOF_VOID_P EQUAL 8)
|
||||
message(STATUS "Building x86_64 binaries.")
|
||||
set(CPU_ARCH_X64 TRUE PARENT_SCOPE)
|
||||
if(NOT MSVC OR CMAKE_CXX_COMPILER_ID STREQUAL "Clang" AND NOT DISABLE_SSE4)
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -msse4.1" PARENT_SCOPE)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -msse4.1" PARENT_SCOPE)
|
||||
elseif(MSVC AND NOT DISABLE_SSE4)
|
||||
# Clang defines these macros, MSVC does not.
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /D__SSE3__ /D__SSE4_1__" PARENT_SCOPE)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /D__SSE3__ /D__SSE4_1__" PARENT_SCOPE)
|
||||
endif()
|
||||
elseif(("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "aarch64" OR "${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "arm64") AND
|
||||
CMAKE_SIZEOF_VOID_P EQUAL 8) # Might have an A64 kernel, e.g. Raspbian.
|
||||
message(STATUS "Building ARM64 binaries.")
|
||||
set(CPU_ARCH_ARM64 TRUE PARENT_SCOPE)
|
||||
elseif("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "arm" OR "${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "armv7-a" OR
|
||||
"${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "armv7l" OR
|
||||
(("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "aarch64" OR "${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "arm64")
|
||||
AND CMAKE_SIZEOF_VOID_P EQUAL 4))
|
||||
message(STATUS "Building ARM32 binaries.")
|
||||
set(CPU_ARCH_ARM32 TRUE PARENT_SCOPE)
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -marm -march=armv7-a -mfpu=neon-vfpv4" PARENT_SCOPE)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -marm -march=armv7-a -mfpu=neon-vfpv4" PARENT_SCOPE)
|
||||
elseif("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "riscv64")
|
||||
message(STATUS "Building RISC-V 64 binaries.")
|
||||
set(CPU_ARCH_RISCV64 TRUE PARENT_SCOPE)
|
||||
|
||||
# Don't want function calls for atomics.
|
||||
if(COMPILER_GCC)
|
||||
set(EXTRA_CFLAGS "${EXTRA_CFLAGS} -finline-atomics")
|
||||
|
||||
# Still need this, apparently.
|
||||
link_libraries("-latomic")
|
||||
endif()
|
||||
|
||||
if(NOT "${CMAKE_BUILD_TYPE}" STREQUAL "Debug")
|
||||
# Frame pointers generate an annoying amount of code on leaf functions.
|
||||
set(EXTRA_CFLAGS "${EXTRA_CFLAGS} -fomit-frame-pointer")
|
||||
endif()
|
||||
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${EXTRA_CFLAGS}" PARENT_SCOPE)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${EXTRA_CFLAGS}" PARENT_SCOPE)
|
||||
else()
|
||||
message(FATAL_ERROR "Unknown system processor: ${CMAKE_SYSTEM_PROCESSOR}")
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
function(detect_page_size)
|
||||
# This is only needed for ARM64, or if the user hasn't overridden it explicitly.
|
||||
# For universal Apple builds, we use preprocessor macros to determine page size.
|
||||
# Similar for Windows, except it's always 4KB.
|
||||
if(NOT CPU_ARCH_ARM64 OR NOT LINUX)
|
||||
unset(HOST_PAGE_SIZE CACHE)
|
||||
unset(HOST_PAGE_SIZE PARENT_SCOPE)
|
||||
unset(HOST_MIN_PAGE_SIZE CACHE)
|
||||
unset(HOST_MIN_PAGE_SIZE PARENT_SCOPE)
|
||||
unset(HOST_MAX_PAGE_SIZE CACHE)
|
||||
unset(HOST_MAX_PAGE_SIZE PARENT_SCOPE)
|
||||
return()
|
||||
elseif(DEFINED HOST_PAGE_SIZE)
|
||||
return()
|
||||
endif()
|
||||
|
||||
if(DEFINED HOST_MIN_PAGE_SIZE OR DEFINED HOST_MAX_PAGE_SIZE)
|
||||
if(NOT DEFINED HOST_MIN_PAGE_SIZE OR NOT DEFINED HOST_MAX_PAGE_SIZE)
|
||||
message(FATAL_ERROR "Both HOST_MIN_PAGE_SIZE and HOST_MAX_PAGE_SIZE must be defined.")
|
||||
endif()
|
||||
return()
|
||||
endif()
|
||||
|
||||
if(CMAKE_CROSSCOMPILING)
|
||||
message(WARNING "Cross-compiling and can't determine page size, assuming default.")
|
||||
return()
|
||||
endif()
|
||||
|
||||
message(STATUS "Determining host page size")
|
||||
set(detect_page_size_file ${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeTmp/src.c)
|
||||
file(WRITE ${detect_page_size_file} "
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
int main() {
|
||||
int res = sysconf(_SC_PAGESIZE);
|
||||
printf(\"%d\", res);
|
||||
return (res > 0) ? EXIT_SUCCESS : EXIT_FAILURE;
|
||||
}")
|
||||
try_run(
|
||||
detect_page_size_run_result
|
||||
detect_page_size_compile_result
|
||||
${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}
|
||||
${detect_page_size_file}
|
||||
RUN_OUTPUT_VARIABLE detect_page_size_output)
|
||||
if(NOT detect_page_size_compile_result OR NOT detect_page_size_run_result EQUAL 0)
|
||||
message(FATAL_ERROR "Could not determine host page size.")
|
||||
else()
|
||||
message(STATUS "Host page size: ${detect_page_size_output}")
|
||||
set(HOST_PAGE_SIZE ${detect_page_size_output} CACHE STRING "Reported host page size")
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
function(detect_cache_line_size)
|
||||
# This is only needed for ARM64, or if the user hasn't overridden it explicitly.
|
||||
if(NOT CPU_ARCH_ARM64 OR HOST_CACHE_LINE_SIZE)
|
||||
unset(HOST_CACHE_LINE_SIZE CACHE)
|
||||
unset(HOST_CACHE_LINE_SIZE PARENT_SCOPE)
|
||||
return()
|
||||
endif()
|
||||
|
||||
if(NOT LINUX)
|
||||
# For universal Apple builds, we use preprocessor macros to determine page size.
|
||||
# Similar for Windows, except it's always 64 bytes.
|
||||
return()
|
||||
endif()
|
||||
|
||||
if(CMAKE_CROSSCOMPILING)
|
||||
message(WARNING "Cross-compiling and can't determine cache line size, assuming default.")
|
||||
return()
|
||||
endif()
|
||||
|
||||
message(STATUS "Determining host cache line size")
|
||||
set(detect_cache_line_size_file ${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeTmp/src.c)
|
||||
file(WRITE ${detect_cache_line_size_file} "
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
int main() {
|
||||
int l1i = sysconf(_SC_LEVEL1_DCACHE_LINESIZE);
|
||||
int l1d = sysconf(_SC_LEVEL1_ICACHE_LINESIZE);
|
||||
int res = (l1i > l1d) ? l1i : l1d;
|
||||
for (int index = 0; index < 16; index++) {
|
||||
char buf[128];
|
||||
snprintf(buf, sizeof(buf), \"/sys/devices/system/cpu/cpu0/cache/index%d/coherency_line_size\", index);
|
||||
FILE* fp = fopen(buf, \"rb\");
|
||||
if (!fp)
|
||||
break;
|
||||
fread(buf, sizeof(buf), 1, fp);
|
||||
fclose(fp);
|
||||
int val = atoi(buf);
|
||||
res = (val > res) ? val : res;
|
||||
}
|
||||
printf(\"%d\", res);
|
||||
return (res > 0) ? EXIT_SUCCESS : EXIT_FAILURE;
|
||||
}")
|
||||
try_run(
|
||||
detect_cache_line_size_run_result
|
||||
detect_cache_line_size_compile_result
|
||||
${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}
|
||||
${detect_cache_line_size_file}
|
||||
RUN_OUTPUT_VARIABLE detect_cache_line_size_output)
|
||||
if(NOT detect_cache_line_size_compile_result OR NOT detect_cache_line_size_run_result EQUAL 0)
|
||||
message(FATAL_ERROR "Could not determine host cache line size.")
|
||||
else()
|
||||
message(STATUS "Host cache line size: ${detect_cache_line_size_output}")
|
||||
set(HOST_CACHE_LINE_SIZE ${detect_cache_line_size_output} CACHE STRING "Reported host cache line size")
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
function(get_scm_version)
|
||||
if(SCM_VERSION)
|
||||
return()
|
||||
endif()
|
||||
|
||||
find_package(Git)
|
||||
if(EXISTS "${PROJECT_SOURCE_DIR}/.git" AND GIT_FOUND)
|
||||
execute_process(
|
||||
COMMAND ${GIT_EXECUTABLE} describe --dirty
|
||||
WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}"
|
||||
OUTPUT_VARIABLE LOCAL_SCM_VERSION
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||
)
|
||||
endif()
|
||||
if(NOT LOCAL_SCM_VERSION)
|
||||
set(SCM_VERSION "unknown" PARENT_SCOPE)
|
||||
else()
|
||||
set(SCM_VERSION ${LOCAL_SCM_VERSION} PARENT_SCOPE)
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
function(add_debug_symbol_flag var)
|
||||
# CMake's regex engine is missing so many features...
|
||||
set(value "${${var}}")
|
||||
if (NOT " ${value} " MATCHES " -g[1-3]? ")
|
||||
message(STATUS "Adding -g1 to ${var}.")
|
||||
set(${var} "${value} -g1" PARENT_SCOPE)
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
function(check_cpp20_feature MACRO MINIMUM_VALUE)
|
||||
set(CACHE_VAR "CHECK_CPP20_FEATURE_${MACRO}")
|
||||
if(NOT DEFINED ${CACHE_VAR})
|
||||
# Create a small source code snippet that fails to compile if the feature is not available.
|
||||
set(TEMP_FILE "${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeTmp/src.cpp")
|
||||
file(WRITE "${TEMP_FILE}" "#include <version>
|
||||
#if !defined(${MACRO}) || ${MACRO} < ${MINIMUM_VALUE}L
|
||||
#error Missing feature
|
||||
#endif
|
||||
")
|
||||
set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY)
|
||||
try_compile(HAS_FEATURE
|
||||
${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY} "${TEMP_FILE}"
|
||||
CXX_STANDARD 20
|
||||
CXX_STANDARD_REQUIRED TRUE
|
||||
)
|
||||
set(${CACHE_VAR} ${HAS_FEATURE} CACHE INTERNAL "Cached feature test result for ${MACRO}")
|
||||
endif()
|
||||
if(NOT HAS_FEATURE)
|
||||
message(FATAL_ERROR "${MACRO} is not supported by your compiler, at least ${MINIMUM_VALUE} is required.")
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
function(check_cpp20_attribute ATTRIBUTE MINIMUM_VALUE)
|
||||
set(CACHE_VAR "CHECK_CPP20_ATTRIBUTE_${MACRO}")
|
||||
if(NOT DEFINED ${CACHE_VAR})
|
||||
set(TEMP_FILE "${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeTmp/src.cpp")
|
||||
file(WRITE "${TEMP_FILE}" "#include <version>
|
||||
#if !defined(__has_cpp_attribute) || __has_cpp_attribute(${ATTRIBUTE}) < ${MINIMUM_VALUE}L
|
||||
#error Missing feature
|
||||
#endif
|
||||
")
|
||||
set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY)
|
||||
try_compile(HAS_FEATURE
|
||||
${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY} "${TEMP_FILE}"
|
||||
CXX_STANDARD 20
|
||||
CXX_STANDARD_REQUIRED TRUE
|
||||
)
|
||||
set(${CACHE_VAR} ${HAS_FEATURE} CACHE INTERNAL "Cached attribute test result for ${MACRO}")
|
||||
endif()
|
||||
if(NOT HAS_FEATURE)
|
||||
message(FATAL_ERROR "${ATTRIBUTE} is not supported by your compiler, at least ${MINIMUM_VALUE} is required.")
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
if(APPLE)
|
||||
function(add_metal_sources target sources library_name metal_std)
|
||||
set(air_files)
|
||||
set(compile_flags -std=${metal_std} -ffast-math)
|
||||
|
||||
foreach(source IN LISTS sources)
|
||||
get_filename_component(source_name ${source} NAME)
|
||||
set(air_file ${CMAKE_CURRENT_BINARY_DIR}/${library_name}/${source_name}.air)
|
||||
list(APPEND air_files ${air_file})
|
||||
|
||||
add_custom_command(
|
||||
OUTPUT ${air_file}
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_CURRENT_BINARY_DIR}/${library_name}
|
||||
COMMAND xcrun metal ${compile_flags} -o ${air_file} -c ${source}
|
||||
DEPENDS ${source}
|
||||
COMMENT "Compiling Metal shader ${source_name}"
|
||||
)
|
||||
endforeach()
|
||||
|
||||
set(metallib_file ${CMAKE_CURRENT_BINARY_DIR}/${library_name}.metallib)
|
||||
|
||||
add_custom_command(
|
||||
OUTPUT ${metallib_file}
|
||||
COMMAND xcrun metallib -o ${metallib_file} ${air_files}
|
||||
DEPENDS ${air_files}
|
||||
COMMENT "Linking Metal library ${library_name}.metallib"
|
||||
)
|
||||
|
||||
target_sources(${target} PRIVATE ${metallib_file})
|
||||
set_source_files_properties(${metallib_file} PROPERTIES MACOSX_PACKAGE_LOCATION Resources)
|
||||
endfunction()
|
||||
endif()
|
||||
|
||||
function(add_resources TARGET DEST_SUBDIR SOURCE_DIR)
|
||||
# Recursively find all files in the source directory
|
||||
file(GLOB_RECURSE SOURCE_FILES CONFIGURE_DEPENDS "${SOURCE_DIR}/*")
|
||||
|
||||
foreach(SOURCE_FILE IN LISTS SOURCE_FILES)
|
||||
# Skip directories
|
||||
if(IS_DIRECTORY "${SOURCE_FILE}")
|
||||
continue()
|
||||
endif()
|
||||
|
||||
# Get the path relative to SOURCE_DIR
|
||||
file(RELATIVE_PATH REL_PATH "${SOURCE_DIR}" "${SOURCE_FILE}")
|
||||
|
||||
# Get the subdirectory portion (if any)
|
||||
get_filename_component(REL_SUBDIR "${REL_PATH}" DIRECTORY)
|
||||
|
||||
if(APPLE)
|
||||
# On macOS, add as source with MACOSX_PACKAGE_LOCATION
|
||||
target_sources(${TARGET} PRIVATE "${SOURCE_FILE}")
|
||||
if(REL_SUBDIR)
|
||||
set_source_files_properties("${SOURCE_FILE}" PROPERTIES
|
||||
MACOSX_PACKAGE_LOCATION "Resources/${REL_SUBDIR}")
|
||||
else()
|
||||
set_source_files_properties("${SOURCE_FILE}" PROPERTIES
|
||||
MACOSX_PACKAGE_LOCATION "Resources")
|
||||
endif()
|
||||
else()
|
||||
# On other platforms, use custom command to copy files
|
||||
if(REL_SUBDIR)
|
||||
set(DEST_PATH "$<TARGET_FILE_DIR:${TARGET}>/${DEST_SUBDIR}/${REL_SUBDIR}")
|
||||
else()
|
||||
set(DEST_PATH "$<TARGET_FILE_DIR:${TARGET}>/${DEST_SUBDIR}")
|
||||
endif()
|
||||
|
||||
add_custom_command(TARGET ${TARGET} POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory "${DEST_PATH}"
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different "${SOURCE_FILE}" "${DEST_PATH}/"
|
||||
COMMENT "Copying ${REL_PATH} to ${DEST_SUBDIR}"
|
||||
)
|
||||
endif()
|
||||
endforeach()
|
||||
endfunction()
|
||||
172
CMakeModules/FindEGL.cmake
Normal file
172
CMakeModules/FindEGL.cmake
Normal file
@@ -0,0 +1,172 @@
|
||||
#.rst:
|
||||
# FindEGL
|
||||
# -------
|
||||
#
|
||||
# Try to find EGL.
|
||||
#
|
||||
# This will define the following variables:
|
||||
#
|
||||
# ``EGL_FOUND``
|
||||
# True if (the requested version of) EGL is available
|
||||
# ``EGL_VERSION``
|
||||
# The version of EGL; note that this is the API version defined in the
|
||||
# headers, rather than the version of the implementation (eg: Mesa)
|
||||
# ``EGL_LIBRARIES``
|
||||
# This can be passed to target_link_libraries() instead of the ``EGL::EGL``
|
||||
# target
|
||||
# ``EGL_INCLUDE_DIRS``
|
||||
# This should be passed to target_include_directories() if the target is not
|
||||
# used for linking
|
||||
# ``EGL_DEFINITIONS``
|
||||
# This should be passed to target_compile_options() if the target is not
|
||||
# used for linking
|
||||
#
|
||||
# If ``EGL_FOUND`` is TRUE, it will also define the following imported target:
|
||||
#
|
||||
# ``EGL::EGL``
|
||||
# The EGL library
|
||||
#
|
||||
# In general we recommend using the imported target, as it is easier to use.
|
||||
# Bear in mind, however, that if the target is in the link interface of an
|
||||
# exported library, it must be made available by the package config file.
|
||||
#
|
||||
# Since pre-1.0.0.
|
||||
|
||||
#=============================================================================
|
||||
# Copyright 2014 Alex Merry <alex.merry@kde.org>
|
||||
# Copyright 2014 Martin Gräßlin <mgraesslin@kde.org>
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions
|
||||
# are met:
|
||||
#
|
||||
# 1. Redistributions of source code must retain the copyright
|
||||
# notice, this list of conditions and the following disclaimer.
|
||||
# 2. Redistributions in binary form must reproduce the copyright
|
||||
# notice, this list of conditions and the following disclaimer in the
|
||||
# documentation and/or other materials provided with the distribution.
|
||||
# 3. The name of the author may not be used to endorse or promote products
|
||||
# derived from this software without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||
# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
#=============================================================================
|
||||
|
||||
include(${CMAKE_CURRENT_LIST_DIR}/ECMFindModuleHelpersStub.cmake)
|
||||
include(CheckCXXSourceCompiles)
|
||||
include(CMakePushCheckState)
|
||||
|
||||
ecm_find_package_version_check(EGL)
|
||||
|
||||
# Use pkg-config to get the directories and then use these values
|
||||
# in the FIND_PATH() and FIND_LIBRARY() calls
|
||||
find_package(PkgConfig)
|
||||
pkg_check_modules(PKG_EGL QUIET egl)
|
||||
|
||||
set(EGL_DEFINITIONS ${PKG_EGL_CFLAGS_OTHER})
|
||||
|
||||
find_path(EGL_INCLUDE_DIR
|
||||
NAMES
|
||||
EGL/egl.h
|
||||
HINTS
|
||||
${PKG_EGL_INCLUDE_DIRS}
|
||||
)
|
||||
find_library(EGL_LIBRARY
|
||||
NAMES
|
||||
EGL
|
||||
HINTS
|
||||
${PKG_EGL_LIBRARY_DIRS}
|
||||
)
|
||||
|
||||
# NB: We do *not* use the version information from pkg-config, as that
|
||||
# is the implementation version (eg: the Mesa version)
|
||||
if(EGL_INCLUDE_DIR)
|
||||
# egl.h has defines of the form EGL_VERSION_x_y for each supported
|
||||
# version; so the header for EGL 1.1 will define EGL_VERSION_1_0 and
|
||||
# EGL_VERSION_1_1. Finding the highest supported version involves
|
||||
# finding all these defines and selecting the highest numbered.
|
||||
file(READ "${EGL_INCLUDE_DIR}/EGL/egl.h" _EGL_header_contents)
|
||||
string(REGEX MATCHALL
|
||||
"[ \t]EGL_VERSION_[0-9_]+"
|
||||
_EGL_version_lines
|
||||
"${_EGL_header_contents}"
|
||||
)
|
||||
unset(_EGL_header_contents)
|
||||
foreach(_EGL_version_line ${_EGL_version_lines})
|
||||
string(REGEX REPLACE
|
||||
"[ \t]EGL_VERSION_([0-9_]+)"
|
||||
"\\1"
|
||||
_version_candidate
|
||||
"${_EGL_version_line}"
|
||||
)
|
||||
string(REPLACE "_" "." _version_candidate "${_version_candidate}")
|
||||
if(NOT DEFINED EGL_VERSION OR EGL_VERSION VERSION_LESS _version_candidate)
|
||||
set(EGL_VERSION "${_version_candidate}")
|
||||
endif()
|
||||
endforeach()
|
||||
unset(_EGL_version_lines)
|
||||
endif()
|
||||
|
||||
cmake_push_check_state(RESET)
|
||||
list(APPEND CMAKE_REQUIRED_LIBRARIES "${EGL_LIBRARY}")
|
||||
list(APPEND CMAKE_REQUIRED_INCLUDES "${EGL_INCLUDE_DIR}")
|
||||
|
||||
check_cxx_source_compiles("
|
||||
#include <EGL/egl.h>
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
EGLint x = 0; EGLDisplay dpy = 0; EGLContext ctx = 0;
|
||||
eglDestroyContext(dpy, ctx);
|
||||
}" HAVE_EGL)
|
||||
|
||||
cmake_pop_check_state()
|
||||
|
||||
set(required_vars EGL_INCLUDE_DIR HAVE_EGL)
|
||||
if(NOT EMSCRIPTEN)
|
||||
list(APPEND required_vars EGL_LIBRARY)
|
||||
endif()
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args(EGL
|
||||
FOUND_VAR
|
||||
EGL_FOUND
|
||||
REQUIRED_VARS
|
||||
${required_vars}
|
||||
VERSION_VAR
|
||||
EGL_VERSION
|
||||
)
|
||||
|
||||
if(EGL_FOUND AND NOT TARGET EGL::EGL)
|
||||
if (EMSCRIPTEN)
|
||||
add_library(EGL::EGL INTERFACE IMPORTED)
|
||||
# Nothing further to be done, system include paths have headers and linkage is implicit.
|
||||
else()
|
||||
add_library(EGL::EGL UNKNOWN IMPORTED)
|
||||
set_target_properties(EGL::EGL PROPERTIES
|
||||
IMPORTED_LOCATION "${EGL_LIBRARY}"
|
||||
INTERFACE_COMPILE_OPTIONS "${EGL_DEFINITIONS}"
|
||||
INTERFACE_INCLUDE_DIRECTORIES "${EGL_INCLUDE_DIR}"
|
||||
)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
mark_as_advanced(EGL_LIBRARY EGL_INCLUDE_DIR HAVE_EGL)
|
||||
|
||||
# compatibility variables
|
||||
set(EGL_LIBRARIES ${EGL_LIBRARY})
|
||||
set(EGL_INCLUDE_DIRS ${EGL_INCLUDE_DIR})
|
||||
set(EGL_VERSION_STRING ${EGL_VERSION})
|
||||
|
||||
include(FeatureSummary)
|
||||
set_package_properties(EGL PROPERTIES
|
||||
URL "https://www.khronos.org/egl/"
|
||||
DESCRIPTION "A platform-agnostic mechanism for creating rendering surfaces for use with other graphics libraries, such as OpenGL|ES and OpenVG."
|
||||
)
|
||||
@@ -1,215 +0,0 @@
|
||||
#[==[
|
||||
Provides the following variables:
|
||||
|
||||
* `FFMPEG_INCLUDE_DIRS`: Include directories necessary to use FFMPEG.
|
||||
* `FFMPEG_LIBRARIES`: Libraries necessary to use FFMPEG. Note that this only
|
||||
includes libraries for the components requested.
|
||||
* `FFMPEG_VERSION`: The version of FFMPEG found.
|
||||
|
||||
The following components are supported:
|
||||
|
||||
* `avcodec`
|
||||
* `avdevice`
|
||||
* `avfilter`
|
||||
* `avformat`
|
||||
* `avresample`
|
||||
* `avutil`
|
||||
* `swresample`
|
||||
* `swscale`
|
||||
|
||||
For each component, the following are provided:
|
||||
|
||||
* `FFMPEG_<component>_FOUND`: Libraries for the component.
|
||||
* `FFMPEG_<component>_INCLUDE_DIRS`: Include directories for
|
||||
the component.
|
||||
* `FFMPEG_<component>_LIBRARIES`: Libraries for the component.
|
||||
* `FFMPEG::<component>`: A target to use with `target_link_libraries`.
|
||||
|
||||
Note that only components requested with `COMPONENTS` or `OPTIONAL_COMPONENTS`
|
||||
are guaranteed to set these variables or provide targets.
|
||||
#]==]
|
||||
|
||||
function (_ffmpeg_find component headername)
|
||||
find_path("FFMPEG_${component}_INCLUDE_DIR"
|
||||
NAMES
|
||||
"lib${component}/${headername}"
|
||||
PATHS
|
||||
"${FFMPEG_ROOT}/include"
|
||||
"${CMAKE_PREFIX_PATH}/include"
|
||||
~/Library/Frameworks
|
||||
/Library/Frameworks
|
||||
/usr/local/include
|
||||
/usr/include
|
||||
/sw/include # Fink
|
||||
/opt/local/include # DarwinPorts
|
||||
/opt/csw/include # Blastwave
|
||||
/opt/include
|
||||
/usr/freeware/include
|
||||
PATH_SUFFIXES
|
||||
ffmpeg
|
||||
DOC "FFMPEG's ${component} include directory")
|
||||
mark_as_advanced("FFMPEG_${component}_INCLUDE_DIR")
|
||||
|
||||
# On Windows, static FFMPEG is sometimes built as `lib<name>.a`.
|
||||
if (WIN32)
|
||||
list(APPEND CMAKE_FIND_LIBRARY_SUFFIXES ".a" ".lib")
|
||||
list(APPEND CMAKE_FIND_LIBRARY_PREFIXES "" "lib")
|
||||
endif ()
|
||||
|
||||
find_library("FFMPEG_${component}_LIBRARY"
|
||||
NAMES
|
||||
"${component}"
|
||||
PATHS
|
||||
"${FFMPEG_ROOT}/lib"
|
||||
"${CMAKE_PREFIX_PATH}/lib"
|
||||
"${CMAKE_PREFIX_PATH}/lib64"
|
||||
~/Library/Frameworks
|
||||
/Library/Frameworks
|
||||
/usr/local/lib
|
||||
/usr/local/lib64
|
||||
/usr/lib
|
||||
/usr/lib64
|
||||
/sw/lib
|
||||
/opt/local/lib
|
||||
/opt/csw/lib
|
||||
/opt/lib
|
||||
/usr/freeware/lib64
|
||||
"${FFMPEG_ROOT}/bin"
|
||||
DOC "FFMPEG's ${component} library")
|
||||
mark_as_advanced("FFMPEG_${component}_LIBRARY")
|
||||
|
||||
if (FFMPEG_${component}_LIBRARY AND FFMPEG_${component}_INCLUDE_DIR)
|
||||
set(_deps_found TRUE)
|
||||
set(_deps_link)
|
||||
foreach (_ffmpeg_dep IN LISTS ARGN)
|
||||
if (TARGET "FFMPEG::${_ffmpeg_dep}")
|
||||
list(APPEND _deps_link "FFMPEG::${_ffmpeg_dep}")
|
||||
else ()
|
||||
set(_deps_found FALSE)
|
||||
endif ()
|
||||
endforeach ()
|
||||
if (_deps_found)
|
||||
if (NOT TARGET "FFMPEG::${component}")
|
||||
add_library("FFMPEG::${component}" UNKNOWN IMPORTED)
|
||||
set_target_properties("FFMPEG::${component}" PROPERTIES
|
||||
IMPORTED_LOCATION "${FFMPEG_${component}_LIBRARY}"
|
||||
INTERFACE_INCLUDE_DIRECTORIES "${FFMPEG_${component}_INCLUDE_DIR}"
|
||||
IMPORTED_LINK_INTERFACE_LIBRARIES "${_deps_link}")
|
||||
endif ()
|
||||
set("FFMPEG_${component}_FOUND" 1
|
||||
PARENT_SCOPE)
|
||||
|
||||
set(version_header_path "${FFMPEG_${component}_INCLUDE_DIR}/lib${component}/version.h")
|
||||
set(major_version_header_path "${FFMPEG_${component}_INCLUDE_DIR}/lib${component}/version_major.h")
|
||||
if (EXISTS "${major_version_header_path}")
|
||||
string(TOUPPER "${component}" component_upper)
|
||||
file(STRINGS "${major_version_header_path}" major_version
|
||||
REGEX "#define *LIB${component_upper}_VERSION_MAJOR ")
|
||||
file(STRINGS "${version_header_path}" version
|
||||
REGEX "#define *LIB${component_upper}_VERSION_(MINOR|MICRO) ")
|
||||
string(REGEX REPLACE ".*_MAJOR *\([0-9]*\).*" "\\1" major "${major_version}")
|
||||
string(REGEX REPLACE ".*_MINOR *\([0-9]*\).*" "\\1" minor "${version}")
|
||||
string(REGEX REPLACE ".*_MICRO *\([0-9]*\).*" "\\1" micro "${version}")
|
||||
if (NOT major STREQUAL "" AND
|
||||
NOT minor STREQUAL "" AND
|
||||
NOT micro STREQUAL "")
|
||||
set("FFMPEG_${component}_VERSION" "${major}.${minor}.${micro}"
|
||||
PARENT_SCOPE)
|
||||
endif ()
|
||||
elseif (EXISTS "${version_header_path}")
|
||||
string(TOUPPER "${component}" component_upper)
|
||||
file(STRINGS "${version_header_path}" version
|
||||
REGEX "#define *LIB${component_upper}_VERSION_(MAJOR|MINOR|MICRO) ")
|
||||
string(REGEX REPLACE ".*_MAJOR *\([0-9]*\).*" "\\1" major "${version}")
|
||||
string(REGEX REPLACE ".*_MINOR *\([0-9]*\).*" "\\1" minor "${version}")
|
||||
string(REGEX REPLACE ".*_MICRO *\([0-9]*\).*" "\\1" micro "${version}")
|
||||
if (NOT major STREQUAL "" AND
|
||||
NOT minor STREQUAL "" AND
|
||||
NOT micro STREQUAL "")
|
||||
set("FFMPEG_${component}_VERSION" "${major}.${minor}.${micro}"
|
||||
PARENT_SCOPE)
|
||||
endif ()
|
||||
endif ()
|
||||
else ()
|
||||
set("FFMPEG_${component}_FOUND" 0
|
||||
PARENT_SCOPE)
|
||||
set(what)
|
||||
if (NOT FFMPEG_${component}_LIBRARY)
|
||||
set(what "library")
|
||||
endif ()
|
||||
if (NOT FFMPEG_${component}_INCLUDE_DIR)
|
||||
if (what)
|
||||
string(APPEND what " or headers")
|
||||
else ()
|
||||
set(what "headers")
|
||||
endif ()
|
||||
endif ()
|
||||
set("FFMPEG_${component}_NOT_FOUND_MESSAGE"
|
||||
"Could not find the ${what} for ${component}."
|
||||
PARENT_SCOPE)
|
||||
endif ()
|
||||
endif ()
|
||||
endfunction ()
|
||||
|
||||
_ffmpeg_find(avutil avutil.h)
|
||||
_ffmpeg_find(avresample avresample.h
|
||||
avutil)
|
||||
_ffmpeg_find(swresample swresample.h
|
||||
avutil)
|
||||
_ffmpeg_find(swscale swscale.h
|
||||
avutil)
|
||||
_ffmpeg_find(avcodec avcodec.h
|
||||
avutil)
|
||||
_ffmpeg_find(avformat avformat.h
|
||||
avcodec avutil)
|
||||
_ffmpeg_find(avfilter avfilter.h
|
||||
avutil)
|
||||
_ffmpeg_find(avdevice avdevice.h
|
||||
avformat avutil)
|
||||
|
||||
if (TARGET FFMPEG::avutil)
|
||||
set(_ffmpeg_version_header_path "${FFMPEG_avutil_INCLUDE_DIR}/libavutil/ffversion.h")
|
||||
if (EXISTS "${_ffmpeg_version_header_path}")
|
||||
file(STRINGS "${_ffmpeg_version_header_path}" _ffmpeg_version
|
||||
REGEX "FFMPEG_VERSION")
|
||||
string(REGEX REPLACE ".*\"n?\(.*\)\"" "\\1" FFMPEG_VERSION "${_ffmpeg_version}")
|
||||
unset(_ffmpeg_version)
|
||||
else ()
|
||||
set(FFMPEG_VERSION FFMPEG_VERSION-NOTFOUND)
|
||||
endif ()
|
||||
unset(_ffmpeg_version_header_path)
|
||||
endif ()
|
||||
|
||||
set(FFMPEG_INCLUDE_DIRS)
|
||||
set(FFMPEG_LIBRARIES)
|
||||
set(_ffmpeg_required_vars)
|
||||
foreach (_ffmpeg_component IN LISTS FFMPEG_FIND_COMPONENTS)
|
||||
if (TARGET "FFMPEG::${_ffmpeg_component}")
|
||||
set(FFMPEG_${_ffmpeg_component}_INCLUDE_DIRS
|
||||
"${FFMPEG_${_ffmpeg_component}_INCLUDE_DIR}")
|
||||
set(FFMPEG_${_ffmpeg_component}_LIBRARIES
|
||||
"${FFMPEG_${_ffmpeg_component}_LIBRARY}")
|
||||
list(APPEND FFMPEG_INCLUDE_DIRS
|
||||
"${FFMPEG_${_ffmpeg_component}_INCLUDE_DIRS}")
|
||||
list(APPEND FFMPEG_LIBRARIES
|
||||
"${FFMPEG_${_ffmpeg_component}_LIBRARIES}")
|
||||
if (FFMEG_FIND_REQUIRED_${_ffmpeg_component})
|
||||
list(APPEND _ffmpeg_required_vars
|
||||
"FFMPEG_${_ffmpeg_required_vars}_INCLUDE_DIRS"
|
||||
"FFMPEG_${_ffmpeg_required_vars}_LIBRARIES")
|
||||
endif ()
|
||||
endif ()
|
||||
endforeach ()
|
||||
unset(_ffmpeg_component)
|
||||
|
||||
if (FFMPEG_INCLUDE_DIRS)
|
||||
list(REMOVE_DUPLICATES FFMPEG_INCLUDE_DIRS)
|
||||
endif ()
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args(FFMPEG
|
||||
REQUIRED_VARS FFMPEG_INCLUDE_DIRS FFMPEG_LIBRARIES ${_ffmpeg_required_vars}
|
||||
VERSION_VAR FFMPEG_VERSION
|
||||
HANDLE_COMPONENTS)
|
||||
unset(_ffmpeg_required_vars)
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
# - Try to find libbacktrace
|
||||
# Once done this will define
|
||||
# LIBBACKTRACE_FOUND - System has libbacktrace
|
||||
# LIBBACKTRACE_INCLUDE_DIRS - The libbacktrace include directories
|
||||
# LIBBACKTRACE_LIBRARIES - The libraries needed to use libbacktrace
|
||||
|
||||
FIND_PATH(
|
||||
LIBBACKTRACE_INCLUDE_DIR backtrace.h
|
||||
HINTS "${CMAKE_PREFIX_PATH}/include" /usr/include /usr/local/include
|
||||
${LIBBACKTRACE_PATH_INCLUDES}
|
||||
)
|
||||
|
||||
FIND_LIBRARY(
|
||||
LIBBACKTRACE_LIBRARY
|
||||
NAMES backtrace
|
||||
PATHS "${CMAKE_PREFIX_PATH}/lib" "${CMAKE_PREFIX_PATH}/lib64" ${ADDITIONAL_LIBRARY_PATHS} ${LIBBACKTRACE_PATH_LIB}
|
||||
)
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args(Libbacktrace DEFAULT_MSG
|
||||
LIBBACKTRACE_LIBRARY LIBBACKTRACE_INCLUDE_DIR)
|
||||
|
||||
if(LIBBACKTRACE_FOUND)
|
||||
add_library(libbacktrace::libbacktrace UNKNOWN IMPORTED)
|
||||
set_target_properties(libbacktrace::libbacktrace PROPERTIES
|
||||
IMPORTED_LOCATION ${LIBBACKTRACE_LIBRARY}
|
||||
INTERFACE_INCLUDE_DIRECTORIES ${LIBBACKTRACE_INCLUDE_DIR}
|
||||
)
|
||||
endif()
|
||||
|
||||
mark_as_advanced(LIBBACKTRACE_INCLUDE_DIR LIBBACKTRACE_LIBRARY)
|
||||
30
CMakeModules/FindOpenSLES.cmake
Normal file
30
CMakeModules/FindOpenSLES.cmake
Normal file
@@ -0,0 +1,30 @@
|
||||
# - Try to find OpenSLES
|
||||
# Once done this will define
|
||||
# OPENSLES_FOUND - System has OpenSLES
|
||||
# OPENSLES_INCLUDE_DIR - The OpenSLES include directory
|
||||
# OPENSLES_LIBRARY - The library needed to use OpenSLES
|
||||
# An imported target OpenSLES::OpenSLES is also created, prefer this
|
||||
|
||||
find_path(OPENSLES_INCLUDE_DIR
|
||||
NAMES SLES/OpenSLES.h
|
||||
)
|
||||
|
||||
find_library(OPENSLES_LIBRARY
|
||||
NAMES OpenSLES
|
||||
)
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args(OpenSLES DEFAULT_MSG
|
||||
OPENSLES_LIBRARY OPENSLES_INCLUDE_DIR)
|
||||
|
||||
if(OpenSLES_FOUND)
|
||||
if(NOT TARGET OpenSLES::OpenSLES)
|
||||
add_library(OpenSLES::OpenSLES UNKNOWN IMPORTED)
|
||||
set_target_properties(OpenSLES::OpenSLES PROPERTIES
|
||||
IMPORTED_LOCATION ${OPENSLES_LIBRARY}
|
||||
INTERFACE_INCLUDE_DIRECTORIES ${OPENSLES_INCLUDE_DIR}
|
||||
)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
mark_as_advanced(OPENSLES_INCLUDE_DIR OPENSLES_LIBRARY )
|
||||
389
CMakeModules/FindSDL2.cmake
Normal file
389
CMakeModules/FindSDL2.cmake
Normal file
@@ -0,0 +1,389 @@
|
||||
# Distributed under the OSI-approved BSD 3-Clause License. See accompanying
|
||||
# file Copyright.txt or https://cmake.org/licensing for details.
|
||||
# Sourced from https://raw.githubusercontent.com/aminosbh/sdl2-cmake-modules/master/FindSDL2.cmake
|
||||
|
||||
# Copyright 2019 Amine Ben Hassouna <amine.benhassouna@gmail.com>
|
||||
# Copyright 2000-2019 Kitware, Inc. and Contributors
|
||||
# All rights reserved.
|
||||
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions
|
||||
# are met:
|
||||
|
||||
# * Redistributions of source code must retain the above copyright
|
||||
# notice, this list of conditions and the following disclaimer.
|
||||
|
||||
# * Redistributions in binary form must reproduce the above copyright
|
||||
# notice, this list of conditions and the following disclaimer in the
|
||||
# documentation and/or other materials provided with the distribution.
|
||||
|
||||
# * Neither the name of Kitware, Inc. nor the names of Contributors
|
||||
# may be used to endorse or promote products derived from this
|
||||
# software without specific prior written permission.
|
||||
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#[=======================================================================[.rst:
|
||||
FindSDL2
|
||||
--------
|
||||
|
||||
Locate SDL2 library
|
||||
|
||||
This module defines the following 'IMPORTED' targets:
|
||||
|
||||
::
|
||||
|
||||
SDL2::Core
|
||||
The SDL2 library, if found.
|
||||
Libraries should link to SDL2::Core
|
||||
|
||||
SDL2::Main
|
||||
The SDL2main library, if found.
|
||||
Applications should link to SDL2::Main instead of SDL2::Core
|
||||
|
||||
|
||||
|
||||
This module will set the following variables in your project:
|
||||
|
||||
::
|
||||
|
||||
SDL2_LIBRARIES, the name of the library to link against
|
||||
SDL2_INCLUDE_DIRS, where to find SDL.h
|
||||
SDL2_FOUND, if false, do not try to link to SDL2
|
||||
SDL2MAIN_FOUND, if false, do not try to link to SDL2main
|
||||
SDL2_VERSION_STRING, human-readable string containing the version of SDL2
|
||||
|
||||
|
||||
|
||||
This module responds to the following cache variables:
|
||||
|
||||
::
|
||||
|
||||
SDL2_PATH
|
||||
Set a custom SDL2 Library path (default: empty)
|
||||
|
||||
SDL2_NO_DEFAULT_PATH
|
||||
Disable search SDL2 Library in default path.
|
||||
If SDL2_PATH (default: ON)
|
||||
Else (default: OFF)
|
||||
|
||||
SDL2_INCLUDE_DIR
|
||||
SDL2 headers path.
|
||||
|
||||
SDL2_LIBRARY
|
||||
SDL2 Library (.dll, .so, .a, etc) path.
|
||||
|
||||
SDL2MAIN_LIBRAY
|
||||
SDL2main Library (.a) path.
|
||||
|
||||
SDL2_BUILDING_LIBRARY
|
||||
This flag is useful only when linking to SDL2_LIBRARIES insead of
|
||||
SDL2::Main. It is required only when building a library that links to
|
||||
SDL2_LIBRARIES, because only applications need main() (No need to also
|
||||
link to SDL2main).
|
||||
If this flag is defined, then no SDL2main will be added to SDL2_LIBRARIES
|
||||
and no SDL2::Main target will be created.
|
||||
|
||||
|
||||
Don't forget to include SDLmain.h and SDLmain.m in your project for the
|
||||
OS X framework based version. (Other versions link to -lSDL2main which
|
||||
this module will try to find on your behalf.) Also for OS X, this
|
||||
module will automatically add the -framework Cocoa on your behalf.
|
||||
|
||||
|
||||
Additional Note: If you see an empty SDL2_LIBRARY in your project
|
||||
configuration, it means CMake did not find your SDL2 library
|
||||
(SDL2.dll, libsdl2.so, SDL2.framework, etc). Set SDL2_LIBRARY to point
|
||||
to your SDL2 library, and configure again. Similarly, if you see an
|
||||
empty SDL2MAIN_LIBRARY, you should set this value as appropriate. These
|
||||
values are used to generate the final SDL2_LIBRARIES variable and the
|
||||
SDL2::Core and SDL2::Main targets, but when these values are unset,
|
||||
SDL2_LIBRARIES, SDL2::Core and SDL2::Main does not get created.
|
||||
|
||||
|
||||
$SDL2DIR is an environment variable that would correspond to the
|
||||
./configure --prefix=$SDL2DIR used in building SDL2. l.e.galup 9-20-02
|
||||
|
||||
|
||||
|
||||
Created by Amine Ben Hassouna:
|
||||
Adapt FindSDL.cmake to SDL2 (FindSDL2.cmake).
|
||||
Add cache variables for more flexibility:
|
||||
SDL2_PATH, SDL2_NO_DEFAULT_PATH (for details, see doc above).
|
||||
Mark 'Threads' as a required dependency for non-OSX systems.
|
||||
Modernize the FindSDL2.cmake module by creating specific targets:
|
||||
SDL2::Core and SDL2::Main (for details, see doc above).
|
||||
|
||||
|
||||
Original FindSDL.cmake module:
|
||||
Modified by Eric Wing. Added code to assist with automated building
|
||||
by using environmental variables and providing a more
|
||||
controlled/consistent search behavior. Added new modifications to
|
||||
recognize OS X frameworks and additional Unix paths (FreeBSD, etc).
|
||||
Also corrected the header search path to follow "proper" SDL
|
||||
guidelines. Added a search for SDLmain which is needed by some
|
||||
platforms. Added a search for threads which is needed by some
|
||||
platforms. Added needed compile switches for MinGW.
|
||||
|
||||
On OSX, this will prefer the Framework version (if found) over others.
|
||||
People will have to manually change the cache value of SDL2_LIBRARY to
|
||||
override this selection or set the SDL2_PATH variable or the CMake
|
||||
environment CMAKE_INCLUDE_PATH to modify the search paths.
|
||||
|
||||
Note that the header path has changed from SDL/SDL.h to just SDL.h
|
||||
This needed to change because "proper" SDL convention is #include
|
||||
"SDL.h", not <SDL/SDL.h>. This is done for portability reasons
|
||||
because not all systems place things in SDL/ (see FreeBSD).
|
||||
#]=======================================================================]
|
||||
|
||||
# Define options for searching SDL2 Library in a custom path
|
||||
|
||||
set(SDL2_PATH "" CACHE STRING "Custom SDL2 Library path")
|
||||
|
||||
set(_SDL2_NO_DEFAULT_PATH OFF)
|
||||
if(SDL2_PATH)
|
||||
set(_SDL2_NO_DEFAULT_PATH ON)
|
||||
endif()
|
||||
|
||||
set(SDL2_NO_DEFAULT_PATH ${_SDL2_NO_DEFAULT_PATH}
|
||||
CACHE BOOL "Disable search SDL2 Library in default path")
|
||||
unset(_SDL2_NO_DEFAULT_PATH)
|
||||
|
||||
set(SDL2_NO_DEFAULT_PATH_CMD)
|
||||
if(SDL2_NO_DEFAULT_PATH)
|
||||
set(SDL2_NO_DEFAULT_PATH_CMD NO_DEFAULT_PATH)
|
||||
endif()
|
||||
|
||||
# Search for the SDL2 include directory
|
||||
find_path(SDL2_INCLUDE_DIR SDL.h
|
||||
HINTS
|
||||
ENV SDL2DIR
|
||||
${SDL2_NO_DEFAULT_PATH_CMD}
|
||||
PATH_SUFFIXES SDL2
|
||||
# path suffixes to search inside ENV{SDL2DIR}
|
||||
include/SDL2 include
|
||||
PATHS ${SDL2_PATH}
|
||||
DOC "Where the SDL2 headers can be found"
|
||||
)
|
||||
|
||||
set(SDL2_INCLUDE_DIRS "${SDL2_INCLUDE_DIR}")
|
||||
|
||||
if(CMAKE_SIZEOF_VOID_P EQUAL 8)
|
||||
set(VC_LIB_PATH_SUFFIX lib/x64)
|
||||
else()
|
||||
set(VC_LIB_PATH_SUFFIX lib/x86)
|
||||
endif()
|
||||
|
||||
# SDL-2.0 is the name used by FreeBSD ports...
|
||||
# don't confuse it for the version number.
|
||||
find_library(SDL2_LIBRARY
|
||||
NAMES SDL2 SDL-2.0
|
||||
HINTS
|
||||
ENV SDL2DIR
|
||||
${SDL2_NO_DEFAULT_PATH_CMD}
|
||||
PATH_SUFFIXES lib ${VC_LIB_PATH_SUFFIX}
|
||||
PATHS ${SDL2_PATH}
|
||||
DOC "Where the SDL2 Library can be found"
|
||||
)
|
||||
|
||||
set(SDL2_LIBRARIES "${SDL2_LIBRARY}")
|
||||
|
||||
if(NOT SDL2_BUILDING_LIBRARY)
|
||||
if(NOT SDL2_INCLUDE_DIR MATCHES ".framework")
|
||||
# Non-OS X framework versions expect you to also dynamically link to
|
||||
# SDL2main. This is mainly for Windows and OS X. Other (Unix) platforms
|
||||
# seem to provide SDL2main for compatibility even though they don't
|
||||
# necessarily need it.
|
||||
|
||||
if(SDL2_PATH)
|
||||
set(SDL2MAIN_LIBRARY_PATHS "${SDL2_PATH}")
|
||||
endif()
|
||||
|
||||
if(NOT SDL2_NO_DEFAULT_PATH)
|
||||
set(SDL2MAIN_LIBRARY_PATHS
|
||||
/sw
|
||||
/opt/local
|
||||
/opt/csw
|
||||
/opt
|
||||
"${SDL2MAIN_LIBRARY_PATHS}"
|
||||
)
|
||||
endif()
|
||||
|
||||
find_library(SDL2MAIN_LIBRARY
|
||||
NAMES SDL2main
|
||||
HINTS
|
||||
ENV SDL2DIR
|
||||
${SDL2_NO_DEFAULT_PATH_CMD}
|
||||
PATH_SUFFIXES lib ${VC_LIB_PATH_SUFFIX}
|
||||
PATHS ${SDL2MAIN_LIBRARY_PATHS}
|
||||
DOC "Where the SDL2main library can be found"
|
||||
)
|
||||
unset(SDL2MAIN_LIBRARY_PATHS)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# SDL2 may require threads on your system.
|
||||
# The Apple build may not need an explicit flag because one of the
|
||||
# frameworks may already provide it.
|
||||
# But for non-OSX systems, I will use the CMake Threads package.
|
||||
if(NOT APPLE)
|
||||
find_package(Threads QUIET)
|
||||
if(NOT CMAKE_THREAD_LIBS_INIT AND NOT WIN32)
|
||||
set(SDL2_THREADS_NOT_FOUND "Could NOT find Threads (Threads is required by SDL2).")
|
||||
if(SDL2_FIND_REQUIRED)
|
||||
message(FATAL_ERROR ${SDL2_THREADS_NOT_FOUND})
|
||||
else()
|
||||
if(NOT SDL2_FIND_QUIETLY)
|
||||
message(STATUS ${SDL2_THREADS_NOT_FOUND})
|
||||
endif()
|
||||
return()
|
||||
endif()
|
||||
unset(SDL2_THREADS_NOT_FOUND)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# MinGW needs an additional link flag, -mwindows
|
||||
# It's total link flags should look like -lmingw32 -lSDL2main -lSDL2 -mwindows
|
||||
if(MINGW)
|
||||
set(MINGW32_LIBRARY mingw32 "-mwindows" CACHE STRING "link flags for MinGW")
|
||||
endif()
|
||||
|
||||
if(SDL2_LIBRARY)
|
||||
# For SDL2main
|
||||
if(SDL2MAIN_LIBRARY AND NOT SDL2_BUILDING_LIBRARY)
|
||||
list(FIND SDL2_LIBRARIES "${SDL2MAIN_LIBRARY}" _SDL2_MAIN_INDEX)
|
||||
if(_SDL2_MAIN_INDEX EQUAL -1)
|
||||
set(SDL2_LIBRARIES "${SDL2MAIN_LIBRARY}" ${SDL2_LIBRARIES})
|
||||
endif()
|
||||
unset(_SDL2_MAIN_INDEX)
|
||||
endif()
|
||||
|
||||
# For OS X, SDL2 uses Cocoa as a backend so it must link to Cocoa.
|
||||
# CMake doesn't display the -framework Cocoa string in the UI even
|
||||
# though it actually is there if I modify a pre-used variable.
|
||||
# I think it has something to do with the CACHE STRING.
|
||||
# So I use a temporary variable until the end so I can set the
|
||||
# "real" variable in one-shot.
|
||||
if(APPLE)
|
||||
set(SDL2_LIBRARIES ${SDL2_LIBRARIES} "-framework Cocoa")
|
||||
endif()
|
||||
|
||||
# For threads, as mentioned Apple doesn't need this.
|
||||
# In fact, there seems to be a problem if I used the Threads package
|
||||
# and try using this line, so I'm just skipping it entirely for OS X.
|
||||
if(NOT APPLE)
|
||||
set(SDL2_LIBRARIES ${SDL2_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT})
|
||||
endif()
|
||||
|
||||
# For MinGW library
|
||||
if(MINGW)
|
||||
set(SDL2_LIBRARIES ${MINGW32_LIBRARY} ${SDL2_LIBRARIES})
|
||||
endif()
|
||||
|
||||
endif()
|
||||
|
||||
# Read SDL2 version
|
||||
if(SDL2_INCLUDE_DIR AND EXISTS "${SDL2_INCLUDE_DIR}/SDL_version.h")
|
||||
file(STRINGS "${SDL2_INCLUDE_DIR}/SDL_version.h" SDL2_VERSION_MAJOR_LINE REGEX "^#define[ \t]+SDL_MAJOR_VERSION[ \t]+[0-9]+$")
|
||||
file(STRINGS "${SDL2_INCLUDE_DIR}/SDL_version.h" SDL2_VERSION_MINOR_LINE REGEX "^#define[ \t]+SDL_MINOR_VERSION[ \t]+[0-9]+$")
|
||||
file(STRINGS "${SDL2_INCLUDE_DIR}/SDL_version.h" SDL2_VERSION_PATCH_LINE REGEX "^#define[ \t]+SDL_PATCHLEVEL[ \t]+[0-9]+$")
|
||||
string(REGEX REPLACE "^#define[ \t]+SDL_MAJOR_VERSION[ \t]+([0-9]+)$" "\\1" SDL2_VERSION_MAJOR "${SDL2_VERSION_MAJOR_LINE}")
|
||||
string(REGEX REPLACE "^#define[ \t]+SDL_MINOR_VERSION[ \t]+([0-9]+)$" "\\1" SDL2_VERSION_MINOR "${SDL2_VERSION_MINOR_LINE}")
|
||||
string(REGEX REPLACE "^#define[ \t]+SDL_PATCHLEVEL[ \t]+([0-9]+)$" "\\1" SDL2_VERSION_PATCH "${SDL2_VERSION_PATCH_LINE}")
|
||||
set(SDL2_VERSION_STRING ${SDL2_VERSION_MAJOR}.${SDL2_VERSION_MINOR}.${SDL2_VERSION_PATCH})
|
||||
unset(SDL2_VERSION_MAJOR_LINE)
|
||||
unset(SDL2_VERSION_MINOR_LINE)
|
||||
unset(SDL2_VERSION_PATCH_LINE)
|
||||
unset(SDL2_VERSION_MAJOR)
|
||||
unset(SDL2_VERSION_MINOR)
|
||||
unset(SDL2_VERSION_PATCH)
|
||||
endif()
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
|
||||
FIND_PACKAGE_HANDLE_STANDARD_ARGS(SDL2
|
||||
REQUIRED_VARS SDL2_LIBRARY SDL2_INCLUDE_DIR
|
||||
VERSION_VAR SDL2_VERSION_STRING)
|
||||
|
||||
if(SDL2MAIN_LIBRARY)
|
||||
FIND_PACKAGE_HANDLE_STANDARD_ARGS(SDL2main
|
||||
REQUIRED_VARS SDL2MAIN_LIBRARY SDL2_INCLUDE_DIR
|
||||
VERSION_VAR SDL2_VERSION_STRING)
|
||||
endif()
|
||||
|
||||
|
||||
mark_as_advanced(SDL2_PATH
|
||||
SDL2_NO_DEFAULT_PATH
|
||||
SDL2_LIBRARY
|
||||
SDL2MAIN_LIBRARY
|
||||
SDL2_INCLUDE_DIR
|
||||
SDL2_BUILDING_LIBRARY)
|
||||
|
||||
|
||||
# SDL2:: targets (SDL2::Core and SDL2::Main)
|
||||
if(SDL2_FOUND)
|
||||
|
||||
# SDL2::Core target
|
||||
if(SDL2_LIBRARY AND NOT TARGET SDL2::Core)
|
||||
add_library(SDL2::Core UNKNOWN IMPORTED)
|
||||
set_target_properties(SDL2::Core PROPERTIES
|
||||
IMPORTED_LOCATION "${SDL2_LIBRARY}"
|
||||
INTERFACE_INCLUDE_DIRECTORIES "${SDL2_INCLUDE_DIR}")
|
||||
|
||||
if(APPLE)
|
||||
# For OS X, SDL2 uses Cocoa as a backend so it must link to Cocoa.
|
||||
# For more details, please see above.
|
||||
set_property(TARGET SDL2::Core APPEND PROPERTY
|
||||
INTERFACE_LINK_OPTIONS "-framework Cocoa")
|
||||
else()
|
||||
# For threads, as mentioned Apple doesn't need this.
|
||||
# For more details, please see above.
|
||||
set_property(TARGET SDL2::Core APPEND PROPERTY
|
||||
INTERFACE_LINK_LIBRARIES Threads::Threads)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# SDL2::Main target
|
||||
# Applications should link to SDL2::Main instead of SDL2::Core
|
||||
# For more details, please see above.
|
||||
if(NOT SDL2_BUILDING_LIBRARY AND NOT TARGET SDL2::Main)
|
||||
|
||||
if(SDL2_INCLUDE_DIR MATCHES ".framework" OR NOT SDL2MAIN_LIBRARY)
|
||||
add_library(SDL2::Main INTERFACE IMPORTED)
|
||||
set_property(TARGET SDL2::Main PROPERTY
|
||||
INTERFACE_LINK_LIBRARIES SDL2::Core)
|
||||
elseif(SDL2MAIN_LIBRARY)
|
||||
# MinGW requires that the mingw32 library is specified before the
|
||||
# libSDL2main.a static library when linking.
|
||||
# The SDL2::MainInternal target is used internally to make sure that
|
||||
# CMake respects this condition.
|
||||
add_library(SDL2::MainInternal UNKNOWN IMPORTED)
|
||||
set_property(TARGET SDL2::MainInternal PROPERTY
|
||||
IMPORTED_LOCATION "${SDL2MAIN_LIBRARY}")
|
||||
set_property(TARGET SDL2::MainInternal PROPERTY
|
||||
INTERFACE_LINK_LIBRARIES SDL2::Core)
|
||||
|
||||
add_library(SDL2::Main INTERFACE IMPORTED)
|
||||
|
||||
if(MINGW)
|
||||
# MinGW needs an additional link flag '-mwindows' and link to mingw32
|
||||
set_property(TARGET SDL2::Main PROPERTY
|
||||
INTERFACE_LINK_LIBRARIES "mingw32" "-mwindows")
|
||||
endif()
|
||||
|
||||
set_property(TARGET SDL2::Main APPEND PROPERTY
|
||||
INTERFACE_LINK_LIBRARIES SDL2::MainInternal)
|
||||
endif()
|
||||
|
||||
endif()
|
||||
endif()
|
||||
@@ -1,32 +0,0 @@
|
||||
# - Try to find UDEV
|
||||
# Once done, this will define
|
||||
#
|
||||
# UDEV_FOUND - system has UDEV
|
||||
# UDEV_INCLUDE_DIRS - the UDEV include directories
|
||||
# UDEV_LIBRARIES - the UDEV library
|
||||
find_package(PkgConfig)
|
||||
|
||||
pkg_check_modules(UDEV_PKGCONF libudev)
|
||||
|
||||
find_path(UDEV_INCLUDE_DIRS
|
||||
NAMES libudev.h
|
||||
PATHS ${UDEV_PKGCONF_INCLUDE_DIRS}
|
||||
)
|
||||
|
||||
find_library(UDEV_LIBRARIES
|
||||
NAMES udev
|
||||
PATHS ${UDEV_PKGCONF_LIBRARY_DIRS}
|
||||
)
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args(UDEV DEFAULT_MSG UDEV_INCLUDE_DIRS UDEV_LIBRARIES)
|
||||
|
||||
mark_as_advanced(UDEV_INCLUDE_DIRS UDEV_LIBRARIES)
|
||||
|
||||
if(UDEV_FOUND AND NOT (TARGET UDEV::UDEV))
|
||||
add_library (UDEV::UDEV UNKNOWN IMPORTED)
|
||||
set_target_properties(UDEV::UDEV
|
||||
PROPERTIES
|
||||
IMPORTED_LOCATION ${UDEV_LIBRARIES}
|
||||
INTERFACE_INCLUDE_DIRECTORIES ${UDEV_INCLUDE_DIRS})
|
||||
endif()
|
||||
@@ -1,166 +0,0 @@
|
||||
# Copyright (C) 2020 Sony Interactive Entertainment Inc.
|
||||
# Copyright (C) 2012 Raphael Kubo da Costa <rakuco@webkit.org>
|
||||
# Copyright (C) 2013 Igalia S.L.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions
|
||||
# are met:
|
||||
# 1. Redistributions of source code must retain the above copyright
|
||||
# notice, this list of conditions and the following disclaimer.
|
||||
# 2. Redistributions in binary form must reproduce the above copyright
|
||||
# notice, this list of conditions and the following disclaimer in the
|
||||
# documentation and/or other materials provided with the distribution.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND ITS CONTRIBUTORS ``AS
|
||||
# IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
||||
# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR ITS
|
||||
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
|
||||
# OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
|
||||
# OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
||||
# ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#[=======================================================================[.rst:
|
||||
FindWebP
|
||||
--------------
|
||||
|
||||
Find WebP headers and libraries.
|
||||
|
||||
Imported Targets
|
||||
^^^^^^^^^^^^^^^^
|
||||
|
||||
``WebP::libwebp``
|
||||
The WebP library, if found.
|
||||
|
||||
``WebP::demux``
|
||||
The WebP demux library, if found.
|
||||
|
||||
Result Variables
|
||||
^^^^^^^^^^^^^^^^
|
||||
|
||||
This will define the following variables in your project:
|
||||
|
||||
``WebP_FOUND``
|
||||
true if (the requested version of) WebP is available.
|
||||
``WebP_VERSION``
|
||||
the version of WebP.
|
||||
``WebP_LIBRARIES``
|
||||
the libraries to link against to use WebP.
|
||||
``WebP_INCLUDE_DIRS``
|
||||
where to find the WebP headers.
|
||||
``WebP_COMPILE_OPTIONS``
|
||||
this should be passed to target_compile_options(), if the
|
||||
target is not used for linking
|
||||
|
||||
#]=======================================================================]
|
||||
|
||||
find_package(PkgConfig QUIET)
|
||||
pkg_check_modules(PC_WEBP QUIET libwebp)
|
||||
set(WebP_COMPILE_OPTIONS ${PC_WEBP_CFLAGS_OTHER})
|
||||
set(WebP_VERSION ${PC_WEBP_CFLAGS_VERSION})
|
||||
|
||||
find_path(WebP_INCLUDE_DIR
|
||||
NAMES webp/decode.h
|
||||
HINTS ${PC_WEBP_INCLUDEDIR} ${PC_WEBP_INCLUDE_DIRS}
|
||||
)
|
||||
|
||||
find_library(WebP_LIBRARY
|
||||
NAMES ${WebP_NAMES} webp libwebp
|
||||
HINTS ${PC_WEBP_LIBDIR} ${PC_WEBP_LIBRARY_DIRS}
|
||||
)
|
||||
|
||||
# There's nothing in the WebP headers that could be used to detect the exact
|
||||
# WebP version being used so don't attempt to do so. A version can only be found
|
||||
# through pkg-config
|
||||
if ("${WebP_FIND_VERSION}" VERSION_GREATER "${WebP_VERSION}")
|
||||
if (WebP_VERSION)
|
||||
message(FATAL_ERROR "Required version (" ${WebP_FIND_VERSION} ") is higher than found version (" ${WebP_VERSION} ")")
|
||||
else ()
|
||||
message(WARNING "Cannot determine WebP version without pkg-config")
|
||||
endif ()
|
||||
endif ()
|
||||
|
||||
# Find components
|
||||
if (WebP_INCLUDE_DIR AND WebP_LIBRARY)
|
||||
set(_WebP_REQUIRED_LIBS_FOUND ON)
|
||||
set(WebP_LIBS_FOUND "WebP (required): ${WebP_LIBRARY}")
|
||||
else ()
|
||||
set(_WebP_REQUIRED_LIBS_FOUND OFF)
|
||||
set(WebP_LIBS_NOT_FOUND "WebP (required)")
|
||||
endif ()
|
||||
|
||||
if ("demux" IN_LIST WebP_FIND_COMPONENTS)
|
||||
find_library(WebP_DEMUX_LIBRARY
|
||||
NAMES ${WebP_DEMUX_NAMES} webpdemux libwebpdemux
|
||||
HINTS ${PC_WEBP_LIBDIR} ${PC_WEBP_LIBRARY_DIRS}
|
||||
)
|
||||
|
||||
if (WebP_DEMUX_LIBRARY)
|
||||
if (WebP_FIND_REQUIRED_demux)
|
||||
list(APPEND WebP_LIBS_FOUND "demux (required): ${WebP_DEMUX_LIBRARY}")
|
||||
else ()
|
||||
list(APPEND WebP_LIBS_FOUND "demux (optional): ${WebP_DEMUX_LIBRARY}")
|
||||
endif ()
|
||||
else ()
|
||||
if (WebP_FIND_REQUIRED_demux)
|
||||
set(_WebP_REQUIRED_LIBS_FOUND OFF)
|
||||
list(APPEND WebP_LIBS_NOT_FOUND "demux (required)")
|
||||
else ()
|
||||
list(APPEND WebP_LIBS_NOT_FOUND "demux (optional)")
|
||||
endif ()
|
||||
endif ()
|
||||
endif ()
|
||||
|
||||
if (NOT WebP_FIND_QUIETLY)
|
||||
if (WebP_LIBS_FOUND)
|
||||
message(STATUS "Found the following WebP libraries:")
|
||||
foreach (found ${WebP_LIBS_FOUND})
|
||||
message(STATUS " ${found}")
|
||||
endforeach ()
|
||||
endif ()
|
||||
if (WebP_LIBS_NOT_FOUND)
|
||||
message(STATUS "The following WebP libraries were not found:")
|
||||
foreach (found ${WebP_LIBS_NOT_FOUND})
|
||||
message(STATUS " ${found}")
|
||||
endforeach ()
|
||||
endif ()
|
||||
endif ()
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args(WebP
|
||||
FOUND_VAR WebP_FOUND
|
||||
REQUIRED_VARS WebP_INCLUDE_DIR WebP_LIBRARY _WebP_REQUIRED_LIBS_FOUND
|
||||
VERSION_VAR WebP_VERSION
|
||||
)
|
||||
|
||||
if (WebP_LIBRARY AND NOT TARGET WebP::libwebp)
|
||||
add_library(WebP::libwebp UNKNOWN IMPORTED GLOBAL)
|
||||
set_target_properties(WebP::libwebp PROPERTIES
|
||||
IMPORTED_LOCATION "${WebP_LIBRARY}"
|
||||
INTERFACE_COMPILE_OPTIONS "${WebP_COMPILE_OPTIONS}"
|
||||
INTERFACE_INCLUDE_DIRECTORIES "${WebP_INCLUDE_DIR}"
|
||||
)
|
||||
endif ()
|
||||
|
||||
if (WebP_DEMUX_LIBRARY AND NOT TARGET WebP::demux)
|
||||
add_library(WebP::demux UNKNOWN IMPORTED GLOBAL)
|
||||
set_target_properties(WebP::demux PROPERTIES
|
||||
IMPORTED_LOCATION "${WebP_DEMUX_LIBRARY}"
|
||||
INTERFACE_COMPILE_OPTIONS "${WebP_COMPILE_OPTIONS}"
|
||||
INTERFACE_INCLUDE_DIRECTORIES "${WebP_INCLUDE_DIR}"
|
||||
)
|
||||
endif ()
|
||||
|
||||
mark_as_advanced(
|
||||
WebP_INCLUDE_DIR
|
||||
WebP_LIBRARY
|
||||
WebP_DEMUX_LIBRARY
|
||||
)
|
||||
|
||||
if (WebP_FOUND)
|
||||
set(WebP_LIBRARIES ${WebP_LIBRARY} ${WebP_DEMUX_LIBRARY})
|
||||
set(WebP_INCLUDE_DIRS ${WebP_INCLUDE_DIR})
|
||||
endif ()
|
||||
@@ -1,186 +0,0 @@
|
||||
# Copyright (C) 2020 Dieter Baron and Thomas Klausner
|
||||
#
|
||||
# The authors can be contacted at <info@libzip.org>
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions
|
||||
# are met:
|
||||
#
|
||||
# 1. Redistributions of source code must retain the above copyright
|
||||
# notice, this list of conditions and the following disclaimer.
|
||||
#
|
||||
# 2. Redistributions in binary form must reproduce the above copyright
|
||||
# notice, this list of conditions and the following disclaimer in
|
||||
# the documentation and/or other materials provided with the
|
||||
# distribution.
|
||||
#
|
||||
# 3. The names of the authors may not be used to endorse or promote
|
||||
# products derived from this software without specific prior
|
||||
# written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS
|
||||
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY
|
||||
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
|
||||
# GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
|
||||
# IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
|
||||
# OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
|
||||
# IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#[=======================================================================[.rst:
|
||||
Findzstd
|
||||
-------
|
||||
|
||||
Finds the Zstandard (zstd) library.
|
||||
|
||||
Imported Targets
|
||||
^^^^^^^^^^^^^^^^
|
||||
|
||||
This module provides the following imported targets, if found:
|
||||
|
||||
``zstd::libzstd_shared``
|
||||
The shared Zstandard library
|
||||
``zstd::libzstd_static``
|
||||
The shared Zstandard library
|
||||
|
||||
Result Variables
|
||||
^^^^^^^^^^^^^^^^
|
||||
|
||||
This will define the following variables:
|
||||
|
||||
``zstd_FOUND``
|
||||
True if the system has the Zstandard library.
|
||||
``zstd_VERSION``
|
||||
The version of the Zstandard library which was found.
|
||||
|
||||
Cache Variables
|
||||
^^^^^^^^^^^^^^^
|
||||
|
||||
The following cache variables may also be set:
|
||||
|
||||
``zstd_INCLUDE_DIR``
|
||||
The directory containing ``zstd.h``.
|
||||
``zstd_STATIC_LIBRARY``
|
||||
The path to the Zstandard static library.
|
||||
``zstd_SHARED_LIBRARY``
|
||||
The path to the Zstandard shared library.
|
||||
``zstd_DLL``
|
||||
The path to the Zstandard DLL.
|
||||
|
||||
#]=======================================================================]
|
||||
|
||||
find_package(PkgConfig)
|
||||
pkg_check_modules(PC_zstd QUIET libzstd)
|
||||
|
||||
find_path(zstd_INCLUDE_DIR
|
||||
NAMES zstd.h
|
||||
HINTS ${PC_zstd_INCLUDE_DIRS}
|
||||
)
|
||||
|
||||
find_file(zstd_DLL
|
||||
NAMES libzstd.dll zstd.dll
|
||||
PATH_SUFFIXES bin
|
||||
HINTS ${PC_zstd_PREFIX}
|
||||
)
|
||||
|
||||
# On Windows, we manually define the library names to avoid mistaking the
|
||||
# implib for the static library
|
||||
if(zstd_DLL)
|
||||
set(_zstd_win_static_name zstd-static)
|
||||
set(_zstd_win_shared_name zstd)
|
||||
else()
|
||||
# vcpkg removes the -static suffix in static builds
|
||||
set(_zstd_win_static_name zstd zstd_static)
|
||||
set(_zstd_win_shared_name)
|
||||
endif()
|
||||
|
||||
set(_previous_suffixes ${CMAKE_FIND_LIBRARY_SUFFIXES})
|
||||
set(CMAKE_FIND_LIBRARY_SUFFIXES ".so" ".dylib" ".dll.a" ".lib")
|
||||
find_library(zstd_SHARED_LIBRARY
|
||||
NAMES zstd ${_zstd_win_shared_name}
|
||||
HINTS ${PC_zstd_LIBDIR}
|
||||
)
|
||||
|
||||
set(CMAKE_FIND_LIBRARY_SUFFIXES ".a" ".lib")
|
||||
find_library(zstd_STATIC_LIBRARY
|
||||
NAMES zstd ${_zstd_win_static_name}
|
||||
HINTS ${PC_zstd_LIBDIR}
|
||||
)
|
||||
set(CMAKE_FIND_LIBRARY_SUFFIXES ${_previous_suffixes})
|
||||
|
||||
# Set zstd_LIBRARY to the shared library or fall back to the static library
|
||||
if(zstd_SHARED_LIBRARY)
|
||||
set(_zstd_LIBRARY ${zstd_SHARED_LIBRARY})
|
||||
else()
|
||||
set(_zstd_LIBRARY ${zstd_STATIC_LIBRARY})
|
||||
endif()
|
||||
|
||||
# Extract version information from the header file
|
||||
if(zstd_INCLUDE_DIR)
|
||||
file(STRINGS ${zstd_INCLUDE_DIR}/zstd.h _ver_major_line
|
||||
REGEX "^#define ZSTD_VERSION_MAJOR *[0-9]+"
|
||||
LIMIT_COUNT 1)
|
||||
string(REGEX MATCH "[0-9]+"
|
||||
zstd_MAJOR_VERSION "${_ver_major_line}")
|
||||
file(STRINGS ${zstd_INCLUDE_DIR}/zstd.h _ver_minor_line
|
||||
REGEX "^#define ZSTD_VERSION_MINOR *[0-9]+"
|
||||
LIMIT_COUNT 1)
|
||||
string(REGEX MATCH "[0-9]+"
|
||||
zstd_MINOR_VERSION "${_ver_minor_line}")
|
||||
file(STRINGS ${zstd_INCLUDE_DIR}/zstd.h _ver_release_line
|
||||
REGEX "^#define ZSTD_VERSION_RELEASE *[0-9]+"
|
||||
LIMIT_COUNT 1)
|
||||
string(REGEX MATCH "[0-9]+"
|
||||
zstd_RELEASE_VERSION "${_ver_release_line}")
|
||||
set(Zstd_VERSION "${zstd_MAJOR_VERSION}.${zstd_MINOR_VERSION}.${zstd_RELEASE_VERSION}")
|
||||
unset(_ver_major_line)
|
||||
unset(_ver_minor_line)
|
||||
unset(_ver_release_line)
|
||||
endif()
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args(zstd
|
||||
FOUND_VAR zstd_FOUND
|
||||
REQUIRED_VARS
|
||||
_zstd_LIBRARY
|
||||
zstd_INCLUDE_DIR
|
||||
VERSION_VAR zstd_VERSION
|
||||
)
|
||||
|
||||
if(zstd_FOUND AND zstd_SHARED_LIBRARY AND NOT TARGET zstd::libzstd_shared)
|
||||
add_library(zstd::libzstd_shared SHARED IMPORTED)
|
||||
if(WIN32)
|
||||
set_target_properties(zstd::libzstd_shared PROPERTIES
|
||||
IMPORTED_LOCATION "${zstd_DLL}"
|
||||
IMPORTED_IMPLIB "${zstd_SHARED_LIBRARY}"
|
||||
)
|
||||
else()
|
||||
set_target_properties(zstd::libzstd_shared PROPERTIES
|
||||
IMPORTED_LOCATION "${zstd_SHARED_LIBRARY}"
|
||||
)
|
||||
endif()
|
||||
|
||||
set_target_properties(zstd::libzstd_shared PROPERTIES
|
||||
INTERFACE_COMPILE_OPTIONS "${PC_zstd_CFLAGS_OTHER}"
|
||||
INTERFACE_INCLUDE_DIRECTORIES "${zstd_INCLUDE_DIR}"
|
||||
)
|
||||
endif()
|
||||
|
||||
if(zstd_FOUND AND zstd_STATIC_LIBRARY AND NOT TARGET zstd::libzstd_static)
|
||||
add_library(zstd::libzstd_static STATIC IMPORTED)
|
||||
set_target_properties(zstd::libzstd_static PROPERTIES
|
||||
IMPORTED_LOCATION "${zstd_STATIC_LIBRARY}"
|
||||
INTERFACE_COMPILE_OPTIONS "${PC_zstd_CFLAGS_OTHER}"
|
||||
INTERFACE_INCLUDE_DIRECTORIES "${zstd_INCLUDE_DIR}"
|
||||
)
|
||||
endif()
|
||||
|
||||
mark_as_advanced(
|
||||
zstd_INCLUDE_DIR
|
||||
zstd_DLL
|
||||
zstd_SHARED_LIBRARY
|
||||
zstd_STATIC_LIBRARY
|
||||
)
|
||||
14
CMakeModules/aarch64-cross-toolchain.cmake
Normal file
14
CMakeModules/aarch64-cross-toolchain.cmake
Normal file
@@ -0,0 +1,14 @@
|
||||
# Source: https://github.com/stenzek/duckstation/issues/626#issuecomment-660718306
|
||||
|
||||
# Target system
|
||||
SET(CMAKE_SYSTEM_NAME Linux)
|
||||
SET(CMAKE_SYSTEM_PROCESSOR aarch64)
|
||||
SET(CMAKE_SYSTEM_VERSION 1)
|
||||
set(CMAKE_CROSSCOMPILING TRUE)
|
||||
|
||||
# Cross compiler
|
||||
SET(CMAKE_C_COMPILER aarch64-linux-gnu-gcc)
|
||||
SET(CMAKE_CXX_COMPILER aarch64-linux-gnu-g++)
|
||||
set(CMAKE_LIBRARY_ARCHITECTURE aarch64-linux-gnu)
|
||||
|
||||
set(THREADS_PTHREAD_ARG "0" CACHE STRING "Result from TRY_RUN" FORCE)
|
||||
28
CMakeSettings.json
Normal file
28
CMakeSettings.json
Normal file
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"configurations": [
|
||||
{
|
||||
"name": "x64-Debug",
|
||||
"generator": "Ninja",
|
||||
"configurationType": "Debug",
|
||||
"inheritEnvironments": [ "msvc_x64_x64" ],
|
||||
"buildRoot": "${projectDir}\\build\\${name}",
|
||||
"installRoot": "${projectDir}\\out\\install\\${name}",
|
||||
"cmakeCommandArgs": "",
|
||||
"buildCommandArgs": "-v",
|
||||
"ctestCommandArgs": "",
|
||||
"variables": []
|
||||
},
|
||||
{
|
||||
"name": "x64-Release",
|
||||
"generator": "Ninja",
|
||||
"configurationType": "RelWithDebInfo",
|
||||
"buildRoot": "${projectDir}\\build\\${name}",
|
||||
"installRoot": "${projectDir}\\out\\install\\${name}",
|
||||
"cmakeCommandArgs": "",
|
||||
"buildCommandArgs": "-v",
|
||||
"ctestCommandArgs": "",
|
||||
"inheritEnvironments": [ "msvc_x64_x64" ],
|
||||
"variables": []
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,91 +1,47 @@
|
||||
# DuckStation Contributors List
|
||||
|
||||
The following people have contributed to the project in some way, and are credited here.
|
||||
|
||||
## Code Contributions
|
||||
|
||||
- Connor McLaughlin - @stenzek
|
||||
- @ggrtk
|
||||
- @CookiePLMonster
|
||||
- @PookaMustard
|
||||
|
||||
## Translators
|
||||
|
||||
- Anderson Cardoso - Portuguese (Br)
|
||||
- @bajolzas - Portuguese (Pt)
|
||||
- posix - @Richard-L, blexx - German
|
||||
- posix - @Richard-L - German
|
||||
- @phoe-nix, @zkdpower - Chinese (Simplified)
|
||||
- Sorer - @MojoJojoDojo - Hebrew
|
||||
- @Hipnosis183, @falsepopsky - Spanish (Latin America)
|
||||
- @IlDucci - Spanish (Spain)
|
||||
- Hipnosis183 - Spanish
|
||||
- @RaydenX93 - Italian
|
||||
- @r57zone - Russian
|
||||
- @6lackmag3 - Russian (Android)
|
||||
- @DenSinH - Dutch
|
||||
- @BenjaminSiskoo - French
|
||||
- mikakunin - Japanese
|
||||
- Zuzia, Seba, @CookiePLMonster - Polish
|
||||
- Foxtrot Uniform - Turkish
|
||||
|
||||
## UI Contributions
|
||||
|
||||
- @kamfretoz
|
||||
- @maxihplay (MBee)
|
||||
|
||||
## Cheat Database
|
||||
|
||||
- Pugsy
|
||||
- Unicorngoulash
|
||||
|
||||
## Game Compatibility Database
|
||||
|
||||
- @Zet-sensei
|
||||
- @DarkFrost89
|
||||
- @macattack222
|
||||
- @HeroponRikiBestest
|
||||
- @picili
|
||||
- @andercard0
|
||||
- @Abbanon
|
||||
- @Shideravan
|
||||
- @mirrornoir
|
||||
- @pryon
|
||||
- @MojoJojoDojo
|
||||
- @heckez-sys
|
||||
- @Damaniel
|
||||
- @RaydenX93
|
||||
- @gp2man
|
||||
- @Richard-L
|
||||
- @pan2marumie3
|
||||
- @CookiePLMonster
|
||||
- @LoStraniero91
|
||||
- @JFD62780
|
||||
- @lmarciano9
|
||||
- @Facepalm38
|
||||
- @Alien-Grey
|
||||
- @dmlipat
|
||||
- @Krusher97
|
||||
- @AngryScotsmanGaming
|
||||
- @PookaMustard
|
||||
- @waspennator
|
||||
- @Serpentario
|
||||
- @QuasarDGames
|
||||
- @egamboau
|
||||
- @goldstinger
|
||||
- @DankRank
|
||||
- @Kesnos-ho
|
||||
- @Facepalm38
|
||||
- @ZL1LAC
|
||||
- @cs50-account
|
||||
- @nxrighthere
|
||||
- @Overload86
|
||||
- @landcaster
|
||||
- @Sekai9
|
||||
- @Zet-sensei
|
||||
- @DarkFrost89
|
||||
- @macattack222
|
||||
- @HeroponRikiBestest
|
||||
- @picili
|
||||
- @andercard0
|
||||
- @Abbanon
|
||||
- @Shideravan
|
||||
- @mirrornoir
|
||||
- @pryon
|
||||
- @MojoJojoDojo
|
||||
- @heckez-sys
|
||||
- @Damaniel
|
||||
- @RaydenX93
|
||||
- @gp2man
|
||||
- @Richard-L
|
||||
- @pan2marumie3
|
||||
- @CookiePLMonster
|
||||
|
||||
## Special Thanks
|
||||
|
||||
The following people did not directly contribute to the emulator, but it would not be in the state if not for them.
|
||||
- nocash (https://problemkaputt.de/) for fantastic documentation.
|
||||
- @PeterLemon for great simple test programs.
|
||||
- amidog for CPU, GTE and GPU test programs.
|
||||
- Jakub Czekański - @JaCzekanski - for collaboration on hardware tests.
|
||||
|
||||
- nocash (https://problemkaputt.de/) for fantastic documentation.
|
||||
- @PeterLemon for great simple test programs.
|
||||
- amidog for CPU, GTE and GPU test programs.
|
||||
- Jakub Czekański - @JaCzekanski - for collaboration on hardware tests.
|
||||
|
||||
413
README.md
413
README.md
@@ -1,229 +1,219 @@
|
||||
# DuckStation - PlayStation 1, aka. PSX Emulator
|
||||
[Latest News](#latest-news) | [Features](#features) | [Screenshots](#screenshots) | [Downloading and Running](#downloading-and-running) | [Libretro Core](#libretro-core) | [Building](#building) | [Disclaimers](#disclaimers)
|
||||
|
||||
[Features](#features) | [Downloading and Running](#downloading-and-running) | [Building](#building) | [Disclaimers](#disclaimers)
|
||||
**Discord Server:** https://discord.gg/Buktv3t
|
||||
|
||||
**Latest Builds for Windows 10/11 (x64/ARM64), Linux (AppImage x64/ARM32/ARM64), and macOS (13.3+ Universal):** https://github.com/stenzek/duckstation/releases/tag/latest
|
||||
**Latest Windows, Linux (AppImage), and Libretro Builds:** https://github.com/stenzek/duckstation/releases/tag/latest
|
||||
|
||||
**Discord Server:** https://www.duckstation.org/discord.html
|
||||
**Game Compatibility List:** https://docs.google.com/spreadsheets/d/1H66MxViRjjE5f8hOl5RQmF5woS1murio2dsLn14kEqo/edit?usp=sharing
|
||||
|
||||
DuckStation is an simulator/emulator of the Sony PlayStation(TM) console, focusing on playability, speed, and long-term maintainability. The goal is to be as accurate as possible while maintaining performance suitable for low-end devices. "Hack" options are discouraged, the default configuration should support all playable games with only some of the enhancements having compatibility issues.
|
||||
|
||||
A PS1 or PS2 "BIOS" ROM image is required to to start the emulator and to play games. You can use an image from any hardware version or region, although mismatching game regions and BIOS regions may have compatibility issues. A ROM image is not provided with the emulator for legal reasons, you should dump this from your own console using Caetla or other means.
|
||||
A "BIOS" ROM image is required to to start the emulator and to play games. You can use an image from any hardware version or region, although mismatching game regions and BIOS regions may have compatibility issues. A ROM image is not provided with the emulator for legal reasons, you should dump this from your own console using Caetla or other means.
|
||||
|
||||
## Latest News
|
||||
|
||||
- 2020/10/05: CD-ROM read speedup enhancement added.
|
||||
- 2020/09/30: CPU overclocking is now supported. Use with caution as it will break games and increase system requirements. It can be set globally or per-game.
|
||||
- 2020/09/25: Cheat support added for libretro core.
|
||||
- 2020/09/23: Game covers added to Qt frontend (see [Adding Game Covers](https://github.com/stenzek/duckstation/wiki/Adding-Game-Covers)).
|
||||
- 2020/09/19: Memory card importer/editor added to Qt frontend.
|
||||
- 2020/09/13: Support for chaining post processing shaders added.
|
||||
- 2020/09/12: Additional texture filtering options added.
|
||||
- 2020/09/09: Basic cheat support added. Not all instructions/commands are supported yet.
|
||||
- 2020/09/01: Many additional user settings available, including memory cards and enhancements. Now you can set these per-game.
|
||||
- 2020/08/25: Automated builds for macOS now available.
|
||||
- 2020/08/22: XInput controller backend added.
|
||||
- 2020/08/20: Per-game setting overrides added. Mostly for compatibility, but some options are customizable.
|
||||
- 2020/08/19: CPU PGXP mode added. It is very slow and incompatible with the recompiler, only use for games which need it.
|
||||
- 2020/08/15: Playlist support/single memcard for multi-disc games in Qt frontend added.
|
||||
- 2020/08/07: Automatic updater for standalone Windows builds.
|
||||
- 2020/08/01: Initial PGXP (geometry/perspective correction) support.
|
||||
- 2020/07/28: Qt frontend supports displaying interface in multiple languages.
|
||||
- 2020/07/23: m3u multi-disc support for libretro core.
|
||||
- 2020/07/22: Support multiple bindings for each controller button/axis.
|
||||
- 2020/07/18: Widescreen hack enhancement added.
|
||||
- 2020/07/04: Vulkan renderer now available in libretro core.
|
||||
- 2020/07/02: Now available as a libretro core.
|
||||
- 2020/07/01: Lightgun support with custom crosshairs.
|
||||
- 2020/06/19: Vulkan hardware renderer added.
|
||||
|
||||
## Features
|
||||
|
||||
DuckStation features a fully-featured frontend built using Qt, as well as a fullscreen/TV UI based on Dear ImGui.
|
||||
DuckStation features a fully-featured frontend built using Qt (pictured), as well as a simplified frontend based on SDL and Dear ImGui. An Android version has been started, but is not yet feature complete.
|
||||
|
||||
<p align="center">
|
||||
<img src="https://raw.githubusercontent.com/stenzek/duckstation/md-images/main-qt.png" alt="Main Window Screenshot" />
|
||||
<img src="https://raw.githubusercontent.com/stenzek/duckstation/md-images/bigduck.png" alt="Fullscreen UI Screenshot" />
|
||||
</p>
|
||||
|
||||
Other features include:
|
||||
|
||||
- CPU Recompiler/JIT (x86-64, armv7/AArch32, AArch64, RISC-V/RV64).
|
||||
- Hardware renderer supporting D3D11, D3D12, OpenGL, Vulkan and Metal APIs.
|
||||
- Upscaling, texture filtering, and true colour (24-bit) in hardware renderers.
|
||||
- PGXP for geometry precision, texture correction, and depth buffer emulation.
|
||||
- Accurate blending via Rasterizer Order Views/Fragment Shader Interlock.
|
||||
- Texture replacement system in hardware renderers.
|
||||
- Vectorized and multi-threaded software renderer.
|
||||
- Motion adaptive deinterlacing.
|
||||
- Adaptive downsampling filter.
|
||||
- Screen rotation for vertical or "TATE" shmup games.
|
||||
- Post processing shader chains (GLSL, Reshade FX and Slang Presets).
|
||||
- Border overlays/bezels displayed around game content.
|
||||
- "Fast boot" for skipping BIOS splash/intro.
|
||||
- Save state support, with runahead and rewind.
|
||||
- Windows, Linux, macOS support.
|
||||
- Supports reading directly from CD, bin/cue images, raw bin/img files, MAME CHD, single-track ECM, MDS/MDF, and unencrypted PBP formats.
|
||||
- Preloading of disc images to RAM to avoid disk sleeping hitches.
|
||||
- Merging of multi-disc games in game list/grid with memory cards shared between discs.
|
||||
- Automatic loading/applying of PPF patches.
|
||||
- Direct booting of homebrew executables.
|
||||
- Direct loading of Portable Sound Format (psf) files.
|
||||
- Time stretched audio when running outside of 100% speed.
|
||||
- Digital and analog controllers for input (rumble is forwarded to host).
|
||||
- GunCon and Justifier lightgun support (simulated with mouse).
|
||||
- NeGcon support.
|
||||
- Controller presets and per-game configuration.
|
||||
- Qt and "Big Picture" UI.
|
||||
- Automatic updates with preview and latest channels.
|
||||
- Automatic content scanning - game titles/hashes are provided by redump.org.
|
||||
- Optional automatic switching of memory cards for each game.
|
||||
- Supports loading cheats from existing lists.
|
||||
- Memory card editor and save importer.
|
||||
- Emulated CPU overclocking.
|
||||
- Integrated and remote debugging.
|
||||
- Multitap controllers (up to 8 devices).
|
||||
- RetroAchievements.
|
||||
- Discord Rich Presence.
|
||||
- Video capture with Media Foundation (Windows) and [FFmpeg](https://www.ffmpeg.org/) (All Platforms) backends.
|
||||
- Free camera function.
|
||||
- Parallel port cartridge emulation.
|
||||
- CPU Recompiler/JIT (x86-64 and AArch64)
|
||||
- Hardware (D3D11, OpenGL, Vulkan) and software rendering
|
||||
- Upscaling, texture filtering, and true colour (24-bit) in hardware renderers
|
||||
- PGXP for geometry precision and texture correction
|
||||
- Post processing shader chains
|
||||
- "Fast boot" for skipping BIOS splash/intro
|
||||
- Save state support
|
||||
- Windows, Linux, **highly experimental** macOS support
|
||||
- Supports bin/cue images, raw bin/img files, and MAME CHD formats.
|
||||
- Direct booting of homebrew executables
|
||||
- Direct loading of Portable Sound Format (psf) files
|
||||
- Digital and analog controllers for input (rumble is forwarded to host)
|
||||
- Namco GunCon lightgun support (simulated with mouse)
|
||||
- NeGcon support
|
||||
- Qt and SDL frontends for desktop
|
||||
- libretro core for Windows and Linux
|
||||
- Automatic updates for Windows builds
|
||||
- Automatic content scanning - game titles/regions are provided by redump.org
|
||||
- Optional automatic switching of memory cards for each game
|
||||
- Supports loading cheats from libretro or PCSXR format lists
|
||||
- Memory card editor and save importer
|
||||
- Emulated CPU overclocking
|
||||
|
||||
## System Requirements
|
||||
- A CPU faster than a potato. But it needs to be x86_64, AArch32/armv7, AArch64/ARMv8, or RISC-V/RV64.
|
||||
- A GPU capable of OpenGL 3.1/OpenGL ES 3.1/Direct3D 11 Feature Level 10.0/Vulkan 1.0. So, basically anything made in the last 10 years or so.
|
||||
- SDL, XInput or DInput compatible game controller (e.g. XB360/XBOne/XBSeries). DualShock 3 users on Windows will need to install the official DualShock 3 drivers included as part of PlayStation Now.
|
||||
- A CPU faster than a potato. But it needs to be 64-bit (either x86_64 or AArch64/ARMv8) otherwise you won't get a recompiler and it'll be slow. There are no plans to add any 32-bit recompilers.
|
||||
- For the hardware renderers, a GPU capable of OpenGL 3.0/OpenGL ES 3.0/Direct3D 11 Feature Level 10.0 (or Vulkan 1.0) and above. So, basically anything made in the last 10 years or so.
|
||||
- SDL or XInput compatible game controller (e.g. XB360/XBOne). DualShock 3 users on Windows will need to install the official DualShock 3 drivers included as part of PlayStation Now.
|
||||
- Optional [SDL game contoller database files](#sdl-game-controller-database) are also supported.
|
||||
|
||||
## Downloading and running
|
||||
Binaries of DuckStation for Windows x64/ARM64, Linux x86_64/ARM32/ARM64 (in AppImage format), and macOS Universal Binaries are available via GitHub Releases and are automatically built with every commit/push.
|
||||
|
||||
As per the terms of CC-BY-NC-ND, redistribution of **unmodified releases and code** is permitted. However, we would prefer if you linked to https://www.duckstation.org/ instead. Please note that pre-configured settings and packages are considered modifications.
|
||||
|
||||
For x86 machines (most systems), you will need a CPU that supports the SSE4.1 instruction set for the "normal" build. This includes all Intel CPUs manufactured after 2007, and AMD CPUs manufactured after 2011. If you have a CPU that is older, you will need to download the "SSE2" build from the releases page, which has lower performance but still supports these CPUs.
|
||||
|
||||
The main releases page is limited to the last 30 releases due to automatic updater limitations. Older releases can be downloaded from https://github.com/duckstation/old-releases/releases.
|
||||
|
||||
### Update Channels
|
||||
|
||||
The automatic updater in DuckStation has two channels: "Stable" and "Preview".
|
||||
- "Stable Releases": Less frequent updates, and tracks the "latest" release on GitHub. Releases in this channel have had more testing.
|
||||
- "Preview Releases": Built whenever a commit is pushed to the repository, and tracks the pre-release on GitHub. This channel contains builds which have had minimal testing, and may contain bugs or issues.
|
||||
|
||||
By default, the updater will track the channel you downloaded from. You can change the channel in `Settings -> Interface -> Updates`.
|
||||
Binaries of DuckStation for Windows 64-bit, x86_64 Linux x86_64 (in AppImage format), and Android ARMv8/AArch64 are available via GitHub Releases and are automatically built with every commit/push. Binaries or packages distributed through other sources may be out of date and are not supported by the developer.
|
||||
|
||||
### Windows
|
||||
|
||||
DuckStation **requires** Windows 10/11, specifically version 1809 or newer. If you are still using Windows 7/8/8.1, DuckStation **will not run** on your operating system. Running these operating systems in 2026 should be considered a security risk, and I would recommend updating to something which receives vendor support.
|
||||
If you must use an older operating system, [v0.1-5624](https://github.com/duckstation/old-releases/releases/tag/v0.1-5624) is the last version which will run. But do not expect to receive any assistance, these builds are no longer supported.
|
||||
**Windows 10 is the only version of Windows supported by the developer.** Windows 7/8 may work, but is not supported. I am aware some users are still using Windows 7, but it is no longer supported by Microsoft and too much effort to get running on modern hardware. Game bugs are unlikely to be affected by the operating system, however performance issues should be verified on Windows 10 before reporting.
|
||||
|
||||
Windows builds are provided in two formats:
|
||||
- **Installer (.exe, recommended):** An installer which extracts DuckStation to your user-local programs directory, and optionally creates Start Menu/Desktop shortcuts.
|
||||
- **Archive (.zip):** A zip archive containing the prebuilt binary. Choose this option if you want a "portable" installation, or do not want to run an installer.
|
||||
To download:
|
||||
- Go to https://github.com/stenzek/duckstation/releases/tag/latest, and download the Windows x64 build. This is a zip archive containing the prebuilt binary.
|
||||
- Alternatively, direct download link: https://github.com/stenzek/duckstation/releases/download/latest/duckstation-windows-x64-release.zip
|
||||
- Extract the archive **to a subdirectory**. The archive has no root subdirectory, so extracting to the current directory will drop a bunch of files in your download directory if you do not extract to a subdirectory.
|
||||
|
||||
To use the installer, simply download the installer from the releases page, run it, and follow the prompts.
|
||||
- Direct download link: https://github.com/stenzek/duckstation/releases/download/latest/duckstation-windows-x64-installer.exe
|
||||
- ARM64 download link (Snapdragon laptops): https://github.com/stenzek/duckstation/releases/download/latest/duckstation-windows-arm64-installer.exe
|
||||
- Legacy SSE2 installer (for pre-2008 CPUs): https://github.com/stenzek/duckstation/releases/download/latest/duckstation-windows-x64-sse2-installer.exe
|
||||
Once downloaded and extracted, you can launch the Qt frontend from `duckstation-qt-x64-ReleaseLTCG.exe`, or the SDL frontend from `duckstation-sdl-x64-ReleaseLTCG.exe`.
|
||||
To set up:
|
||||
1. Either configure the path to a BIOS image in the settings, or copy one or more PlayStation BIOS images to the bios/ subdirectory. On Windows, by default this will be located in `C:\Users\YOUR_USERNAME\Documents\DuckStation\bios`. If you don't want to use the Documents directory to save the BIOS/memory cards/etc, you can use portable mode. See [User directory](#user-directories).
|
||||
2. If using the SDL frontend, add the directories containing your disc images by clicking `Settings->Add Game Directory`.
|
||||
2. Select a game from the list, or open a disc image file and enjoy.
|
||||
|
||||
The installer is still a new addition, so if you encounter issues please let us know via Discord.
|
||||
**If you get an error about `vcruntime140_1.dll` being missing, you will need to update your Visual C++ runtime.** You can do that from this page: https://support.microsoft.com/en-au/help/2977003/the-latest-supported-visual-c-downloads. Specifically, you want the x64 runtime, which can be downloaded from https://aka.ms/vs/16/release/vc_redist.x64.exe.
|
||||
|
||||
To use the archive or portable installation, follow these steps:
|
||||
1. Download https://github.com/stenzek/duckstation/releases/download/latest/duckstation-windows-x64-release.zip. If you have an ARM64 Windows machine such as Snapdragon, download `duckstation-windows-arm64-release.zip` instead.
|
||||
2. Extract the archive **to a subdirectory**. The archive has no root subdirectory, so extracting to the current directory will drop a bunch of files in your download directory if you do not extract to a subdirectory.
|
||||
3. If you want a portable installation (see [User Directories](#user-directories)), create an empty file named `portable.txt` in the same directory as the executable.
|
||||
4. Once downloaded and extracted, you can launch the emulator with `duckstation-qt-x64-ReleaseLTCG.exe`. Follow the Setup Wizard to get started.
|
||||
|
||||
**If you get an error about `vcruntime140_1.dll` being missing, you will need to update your Visual C++ runtime.** You can do that from this page: https://support.microsoft.com/en-au/help/2977003/the-latest-supported-visual-c-downloads. Specifically, you want the x64 runtime, which can be downloaded from https://aka.ms/vs/17/release/vc_redist.x64.exe.
|
||||
The Qt frontend includes an automatic update checker. Builds downloaded after 2020/08/07 will automatically check for updates each time the emulator starts, this can be disabled in Settings. Alternatively, you can force an update check by clicking `Help->Check for Updates`.
|
||||
|
||||
### Linux
|
||||
|
||||
DuckStation is provided for x86_64/ARM32/ARM64 Linux in AppImage formats.
|
||||
Prebuilt binaries for 64-bit Linux distros are available for download in the AppImage format. However, these binaries may be incompatible with older Linux distros (e.g. Ubuntu distros earlier than 18.04.4 LTS) due to older distros not providing newer versions of the C/C++ standard libraries required by the AppImage binaries.
|
||||
|
||||
#### AppImage
|
||||
**Linux users are encouraged to build from source when possible and optionally create their own AppImages for features such as desktop integration if desired.**
|
||||
|
||||
The AppImages require a distribution equivalent to Ubuntu 22.04 or newer to run.
|
||||
|
||||
1. Download https://github.com/stenzek/duckstation/releases/download/latest/DuckStation-x64.AppImage. If you have an ARM64 Linux machine, you should download `DuckStation-arm64.AppImage`.
|
||||
2. Run `chmod a+x` on the downloaded AppImage -- following this step, the AppImage can be run like a typical executable. Alternatively, in your file manager of choice, enable execute permissions via the file properties dialog.
|
||||
3. When running the AppImage for the first time, it will prompt to create a launcher shortcut.
|
||||
|
||||
If you were previously using the Flatpak package, to migrate your data from the Flatpak to the AppImage, you can run the following command:
|
||||
```bash
|
||||
mv ~/.var/app/org.duckstation.DuckStation/config/duckstation ~/.local/share
|
||||
```
|
||||
|
||||
You will need to re-add your game directories after switching to the AppImage.
|
||||
To download:
|
||||
- Go to https://github.com/stenzek/duckstation/releases/tag/latest, and download either `duckstation-qt-x64.AppImage` or `duckstation-sdl-x64.AppImage` for your desired frontend. Keep in mind that keyboard/controller bindings are currently not customizable through the SDL frontend and should be customized through the Qt frontend instead.
|
||||
- Run `chmod a+x` on the downloaded AppImage -- following this step, the AppImage can be run like a typical executable.
|
||||
- Optionally use a program such as [appimaged](https://github.com/AppImage/appimaged) or [AppImageLauncher](https://github.com/TheAssassin/AppImageLauncher) for desktop integration. [AppImageUpdate](https://github.com/AppImage/AppImageUpdate) can be used alongside appimaged to easily update your DuckStation AppImage.
|
||||
|
||||
### macOS
|
||||
|
||||
Universal macOS builds are provided for both x86_64 (Intel) and ARM64 (Apple Silicon).
|
||||
|
||||
macOS Ventura (13.3) is required, as this is also the minimum requirement for Qt.
|
||||
|
||||
To download:
|
||||
- Go to https://github.com/stenzek/duckstation/releases/tag/latest, and download the Mac build. This is a zip archive containing the prebuilt binary.
|
||||
- Alternatively, direct download link: https://github.com/stenzek/duckstation/releases/download/latest/duckstation-mac-release.zip
|
||||
- Extract the zip archive. If you're using Safari, apparently this happens automatically. This will give you DuckStation.app.
|
||||
- Right click DuckStation.app, and click Open. As the package is not signed (Mac certificates are expensive), you must do this the first time you open it. Subsequent runs can be done by double-clicking.
|
||||
|
||||
1. Download https://github.com/stenzek/duckstation/releases/download/latest/duckstation-mac-release.zip.
|
||||
2. Extract the zip by double-clicking it.
|
||||
3. Open `DuckStation.app`, optionally moving it to your desired location first.
|
||||
macOS support is considered experimental and not actively supported by the developer; the builds are provided here as a courtesy. Please feel free to submit issues, but it may be some time before
|
||||
they are investigated.
|
||||
|
||||
If you receive a message about the app being from an unidentified developer:
|
||||
**macOS builds do not support automatic updates yet.** If there is sufficient demand, this may be something I will consider.
|
||||
|
||||
1. Open System Settings -> Privacy & Security, or enter "Gatekeeper" in the search bar.
|
||||
2. Under the "Security" section, there should be a message about DuckStation being blocked. Click "Open Anyway".
|
||||
|
||||
Unfortunately this is required as Apple requires code signing for apps to be run without warnings, and I do not have a code signing certificate since a yearly cost is out of the question for a project which brings in zero revenue.
|
||||
|
||||
### Android
|
||||
|
||||
You will need a device with armv7 (32-bit ARM), AArch64 (64-bit ARM), or x86_64 (64-bit x86). 64-bit is preferred, the requirements are higher for 32-bit, you'll probably want at least a 1.5GHz CPU.
|
||||
A prebuilt APK is now available for Android. However, please keep in mind that the Android version is not yet feature complete, it is more of a preview of things to come. You will need a device running a 64-bit AArch64 userland (anything made in the last few years).
|
||||
|
||||
Download from Google Play: https://play.google.com/store/apps/details?id=com.github.stenzek.duckstation
|
||||
Download link: https://github.com/stenzek/duckstation/releases/download/latest/duckstation-android-aarch64.apk
|
||||
|
||||
**No support is provided for the Android app**, it is free and your expectations should be in line with that. Please **do not** email me about issues about it, or ask for help, you will be ignored.
|
||||
The main limitations are:
|
||||
- User directory is currently hardcoded to `<external storage path>/duckstation`. This is usually `/storage/emulated/0` or `/sdcard`'. So BIOS files go in `/sdcard/duckstation/bios`.
|
||||
- Lack of options in menu when emulator is running.
|
||||
- Performance is currently lower than the desktop x86_64 counterpart.
|
||||
|
||||
To use:
|
||||
1. Install and run the app for the first time.
|
||||
2. Follow the setup wizard.
|
||||
- Install and run the app for the first time.
|
||||
- This will create `/sdcard/duckstation`. Drop your BIOS files in `/sdcard/duckstation/bios`.
|
||||
- Add game directories by hitting the `+` icon and selecting a directory.
|
||||
- Tap a game to start.
|
||||
|
||||
If you have an external controller, you will need to map the buttons and sticks in settings.
|
||||
|
||||
### Title Information
|
||||
|
||||
PlayStation game discs do not contain title information. For game titles, we use the redump.org database cross-referenced with the game's executable code.
|
||||
This database can be manually downloaded and added as `cache/redump.dat`, or automatically downloaded by going into the `Game List Settings` in the Qt Frontend,
|
||||
and clicking `Update Redump Database`.
|
||||
|
||||
### Region detection and BIOS images
|
||||
By default, DuckStation will emulate the region check present in the CD-ROM controller of the console. This means that when the region of the console does not match the disc, it will refuse to boot, giving a "Please insert PlayStation CD-ROM" message. DuckStation supports automatic detection disc regions, and if you set the console region to auto-detect as well, this should never be a problem.
|
||||
|
||||
If you wish to use auto-detection, you do not need to change the BIOS path each time you switch regions. Simply place the BIOS images for the other regions in the **same directory** as the configured image. This will probably be in the `bios/` subdirectory. Then set the console region to "Auto-Detect", and everything should work fine. The console/log will tell you if you are missing the image for the disc's region.
|
||||
|
||||
Some users have been confused by the "BIOS Path" option, the reason it is a path and not a directory is so that an unknown BIOS revision can be used/tested.
|
||||
|
||||
Alternatively, the region checking can be disabled in the console options tab. This is the only way to play unlicensed games or homebrew which does not supply a correct region string on the disc, aside from using fastboot which skips the check entirely.
|
||||
|
||||
Mismatching the disc and console regions with the check disabled is supported, but may break games if they are patching the BIOS and expecting specific content.
|
||||
|
||||
### LibCrypt protection and SBI files
|
||||
|
||||
A number of PAL region games use LibCrypt protection, requiring additional CD subchannel information to run properly. libcrypt not functioning usually manifests as hanging or crashing, but can sometimes affect gameplay too, depending on how the game implemented it.
|
||||
|
||||
For these games, make sure that the CD image and its corresponding SBI (.sbi) file have the same name and are placed in the same directory. DuckStation will automatically load the SBI file when it is found next to the CD image.
|
||||
|
||||
For example, if your disc image was named `Spyro3.cue`, you would place the SBI file in the same directory, and name it `Spyro3.sbi`.
|
||||
|
||||
CHD images with built-in subchannel information are also supported.
|
||||
|
||||
If you are playing directly from a disc and your CD/DVD drive does not support subchannel reading, or has a skew with the returned SubQ, you can place the SBI file in the `subchannels` directory under the user directory, with the serial or title of the game.
|
||||
|
||||
### Cheats and patch database
|
||||
|
||||
DuckStation ships with a built-in cheat and patch database, both provided by the community. Contributions to these are welcome at https://github.com/duckstation/chtdb.
|
||||
|
||||
Each release includes the latest version of the database, however you are free to manually update to the latest version as well.
|
||||
A number of PAL region games use LibCrypt protection, requiring additional CD subchannel information to run properly. For these games, make sure that the CD image and its corresponding SBI (.sbi) file have the same name and are placed in the same directory. DuckStation will automatically load the SBI file when it is found next to the CD image.
|
||||
|
||||
## Building
|
||||
|
||||
### Windows
|
||||
Requirements:
|
||||
- Visual Studio 2026 or newer with the "Desktop development with C++" workload installed.
|
||||
|
||||
1. Clone the respository: `git clone https://github.com/stenzek/duckstation.git`.
|
||||
2. Download the dependencies pack from https://github.com/stenzek/duckstation-ext-qt-minimal/releases/download/latest/deps-x64.7z, and extract it to `dep\msvc`.
|
||||
3. Open the Visual Studio solution `duckstation.sln` in the root, or "Open Folder" for cmake build.
|
||||
4. Build solution.
|
||||
5. Binaries are located in `bin/x64`.
|
||||
6. Run `duckstation-qt-x64-Release.exe` or whichever config you used.
|
||||
- Visual Studio 2019
|
||||
|
||||
1. Clone the respository with submodules (`git clone --recursive` or `git clone` and `git submodule update --init`).
|
||||
2. Open the Visual Studio solution `duckstation.sln` in the root, or "Open Folder" for cmake build.
|
||||
3. Build solution.
|
||||
4. Binaries are located in `bin/x64`.
|
||||
5. Run `duckstation-sdl-x64-Release.exe`/`duckstation-qt-x64-Release.exe` or whichever config you used.
|
||||
|
||||
### Linux
|
||||
Requirements (Debian/Ubuntu package names):
|
||||
- CMake (`cmake`)
|
||||
- SDL2 (`libsdl2-dev`)
|
||||
- GTK2.0 for file selector (`libgtk2.0-dev`)
|
||||
- Qt 5 (`qtbase5-dev`, `qtbase5-private-dev`, `qtbase5-dev-tools`, `qttools5-dev`)
|
||||
- Optional for faster building: Ninja (`ninja-build`)
|
||||
|
||||
#### Required Dependencies
|
||||
|
||||
Ubuntu/Debian package names:
|
||||
```
|
||||
autoconf automake build-essential clang cmake curl extra-cmake-modules git libasound2-dev libcurl4-openssl-dev libdbus-1-dev libdecor-0-dev libegl-dev libevdev-dev libfontconfig-dev libfreetype-dev libgtk-3-dev libgudev-1.0-dev libharfbuzz-dev libinput-dev libopengl-dev libpipewire-0.3-dev libpulse-dev libssl-dev libudev-dev libwayland-dev libx11-dev libx11-xcb-dev libxcb1-dev libxcb-composite0-dev libxcb-cursor-dev libxcb-damage0-dev libxcb-glx0-dev libxcb-icccm4-dev libxcb-image0-dev libxcb-keysyms1-dev libxcb-present-dev libxcb-randr0-dev libxcb-render0-dev libxcb-render-util0-dev libxcb-shape0-dev libxcb-shm0-dev libxcb-sync-dev libxcb-util-dev libxcb-xfixes0-dev libxcb-xinput-dev libxcb-xkb-dev libxext-dev libxkbcommon-x11-dev libxrandr-dev libxss-dev libtool lld llvm nasm ninja-build pkg-config zlib1g-dev
|
||||
```
|
||||
|
||||
Fedora package names:
|
||||
```
|
||||
alsa-lib-devel autoconf automake brotli-devel clang cmake dbus-devel egl-wayland-devel extra-cmake-modules fontconfig-devel gcc-c++ gtk3-devel libavcodec-free-devel libavformat-free-devel libavutil-free-devel libcurl-devel libdecor-devel libevdev-devel libICE-devel libinput-devel libSM-devel libswresample-free-devel libswscale-free-devel libX11-devel libXau-devel libxcb-devel libXcomposite-devel libXcursor-devel libXext-devel libXfixes-devel libXft-devel libXi-devel libxkbcommon-devel libxkbcommon-x11-devel libXpresent-devel libXrandr-devel libXrender-devel libXScrnSaver-devel libtool lld llvm make mesa-libEGL-devel mesa-libGL-devel nasm ninja-build openssl-devel patch pcre2-devel perl-Digest-SHA pipewire-devel pulseaudio-libs-devel systemd-devel wayland-devel xcb-util-cursor-devel xcb-util-devel xcb-util-errors-devel xcb-util-image-devel xcb-util-keysyms-devel xcb-util-renderutil-devel xcb-util-wm-devel xcb-util-xrm-devel zlib-devel
|
||||
```
|
||||
|
||||
#### Building
|
||||
|
||||
1. Clone the repository: `git clone https://github.com/stenzek/duckstation.git`, `cd duckstation`.
|
||||
2. Build dependencies. You can save these outside of the tree if you like. This will take a while. `scripts/deps/build-dependencies-linux.sh deps`.
|
||||
3. Run CMake to configure the build system. Assuming a build subdirectory of `build-release`, run `cmake -B build-release -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_EXE_LINKER_FLAGS_INIT="-fuse-ld=lld" -DCMAKE_MODULE_LINKER_FLAGS_INIT="-fuse-ld=lld" -DCMAKE_SHARED_LINKER_FLAGS_INIT="-fuse-ld=lld" -DCMAKE_PREFIX_PATH="$PWD/deps" -G Ninja`. If you want a release (optimized) build, include `-DCMAKE_BUILD_TYPE=Release -DCMAKE_INTERPROCEDURAL_OPTIMIZATION=ON`.
|
||||
4. Compile the source code. For the example above, run `ninja -C build-release`
|
||||
5. Run the binary, located in the build directory under `./build-release/bin/duckstation-qt`.
|
||||
1. Clone the repository. Submodules aren't necessary, there is only one and it is only used for Windows.
|
||||
2. Create a build directory, either in-tree or elsewhere.
|
||||
3. Run cmake to configure the build system. Assuming a build subdirectory of `build-release`, `cd build-release && cmake -DCMAKE_BUILD_TYPE=Release -GNinja ..`.
|
||||
4. Compile the source code. For the example above, run `ninja`.
|
||||
5. Run the binary, located in the build directory under `bin/duckstation-sdl`, or `bin/duckstation-qt`.
|
||||
|
||||
### macOS
|
||||
**NOTE:** macOS is highly experimental and not tested by the developer. Use at your own risk, things may be horribly broken.
|
||||
|
||||
Requirements:
|
||||
- CMake
|
||||
- Xcode
|
||||
- CMake (installed by default? otherwise, `brew install cmake`)
|
||||
- SDL2 (`brew install sdl2`)
|
||||
- Qt 5 (`brew install qt5`)
|
||||
|
||||
1. Clone the repository: `git clone https://github.com/stenzek/duckstation.git`.
|
||||
2. Build the dependencies. This will take a while. `scripts/deps/build-dependencies-mac.sh deps`.
|
||||
2. Run CMake to configure the build system: `cmake -Bbuild-release -DCMAKE_BUILD_TYPE=Release -DCMAKE_INTERPROCEDURAL_OPTIMIZATION=ON -DCMAKE_PREFIX_PATH="$PWD/deps"`.
|
||||
4. Compile the source code: `cmake --build build-release --parallel`.
|
||||
5. Run the binary, located in the build directory under `bin/DuckStation.app`.
|
||||
1. Clone the repository. Submodules aren't necessary, there is only one and it is only used for Windows.
|
||||
2. Clone the mac externals repository (for MoltenVK): `git clone https://github.com/stenzek/duckstation-ext-mac.git dep/mac`.
|
||||
2. Create a build directory, either in-tree or elsewhere, e.g. `mkdir build-release`, `cd build-release`.
|
||||
3. Run cmake to configure the build system: `cmake -DCMAKE_BUILD_TYPE=Release -DQt5_DIR=/usr/local/opt/qt/lib/cmake/Qt5 ..`. You may need to tweak `Qt5_DIR` depending on your system.
|
||||
4. Compile the source code: `make`. Use `make -jN` where `N` is the number of CPU cores in your system for a faster build.
|
||||
5. Run the binary, located in the build directory under `bin/duckstation-sdl`, or `bin/DuckStation.app` for Qt.
|
||||
|
||||
### Android
|
||||
**NOTE:** The Android frontend is still incomplete, not all functionality is available yet. User directory is hardcoded to `/sdcard/duckstation` for now.
|
||||
|
||||
Requirements:
|
||||
- Android Studio with the NDK and CMake installed
|
||||
|
||||
1. Clone the repository. Submodules aren't necessary, there is only one and it is only used for Windows.
|
||||
2. Open the project in the `android` directory.
|
||||
3. Select Build -> Build Bundle(s) / APKs(s) -> Build APK(s).
|
||||
4. Install APK on device, or use Run menu for attached device.
|
||||
|
||||
## User Directories
|
||||
The "User Directory" is where you should place your BIOS images, where settings are saved to, and memory cards/save states are saved by default.
|
||||
@@ -231,56 +221,97 @@ An optional [SDL game controller database file](#sdl-game-controller-database) c
|
||||
|
||||
This is located in the following places depending on the platform you're using:
|
||||
|
||||
- Windows: `AppData\Local\DuckStation` (old installs will use `Documents\DuckStation`).
|
||||
- Windows: My Documents\DuckStation
|
||||
- Linux: `$XDG_DATA_HOME/duckstation`, or `~/.local/share/duckstation`.
|
||||
- macOS: `~/Library/Application Support/DuckStation`.
|
||||
|
||||
So, if you were using Linux, you would place your BIOS images in `~/.local/share/duckstation/bios`. This directory will be created upon running DuckStation for the first time.
|
||||
So, if you were using Linux, you would place your BIOS images in `~/.local/share/duckstation/bios`. This directory will be created upon running DuckStation
|
||||
for the first time.
|
||||
|
||||
If you wish to use a "portable" build, where the user directory is the same as where the executable is located, create an empty file named `portable.txt` in the same directory as the DuckStation executable.
|
||||
|
||||
A shortcut to open the user directory is available by selecting `Open Data Directory` from the `Tools` menu.
|
||||
|
||||
DuckStation allows you to override certain resources by placing files in the `resources` subdirectory of the user directory. This includes images and sound effects (e.g. menu navigation/achievement unlock). Please refer to https://github.com/stenzek/duckstation/wiki/Resource-Overrides for more information.
|
||||
If you wish to use a "portable" build, where the user directory is the same as where the executable is located, create an empty file named `portable.txt`
|
||||
in the same directory as the DuckStation executable.
|
||||
|
||||
## Bindings for Qt frontend
|
||||
Your keyboard or game controller can be used to simulate a variety of PlayStation controllers. Controller input is supported through DInput, XInput, and SDL backends and can be changed through `Settings -> Controllers`.
|
||||
Your keyboard and any SDL-compatible game controller can be used to simulate the PS Controller. To bind keys/controllers to buttons, go to
|
||||
`Settings -> Port Settings`. Each of the buttons will be listed, along with the corresponding key it is bound to. To re-bind the button to a new key,
|
||||
click the button next to button name, and press the key/button you want to use within 5 seconds.
|
||||
|
||||
To bind your input device, go to `Settings -> Controllers`, and select the virtual controller you want to map. Automatic mapping handles the majority of controllers. However, if you need to manually bind a controller, click the box below the button/axis name, and press the key or button on your input device that you wish to bind to.
|
||||
**Currently, it is only possible to bind one input to each controller button/axis. Multiple bindings per button are planned for the future.**
|
||||
|
||||
## Bindings for SDL frontend
|
||||
Keyboard bindings in the SDL frontend are currently not customizable in the frontend itself. You should use the Qt frontend to set up your key/controller bindings first.
|
||||
|
||||
## SDL Game Controller Database
|
||||
DuckStation releases ship with a database of game controller mappings for the SDL controller backend, courtesy of https://github.com/mdqinc/SDL_GameControllerDB. The included `gamecontrollerdb.txt` file can be found in the `resources` subdirectory of the DuckStation program directory.
|
||||
DuckStation uses the SDL2 GameController API for input handling which requires controller devices to have known input mappings.
|
||||
SDL2 provides an embedded database of recognised controllers in its own source code, however it is rather small and thus limited in practice.
|
||||
|
||||
If you are experiencing issues binding your controller with the SDL controller backend, you may need to add a custom mapping to the database file. Make a copy of `gamecontrollerdb.txt` and place it in your [user directory](#user-directories) (or directly in the program directory, if running in portable mode) and then follow the instructions in the [SDL_GameControllerDB repository](https://github.com/mdqinc/SDL_GameControllerDB) for creating a new mapping. Add this mapping to the new copy of `gamecontrollerdb.txt` and your controller should then be recognized properly.
|
||||
There is an officially endorsed [community sourced database](https://github.com/gabomdq/SDL_GameControllerDB) that can be used to support a much broader range of game controllers in DuckStation.
|
||||
If your controller is not recognized by DuckStation but can be found in the community database above, just download a recent copy of the `gamecontrollerdb.txt` database file and place it in your [User directory](#user-directories). Your controller should now be recognized by DuckStation.
|
||||
|
||||
Alternatively, you can also create your own custom controller mappings from scratch easily using readily available tools. See the referenced community database repository for more information.
|
||||
|
||||
Using a mappings database is specially useful when using non-XInput game controllers with DuckStation.
|
||||
|
||||
## Default bindings
|
||||
|
||||
Bindings for controllers and hotkeys can be changed in `Settings -> Controllers`.
|
||||
|
||||
Controller 1:
|
||||
- **Left Stick:** W/A/S/D
|
||||
- **Right Stick:** T/F/G/H
|
||||
- **D-Pad:** Up/Left/Down/Right
|
||||
- **Triangle/Square/Circle/Cross:** I/J/L/K
|
||||
- **D-Pad:** W/A/S/D
|
||||
- **Triangle/Square/Circle/Cross:** Numpad8/Numpad4/Numpad6/Numpad2
|
||||
- **L1/R1:** Q/E
|
||||
- **L2/R2:** 1/3
|
||||
- **L3/R3:** 2/4
|
||||
- **L2/L2:** 1/3
|
||||
- **Start:** Enter
|
||||
- **Select:** Backspace
|
||||
|
||||
Hotkeys:
|
||||
- **Escape:** Open Pause Menu
|
||||
- **F1:** Load State
|
||||
- **F2:** Save State
|
||||
- **F3:** Select Previous Save State
|
||||
- **F4:** Select Next Save State
|
||||
- **F10:** Save Screenshot
|
||||
- **F11:** Toggle Fullscreen
|
||||
- **Tab:** Temporarily Disable Speed Limiter
|
||||
- **Space:** Pause/Resume Emulation
|
||||
- **Escape:** Power off console
|
||||
- **ALT+ENTER:** Toggle fullscreen
|
||||
- **Tab:** Temporarily disable speed limiter
|
||||
- **Pause/Break:** Pause/resume emulation
|
||||
- **Page Up/Down:** Increase/decrease resolution scale in hardware renderers
|
||||
- **End:** Toggle software renderer
|
||||
|
||||
## Libretro Core
|
||||
|
||||
DuckStation is available as a libretro core, which can be loaded into a frontend such as RetroArch. It supports most features of the full frontend, within the constraints and limitations of being a libretro core.
|
||||
|
||||
Prebuilt binaries for 64-bit Windows, Linux and Android can be found on the releases page. Direct links:
|
||||
- 64-bit Windows: https://github.com/stenzek/duckstation/releases/download/latest/duckstation_libretro.dll.zip
|
||||
- 64-bit Linux: https://github.com/stenzek/duckstation/releases/download/latest/duckstation_libretro_x64.so.zip
|
||||
- AArch64 Linux: https://github.com/stenzek/duckstation/releases/download/latest/duckstation_libretro_linux_aarch64.so.zip
|
||||
- AArch64 Android: https://github.com/stenzek/duckstation/releases/download/latest/duckstation_libretro_android_aarch64.so.zip
|
||||
|
||||
To use, download and extract, and install the core file in RetroArch or your preferred frontend.
|
||||
|
||||
To build on Windows, use cmake using the following commands from a `x64 Native Tools Command Prompt for VS 2019`:
|
||||
- mkdir build
|
||||
- cd build
|
||||
- cmake -G Ninja -DCMAKE_BUILD_TYPE=Release -DBUILD_LIBRETRO_CORE=ON ..
|
||||
|
||||
You should then have a file named `duckstation_libretro.dll` which can be loaded as a core.
|
||||
|
||||
To build on Linux, follow the same instructions as for a normal build, but for cmake use `cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_LIBRETRO_CORE=ON ..`. The shared library will be named `duckstation_libretro.so` in the current directory.
|
||||
|
||||
## Tests
|
||||
- Passes amidog's CPU and GTE tests in both interpreter and recompiler modes, partial passing of CPX tests
|
||||
|
||||
## Screenshots
|
||||
<p align="center">
|
||||
<a href="https://raw.githubusercontent.com/stenzek/duckstation/md-images/monkey.jpg"><img src="https://raw.githubusercontent.com/stenzek/duckstation/md-images/monkey.jpg" alt="Monkey Hero" width="400" /></a>
|
||||
<a href="https://raw.githubusercontent.com/stenzek/duckstation/md-images/rrt4.jpg"><img src="https://raw.githubusercontent.com/stenzek/duckstation/md-images/rrt4.jpg" alt="Ridge Racer Type 4" width="400" /></a>
|
||||
<a href="https://raw.githubusercontent.com/stenzek/duckstation/md-images/tr2.jpg"><img src="https://raw.githubusercontent.com/stenzek/duckstation/md-images/tr2.jpg" alt="Tomb Raider 2" width="400" /></a>
|
||||
<a href="https://raw.githubusercontent.com/stenzek/duckstation/md-images/quake2.jpg"><img src="https://raw.githubusercontent.com/stenzek/duckstation/md-images/quake2.jpg" alt="Quake 2" width="400" /></a>
|
||||
<a href="https://raw.githubusercontent.com/stenzek/duckstation/md-images/croc.jpg"><img src="https://raw.githubusercontent.com/stenzek/duckstation/md-images/croc.jpg" alt="Croc" width="400" /></a>
|
||||
<a href="https://raw.githubusercontent.com/stenzek/duckstation/md-images/croc2.jpg"><img src="https://raw.githubusercontent.com/stenzek/duckstation/md-images/croc2.jpg" alt="Croc 2" width="400" /></a>
|
||||
<a href="https://raw.githubusercontent.com/stenzek/duckstation/md-images/ff7.jpg"><img src="https://raw.githubusercontent.com/stenzek/duckstation/md-images/ff7.jpg" alt="Final Fantasy 7" width="400" /></a>
|
||||
<a href="https://raw.githubusercontent.com/stenzek/duckstation/md-images/ff8.jpg"><img src="https://raw.githubusercontent.com/stenzek/duckstation/md-images/ff8.jpg" alt="Final Fantasy 8" width="400" /></a>
|
||||
<a href="https://raw.githubusercontent.com/stenzek/duckstation/md-images/main.png"><img src="https://raw.githubusercontent.com/stenzek/duckstation/md-images/main.png" alt="SDL Frontend" width="400" /></a>
|
||||
<a href="https://raw.githubusercontent.com/stenzek/duckstation/md-images/spyro.jpg"><img src="https://raw.githubusercontent.com/stenzek/duckstation/md-images/spyro.jpg" alt="Spyro 2" width="400" /></a>
|
||||
<a href="https://raw.githubusercontent.com/stenzek/duckstation/md-images/gamegrid.png"><img src="https://raw.githubusercontent.com/stenzek/duckstation/md-images/gamegrid.png" alt="Game Grid" width="400" /></a>
|
||||
</p>
|
||||
|
||||
## Disclaimers
|
||||
|
||||
Icon by icons8: https://icons8.com/icon/74847/platforms.undefined.short-title
|
||||
|
||||
"PlayStation" and "PSX" are registered trademarks of Sony Interactive Entertainment Europe Limited. This project is not affiliated in any way with Sony Interactive Entertainment.
|
||||
|
||||
|
||||
|
||||
222
README.pt-br.md
222
README.pt-br.md
@@ -1,222 +0,0 @@
|
||||
Tradução:
|
||||
|
||||
# DuckStation - Emulador de PlayStation 1, também conhecido como PSX
|
||||
|
||||
[Últimas Notícias](#latest-news) | [Recursos](#features) | [Download e Execução](#downloading-and-running) | [Compilação](#building) | [Avisos Legais](#disclaimers)
|
||||
|
||||
**Últimas Versões para Windows 10/11, Linux (AppImage/Flatpak) e macOS:** https://github.com/stenzek/duckstation/releases/tag/latest
|
||||
|
||||
**Lista de Compatibilidade de Jogos:** https://docs.google.com/spreadsheets/d/1H66MxViRjjE5f8hOl5RQmF5woS1murio2dsLn14kEqo/edit
|
||||
|
||||
**Wiki:** https://www.duckstation.org/wiki/
|
||||
|
||||
DuckStation é um simulador/emulador do console Sony PlayStation(TM), focando na jogabilidade, velocidade e manutenção a longo prazo. O objetivo é ser o mais preciso possível, mantendo um desempenho adequado para dispositivos de baixo desempenho. Opções de "hack" não são recomendadas, a configuração padrão deve suportar todos os jogos jogáveis, com apenas algumas das melhorias tendo problemas de compatibilidade.
|
||||
|
||||
Uma imagem ROM do "BIOS" é necessária para iniciar o emulador e jogar os jogos. Você pode usar uma imagem de qualquer versão de hardware ou região, embora regiões de jogos e regiões de BIOS que não idênticas podem resultar em problemas de compatibilidade. A imagem ROM ou Jogo não é fornecida com o emulador por motivos legais; você deve obter do seu próprio console usando Caetla ou outros meios.
|
||||
|
||||
## Recursos
|
||||
|
||||
O DuckStation possui uma interface totalmente funcional construída usando Qt, bem como uma interface de tela cheia/TV baseada no Dear ImGui.
|
||||
|
||||
<p align="center">
|
||||
<img src="https://raw.githubusercontent.com/stenzek/duckstation/md-images/main-qt.png" alt="Captura de Tela da Janela Principal" />
|
||||
<img src="https://raw.githubusercontent.com/stenzek/duckstation/md-images/bigduck.png" alt="Captura de Tela da Interface de Tela Cheia" />
|
||||
</p>
|
||||
|
||||
Outros recursos incluem:
|
||||
|
||||
- Recompilador de CPU/JIT (x86-64, armv7/AArch32 e AArch64).
|
||||
- Renderização de hardware (D3D11, D3D12, OpenGL, Vulkan, Metal) e renderização de software.
|
||||
- Ampliação, filtragem de textura e cor verdadeira (24 bits) nos renderizadores de hardware.
|
||||
- PGXP para precisão de geometria, correção de textura e emulação de buffer de profundidade.
|
||||
- Filtro de downsampling adaptativo.
|
||||
- Cadeias de shaders de pós-processamento (GLSL e Reshade FX experimental).
|
||||
- "Inicialização rápida" para pular a tela de abertura/intro do BIOS.
|
||||
- Suporte a salvar estados.
|
||||
- Suporte para Windows, Linux e macOS.
|
||||
- Suporta imagens bin/cue, arquivos bin/img crus, MAME CHD, ECM de única faixa, MDS/MDF e formatos PBP não criptografados.
|
||||
- Inicialização direta de executáveis homebrew.
|
||||
- Carregamento direto de arquivos Portable Sound Format (psf).
|
||||
- Controles digitais e analógicos.
|
||||
- Suporte ao lightgun Namco GunCon (simulado com o mouse).
|
||||
- Suporte ao NeGcon.
|
||||
- Interface Qt e "Big Picture".
|
||||
- Atualizações automáticas a partir dos canais oficiais.
|
||||
- Verificação automática de conteúdo - os títulos/jogos são fornecidos por redump.org.
|
||||
- Troca automática opcional de cartões de memória para cada jogo.
|
||||
- Suporta carregar trapaças de listas existentes.
|
||||
- Editor de cartões de memória e importador de salvamento.
|
||||
- Overclock emulado de CPU.
|
||||
- Depuração integrada e remota.
|
||||
- Controles multitap (até 8 dispositivos).
|
||||
- RetroAchievements.
|
||||
- Carregamento/aplicação automática de patches PPF.
|
||||
|
||||
## Requisitos do Sistema
|
||||
- Um CPU rápido. Mas precisa ser x86_64, AArch32/armv7 ou AArch64/ARMv8, caso contrário, o recompilação será lenta.
|
||||
- Para os renderizadores de hardware, é necessário uma GPU compatível com OpenGL 3.1/OpenGL ES 3.1/Direct3D 11 Feature Level 10.0 (ou Vulkan 1.0) e superior. basicamente, qualquer computador produzido nos últimos 10 anos mais ou menos deve dar conta.
|
||||
- Controlador de jogo compatível com SDL, XInput ou DInput (por exemplo, XB360/XBOne/XBSeries). Usuários de DualShock 3 no Windows precisarão instalar os drivers oficiais do DualShock 3 incluídos como parte do PlayStation Now.
|
||||
|
||||
## Download e Execução
|
||||
Executáveis do DuckStation para Windows x64/ARM64, Linux x86_64 (nos formatos AppImage/Flatpak) e para macOS estão disponíveis via GitHub na aba Releases e são automaticamente compilados a cada commit/envio. Executáveis ou pacotes distribuídos por outras fontes podem estar desatualizados e não são suportados pelo desenvolvedor, por favor, entre em contato com eles para obter suporte, não conosco.
|
||||
|
||||
### Windows
|
||||
|
||||
DuckStation **requer** Windows 10/11, especificamente a versão 1809 ou mais recente. Se você ainda estiver usando Windows 7/8/8.1, o DuckStation **não funcionará** no seu sistema operacional. Usar esses sistemas operacionais em 2023 deve ser considerado um risco de segurança, recomendaria atualizar para algo que receba suporte do fornecedor.
|
||||
Se você precisa usar um sistema operacional mais antigo, [v0.1-5624](https://github.com/stenzek/duckstation/releases/tag/v0.1-5624) é a última versão que funcionará. Mas não espere receber nenhuma assistência, essas compilações não são mais suportadas.
|
||||
|
||||
Para baixar:
|
||||
- Acesse https://github.com/stenzek/duckstation/releases/tag/latest e baixe a compilação do Windows x64. Este é um arquivo ZIP contendo o executável pré-compilado.
|
||||
- Alternativamente, link de download direto: https://github.com/stenzek/duckstation/releases/download/latest/duckstation-windows-x64-release.zip
|
||||
- Extraia o arquivo ZIP **para uma pasta**. O arquivo ZIP não tem um subdiretório raiz, então, se você não extrair para um subdiretório, ele irá despejar vários arquivos no seu diretório de download.
|
||||
|
||||
Depois de baixado e extraído, pode iniciar o emulador com `duckstation-qt-x64-ReleaseLTCG.exe`. Siga o Assistente de configuração para começar.
|
||||
|
||||
**Se você receber um erro sobre a falta de `vcruntime140_1.dll`, precisará atualizar sua runtime do Visual C++.**faça da seguinte forma, nesta página: https://support.microsoft.com/en-au/help/2977003/the-latest-supported-visual-c-downloads. Especificamente, você deseja a runtime x64, que pode ser baixada em https://aka.ms/vs/17/release/vc_redist.x64.exe.
|
||||
|
||||
### Linux
|
||||
|
||||
As únicas versões suportadas do DuckStation para Linux são o AppImage e o Flatpak na página de lançamentos. Se você instalou o DuckStation de outra fonte ou distribuição (por exemplo, EmuDeck), você deve entrar em contato com o responsável para suporte, nós não temos controle sobre isso.
|
||||
|
||||
#### AppImage
|
||||
|
||||
Os AppImages requerem uma distribuição equivalente ao Ubuntu 22.04 ou mais recente para serem executados.
|
||||
|
||||
- Acesse https://github.com/stenzek/duckstation/releases/tag/latest e baixe `duckstation-x64.AppImage`.
|
||||
- Execute `chmod a+x` no AppImage baixado -- após este passo, o AppImage pode ser executado como um executável típico.
|
||||
|
||||
#### Flatpak
|
||||
|
||||
- Acesse https://github.com/stenzek/duckstation/releases/tag/latest e baixe `duckstation-x64.flatpak`.
|
||||
- Execute `flatpak install ./duckstation-x64.flatpak`.
|
||||
|
||||
ou, se você tiver o FlatHub configurado:
|
||||
- Execute `flatpak install org.duckstation.DuckStation`.
|
||||
|
||||
Use `flatpak run org.duckstation.DuckStation` para iniciar, ou selecione `DuckStation` no lançador do seu ambiente de desktop. Siga o Assistente de Configuração para começar.
|
||||
|
||||
### macOS
|
||||
|
||||
São fornecidas compilações universais do macOS para x86_64 (Intel) e ARM64 (Apple Silicon).
|
||||
|
||||
macOS Ventura (13.3) é necessário, pois também é o requisito mínimo para o Qt.
|
||||
|
||||
Para baixar:
|
||||
- Acesse https://github.com/stenzek/duckstation/releases/tag/latest e baixe `duckstation-mac-release.zip`.
|
||||
- Extraia o arquivo ZIP dando um duplo clique nele.
|
||||
- Abra o `DuckStation.app`, opcionalmente movendo-o para a localização desejada antes.
|
||||
- Dependendo da configuração do GateKeeper, você pode precisar clicar com o botão direito -> Abrir na primeira vez que executá-lo, já que certificados de assinatura de código estão fora de questão para um projeto que não gera receita alguma.
|
||||
|
||||
### Android
|
||||
|
||||
Você precisará de um dispositivo com armv7 (32 bits ARM), AArch64 (64 bits ARM) ou x86_64 (64 bits x86). 64 bits são preferíveis, os requisitos são mais altos para 32 bits, você provavelmente vai querer pelo menos um CPU de 1,5 GHz.
|
||||
|
||||
A distribuição pelo Google Play é o mecanismo de distribuição recomendado e resultará em tamanhos de download menores: https://play.google.com/store/apps/details?id=com.github.stenzek.duckstation
|
||||
|
||||
**Não é fornecido suporte para o aplicativo Android**, ele é gratuito e suas expectativas devem estar alinhadas com isso. Por favor, **não** me envie e-mails sobre problemas relacionados a ele, eles serão ignorados.
|
||||
|
||||
Se você precisar usar um APK, os links para download estão listados em https://www.duckstation.org/android/
|
||||
|
||||
Para usar:
|
||||
1. Instale e execute o aplicativo pela primeira vez.
|
||||
2. Adicione diretórios de jogos tocando no botão de adição e selecionando um diretório. Você pode adicionar diretórios adicionais depois selecionando "Editar Diretórios de Jogos" no menu.
|
||||
3. Toque em um jogo para começar. Quando você inicia um jogo pela primeira vez, ele pedirá para importar uma imagem de BIOS.
|
||||
|
||||
Se você tiver um controle externo, precisará mapear os botões e analogicos nas configurações.
|
||||
|
||||
### Proteção LibCrypt e arquivos SBI
|
||||
|
||||
Alguns jogos da região PAL usam a proteção LibCrypt, que requer informações adicionais de subcanal de CD para funcionar corretamente. O não funcionamento do libcrypt geralmente se manifesta como travamentos, mas às vezes pode afetar a jogabilidade, dependendo de como o jogo o implementou.
|
||||
|
||||
Para esses jogos, certifique-se de que a imagem do CD e seu arquivo correspondente SBI (.sbi) tenham o mesmo nome e estejam na mesma pasta. O DuckStation carregará automaticamente o arquivo SBI quando ele for encontrado ao lado da imagem do CD.
|
||||
|
||||
Por exemplo, se sua imagem de disco se chamasse `Spyro3.cue`, você colocaria o arquivo SBI na mesma pasta e o nomearia como `Spyro3.sbi`.
|
||||
|
||||
## Compilação
|
||||
|
||||
### Windows
|
||||
Requisitos:
|
||||
- Visual Studio 2022
|
||||
|
||||
1. Clone o repositório: `git clone https://github.com/stenzek/duckstation.git`.
|
||||
2. Baixe o pacote de dependências em https://github.com/stenzek/duckstation-ext-qt-minimal/releases/download/latest/deps-x64.7z e extraia-o para `dep\msvc`.
|
||||
3. Abra a solução do Visual Studio `duckstation.sln` na raiz ou "Abrir Pasta" para a compilação com CMake.
|
||||
4. Compile a solução.
|
||||
5. Os binários estão localizados em `bin/x64`.
|
||||
6. Execute `duckstation-qt-x64-Release.exe` ou a configuração que você usou.
|
||||
|
||||
### Linux
|
||||
Requisitos (nomes de pacotes Debian/Ubuntu):
|
||||
- CMake (`cmake`)
|
||||
- SDL2 (pelo menos a versão 2.28.2) (`libsdl2-dev` `libxrandr-dev`)
|
||||
- pkgconfig (`pkg-config`)
|
||||
- Qt 6 (pelo menos a versão 6.5.1) (`qt6-base-dev` `qt6-base-private-dev` `qt6-base-dev-tools` `qt6-tools-dev` `libqt6svg6`)
|
||||
- git (`git`) (Nota: necessário para clonar o repositório e na hora da compilação)
|
||||
- Quando o Wayland estiver habilitado (padrão): (`libwayland-dev` `libwayland-egl-backend-dev` `extra-cmake-modules` `qt6-wayland`)
|
||||
- libcurl (`libcurl4-openssl-dev`)
|
||||
- Opcional para compilação mais rápida: Ninja (`ninja-build`)
|
||||
|
||||
1. Clone o repositório: `git clone https://github.com/stenzek/duckstation.git -b dev`.
|
||||
2. Crie um diretório de compilação, seja dentro ou fora do diretório de origem.
|
||||
3. Execute o CMake para configurar o sistema de compilação. Supondo que o diretório de compilação seja `build-release`, execute `cmake -Bbuild-release -DCMAKE_BUILD_TYPE=Release`. Se você tiver o Ninja instalado, adicione `-GNinja` ao final da linha de comando do CMake para compilações mais rápidas.
|
||||
4. Compile o código-fonte. Para o exemplo acima, execute `cmake --build build-release --parallel`.
|
||||
5. Execute o binário, que está localizado no diretório de compilação em `bin/duckstation-qt`.
|
||||
|
||||
### macOS
|
||||
|
||||
Requisitos:
|
||||
- CMake
|
||||
- SDL2 (pelo menos a versão 2.28.2)
|
||||
- Qt 6 (pelo menos a versão 6.5.1)
|
||||
|
||||
Opcional (recomendado para compilações mais rápidas):
|
||||
- Ninja
|
||||
|
||||
1. Clone o repositório: `git clone https://github.com/stenzek/duckstation.git`.
|
||||
2. Execute o CMake para configurar o sistema de compilação: `cmake -Bbuild-release -DCMAKE_BUILD_TYPE=Release`. Você pode precisar especificar `-DQt6_DIR` dependendo do seu sistema. Se você tiver o Ninja instalado, adicione `-GNinja` ao final da linha de comando do CMake para compilações mais rápidas.
|
||||
4. Compile o código-fonte: `cmake --build build-release --parallel`.
|
||||
5. Execute o binário, que está localizado no diretório de compilação em `bin/DuckStation.app`.
|
||||
|
||||
## Diretórios de Usuários
|
||||
O "Diretório de Usuário" é onde você deve colocar suas imagens da BIOS, onde as configurações são salvas e onde os cartões de memória e estados de salvamento são salvos por padrão. Um [arquivo opcional de banco de dados de controle de jogo SDL](#sdl-game-controller-database) também pode ser colocado aqui.
|
||||
|
||||
Ele está localizado nos seguintes lugares, dependendo da plataforma que você está usando:
|
||||
|
||||
- Windows: Meus Documentos\DuckStation
|
||||
- Linux: `$XDG_DATA_HOME/duckstation`, ou `~/.local/share/duckstation`.
|
||||
- macOS: `~/Library/Application Support/DuckStation`.
|
||||
|
||||
Portanto, se você estiver usando o Linux, sugiro colocar suas imagens do BIOS em `~/.local/share/duckstation/bios`. Este diretório será criado na primeira vez que você executar o DuckStation.
|
||||
|
||||
Se você deseja usar uma compilação "portátil", onde o diretório do usuário é o mesmo onde o executável está localizado, crie um arquivo vazio chamado `portable.txt` no mesmo diretório onde o executável do DuckStation está.
|
||||
|
||||
## Associações para a interface Qt
|
||||
Seu teclado ou controle podem ser usados para simular uma variedade de controles de PlayStation. A entrada do controle é suportada através dos back-ends DInput, XInput e SDL e pode ser alterada em `Configurações -> Configurações Gerais`.
|
||||
|
||||
Para atribuir seu dispositivo de entrada, vá para `Configurações -> Configurações do Controle`. Cada um dos botões/eixos para o controle emulador será listado, juntamente com a tecla/botão correspondente do seu dispositivo a que ele atualmente em uso. Para atribuir novamente, clique na caixa ao lado do nome do botão/eixo e pressione a tecla ou botão do seu dispositivo de entrada que deseja atribuir. Ao atribuir a vibração, basta pressionar qualquer botão no controle para o qual você deseja que seja configurado.
|
||||
|
||||
## Banco de Dados de Controle de Jogo SDL
|
||||
Os lançamentos do DuckStation incluem um banco de dados de mapeamentos de controle de jogo para o back-end do controle SDL, cortesia de https://github.com/gabomdq/SDL_GameControllerDB. O arquivo `gamecontrollerdb.txt` incluído pode ser encontrado no subdiretório `database` do diretório do programa DuckStation.
|
||||
|
||||
Se você estiver tendo problemas para associar seu controle com o back-end do controlador SDL, pode ser necessário adicionar um mapeamento personalizado ao arquivo de banco de dados. Faça uma cópia de `gamecontrollerdb.txt` e coloque-o no seu [diretório de usuário](#user-directories) (ou diretamente no diretório do programa, se estiver executando em modo portátil) e siga as instruções no [repositório SDL_GameControllerDB](https://github.com/gabomdq/SDL_GameControllerDB) para criar um novo mapeamento. Adicione este mapeamento à nova cópia de `gamecontrollerdb.txt` e seu controle deve ser reconhecido corretamente.
|
||||
|
||||
## Atribuições padrão
|
||||
Controle 1:
|
||||
- **D-Pad:** W/A/S/D
|
||||
- **Triângulo/Quadrado/Círculo/Cruz:** Numpad8/Numpad4/Numpad6/Numpad2
|
||||
- **L1/R1:** Q/E
|
||||
- **L2/R2:** 1/3
|
||||
- **Start:** Enter
|
||||
- **Select:** Backspace
|
||||
|
||||
Atalhos:
|
||||
- **Esc:** Abrir Menu de Pausa
|
||||
- **F11:** Alternar Tela Cheia
|
||||
- **Tab:** Desativar Temporariamente o Limitador de Velocidade
|
||||
- **Espaço:** Pausar/Continuar Emulação
|
||||
|
||||
## Avisos Legais
|
||||
|
||||
Ícone por icons8: https://icons8.com/icon/74847/platforms.undefined.short-title
|
||||
|
||||
"PlayStation" e "PSX" são marcas registradas da Sony Interactive Entertainment Europe Limited. Este projeto não está afiliado de forma alguma com a Sony Interactive Entertainment.
|
||||
14
android/.gitignore
vendored
Normal file
14
android/.gitignore
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
*.iml
|
||||
.gradle
|
||||
/local.properties
|
||||
/.idea/caches
|
||||
/.idea/libraries
|
||||
/.idea/modules.xml
|
||||
/.idea/workspace.xml
|
||||
/.idea/navEditor.xml
|
||||
/.idea/assetWizardSettings.xml
|
||||
.DS_Store
|
||||
/build
|
||||
/captures
|
||||
.externalNativeBuild
|
||||
.cxx
|
||||
1
android/.idea/.name
generated
Normal file
1
android/.idea/.name
generated
Normal file
@@ -0,0 +1 @@
|
||||
DuckStation
|
||||
116
android/.idea/codeStyles/Project.xml
generated
Normal file
116
android/.idea/codeStyles/Project.xml
generated
Normal file
@@ -0,0 +1,116 @@
|
||||
<component name="ProjectCodeStyleConfiguration">
|
||||
<code_scheme name="Project" version="173">
|
||||
<codeStyleSettings language="XML">
|
||||
<indentOptions>
|
||||
<option name="CONTINUATION_INDENT_SIZE" value="4" />
|
||||
</indentOptions>
|
||||
<arrangement>
|
||||
<rules>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>xmlns:android</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>xmlns:.*</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
<order>BY_NAME</order>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*:id</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*:name</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>name</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>style</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
<order>BY_NAME</order>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
<order>ANDROID_ATTRIBUTE_ORDER</order>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>.*</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
<order>BY_NAME</order>
|
||||
</rule>
|
||||
</section>
|
||||
</rules>
|
||||
</arrangement>
|
||||
</codeStyleSettings>
|
||||
</code_scheme>
|
||||
</component>
|
||||
5
android/.idea/codeStyles/codeStyleConfig.xml
generated
Normal file
5
android/.idea/codeStyles/codeStyleConfig.xml
generated
Normal file
@@ -0,0 +1,5 @@
|
||||
<component name="ProjectCodeStyleConfiguration">
|
||||
<state>
|
||||
<option name="PREFERRED_PROJECT_CODE_STYLE" value="Default" />
|
||||
</state>
|
||||
</component>
|
||||
20
android/.idea/gradle.xml
generated
Normal file
20
android/.idea/gradle.xml
generated
Normal file
@@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="GradleMigrationSettings" migrationVersion="1" />
|
||||
<component name="GradleSettings">
|
||||
<option name="linkedExternalProjectsSettings">
|
||||
<GradleProjectSettings>
|
||||
<option name="testRunner" value="PLATFORM" />
|
||||
<option name="distributionType" value="DEFAULT_WRAPPED" />
|
||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||
<option name="modules">
|
||||
<set>
|
||||
<option value="$PROJECT_DIR$" />
|
||||
<option value="$PROJECT_DIR$/app" />
|
||||
</set>
|
||||
</option>
|
||||
<option name="resolveModulePerSourceSet" value="false" />
|
||||
</GradleProjectSettings>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
||||
25
android/.idea/jarRepositories.xml
generated
Normal file
25
android/.idea/jarRepositories.xml
generated
Normal file
@@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="RemoteRepositoriesConfiguration">
|
||||
<remote-repository>
|
||||
<option name="id" value="central" />
|
||||
<option name="name" value="Maven Central repository" />
|
||||
<option name="url" value="https://repo1.maven.org/maven2" />
|
||||
</remote-repository>
|
||||
<remote-repository>
|
||||
<option name="id" value="jboss.community" />
|
||||
<option name="name" value="JBoss Community repository" />
|
||||
<option name="url" value="https://repository.jboss.org/nexus/content/repositories/public/" />
|
||||
</remote-repository>
|
||||
<remote-repository>
|
||||
<option name="id" value="BintrayJCenter" />
|
||||
<option name="name" value="BintrayJCenter" />
|
||||
<option name="url" value="https://jcenter.bintray.com/" />
|
||||
</remote-repository>
|
||||
<remote-repository>
|
||||
<option name="id" value="Google" />
|
||||
<option name="name" value="Google" />
|
||||
<option name="url" value="https://dl.google.com/dl/android/maven2/" />
|
||||
</remote-repository>
|
||||
</component>
|
||||
</project>
|
||||
9
android/.idea/misc.xml
generated
Normal file
9
android/.idea/misc.xml
generated
Normal file
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" project-jdk-name="1.8" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/build/classes" />
|
||||
</component>
|
||||
<component name="ProjectType">
|
||||
<option name="id" value="Android" />
|
||||
</component>
|
||||
</project>
|
||||
12
android/.idea/runConfigurations.xml
generated
Normal file
12
android/.idea/runConfigurations.xml
generated
Normal file
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="RunConfigurationProducerService">
|
||||
<option name="ignoredProducers">
|
||||
<set>
|
||||
<option value="org.jetbrains.plugins.gradle.execution.test.runner.AllInPackageGradleConfigurationProducer" />
|
||||
<option value="org.jetbrains.plugins.gradle.execution.test.runner.TestClassGradleConfigurationProducer" />
|
||||
<option value="org.jetbrains.plugins.gradle.execution.test.runner.TestMethodGradleConfigurationProducer" />
|
||||
</set>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
||||
6
android/.idea/vcs.xml
generated
Normal file
6
android/.idea/vcs.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$/.." vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
1
android/app/.gitignore
vendored
Normal file
1
android/app/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/build
|
||||
80
android/app/build.gradle
Normal file
80
android/app/build.gradle
Normal file
@@ -0,0 +1,80 @@
|
||||
apply plugin: 'com.android.application'
|
||||
|
||||
android {
|
||||
compileSdkVersion 29
|
||||
buildToolsVersion "29.0.2"
|
||||
defaultConfig {
|
||||
applicationId "com.github.stenzek.duckstation"
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 29
|
||||
versionCode(getBuildVersionCode())
|
||||
versionName "${getVersion()}"
|
||||
}
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
path "../../CMakeLists.txt"
|
||||
version "3.10.2"
|
||||
}
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
defaultConfig {
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
arguments "-DCMAKE_BUILD_TYPE=RelWithDebInfo"
|
||||
abiFilters "arm64-v8a"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||
implementation 'androidx.appcompat:appcompat:1.0.2'
|
||||
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
|
||||
implementation 'com.google.android.material:material:1.1.0'
|
||||
implementation 'androidx.preference:preference:1.1.0-alpha05'
|
||||
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
|
||||
implementation "androidx.viewpager2:viewpager2:1.0.0"
|
||||
testImplementation 'junit:junit:4.12'
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.1.0'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
|
||||
}
|
||||
|
||||
// Adapted from Dolphin.
|
||||
|
||||
def getVersion() {
|
||||
def versionNumber = '0.0-unknown'
|
||||
|
||||
try {
|
||||
versionNumber = 'git describe --tags --exclude latest'.execute([], project.rootDir).text
|
||||
.trim()
|
||||
.replaceAll(/(-0)?-[^-]+$/, "")
|
||||
} catch (Exception e) {
|
||||
logger.error('Cannot find git, defaulting to dummy version number')
|
||||
}
|
||||
|
||||
return versionNumber
|
||||
}
|
||||
|
||||
|
||||
def getBuildVersionCode() {
|
||||
try {
|
||||
def versionNumber = 'git rev-list --first-parent --count HEAD'.execute([], project.rootDir).text
|
||||
.trim()
|
||||
return Integer.valueOf(versionNumber);
|
||||
} catch (Exception e) {
|
||||
logger.error('Cannot find git, defaulting to dummy version number')
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
21
android/app/proguard-rules.pro
vendored
Normal file
21
android/app/proguard-rules.pro
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
# Add project specific ProGuard rules here.
|
||||
# You can control the set of applied configuration files using the
|
||||
# proguardFiles setting in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
|
||||
# Uncomment this to preserve the line number information for
|
||||
# debugging stack traces.
|
||||
#-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.github.stenzek.duckstation;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.test.platform.app.InstrumentationRegistry;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* Instrumented test, which will execute on an Android device.
|
||||
*
|
||||
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
|
||||
*/
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class ExampleInstrumentedTest {
|
||||
@Test
|
||||
public void useAppContext() {
|
||||
// Context of the app under test.
|
||||
Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
|
||||
|
||||
assertEquals("com.github.stenzek.duckstation", appContext.getPackageName());
|
||||
}
|
||||
}
|
||||
19
android/app/src/cpp/CMakeLists.txt
Normal file
19
android/app/src/cpp/CMakeLists.txt
Normal file
@@ -0,0 +1,19 @@
|
||||
set(SRCS
|
||||
android_host_interface.cpp
|
||||
android_host_interface.h
|
||||
android_settings_interface.cpp
|
||||
android_settings_interface.h
|
||||
)
|
||||
|
||||
add_library(duckstation-native SHARED ${SRCS})
|
||||
target_link_libraries(duckstation-native PRIVATE android frontend-common core common glad imgui)
|
||||
|
||||
find_package(OpenSLES)
|
||||
if(OPENSLES_FOUND)
|
||||
message("Enabling OpenSL ES audio stream")
|
||||
target_sources(duckstation-native PRIVATE
|
||||
opensles_audio_stream.cpp
|
||||
opensles_audio_stream.h)
|
||||
target_link_libraries(duckstation-native PRIVATE OpenSLES::OpenSLES)
|
||||
target_compile_definitions(duckstation-native PRIVATE "-DUSE_OPENSLES=1")
|
||||
endif()
|
||||
896
android/app/src/cpp/android_host_interface.cpp
Normal file
896
android/app/src/cpp/android_host_interface.cpp
Normal file
@@ -0,0 +1,896 @@
|
||||
#include "android_host_interface.h"
|
||||
#include "common/assert.h"
|
||||
#include "common/audio_stream.h"
|
||||
#include "common/file_system.h"
|
||||
#include "common/log.h"
|
||||
#include "common/string.h"
|
||||
#include "common/timestamp.h"
|
||||
#include "core/bios.h"
|
||||
#include "core/cheats.h"
|
||||
#include "core/controller.h"
|
||||
#include "core/gpu.h"
|
||||
#include "core/host_display.h"
|
||||
#include "core/system.h"
|
||||
#include "frontend-common/game_list.h"
|
||||
#include "frontend-common/imgui_styles.h"
|
||||
#include "frontend-common/opengl_host_display.h"
|
||||
#include "frontend-common/vulkan_host_display.h"
|
||||
#include <android/native_window_jni.h>
|
||||
#include <cmath>
|
||||
#include <imgui.h>
|
||||
Log_SetChannel(AndroidHostInterface);
|
||||
|
||||
#ifdef USE_OPENSLES
|
||||
#include "opensles_audio_stream.h"
|
||||
#endif
|
||||
|
||||
static JavaVM* s_jvm;
|
||||
static jclass s_AndroidHostInterface_class;
|
||||
static jmethodID s_AndroidHostInterface_constructor;
|
||||
static jfieldID s_AndroidHostInterface_field_mNativePointer;
|
||||
static jmethodID s_AndroidHostInterface_method_reportError;
|
||||
static jmethodID s_AndroidHostInterface_method_reportMessage;
|
||||
static jmethodID s_EmulationActivity_method_reportError;
|
||||
static jmethodID s_EmulationActivity_method_reportMessage;
|
||||
static jmethodID s_EmulationActivity_method_onEmulationStarted;
|
||||
static jmethodID s_EmulationActivity_method_onEmulationStopped;
|
||||
static jmethodID s_EmulationActivity_method_onGameTitleChanged;
|
||||
static jclass s_CheatCode_class;
|
||||
static jmethodID s_CheatCode_constructor;
|
||||
|
||||
namespace AndroidHelpers {
|
||||
// helper for retrieving the current per-thread jni environment
|
||||
JNIEnv* GetJNIEnv()
|
||||
{
|
||||
JNIEnv* env;
|
||||
if (s_jvm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK)
|
||||
return nullptr;
|
||||
else
|
||||
return env;
|
||||
}
|
||||
|
||||
AndroidHostInterface* GetNativeClass(JNIEnv* env, jobject obj)
|
||||
{
|
||||
return reinterpret_cast<AndroidHostInterface*>(
|
||||
static_cast<uintptr_t>(env->GetLongField(obj, s_AndroidHostInterface_field_mNativePointer)));
|
||||
}
|
||||
|
||||
std::string JStringToString(JNIEnv* env, jstring str)
|
||||
{
|
||||
if (str == nullptr)
|
||||
return {};
|
||||
|
||||
jsize length = env->GetStringUTFLength(str);
|
||||
if (length == 0)
|
||||
return {};
|
||||
|
||||
const char* data = env->GetStringUTFChars(str, nullptr);
|
||||
Assert(data != nullptr);
|
||||
|
||||
std::string ret(data, length);
|
||||
env->ReleaseStringUTFChars(str, data);
|
||||
|
||||
return ret;
|
||||
}
|
||||
} // namespace AndroidHelpers
|
||||
|
||||
AndroidHostInterface::AndroidHostInterface(jobject java_object, jobject context_object, std::string user_directory)
|
||||
: m_java_object(java_object), m_settings_interface(context_object)
|
||||
{
|
||||
m_user_directory = std::move(user_directory);
|
||||
}
|
||||
|
||||
AndroidHostInterface::~AndroidHostInterface()
|
||||
{
|
||||
ImGui::DestroyContext();
|
||||
AndroidHelpers::GetJNIEnv()->DeleteGlobalRef(m_java_object);
|
||||
}
|
||||
|
||||
bool AndroidHostInterface::Initialize()
|
||||
{
|
||||
if (!CommonHostInterface::Initialize())
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void AndroidHostInterface::Shutdown()
|
||||
{
|
||||
HostInterface::Shutdown();
|
||||
}
|
||||
|
||||
const char* AndroidHostInterface::GetFrontendName() const
|
||||
{
|
||||
return "DuckStation Android";
|
||||
}
|
||||
|
||||
void AndroidHostInterface::RequestExit()
|
||||
{
|
||||
ReportError("Ignoring RequestExit()");
|
||||
}
|
||||
|
||||
void AndroidHostInterface::ReportError(const char* message)
|
||||
{
|
||||
CommonHostInterface::ReportError(message);
|
||||
|
||||
JNIEnv* env = AndroidHelpers::GetJNIEnv();
|
||||
jstring message_jstr = env->NewStringUTF(message);
|
||||
if (m_emulation_activity_object)
|
||||
env->CallVoidMethod(m_emulation_activity_object, s_EmulationActivity_method_reportError, message_jstr);
|
||||
else
|
||||
env->CallVoidMethod(m_java_object, s_AndroidHostInterface_method_reportError, message_jstr);
|
||||
}
|
||||
|
||||
void AndroidHostInterface::ReportMessage(const char* message)
|
||||
{
|
||||
CommonHostInterface::ReportMessage(message);
|
||||
|
||||
JNIEnv* env = AndroidHelpers::GetJNIEnv();
|
||||
jstring message_jstr = env->NewStringUTF(message);
|
||||
if (m_emulation_activity_object)
|
||||
env->CallVoidMethod(m_emulation_activity_object, s_EmulationActivity_method_reportMessage, message_jstr);
|
||||
else
|
||||
env->CallVoidMethod(m_java_object, s_AndroidHostInterface_method_reportMessage, message_jstr);
|
||||
}
|
||||
|
||||
std::string AndroidHostInterface::GetStringSettingValue(const char* section, const char* key, const char* default_value)
|
||||
{
|
||||
return m_settings_interface.GetStringValue(section, key, default_value);
|
||||
}
|
||||
|
||||
bool AndroidHostInterface::GetBoolSettingValue(const char* section, const char* key, bool default_value /* = false */)
|
||||
{
|
||||
return m_settings_interface.GetBoolValue(section, key, default_value);
|
||||
}
|
||||
|
||||
int AndroidHostInterface::GetIntSettingValue(const char* section, const char* key, int default_value /* = 0 */)
|
||||
{
|
||||
return m_settings_interface.GetIntValue(section, key, default_value);
|
||||
}
|
||||
|
||||
float AndroidHostInterface::GetFloatSettingValue(const char* section, const char* key, float default_value /* = 0.0f */)
|
||||
{
|
||||
return m_settings_interface.GetFloatValue(section, key, default_value);
|
||||
}
|
||||
|
||||
void AndroidHostInterface::SetUserDirectory()
|
||||
{
|
||||
// Already set in constructor.
|
||||
Assert(!m_user_directory.empty());
|
||||
}
|
||||
|
||||
void AndroidHostInterface::LoadSettings()
|
||||
{
|
||||
CommonHostInterface::LoadSettings(m_settings_interface);
|
||||
CommonHostInterface::FixIncompatibleSettings(false);
|
||||
CommonHostInterface::UpdateInputMap(m_settings_interface);
|
||||
}
|
||||
|
||||
void AndroidHostInterface::UpdateInputMap()
|
||||
{
|
||||
CommonHostInterface::UpdateInputMap(m_settings_interface);
|
||||
}
|
||||
|
||||
bool AndroidHostInterface::IsEmulationThreadPaused() const
|
||||
{
|
||||
return System::IsValid() && System::IsPaused();
|
||||
}
|
||||
|
||||
bool AndroidHostInterface::StartEmulationThread(jobject emulation_activity, ANativeWindow* initial_surface,
|
||||
SystemBootParameters boot_params, bool resume_state)
|
||||
{
|
||||
Assert(!IsEmulationThreadRunning());
|
||||
|
||||
emulation_activity = AndroidHelpers::GetJNIEnv()->NewGlobalRef(emulation_activity);
|
||||
|
||||
Log_DevPrintf("Starting emulation thread...");
|
||||
m_emulation_thread_stop_request.store(false);
|
||||
m_emulation_thread = std::thread(&AndroidHostInterface::EmulationThreadEntryPoint, this, emulation_activity,
|
||||
initial_surface, std::move(boot_params), resume_state);
|
||||
return true;
|
||||
}
|
||||
|
||||
void AndroidHostInterface::PauseEmulationThread(bool paused)
|
||||
{
|
||||
Assert(IsEmulationThreadRunning());
|
||||
RunOnEmulationThread([this, paused]() { PauseSystem(paused); });
|
||||
}
|
||||
|
||||
void AndroidHostInterface::StopEmulationThread()
|
||||
{
|
||||
if (!IsEmulationThreadRunning())
|
||||
return;
|
||||
|
||||
Log_InfoPrint("Stopping emulation thread...");
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(m_mutex);
|
||||
m_emulation_thread_stop_request.store(true);
|
||||
m_sleep_cv.notify_one();
|
||||
}
|
||||
m_emulation_thread.join();
|
||||
Log_InfoPrint("Emulation thread stopped");
|
||||
}
|
||||
|
||||
void AndroidHostInterface::RunOnEmulationThread(std::function<void()> function, bool blocking)
|
||||
{
|
||||
if (!IsEmulationThreadRunning())
|
||||
{
|
||||
function();
|
||||
return;
|
||||
}
|
||||
|
||||
m_mutex.lock();
|
||||
m_callback_queue.push_back(std::move(function));
|
||||
m_sleep_cv.notify_one();
|
||||
|
||||
if (blocking)
|
||||
{
|
||||
// TODO: Don't spin
|
||||
for (;;)
|
||||
{
|
||||
if (m_callback_queue.empty())
|
||||
break;
|
||||
|
||||
m_mutex.unlock();
|
||||
m_mutex.lock();
|
||||
}
|
||||
}
|
||||
|
||||
m_mutex.unlock();
|
||||
}
|
||||
|
||||
void AndroidHostInterface::EmulationThreadEntryPoint(jobject emulation_activity, ANativeWindow* initial_surface,
|
||||
SystemBootParameters boot_params, bool resume_state)
|
||||
{
|
||||
JNIEnv* thread_env;
|
||||
if (s_jvm->AttachCurrentThread(&thread_env, nullptr) != JNI_OK)
|
||||
{
|
||||
ReportError("Failed to attach JNI to thread");
|
||||
return;
|
||||
}
|
||||
|
||||
CreateImGuiContext();
|
||||
m_surface = initial_surface;
|
||||
m_emulation_activity_object = emulation_activity;
|
||||
ApplySettings(true);
|
||||
|
||||
// Boot system.
|
||||
bool boot_result = false;
|
||||
if (resume_state)
|
||||
{
|
||||
if (boot_params.filename.empty())
|
||||
boot_result = ResumeSystemFromMostRecentState();
|
||||
else
|
||||
boot_result = ResumeSystemFromState(boot_params.filename.c_str(), true);
|
||||
}
|
||||
else
|
||||
{
|
||||
boot_result = BootSystem(boot_params);
|
||||
}
|
||||
|
||||
if (!boot_result)
|
||||
{
|
||||
ReportFormattedError("Failed to boot system on emulation thread (file:%s).", boot_params.filename.c_str());
|
||||
DestroyImGuiContext();
|
||||
thread_env->CallVoidMethod(m_emulation_activity_object, s_EmulationActivity_method_onEmulationStopped);
|
||||
thread_env->DeleteGlobalRef(m_emulation_activity_object);
|
||||
m_emulation_activity_object = {};
|
||||
s_jvm->DetachCurrentThread();
|
||||
return;
|
||||
}
|
||||
|
||||
// System is ready to go.
|
||||
thread_env->CallVoidMethod(m_emulation_activity_object, s_EmulationActivity_method_onEmulationStarted);
|
||||
EmulationThreadLoop();
|
||||
|
||||
thread_env->CallVoidMethod(m_emulation_activity_object, s_EmulationActivity_method_onEmulationStopped);
|
||||
PowerOffSystem();
|
||||
DestroyImGuiContext();
|
||||
thread_env->DeleteGlobalRef(m_emulation_activity_object);
|
||||
m_emulation_activity_object = {};
|
||||
s_jvm->DetachCurrentThread();
|
||||
}
|
||||
|
||||
void AndroidHostInterface::EmulationThreadLoop()
|
||||
{
|
||||
for (;;)
|
||||
{
|
||||
// run any events
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(m_mutex);
|
||||
for (;;)
|
||||
{
|
||||
while (!m_callback_queue.empty())
|
||||
{
|
||||
auto callback = std::move(m_callback_queue.front());
|
||||
m_callback_queue.pop_front();
|
||||
lock.unlock();
|
||||
callback();
|
||||
lock.lock();
|
||||
}
|
||||
|
||||
if (m_emulation_thread_stop_request.load())
|
||||
return;
|
||||
|
||||
if (System::IsPaused())
|
||||
{
|
||||
// paused, wait for us to resume
|
||||
m_sleep_cv.wait(lock);
|
||||
}
|
||||
else
|
||||
{
|
||||
// done with callbacks, run the frame
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// simulate the system if not paused
|
||||
if (System::IsRunning())
|
||||
System::RunFrame();
|
||||
|
||||
// rendering
|
||||
{
|
||||
DrawImGuiWindows();
|
||||
|
||||
m_display->Render();
|
||||
ImGui::NewFrame();
|
||||
|
||||
if (System::IsRunning())
|
||||
{
|
||||
System::UpdatePerformanceCounters();
|
||||
|
||||
if (m_speed_limiter_enabled)
|
||||
System::Throttle();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool AndroidHostInterface::AcquireHostDisplay()
|
||||
{
|
||||
WindowInfo wi;
|
||||
wi.type = WindowInfo::Type::Android;
|
||||
wi.window_handle = m_surface;
|
||||
wi.surface_width = ANativeWindow_getWidth(m_surface);
|
||||
wi.surface_height = ANativeWindow_getHeight(m_surface);
|
||||
|
||||
std::unique_ptr<HostDisplay> display;
|
||||
switch (g_settings.gpu_renderer)
|
||||
{
|
||||
case GPURenderer::HardwareVulkan:
|
||||
display = std::make_unique<FrontendCommon::VulkanHostDisplay>();
|
||||
break;
|
||||
|
||||
case GPURenderer::HardwareOpenGL:
|
||||
default:
|
||||
display = std::make_unique<FrontendCommon::OpenGLHostDisplay>();
|
||||
break;
|
||||
}
|
||||
|
||||
if (!display->CreateRenderDevice(wi, {}, g_settings.gpu_use_debug_device) ||
|
||||
!display->InitializeRenderDevice(GetShaderCacheBasePath(), g_settings.gpu_use_debug_device))
|
||||
{
|
||||
ReportError("Failed to acquire host display.");
|
||||
display->DestroyRenderDevice();
|
||||
return false;
|
||||
}
|
||||
|
||||
m_display = std::move(display);
|
||||
|
||||
if (!CreateHostDisplayResources())
|
||||
{
|
||||
ReportError("Failed to create host display resources");
|
||||
ReleaseHostDisplay();
|
||||
return false;
|
||||
}
|
||||
|
||||
ImGui::NewFrame();
|
||||
return true;
|
||||
}
|
||||
|
||||
void AndroidHostInterface::ReleaseHostDisplay()
|
||||
{
|
||||
ReleaseHostDisplayResources();
|
||||
m_display->DestroyRenderDevice();
|
||||
m_display.reset();
|
||||
}
|
||||
|
||||
std::unique_ptr<AudioStream> AndroidHostInterface::CreateAudioStream(AudioBackend backend)
|
||||
{
|
||||
#ifdef USE_OPENSLES
|
||||
if (backend == AudioBackend::OpenSLES)
|
||||
return OpenSLESAudioStream::Create();
|
||||
#endif
|
||||
|
||||
return CommonHostInterface::CreateAudioStream(backend);
|
||||
}
|
||||
|
||||
void AndroidHostInterface::OnSystemDestroyed()
|
||||
{
|
||||
CommonHostInterface::OnSystemDestroyed();
|
||||
ClearOSDMessages();
|
||||
}
|
||||
|
||||
void AndroidHostInterface::OnRunningGameChanged()
|
||||
{
|
||||
CommonHostInterface::OnRunningGameChanged();
|
||||
|
||||
if (m_emulation_activity_object)
|
||||
{
|
||||
JNIEnv* env = AndroidHelpers::GetJNIEnv();
|
||||
jstring title_string = env->NewStringUTF(System::GetRunningTitle().c_str());
|
||||
env->CallVoidMethod(m_emulation_activity_object, s_EmulationActivity_method_onGameTitleChanged, title_string);
|
||||
}
|
||||
}
|
||||
|
||||
void AndroidHostInterface::SurfaceChanged(ANativeWindow* surface, int format, int width, int height)
|
||||
{
|
||||
Log_InfoPrintf("SurfaceChanged %p %d %d %d", surface, format, width, height);
|
||||
if (m_surface == surface)
|
||||
{
|
||||
if (m_display)
|
||||
m_display->ResizeRenderWindow(width, height);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
m_surface = surface;
|
||||
|
||||
if (m_display)
|
||||
{
|
||||
WindowInfo wi;
|
||||
wi.type = surface ? WindowInfo::Type::Android : WindowInfo::Type::Surfaceless;
|
||||
wi.window_handle = surface;
|
||||
wi.surface_width = width;
|
||||
wi.surface_height = height;
|
||||
|
||||
m_display->ChangeRenderWindow(wi);
|
||||
}
|
||||
}
|
||||
|
||||
void AndroidHostInterface::CreateImGuiContext()
|
||||
{
|
||||
ImGui::CreateContext();
|
||||
|
||||
const float framebuffer_scale = 2.0f;
|
||||
|
||||
auto& io = ImGui::GetIO();
|
||||
io.IniFilename = nullptr;
|
||||
io.DisplayFramebufferScale.x = framebuffer_scale;
|
||||
io.DisplayFramebufferScale.y = framebuffer_scale;
|
||||
ImGui::GetStyle().ScaleAllSizes(framebuffer_scale);
|
||||
|
||||
ImGui::StyleColorsDarker();
|
||||
ImGui::AddRobotoRegularFont(15.0f * framebuffer_scale);
|
||||
}
|
||||
|
||||
void AndroidHostInterface::DestroyImGuiContext()
|
||||
{
|
||||
ImGui::DestroyContext();
|
||||
}
|
||||
|
||||
void AndroidHostInterface::SetControllerType(u32 index, std::string_view type_name)
|
||||
{
|
||||
ControllerType type =
|
||||
Settings::ParseControllerTypeName(std::string(type_name).c_str()).value_or(ControllerType::None);
|
||||
|
||||
if (!IsEmulationThreadRunning())
|
||||
{
|
||||
g_settings.controller_types[index] = type;
|
||||
return;
|
||||
}
|
||||
|
||||
RunOnEmulationThread(
|
||||
[index, type]() {
|
||||
Log_InfoPrintf("Changing controller slot %d to %s", index, Settings::GetControllerTypeName(type));
|
||||
g_settings.controller_types[index] = type;
|
||||
System::UpdateControllers();
|
||||
},
|
||||
false);
|
||||
}
|
||||
|
||||
void AndroidHostInterface::SetControllerButtonState(u32 index, s32 button_code, bool pressed)
|
||||
{
|
||||
if (!IsEmulationThreadRunning())
|
||||
return;
|
||||
|
||||
RunOnEmulationThread(
|
||||
[index, button_code, pressed]() {
|
||||
Controller* controller = System::GetController(index);
|
||||
if (!controller)
|
||||
return;
|
||||
|
||||
controller->SetButtonState(button_code, pressed);
|
||||
},
|
||||
false);
|
||||
}
|
||||
|
||||
void AndroidHostInterface::SetControllerAxisState(u32 index, s32 button_code, float value)
|
||||
{
|
||||
if (!IsEmulationThreadRunning())
|
||||
return;
|
||||
|
||||
RunOnEmulationThread(
|
||||
[index, button_code, value]() {
|
||||
Controller* controller = System::GetController(index);
|
||||
if (!controller)
|
||||
return;
|
||||
|
||||
controller->SetAxisState(button_code, value);
|
||||
},
|
||||
false);
|
||||
}
|
||||
|
||||
void AndroidHostInterface::RefreshGameList(bool invalidate_cache, bool invalidate_database)
|
||||
{
|
||||
m_game_list->SetSearchDirectoriesFromSettings(m_settings_interface);
|
||||
m_game_list->Refresh(invalidate_cache, invalidate_database);
|
||||
}
|
||||
|
||||
void AndroidHostInterface::ApplySettings(bool display_osd_messages)
|
||||
{
|
||||
Settings old_settings = std::move(g_settings);
|
||||
CommonHostInterface::LoadSettings(m_settings_interface);
|
||||
CommonHostInterface::FixIncompatibleSettings(display_osd_messages);
|
||||
CheckForSettingsChanges(old_settings);
|
||||
}
|
||||
|
||||
extern "C" jint JNI_OnLoad(JavaVM* vm, void* reserved)
|
||||
{
|
||||
Log::SetDebugOutputParams(true, nullptr, LOGLEVEL_DEV);
|
||||
s_jvm = vm;
|
||||
|
||||
// Create global reference so it doesn't get cleaned up.
|
||||
JNIEnv* env = AndroidHelpers::GetJNIEnv();
|
||||
if ((s_AndroidHostInterface_class = env->FindClass("com/github/stenzek/duckstation/AndroidHostInterface")) ==
|
||||
nullptr ||
|
||||
(s_AndroidHostInterface_class = static_cast<jclass>(env->NewGlobalRef(s_AndroidHostInterface_class))) ==
|
||||
nullptr ||
|
||||
(s_CheatCode_class = env->FindClass("com/github/stenzek/duckstation/CheatCode")) == nullptr ||
|
||||
(s_CheatCode_class = static_cast<jclass>(env->NewGlobalRef(s_CheatCode_class))) == nullptr)
|
||||
{
|
||||
Log_ErrorPrint("AndroidHostInterface class lookup failed");
|
||||
return -1;
|
||||
}
|
||||
|
||||
jclass emulation_activity_class;
|
||||
if ((s_AndroidHostInterface_constructor =
|
||||
env->GetMethodID(s_AndroidHostInterface_class, "<init>", "(Landroid/content/Context;)V")) == nullptr ||
|
||||
(s_AndroidHostInterface_field_mNativePointer =
|
||||
env->GetFieldID(s_AndroidHostInterface_class, "mNativePointer", "J")) == nullptr ||
|
||||
(s_AndroidHostInterface_method_reportError =
|
||||
env->GetMethodID(s_AndroidHostInterface_class, "reportError", "(Ljava/lang/String;)V")) == nullptr ||
|
||||
(s_AndroidHostInterface_method_reportMessage =
|
||||
env->GetMethodID(s_AndroidHostInterface_class, "reportMessage", "(Ljava/lang/String;)V")) == nullptr ||
|
||||
(emulation_activity_class = env->FindClass("com/github/stenzek/duckstation/EmulationActivity")) == nullptr ||
|
||||
(s_EmulationActivity_method_reportError =
|
||||
env->GetMethodID(emulation_activity_class, "reportError", "(Ljava/lang/String;)V")) == nullptr ||
|
||||
(s_EmulationActivity_method_reportMessage =
|
||||
env->GetMethodID(emulation_activity_class, "reportMessage", "(Ljava/lang/String;)V")) == nullptr ||
|
||||
(s_EmulationActivity_method_onEmulationStarted =
|
||||
env->GetMethodID(emulation_activity_class, "onEmulationStarted", "()V")) == nullptr ||
|
||||
(s_EmulationActivity_method_onEmulationStopped =
|
||||
env->GetMethodID(emulation_activity_class, "onEmulationStopped", "()V")) == nullptr ||
|
||||
(s_EmulationActivity_method_onGameTitleChanged =
|
||||
env->GetMethodID(emulation_activity_class, "onGameTitleChanged", "(Ljava/lang/String;)V")) == nullptr ||
|
||||
(s_CheatCode_constructor = env->GetMethodID(s_CheatCode_class, "<init>", "(ILjava/lang/String;Z)V")) == nullptr)
|
||||
{
|
||||
Log_ErrorPrint("AndroidHostInterface lookups failed");
|
||||
return -1;
|
||||
}
|
||||
|
||||
return JNI_VERSION_1_6;
|
||||
}
|
||||
|
||||
#define DEFINE_JNI_METHOD(return_type, name) \
|
||||
extern "C" JNIEXPORT return_type JNICALL Java_com_github_stenzek_duckstation_##name(JNIEnv* env)
|
||||
|
||||
#define DEFINE_JNI_ARGS_METHOD(return_type, name, ...) \
|
||||
extern "C" JNIEXPORT return_type JNICALL Java_com_github_stenzek_duckstation_##name(JNIEnv* env, __VA_ARGS__)
|
||||
|
||||
DEFINE_JNI_ARGS_METHOD(jobject, AndroidHostInterface_create, jobject unused, jobject context_object,
|
||||
jstring user_directory)
|
||||
{
|
||||
Log::SetDebugOutputParams(true, nullptr, LOGLEVEL_DEBUG);
|
||||
|
||||
// initialize the java side
|
||||
jobject java_obj = env->NewObject(s_AndroidHostInterface_class, s_AndroidHostInterface_constructor, context_object);
|
||||
if (!java_obj)
|
||||
{
|
||||
Log_ErrorPrint("Failed to create Java AndroidHostInterface");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
jobject java_obj_ref = env->NewGlobalRef(java_obj);
|
||||
Assert(java_obj_ref != nullptr);
|
||||
|
||||
// initialize the C++ side
|
||||
std::string user_directory_str = AndroidHelpers::JStringToString(env, user_directory);
|
||||
AndroidHostInterface* cpp_obj = new AndroidHostInterface(java_obj_ref, context_object, std::move(user_directory_str));
|
||||
if (!cpp_obj->Initialize())
|
||||
{
|
||||
// TODO: Do we need to release the original java object reference?
|
||||
Log_ErrorPrint("Failed to create C++ AndroidHostInterface");
|
||||
env->DeleteGlobalRef(java_obj_ref);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
env->SetLongField(java_obj, s_AndroidHostInterface_field_mNativePointer,
|
||||
static_cast<long>(reinterpret_cast<uintptr_t>(cpp_obj)));
|
||||
|
||||
return java_obj;
|
||||
}
|
||||
|
||||
DEFINE_JNI_ARGS_METHOD(jboolean, AndroidHostInterface_isEmulationThreadRunning, jobject obj)
|
||||
{
|
||||
return AndroidHelpers::GetNativeClass(env, obj)->IsEmulationThreadRunning();
|
||||
}
|
||||
|
||||
DEFINE_JNI_ARGS_METHOD(jboolean, AndroidHostInterface_startEmulationThread, jobject obj, jobject emulationActivity,
|
||||
jobject surface, jstring filename, jboolean resume_state, jstring state_filename)
|
||||
{
|
||||
ANativeWindow* native_surface = ANativeWindow_fromSurface(env, surface);
|
||||
if (!native_surface)
|
||||
{
|
||||
Log_ErrorPrint("ANativeWindow_fromSurface() returned null");
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string state_filename_str = AndroidHelpers::JStringToString(env, state_filename);
|
||||
|
||||
SystemBootParameters boot_params;
|
||||
boot_params.filename = AndroidHelpers::JStringToString(env, filename);
|
||||
|
||||
return AndroidHelpers::GetNativeClass(env, obj)->StartEmulationThread(emulationActivity, native_surface,
|
||||
std::move(boot_params), resume_state);
|
||||
}
|
||||
|
||||
DEFINE_JNI_ARGS_METHOD(void, AndroidHostInterface_stopEmulationThread, jobject obj)
|
||||
{
|
||||
AndroidHelpers::GetNativeClass(env, obj)->StopEmulationThread();
|
||||
}
|
||||
|
||||
DEFINE_JNI_ARGS_METHOD(void, AndroidHostInterface_surfaceChanged, jobject obj, jobject surface, jint format, jint width,
|
||||
jint height)
|
||||
{
|
||||
ANativeWindow* native_surface = surface ? ANativeWindow_fromSurface(env, surface) : nullptr;
|
||||
if (surface && !native_surface)
|
||||
Log_ErrorPrint("ANativeWindow_fromSurface() returned null");
|
||||
|
||||
AndroidHostInterface* hi = AndroidHelpers::GetNativeClass(env, obj);
|
||||
hi->RunOnEmulationThread(
|
||||
[hi, native_surface, format, width, height]() { hi->SurfaceChanged(native_surface, format, width, height); },
|
||||
false);
|
||||
}
|
||||
|
||||
DEFINE_JNI_ARGS_METHOD(void, AndroidHostInterface_setControllerType, jobject obj, jint index, jstring controller_type)
|
||||
{
|
||||
AndroidHelpers::GetNativeClass(env, obj)->SetControllerType(index,
|
||||
AndroidHelpers::JStringToString(env, controller_type));
|
||||
}
|
||||
|
||||
DEFINE_JNI_ARGS_METHOD(void, AndroidHostInterface_setControllerButtonState, jobject obj, jint index, jint button_code,
|
||||
jboolean pressed)
|
||||
{
|
||||
AndroidHelpers::GetNativeClass(env, obj)->SetControllerButtonState(index, button_code, pressed);
|
||||
}
|
||||
|
||||
DEFINE_JNI_ARGS_METHOD(jint, AndroidHostInterface_getControllerButtonCode, jobject unused, jstring controller_type,
|
||||
jstring button_name)
|
||||
{
|
||||
std::optional<ControllerType> type =
|
||||
Settings::ParseControllerTypeName(AndroidHelpers::JStringToString(env, controller_type).c_str());
|
||||
if (!type)
|
||||
return -1;
|
||||
|
||||
std::optional<s32> code =
|
||||
Controller::GetButtonCodeByName(type.value(), AndroidHelpers::JStringToString(env, button_name));
|
||||
return code.value_or(-1);
|
||||
}
|
||||
|
||||
DEFINE_JNI_ARGS_METHOD(void, AndroidHostInterface_setControllerAxisState, jobject obj, jint index, jint button_code,
|
||||
jfloat value)
|
||||
{
|
||||
AndroidHelpers::GetNativeClass(env, obj)->SetControllerAxisState(index, button_code, value);
|
||||
}
|
||||
|
||||
DEFINE_JNI_ARGS_METHOD(jint, AndroidHostInterface_getControllerAxisCode, jobject unused, jstring controller_type,
|
||||
jstring axis_name)
|
||||
{
|
||||
std::optional<ControllerType> type =
|
||||
Settings::ParseControllerTypeName(AndroidHelpers::JStringToString(env, controller_type).c_str());
|
||||
if (!type)
|
||||
return -1;
|
||||
|
||||
std::optional<s32> code =
|
||||
Controller::GetAxisCodeByName(type.value(), AndroidHelpers::JStringToString(env, axis_name));
|
||||
return code.value_or(-1);
|
||||
}
|
||||
|
||||
DEFINE_JNI_ARGS_METHOD(void, AndroidHostInterface_refreshGameList, jobject obj, jboolean invalidate_cache,
|
||||
jboolean invalidate_database)
|
||||
{
|
||||
AndroidHelpers::GetNativeClass(env, obj)->RefreshGameList(invalidate_cache, invalidate_database);
|
||||
}
|
||||
|
||||
static const char* DiscRegionToString(DiscRegion region)
|
||||
{
|
||||
static std::array<const char*, 4> names = {{"NTSC_J", "NTSC_U", "PAL", "Other"}};
|
||||
return names[static_cast<int>(region)];
|
||||
}
|
||||
|
||||
DEFINE_JNI_ARGS_METHOD(jarray, AndroidHostInterface_getGameListEntries, jobject obj)
|
||||
{
|
||||
jclass entry_class = env->FindClass("com/github/stenzek/duckstation/GameListEntry");
|
||||
Assert(entry_class != nullptr);
|
||||
|
||||
jmethodID entry_constructor = env->GetMethodID(entry_class, "<init>",
|
||||
"(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;JLjava/lang/"
|
||||
"String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V");
|
||||
Assert(entry_constructor != nullptr);
|
||||
|
||||
AndroidHostInterface* hi = AndroidHelpers::GetNativeClass(env, obj);
|
||||
jobjectArray entry_array = env->NewObjectArray(hi->GetGameList()->GetEntryCount(), entry_class, nullptr);
|
||||
Assert(entry_array != nullptr);
|
||||
|
||||
u32 counter = 0;
|
||||
for (const GameListEntry& entry : hi->GetGameList()->GetEntries())
|
||||
{
|
||||
const Timestamp modified_ts(
|
||||
Timestamp::FromUnixTimestamp(static_cast<Timestamp::UnixTimestampValue>(entry.last_modified_time)));
|
||||
|
||||
jstring path = env->NewStringUTF(entry.path.c_str());
|
||||
jstring code = env->NewStringUTF(entry.code.c_str());
|
||||
jstring title = env->NewStringUTF(entry.title.c_str());
|
||||
jstring region = env->NewStringUTF(DiscRegionToString(entry.region));
|
||||
jstring type = env->NewStringUTF(GameList::EntryTypeToString(entry.type));
|
||||
jstring compatibility_rating =
|
||||
env->NewStringUTF(GameList::EntryCompatibilityRatingToString(entry.compatibility_rating));
|
||||
jstring modified_time = env->NewStringUTF(modified_ts.ToString("%Y/%m/%d, %H:%M:%S"));
|
||||
jlong size = entry.total_size;
|
||||
|
||||
jobject entry_jobject = env->NewObject(entry_class, entry_constructor, path, code, title, size, modified_time,
|
||||
region, type, compatibility_rating);
|
||||
|
||||
env->SetObjectArrayElement(entry_array, counter++, entry_jobject);
|
||||
}
|
||||
|
||||
return entry_array;
|
||||
}
|
||||
|
||||
DEFINE_JNI_ARGS_METHOD(void, AndroidHostInterface_applySettings, jobject obj)
|
||||
{
|
||||
AndroidHostInterface* hi = AndroidHelpers::GetNativeClass(env, obj);
|
||||
if (hi->IsEmulationThreadRunning())
|
||||
{
|
||||
hi->RunOnEmulationThread([hi]() { hi->ApplySettings(false); });
|
||||
}
|
||||
else
|
||||
{
|
||||
hi->ApplySettings(false);
|
||||
}
|
||||
}
|
||||
|
||||
DEFINE_JNI_ARGS_METHOD(void, AndroidHostInterface_resetSystem, jobject obj, jboolean global, jint slot)
|
||||
{
|
||||
AndroidHostInterface* hi = AndroidHelpers::GetNativeClass(env, obj);
|
||||
hi->RunOnEmulationThread([hi]() { hi->ResetSystem(); });
|
||||
}
|
||||
|
||||
DEFINE_JNI_ARGS_METHOD(void, AndroidHostInterface_loadState, jobject obj, jboolean global, jint slot)
|
||||
{
|
||||
AndroidHostInterface* hi = AndroidHelpers::GetNativeClass(env, obj);
|
||||
hi->RunOnEmulationThread([hi, global, slot]() { hi->LoadState(global, slot); });
|
||||
}
|
||||
|
||||
DEFINE_JNI_ARGS_METHOD(void, AndroidHostInterface_saveState, jobject obj, jboolean global, jint slot)
|
||||
{
|
||||
AndroidHostInterface* hi = AndroidHelpers::GetNativeClass(env, obj);
|
||||
hi->RunOnEmulationThread([hi, global, slot]() { hi->SaveState(global, slot); });
|
||||
}
|
||||
|
||||
DEFINE_JNI_ARGS_METHOD(void, AndroidHostInterface_saveResumeState, jobject obj, jboolean wait_for_completion)
|
||||
{
|
||||
AndroidHostInterface* hi = AndroidHelpers::GetNativeClass(env, obj);
|
||||
hi->RunOnEmulationThread([hi]() { hi->SaveResumeSaveState(); }, wait_for_completion);
|
||||
}
|
||||
|
||||
DEFINE_JNI_ARGS_METHOD(void, AndroidHostInterface_setDisplayAlignment, jobject obj, jint alignment)
|
||||
{
|
||||
AndroidHostInterface* hi = AndroidHelpers::GetNativeClass(env, obj);
|
||||
hi->RunOnEmulationThread(
|
||||
[hi, alignment]() { hi->GetDisplay()->SetDisplayAlignment(static_cast<HostDisplay::Alignment>(alignment)); });
|
||||
}
|
||||
|
||||
DEFINE_JNI_ARGS_METHOD(bool, AndroidHostInterface_hasSurface, jobject obj)
|
||||
{
|
||||
AndroidHostInterface* hi = AndroidHelpers::GetNativeClass(env, obj);
|
||||
HostDisplay* display = hi->GetDisplay();
|
||||
if (display)
|
||||
return display->HasRenderSurface();
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
DEFINE_JNI_ARGS_METHOD(bool, AndroidHostInterface_isEmulationThreadPaused, jobject obj)
|
||||
{
|
||||
AndroidHostInterface* hi = AndroidHelpers::GetNativeClass(env, obj);
|
||||
return hi->IsEmulationThreadPaused();
|
||||
}
|
||||
|
||||
DEFINE_JNI_ARGS_METHOD(void, AndroidHostInterface_pauseEmulationThread, jobject obj, jboolean paused)
|
||||
{
|
||||
AndroidHostInterface* hi = AndroidHelpers::GetNativeClass(env, obj);
|
||||
hi->PauseEmulationThread(paused);
|
||||
}
|
||||
|
||||
DEFINE_JNI_ARGS_METHOD(jobject, AndroidHostInterface_getCheatList, jobject obj)
|
||||
{
|
||||
if (!System::IsValid() || !System::HasCheatList())
|
||||
return nullptr;
|
||||
|
||||
CheatList* cl = System::GetCheatList();
|
||||
const u32 count = cl->GetCodeCount();
|
||||
|
||||
jobjectArray arr = env->NewObjectArray(count, s_CheatCode_class, nullptr);
|
||||
for (u32 i = 0; i < count; i++)
|
||||
{
|
||||
const CheatCode& cc = cl->GetCode(i);
|
||||
|
||||
jobject java_cc = env->NewObject(s_CheatCode_class, s_CheatCode_constructor, static_cast<jint>(i),
|
||||
env->NewStringUTF(cc.description.c_str()), cc.enabled);
|
||||
env->SetObjectArrayElement(arr, i, java_cc);
|
||||
}
|
||||
|
||||
return arr;
|
||||
}
|
||||
|
||||
DEFINE_JNI_ARGS_METHOD(void, AndroidHostInterface_setCheatEnabled, jobject obj, jint index, jboolean enabled)
|
||||
{
|
||||
if (!System::IsValid() || !System::HasCheatList())
|
||||
return;
|
||||
|
||||
AndroidHostInterface* hi = AndroidHelpers::GetNativeClass(env, obj);
|
||||
hi->RunOnEmulationThread([index, enabled, hi]() { hi->SetCheatCodeState(static_cast<u32>(index), enabled, true); });
|
||||
}
|
||||
|
||||
DEFINE_JNI_ARGS_METHOD(void, AndroidHostInterface_addOSDMessage, jobject obj, jstring message, jfloat duration)
|
||||
{
|
||||
AndroidHostInterface* hi = AndroidHelpers::GetNativeClass(env, obj);
|
||||
hi->AddOSDMessage(AndroidHelpers::JStringToString(env, message), duration);
|
||||
}
|
||||
|
||||
DEFINE_JNI_ARGS_METHOD(jboolean, AndroidHostInterface_hasAnyBIOSImages, jobject obj)
|
||||
{
|
||||
AndroidHostInterface* hi = AndroidHelpers::GetNativeClass(env, obj);
|
||||
return hi->HasAnyBIOSImages();
|
||||
}
|
||||
|
||||
DEFINE_JNI_ARGS_METHOD(jstring, AndroidHostInterface_importBIOSImage, jobject obj, jbyteArray data)
|
||||
{
|
||||
AndroidHostInterface* hi = AndroidHelpers::GetNativeClass(env, obj);
|
||||
|
||||
const jsize len = env->GetArrayLength(data);
|
||||
if (len != BIOS::BIOS_SIZE)
|
||||
return nullptr;
|
||||
|
||||
BIOS::Image image;
|
||||
image.resize(static_cast<size_t>(len));
|
||||
env->GetByteArrayRegion(data, 0, len, reinterpret_cast<jbyte*>(image.data()));
|
||||
|
||||
const BIOS::Hash hash = BIOS::GetHash(image);
|
||||
const BIOS::ImageInfo* ii = BIOS::GetImageInfoForHash(hash);
|
||||
|
||||
const std::string dest_path(hi->GetUserDirectoryRelativePath("bios/%s.bin", hash.ToString().c_str()));
|
||||
if (FileSystem::FileExists(dest_path.c_str()) ||
|
||||
!FileSystem::WriteBinaryFile(dest_path.c_str(), image.data(), image.size()))
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (ii)
|
||||
return env->NewStringUTF(ii->description);
|
||||
else
|
||||
return env->NewStringUTF(hash.ToString().c_str());
|
||||
}
|
||||
94
android/app/src/cpp/android_host_interface.h
Normal file
94
android/app/src/cpp/android_host_interface.h
Normal file
@@ -0,0 +1,94 @@
|
||||
#pragma once
|
||||
#include "android_settings_interface.h"
|
||||
#include "common/event.h"
|
||||
#include "frontend-common/common_host_interface.h"
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <condition_variable>
|
||||
#include <functional>
|
||||
#include <jni.h>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
|
||||
struct ANativeWindow;
|
||||
|
||||
class Controller;
|
||||
|
||||
class AndroidHostInterface final : public CommonHostInterface
|
||||
{
|
||||
public:
|
||||
AndroidHostInterface(jobject java_object, jobject context_object, std::string user_directory);
|
||||
~AndroidHostInterface() override;
|
||||
|
||||
bool Initialize() override;
|
||||
void Shutdown() override;
|
||||
|
||||
const char* GetFrontendName() const override;
|
||||
void RequestExit() override;
|
||||
|
||||
void ReportError(const char* message) override;
|
||||
void ReportMessage(const char* message) override;
|
||||
|
||||
std::string GetStringSettingValue(const char* section, const char* key, const char* default_value = "") override;
|
||||
bool GetBoolSettingValue(const char* section, const char* key, bool default_value = false) override;
|
||||
int GetIntSettingValue(const char* section, const char* key, int default_value = 0) override;
|
||||
float GetFloatSettingValue(const char* section, const char* key, float default_value = 0.0f) override;
|
||||
|
||||
bool IsEmulationThreadRunning() const { return m_emulation_thread.joinable(); }
|
||||
bool IsEmulationThreadPaused() const;
|
||||
bool StartEmulationThread(jobject emulation_activity, ANativeWindow* initial_surface,
|
||||
SystemBootParameters boot_params, bool resume_state);
|
||||
void RunOnEmulationThread(std::function<void()> function, bool blocking = false);
|
||||
void PauseEmulationThread(bool paused);
|
||||
void StopEmulationThread();
|
||||
|
||||
void SurfaceChanged(ANativeWindow* surface, int format, int width, int height);
|
||||
|
||||
void SetControllerType(u32 index, std::string_view type_name);
|
||||
void SetControllerButtonState(u32 index, s32 button_code, bool pressed);
|
||||
void SetControllerAxisState(u32 index, s32 button_code, float value);
|
||||
|
||||
void RefreshGameList(bool invalidate_cache, bool invalidate_database);
|
||||
void ApplySettings(bool display_osd_messages);
|
||||
|
||||
protected:
|
||||
void SetUserDirectory() override;
|
||||
void LoadSettings() override;
|
||||
void UpdateInputMap() override;
|
||||
|
||||
bool AcquireHostDisplay() override;
|
||||
void ReleaseHostDisplay() override;
|
||||
std::unique_ptr<AudioStream> CreateAudioStream(AudioBackend backend) override;
|
||||
|
||||
void OnSystemDestroyed() override;
|
||||
void OnRunningGameChanged() override;
|
||||
|
||||
private:
|
||||
void EmulationThreadEntryPoint(jobject emulation_activity, ANativeWindow* initial_surface,
|
||||
SystemBootParameters boot_params, bool resume_state);
|
||||
void EmulationThreadLoop();
|
||||
|
||||
void CreateImGuiContext();
|
||||
void DestroyImGuiContext();
|
||||
|
||||
jobject m_java_object = {};
|
||||
jobject m_emulation_activity_object = {};
|
||||
|
||||
AndroidSettingsInterface m_settings_interface;
|
||||
|
||||
ANativeWindow* m_surface = nullptr;
|
||||
|
||||
std::mutex m_mutex;
|
||||
std::condition_variable m_sleep_cv;
|
||||
std::deque<std::function<void()>> m_callback_queue;
|
||||
|
||||
std::thread m_emulation_thread;
|
||||
std::atomic_bool m_emulation_thread_stop_request{false};
|
||||
};
|
||||
|
||||
namespace AndroidHelpers {
|
||||
JNIEnv* GetJNIEnv();
|
||||
AndroidHostInterface* GetNativeClass(JNIEnv* env, jobject obj);
|
||||
std::string JStringToString(JNIEnv* env, jstring str);
|
||||
} // namespace AndroidHelpers
|
||||
191
android/app/src/cpp/android_settings_interface.cpp
Normal file
191
android/app/src/cpp/android_settings_interface.cpp
Normal file
@@ -0,0 +1,191 @@
|
||||
#include "android_settings_interface.h"
|
||||
#include "android_host_interface.h"
|
||||
#include "common/assert.h"
|
||||
#include "common/log.h"
|
||||
#include "common/string.h"
|
||||
#include "common/string_util.h"
|
||||
#include <algorithm>
|
||||
Log_SetChannel(AndroidSettingsInterface);
|
||||
|
||||
ALWAYS_INLINE TinyString GetSettingKey(const char* section, const char* key)
|
||||
{
|
||||
return TinyString::FromFormat("%s/%s", section, key);
|
||||
}
|
||||
|
||||
AndroidSettingsInterface::AndroidSettingsInterface(jobject java_context)
|
||||
{
|
||||
JNIEnv* env = AndroidHelpers::GetJNIEnv();
|
||||
jclass c_preference_manager = env->FindClass("androidx/preference/PreferenceManager");
|
||||
jclass c_set = env->FindClass("java/util/Set");
|
||||
jmethodID m_get_default_shared_preferences =
|
||||
env->GetStaticMethodID(c_preference_manager, "getDefaultSharedPreferences",
|
||||
"(Landroid/content/Context;)Landroid/content/SharedPreferences;");
|
||||
Assert(c_preference_manager && c_set && m_get_default_shared_preferences);
|
||||
|
||||
m_java_shared_preferences =
|
||||
env->CallStaticObjectMethod(c_preference_manager, m_get_default_shared_preferences, java_context);
|
||||
Assert(m_java_shared_preferences);
|
||||
m_java_shared_preferences = env->NewGlobalRef(m_java_shared_preferences);
|
||||
jclass c_shared_preferences = env->GetObjectClass(m_java_shared_preferences);
|
||||
|
||||
m_get_boolean = env->GetMethodID(c_shared_preferences, "getBoolean", "(Ljava/lang/String;Z)Z");
|
||||
m_get_int = env->GetMethodID(c_shared_preferences, "getInt", "(Ljava/lang/String;I)I");
|
||||
m_get_float = env->GetMethodID(c_shared_preferences, "getFloat", "(Ljava/lang/String;F)F");
|
||||
m_get_string =
|
||||
env->GetMethodID(c_shared_preferences, "getString", "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;");
|
||||
m_get_string_set =
|
||||
env->GetMethodID(c_shared_preferences, "getStringSet", "(Ljava/lang/String;Ljava/util/Set;)Ljava/util/Set;");
|
||||
m_set_to_array = env->GetMethodID(c_set, "toArray", "()[Ljava/lang/Object;");
|
||||
Assert(m_get_boolean && m_get_int && m_get_float && m_get_string && m_get_string_set && m_set_to_array);
|
||||
}
|
||||
|
||||
AndroidSettingsInterface::~AndroidSettingsInterface()
|
||||
{
|
||||
if (m_java_shared_preferences)
|
||||
AndroidHelpers::GetJNIEnv()->DeleteGlobalRef(m_java_shared_preferences);
|
||||
}
|
||||
|
||||
void AndroidSettingsInterface::Clear()
|
||||
{
|
||||
Log_ErrorPrint("Not implemented");
|
||||
}
|
||||
|
||||
int AndroidSettingsInterface::GetIntValue(const char* section, const char* key, int default_value /*= 0*/)
|
||||
{
|
||||
JNIEnv* env = AndroidHelpers::GetJNIEnv();
|
||||
|
||||
// Some of these settings are string lists...
|
||||
jstring string_object = reinterpret_cast<jstring>(
|
||||
env->CallObjectMethod(m_java_shared_preferences, m_get_string, env->NewStringUTF(GetSettingKey(section, key)),
|
||||
env->NewStringUTF(TinyString::FromFormat("%d", default_value))));
|
||||
if (env->ExceptionCheck())
|
||||
{
|
||||
env->ExceptionClear();
|
||||
|
||||
// it might actually be an int (e.g. seek bar preference)
|
||||
const int int_value = static_cast<int>(env->CallIntMethod(m_java_shared_preferences, m_get_int,
|
||||
env->NewStringUTF(GetSettingKey(section, key)), default_value));
|
||||
if (env->ExceptionCheck())
|
||||
{
|
||||
env->ExceptionClear();
|
||||
return default_value;
|
||||
}
|
||||
|
||||
return int_value;
|
||||
}
|
||||
|
||||
if (!string_object)
|
||||
return default_value;
|
||||
|
||||
const char* data = env->GetStringUTFChars(string_object, nullptr);
|
||||
Assert(data != nullptr);
|
||||
|
||||
std::optional<int> value = StringUtil::FromChars<int>(data);
|
||||
env->ReleaseStringUTFChars(string_object, data);
|
||||
return value.value_or(default_value);
|
||||
}
|
||||
|
||||
float AndroidSettingsInterface::GetFloatValue(const char* section, const char* key, float default_value /*= 0.0f*/)
|
||||
{
|
||||
JNIEnv* env = AndroidHelpers::GetJNIEnv();
|
||||
#if 0
|
||||
return static_cast<float>(env->CallFloatMethod(m_java_shared_preferences, m_get_float,
|
||||
env->NewStringUTF(GetSettingKey(section, key)), default_value));
|
||||
#else
|
||||
// Some of these settings are string lists...
|
||||
jstring string_object = reinterpret_cast<jstring>(
|
||||
env->CallObjectMethod(m_java_shared_preferences, m_get_string, env->NewStringUTF(GetSettingKey(section, key)),
|
||||
env->NewStringUTF(TinyString::FromFormat("%f", default_value))));
|
||||
if (!string_object)
|
||||
return default_value;
|
||||
|
||||
const char* data = env->GetStringUTFChars(string_object, nullptr);
|
||||
Assert(data != nullptr);
|
||||
|
||||
std::optional<float> value = StringUtil::FromChars<float>(data);
|
||||
env->ReleaseStringUTFChars(string_object, data);
|
||||
return value.value_or(default_value);
|
||||
#endif
|
||||
}
|
||||
|
||||
bool AndroidSettingsInterface::GetBoolValue(const char* section, const char* key, bool default_value /*= false*/)
|
||||
{
|
||||
JNIEnv* env = AndroidHelpers::GetJNIEnv();
|
||||
return static_cast<bool>(env->CallBooleanMethod(m_java_shared_preferences, m_get_boolean,
|
||||
env->NewStringUTF(GetSettingKey(section, key)), default_value));
|
||||
}
|
||||
|
||||
std::string AndroidSettingsInterface::GetStringValue(const char* section, const char* key,
|
||||
const char* default_value /*= ""*/)
|
||||
{
|
||||
JNIEnv* env = AndroidHelpers::GetJNIEnv();
|
||||
jobject string_object =
|
||||
env->CallObjectMethod(m_java_shared_preferences, m_get_string, env->NewStringUTF(GetSettingKey(section, key)),
|
||||
env->NewStringUTF(default_value));
|
||||
return AndroidHelpers::JStringToString(env, reinterpret_cast<jstring>(string_object));
|
||||
}
|
||||
|
||||
void AndroidSettingsInterface::SetIntValue(const char* section, const char* key, int value)
|
||||
{
|
||||
Log_ErrorPrintf("SetIntValue(\"%s\", \"%s\", %d) not implemented", section, key, value);
|
||||
}
|
||||
|
||||
void AndroidSettingsInterface::SetFloatValue(const char* section, const char* key, float value)
|
||||
{
|
||||
Log_ErrorPrintf("SetFloatValue(\"%s\", \"%s\", %f) not implemented", section, key, value);
|
||||
}
|
||||
|
||||
void AndroidSettingsInterface::SetBoolValue(const char* section, const char* key, bool value)
|
||||
{
|
||||
Log_ErrorPrintf("SetBoolValue(\"%s\", \"%s\", %u) not implemented", section, key, static_cast<unsigned>(value));
|
||||
}
|
||||
|
||||
void AndroidSettingsInterface::SetStringValue(const char* section, const char* key, const char* value)
|
||||
{
|
||||
Log_ErrorPrintf("SetStringValue(\"%s\", \"%s\", \"%s\") not implemented", section, key, value);
|
||||
}
|
||||
|
||||
void AndroidSettingsInterface::DeleteValue(const char* section, const char* key)
|
||||
{
|
||||
Log_ErrorPrintf("DeleteValue(\"%s\", \"%s\") not implemented", section, key);
|
||||
}
|
||||
|
||||
std::vector<std::string> AndroidSettingsInterface::GetStringList(const char* section, const char* key)
|
||||
{
|
||||
JNIEnv* env = AndroidHelpers::GetJNIEnv();
|
||||
jobject values_set = env->CallObjectMethod(m_java_shared_preferences, m_get_string_set,
|
||||
env->NewStringUTF(GetSettingKey(section, key)), nullptr);
|
||||
if (!values_set)
|
||||
return {};
|
||||
|
||||
jobjectArray values_array = reinterpret_cast<jobjectArray>(env->CallObjectMethod(values_set, m_set_to_array));
|
||||
if (!values_array)
|
||||
return {};
|
||||
|
||||
jsize size = env->GetArrayLength(values_array);
|
||||
std::vector<std::string> values;
|
||||
values.reserve(size);
|
||||
for (jsize i = 0; i < size; i++)
|
||||
values.push_back(
|
||||
AndroidHelpers::JStringToString(env, reinterpret_cast<jstring>(env->GetObjectArrayElement(values_array, i))));
|
||||
|
||||
return values;
|
||||
}
|
||||
|
||||
void AndroidSettingsInterface::SetStringList(const char* section, const char* key,
|
||||
const std::vector<std::string>& items)
|
||||
{
|
||||
Log_ErrorPrintf("SetStringList(\"%s\", \"%s\") not implemented", section, key);
|
||||
}
|
||||
|
||||
bool AndroidSettingsInterface::RemoveFromStringList(const char* section, const char* key, const char* item)
|
||||
{
|
||||
Log_ErrorPrintf("RemoveFromStringList(\"%s\", \"%s\", \"%s\") not implemented", section, key, item);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AndroidSettingsInterface::AddToStringList(const char* section, const char* key, const char* item)
|
||||
{
|
||||
Log_ErrorPrintf("AddToStringList(\"%s\", \"%s\", \"%s\") not implemented", section, key, item);
|
||||
return false;
|
||||
}
|
||||
37
android/app/src/cpp/android_settings_interface.h
Normal file
37
android/app/src/cpp/android_settings_interface.h
Normal file
@@ -0,0 +1,37 @@
|
||||
#pragma once
|
||||
#include "core/settings.h"
|
||||
#include <jni.h>
|
||||
|
||||
class AndroidSettingsInterface : public SettingsInterface
|
||||
{
|
||||
public:
|
||||
AndroidSettingsInterface(jobject java_context);
|
||||
~AndroidSettingsInterface();
|
||||
|
||||
void Clear() override;
|
||||
|
||||
int GetIntValue(const char* section, const char* key, int default_value = 0) override;
|
||||
float GetFloatValue(const char* section, const char* key, float default_value = 0.0f) override;
|
||||
bool GetBoolValue(const char* section, const char* key, bool default_value = false) override;
|
||||
std::string GetStringValue(const char* section, const char* key, const char* default_value = "") override;
|
||||
|
||||
void SetIntValue(const char* section, const char* key, int value) override;
|
||||
void SetFloatValue(const char* section, const char* key, float value) override;
|
||||
void SetBoolValue(const char* section, const char* key, bool value) override;
|
||||
void SetStringValue(const char* section, const char* key, const char* value) override;
|
||||
void DeleteValue(const char* section, const char* key) override;
|
||||
|
||||
std::vector<std::string> GetStringList(const char* section, const char* key) override;
|
||||
void SetStringList(const char* section, const char* key, const std::vector<std::string>& items) override;
|
||||
bool RemoveFromStringList(const char* section, const char* key, const char* item) override;
|
||||
bool AddToStringList(const char* section, const char* key, const char* item) override;
|
||||
|
||||
private:
|
||||
jobject m_java_shared_preferences{};
|
||||
jmethodID m_get_boolean{};
|
||||
jmethodID m_get_int{};
|
||||
jmethodID m_get_float{};
|
||||
jmethodID m_get_string{};
|
||||
jmethodID m_get_string_set{};
|
||||
jmethodID m_set_to_array{};
|
||||
};
|
||||
209
android/app/src/cpp/opensles_audio_stream.cpp
Normal file
209
android/app/src/cpp/opensles_audio_stream.cpp
Normal file
@@ -0,0 +1,209 @@
|
||||
#include "opensles_audio_stream.h"
|
||||
#include "common/assert.h"
|
||||
#include "common/log.h"
|
||||
#include <cmath>
|
||||
Log_SetChannel(OpenSLESAudioStream);
|
||||
|
||||
// Based off Dolphin's OpenSLESStream class.
|
||||
|
||||
OpenSLESAudioStream::OpenSLESAudioStream() = default;
|
||||
|
||||
OpenSLESAudioStream::~OpenSLESAudioStream()
|
||||
{
|
||||
if (IsOpen())
|
||||
OpenSLESAudioStream::CloseDevice();
|
||||
}
|
||||
|
||||
std::unique_ptr<AudioStream> OpenSLESAudioStream::Create()
|
||||
{
|
||||
return std::make_unique<OpenSLESAudioStream>();
|
||||
}
|
||||
|
||||
bool OpenSLESAudioStream::OpenDevice()
|
||||
{
|
||||
DebugAssert(!IsOpen());
|
||||
|
||||
SLresult res = slCreateEngine(&m_engine, 0, nullptr, 0, nullptr, nullptr);
|
||||
if (res != SL_RESULT_SUCCESS)
|
||||
{
|
||||
Log_ErrorPrintf("slCreateEngine failed: %d", res);
|
||||
return false;
|
||||
}
|
||||
|
||||
res = (*m_engine)->Realize(m_engine, SL_BOOLEAN_FALSE);
|
||||
if (res != SL_RESULT_SUCCESS)
|
||||
{
|
||||
Log_ErrorPrintf("Realize(Engine) failed: %d", res);
|
||||
CloseDevice();
|
||||
return false;
|
||||
}
|
||||
|
||||
res = (*m_engine)->GetInterface(m_engine, SL_IID_ENGINE, &m_engine_engine);
|
||||
if (res != SL_RESULT_SUCCESS)
|
||||
{
|
||||
Log_ErrorPrintf("GetInterface(SL_IID_ENGINE) failed: %d", res);
|
||||
CloseDevice();
|
||||
return false;
|
||||
}
|
||||
|
||||
res = (*m_engine_engine)->CreateOutputMix(m_engine_engine, &m_output_mix, 0, 0, 0);
|
||||
if (res != SL_RESULT_SUCCESS)
|
||||
{
|
||||
Log_ErrorPrintf("CreateOutputMix failed: %d", res);
|
||||
CloseDevice();
|
||||
return false;
|
||||
}
|
||||
|
||||
res = (*m_output_mix)->Realize(m_output_mix, SL_BOOLEAN_FALSE);
|
||||
if (res != SL_RESULT_SUCCESS)
|
||||
{
|
||||
Log_ErrorPrintf("Realize(OutputMix) mix failed: %d", res);
|
||||
CloseDevice();
|
||||
return false;
|
||||
}
|
||||
|
||||
SLDataLocator_AndroidSimpleBufferQueue dloc_bq{SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, NUM_BUFFERS};
|
||||
SLDataFormat_PCM format = {SL_DATAFORMAT_PCM,
|
||||
m_channels,
|
||||
m_output_sample_rate * 1000u,
|
||||
SL_PCMSAMPLEFORMAT_FIXED_16,
|
||||
SL_PCMSAMPLEFORMAT_FIXED_16,
|
||||
SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT,
|
||||
SL_BYTEORDER_LITTLEENDIAN};
|
||||
SLDataSource dsrc{&dloc_bq, &format};
|
||||
SLDataLocator_OutputMix dloc_outputmix{SL_DATALOCATOR_OUTPUTMIX, m_output_mix};
|
||||
SLDataSink dsink{&dloc_outputmix, nullptr};
|
||||
|
||||
const std::array<SLInterfaceID, 2> ap_interfaces = {{SL_IID_BUFFERQUEUE, SL_IID_VOLUME}};
|
||||
const std::array<SLboolean, 2> ap_interfaces_req = {{true, true}};
|
||||
res = (*m_engine_engine)
|
||||
->CreateAudioPlayer(m_engine_engine, &m_player, &dsrc, &dsink, static_cast<u32>(ap_interfaces.size()),
|
||||
ap_interfaces.data(), ap_interfaces_req.data());
|
||||
if (res != SL_RESULT_SUCCESS)
|
||||
{
|
||||
Log_ErrorPrintf("CreateAudioPlayer failed: %d", res);
|
||||
CloseDevice();
|
||||
return false;
|
||||
}
|
||||
|
||||
res = (*m_player)->Realize(m_player, SL_BOOLEAN_FALSE);
|
||||
if (res != SL_RESULT_SUCCESS)
|
||||
{
|
||||
Log_ErrorPrintf("Realize(AudioPlayer) failed: %d", res);
|
||||
CloseDevice();
|
||||
return false;
|
||||
}
|
||||
|
||||
res = (*m_player)->GetInterface(m_player, SL_IID_PLAY, &m_play_interface);
|
||||
if (res != SL_RESULT_SUCCESS)
|
||||
{
|
||||
Log_ErrorPrintf("GetInterface(SL_IID_PLAY) failed: %d", res);
|
||||
CloseDevice();
|
||||
return false;
|
||||
}
|
||||
|
||||
res = (*m_player)->GetInterface(m_player, SL_IID_BUFFERQUEUE, &m_buffer_queue_interface);
|
||||
if (res != SL_RESULT_SUCCESS)
|
||||
{
|
||||
Log_ErrorPrintf("GetInterface(SL_IID_BUFFERQUEUE) failed: %d", res);
|
||||
CloseDevice();
|
||||
return false;
|
||||
}
|
||||
|
||||
res = (*m_player)->GetInterface(m_player, SL_IID_VOLUME, &m_volume_interface);
|
||||
if (res != SL_RESULT_SUCCESS)
|
||||
{
|
||||
Log_ErrorPrintf("GetInterface(SL_IID_VOLUME) failed: %d", res);
|
||||
CloseDevice();
|
||||
return false;
|
||||
}
|
||||
|
||||
res = (*m_buffer_queue_interface)->RegisterCallback(m_buffer_queue_interface, BufferCallback, this);
|
||||
if (res != SL_RESULT_SUCCESS)
|
||||
{
|
||||
Log_ErrorPrintf("Failed to register callback: %d", res);
|
||||
CloseDevice();
|
||||
return false;
|
||||
}
|
||||
|
||||
for (u32 i = 0; i < NUM_BUFFERS; i++)
|
||||
m_buffers[i] = std::make_unique<SampleType[]>(m_buffer_size * m_channels);
|
||||
|
||||
Log_InfoPrintf("OpenSL ES device opened: %uhz, %u channels, %u buffer size, %u buffers",
|
||||
m_output_sample_rate, m_channels, m_buffer_size, NUM_BUFFERS);
|
||||
return true;
|
||||
}
|
||||
|
||||
void OpenSLESAudioStream::PauseDevice(bool paused)
|
||||
{
|
||||
if (m_paused == paused)
|
||||
return;
|
||||
|
||||
SLresult res = (*m_play_interface)->SetPlayState(m_play_interface, paused ? SL_PLAYSTATE_PAUSED : SL_PLAYSTATE_PLAYING);
|
||||
if (res != SL_RESULT_SUCCESS)
|
||||
Log_ErrorPrintf("SetPlayState failed: %d", res);
|
||||
|
||||
if (!paused && !m_buffer_enqueued)
|
||||
{
|
||||
m_buffer_enqueued = true;
|
||||
EnqueueBuffer();
|
||||
}
|
||||
|
||||
m_paused = paused;
|
||||
}
|
||||
|
||||
void OpenSLESAudioStream::CloseDevice()
|
||||
{
|
||||
m_buffers = {};
|
||||
m_current_buffer = 0;
|
||||
m_paused = true;
|
||||
m_buffer_enqueued = false;
|
||||
|
||||
if (m_player)
|
||||
{
|
||||
(*m_player)->Destroy(m_player);
|
||||
m_volume_interface = {};
|
||||
m_buffer_queue_interface = {};
|
||||
m_play_interface = {};
|
||||
m_player = {};
|
||||
}
|
||||
if (m_output_mix)
|
||||
{
|
||||
(*m_output_mix)->Destroy(m_output_mix);
|
||||
m_output_mix = {};
|
||||
}
|
||||
(*m_engine)->Destroy(m_engine);
|
||||
m_engine_engine = {};
|
||||
m_engine = {};
|
||||
}
|
||||
|
||||
void OpenSLESAudioStream::SetOutputVolume(u32 volume)
|
||||
{
|
||||
const SLmillibel attenuation = (volume == 0) ?
|
||||
SL_MILLIBEL_MIN :
|
||||
static_cast<SLmillibel>(2000.0f * std::log10(static_cast<float>(volume) / 100.0f));
|
||||
SLresult res = (*m_volume_interface)->SetVolumeLevel(m_volume_interface, attenuation);
|
||||
if (res != SL_RESULT_SUCCESS)
|
||||
Log_ErrorPrintf("SetVolumeLevel failed: %d", res);
|
||||
}
|
||||
|
||||
void OpenSLESAudioStream::EnqueueBuffer()
|
||||
{
|
||||
SampleType* samples = m_buffers[m_current_buffer].get();
|
||||
ReadFrames(samples, m_buffer_size, false);
|
||||
|
||||
SLresult res = (*m_buffer_queue_interface)
|
||||
->Enqueue(m_buffer_queue_interface, samples, m_buffer_size * m_channels * sizeof(SampleType));
|
||||
if (res != SL_RESULT_SUCCESS)
|
||||
Log_ErrorPrintf("Enqueue buffer failed: %d", res);
|
||||
|
||||
m_current_buffer = (m_current_buffer + 1) % NUM_BUFFERS;
|
||||
}
|
||||
|
||||
void OpenSLESAudioStream::BufferCallback(SLAndroidSimpleBufferQueueItf buffer_queue, void* context)
|
||||
{
|
||||
OpenSLESAudioStream* const this_ptr = static_cast<OpenSLESAudioStream*>(context);
|
||||
this_ptr->EnqueueBuffer();
|
||||
}
|
||||
|
||||
void OpenSLESAudioStream::FramesAvailable() {}
|
||||
48
android/app/src/cpp/opensles_audio_stream.h
Normal file
48
android/app/src/cpp/opensles_audio_stream.h
Normal file
@@ -0,0 +1,48 @@
|
||||
#pragma once
|
||||
#include "common/audio_stream.h"
|
||||
#include <SLES/OpenSLES.h>
|
||||
#include <SLES/OpenSLES_Android.h>
|
||||
#include <array>
|
||||
#include <memory>
|
||||
|
||||
class OpenSLESAudioStream final : public AudioStream
|
||||
{
|
||||
public:
|
||||
OpenSLESAudioStream();
|
||||
~OpenSLESAudioStream();
|
||||
|
||||
static std::unique_ptr<AudioStream> Create();
|
||||
|
||||
void SetOutputVolume(u32 volume) override;
|
||||
|
||||
protected:
|
||||
enum : u32
|
||||
{
|
||||
NUM_BUFFERS = 2
|
||||
};
|
||||
|
||||
ALWAYS_INLINE bool IsOpen() const { return (m_engine != nullptr); }
|
||||
|
||||
bool OpenDevice() override;
|
||||
void PauseDevice(bool paused) override;
|
||||
void CloseDevice() override;
|
||||
void FramesAvailable() override;
|
||||
|
||||
void EnqueueBuffer();
|
||||
|
||||
static void BufferCallback(SLAndroidSimpleBufferQueueItf buffer_queue, void* context);
|
||||
|
||||
SLObjectItf m_engine{};
|
||||
SLEngineItf m_engine_engine{};
|
||||
SLObjectItf m_output_mix{};
|
||||
|
||||
SLObjectItf m_player{};
|
||||
SLPlayItf m_play_interface{};
|
||||
SLAndroidSimpleBufferQueueItf m_buffer_queue_interface{};
|
||||
SLVolumeItf m_volume_interface{};
|
||||
|
||||
std::array<std::unique_ptr<SampleType[]>, NUM_BUFFERS> m_buffers;
|
||||
u32 m_current_buffer = 0;
|
||||
bool m_paused = true;
|
||||
bool m_buffer_enqueued = false;
|
||||
};
|
||||
49
android/app/src/main/AndroidManifest.xml
Normal file
49
android/app/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1,49 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.github.stenzek.duckstation">
|
||||
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:requestLegacyExternalStorage="true"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/AppTheme">
|
||||
<activity
|
||||
android:name=".EmulationActivity"
|
||||
android:configChanges="orientation|keyboardHidden|screenSize"
|
||||
android:label="@string/title_activity_emulation"
|
||||
android:parentActivityName=".MainActivity"
|
||||
android:theme="@style/Theme.AppCompat.DayNight.NoActionBar"
|
||||
android:immersive="true">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="com.github.stenzek.duckstation.MainActivity" />
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".SettingsActivity"
|
||||
android:configChanges="orientation|keyboardHidden|screenSize"
|
||||
android:label="@string/title_activity_settings"
|
||||
android:parentActivityName=".MainActivity">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="com.github.stenzek.duckstation.MainActivity" />
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:configChanges="orientation|keyboardHidden|screenSize"
|
||||
android:label="@string/app_name"
|
||||
android:theme="@style/AppTheme.NoActionBar">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
BIN
android/app/src/main/ic_launcher-web.png
Normal file
BIN
android/app/src/main/ic_launcher-web.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 37 KiB |
@@ -0,0 +1,107 @@
|
||||
package com.github.stenzek.duckstation;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Environment;
|
||||
import android.util.Log;
|
||||
import android.view.Surface;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.google.android.material.snackbar.Snackbar;
|
||||
|
||||
public class AndroidHostInterface {
|
||||
public final static int DISPLAY_ALIGNMENT_TOP_OR_LEFT = 0;
|
||||
public final static int DISPLAY_ALIGNMENT_CENTER = 1;
|
||||
public final static int DISPLAY_ALIGNMENT_RIGHT_OR_BOTTOM = 2;
|
||||
|
||||
private long mNativePointer;
|
||||
private Context mContext;
|
||||
|
||||
static public native AndroidHostInterface create(Context context, String userDirectory);
|
||||
|
||||
public AndroidHostInterface(Context context) {
|
||||
this.mContext = context;
|
||||
}
|
||||
|
||||
public void reportError(String message) {
|
||||
Toast.makeText(mContext, message, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
|
||||
public void reportMessage(String message) {
|
||||
Toast.makeText(mContext, message, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
|
||||
public native boolean isEmulationThreadRunning();
|
||||
|
||||
public native boolean startEmulationThread(EmulationActivity emulationActivity, Surface surface, String filename, boolean resumeState, String state_filename);
|
||||
|
||||
public native boolean isEmulationThreadPaused();
|
||||
|
||||
public native void pauseEmulationThread(boolean paused);
|
||||
|
||||
public native void stopEmulationThread();
|
||||
|
||||
public native boolean hasSurface();
|
||||
|
||||
public native void surfaceChanged(Surface surface, int format, int width, int height);
|
||||
|
||||
// TODO: Find a better place for this.
|
||||
public native void setControllerType(int index, String typeName);
|
||||
|
||||
public native void setControllerButtonState(int index, int buttonCode, boolean pressed);
|
||||
|
||||
public native void setControllerAxisState(int index, int axisCode, float value);
|
||||
|
||||
public static native int getControllerButtonCode(String controllerType, String buttonName);
|
||||
|
||||
public static native int getControllerAxisCode(String controllerType, String axisName);
|
||||
|
||||
public native void refreshGameList(boolean invalidateCache, boolean invalidateDatabase);
|
||||
|
||||
public native GameListEntry[] getGameListEntries();
|
||||
|
||||
public native void resetSystem();
|
||||
|
||||
public native void loadState(boolean global, int slot);
|
||||
|
||||
public native void saveState(boolean global, int slot);
|
||||
|
||||
public native void saveResumeState(boolean waitForCompletion);
|
||||
|
||||
public native void applySettings();
|
||||
|
||||
public native void setDisplayAlignment(int alignment);
|
||||
|
||||
public native CheatCode[] getCheatList();
|
||||
public native void setCheatEnabled(int index, boolean enabled);
|
||||
|
||||
public native void addOSDMessage(String message, float duration);
|
||||
|
||||
public native boolean hasAnyBIOSImages();
|
||||
public native String importBIOSImage(byte[] data);
|
||||
|
||||
static {
|
||||
System.loadLibrary("duckstation-native");
|
||||
}
|
||||
|
||||
static private AndroidHostInterface mInstance;
|
||||
|
||||
static public boolean createInstance(Context context) {
|
||||
// Set user path.
|
||||
String externalStorageDirectory = Environment.getExternalStorageDirectory().getAbsolutePath();
|
||||
if (externalStorageDirectory.isEmpty())
|
||||
externalStorageDirectory = "/sdcard";
|
||||
|
||||
externalStorageDirectory += "/duckstation";
|
||||
Log.i("AndroidHostInterface", "User directory: " + externalStorageDirectory);
|
||||
mInstance = create(context, externalStorageDirectory);
|
||||
return mInstance != null;
|
||||
}
|
||||
|
||||
static public boolean hasInstance() {
|
||||
return mInstance != null;
|
||||
}
|
||||
|
||||
static public AndroidHostInterface getInstance() {
|
||||
return mInstance;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.github.stenzek.duckstation;
|
||||
|
||||
public class CheatCode {
|
||||
private int mIndex;
|
||||
private String mName;
|
||||
private boolean mEnabled;
|
||||
|
||||
public CheatCode(int index, String name, boolean enabled) {
|
||||
mIndex = index;
|
||||
mName = name;
|
||||
mEnabled = enabled;
|
||||
}
|
||||
|
||||
public int getIndex() { return mIndex; }
|
||||
public String getName() { return mName; }
|
||||
public boolean isEnabled() { return mEnabled; }
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.github.stenzek.duckstation;
|
||||
|
||||
public enum ConsoleRegion {
|
||||
AutoDetect,
|
||||
NTSC_J,
|
||||
NTSC_U,
|
||||
PAL
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.github.stenzek.duckstation;
|
||||
|
||||
public enum DiscRegion {
|
||||
NTSC_J,
|
||||
NTSC_U,
|
||||
PAL,
|
||||
Other
|
||||
}
|
||||
@@ -0,0 +1,467 @@
|
||||
package com.github.stenzek.duckstation;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.ActionBar;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.res.Configuration;
|
||||
import android.hardware.input.InputManager;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.util.AndroidException;
|
||||
import android.util.Log;
|
||||
import android.view.Menu;
|
||||
import android.view.SurfaceHolder;
|
||||
import android.view.View;
|
||||
import android.view.MenuItem;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.appcompat.widget.PopupMenu;
|
||||
import androidx.preference.PreferenceManager;
|
||||
|
||||
/**
|
||||
* An example full-screen activity that shows and hides the system UI (i.e.
|
||||
* status bar and navigation/system bar) with user interaction.
|
||||
*/
|
||||
public class EmulationActivity extends AppCompatActivity implements SurfaceHolder.Callback {
|
||||
/**
|
||||
* Settings interfaces.
|
||||
*/
|
||||
private SharedPreferences mPreferences;
|
||||
private boolean mWasDestroyed = false;
|
||||
private boolean mStopRequested = false;
|
||||
private boolean mWasPausedOnSurfaceLoss = false;
|
||||
private boolean mApplySettingsOnSurfaceRestored = false;
|
||||
private String mGameTitle = null;
|
||||
private EmulationSurfaceView mContentView;
|
||||
|
||||
private boolean getBooleanSetting(String key, boolean defaultValue) {
|
||||
return mPreferences.getBoolean(key, defaultValue);
|
||||
}
|
||||
|
||||
private void setBooleanSetting(String key, boolean value) {
|
||||
SharedPreferences.Editor editor = mPreferences.edit();
|
||||
editor.putBoolean(key, value);
|
||||
editor.apply();
|
||||
}
|
||||
|
||||
private String getStringSetting(String key, String defaultValue) {
|
||||
return mPreferences.getString(key, defaultValue);
|
||||
}
|
||||
|
||||
private void setStringSetting(String key, String value) {
|
||||
SharedPreferences.Editor editor = mPreferences.edit();
|
||||
editor.putString(key, value);
|
||||
editor.apply();
|
||||
}
|
||||
|
||||
public void reportError(String message) {
|
||||
Log.e("EmulationActivity", message);
|
||||
|
||||
Object lock = new Object();
|
||||
runOnUiThread(() -> {
|
||||
// Toast.makeText(this, message, Toast.LENGTH_LONG);
|
||||
new AlertDialog.Builder(this)
|
||||
.setTitle("Error")
|
||||
.setMessage(message)
|
||||
.setPositiveButton("OK", (dialog, button) -> {
|
||||
dialog.dismiss();
|
||||
synchronized (lock) {
|
||||
lock.notify();
|
||||
}
|
||||
})
|
||||
.create()
|
||||
.show();
|
||||
});
|
||||
|
||||
synchronized (lock) {
|
||||
try {
|
||||
lock.wait();
|
||||
} catch (InterruptedException e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void reportMessage(String message) {
|
||||
Log.i("EmulationActivity", message);
|
||||
runOnUiThread(() -> {
|
||||
Toast.makeText(this, message, Toast.LENGTH_SHORT);
|
||||
});
|
||||
}
|
||||
|
||||
public void onEmulationStarted() {
|
||||
}
|
||||
|
||||
public void onEmulationStopped() {
|
||||
runOnUiThread(() -> {
|
||||
AndroidHostInterface.getInstance().stopEmulationThread();
|
||||
if (!mWasDestroyed && !mStopRequested)
|
||||
finish();
|
||||
});
|
||||
}
|
||||
|
||||
public void onGameTitleChanged(String title) {
|
||||
runOnUiThread(() -> {
|
||||
mGameTitle = title;
|
||||
});
|
||||
}
|
||||
|
||||
private void applySettings() {
|
||||
if (!AndroidHostInterface.getInstance().isEmulationThreadRunning())
|
||||
return;
|
||||
|
||||
if (AndroidHostInterface.getInstance().hasSurface())
|
||||
AndroidHostInterface.getInstance().applySettings();
|
||||
else
|
||||
mApplySettingsOnSurfaceRestored = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void surfaceCreated(SurfaceHolder holder) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
|
||||
// Once we get a surface, we can boot.
|
||||
if (AndroidHostInterface.getInstance().isEmulationThreadRunning()) {
|
||||
final boolean hadSurface = AndroidHostInterface.getInstance().hasSurface();
|
||||
AndroidHostInterface.getInstance().surfaceChanged(holder.getSurface(), format, width, height);
|
||||
updateOrientation();
|
||||
|
||||
if (holder.getSurface() != null && !hadSurface && AndroidHostInterface.getInstance().isEmulationThreadPaused() && !mWasPausedOnSurfaceLoss) {
|
||||
AndroidHostInterface.getInstance().pauseEmulationThread(false);
|
||||
}
|
||||
|
||||
if (mApplySettingsOnSurfaceRestored) {
|
||||
AndroidHostInterface.getInstance().applySettings();
|
||||
mApplySettingsOnSurfaceRestored = false;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
final String bootPath = getIntent().getStringExtra("bootPath");
|
||||
final boolean resumeState = getIntent().getBooleanExtra("resumeState", false);
|
||||
final String bootSaveStatePath = getIntent().getStringExtra("saveStatePath");
|
||||
|
||||
AndroidHostInterface.getInstance().startEmulationThread(this, holder.getSurface(), bootPath, resumeState, bootSaveStatePath);
|
||||
updateOrientation();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void surfaceDestroyed(SurfaceHolder holder) {
|
||||
if (!AndroidHostInterface.getInstance().isEmulationThreadRunning())
|
||||
return;
|
||||
|
||||
Log.i("EmulationActivity", "Surface destroyed");
|
||||
|
||||
// Save the resume state in case we never get back again...
|
||||
if (!mStopRequested)
|
||||
AndroidHostInterface.getInstance().saveResumeState(true);
|
||||
|
||||
mWasPausedOnSurfaceLoss = AndroidHostInterface.getInstance().isEmulationThreadPaused();
|
||||
AndroidHostInterface.getInstance().pauseEmulationThread(true);
|
||||
AndroidHostInterface.getInstance().surfaceChanged(null, 0, 0, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
mPreferences = PreferenceManager.getDefaultSharedPreferences(this);
|
||||
Log.i("EmulationActivity", "OnCreate");
|
||||
|
||||
enableFullscreenImmersive();
|
||||
setContentView(R.layout.activity_emulation);
|
||||
|
||||
mContentView = findViewById(R.id.fullscreen_content);
|
||||
mContentView.getHolder().addCallback(this);
|
||||
mContentView.setFocusable(true);
|
||||
mContentView.requestFocus();
|
||||
|
||||
// Hook up controller input.
|
||||
updateControllers();
|
||||
registerInputDeviceListener();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostCreate(Bundle savedInstanceState) {
|
||||
super.onPostCreate(savedInstanceState);
|
||||
enableFullscreenImmersive();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostResume() {
|
||||
super.onPostResume();
|
||||
enableFullscreenImmersive();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
Log.i("EmulationActivity", "OnStop");
|
||||
if (AndroidHostInterface.getInstance().isEmulationThreadRunning()) {
|
||||
mWasDestroyed = true;
|
||||
AndroidHostInterface.getInstance().stopEmulationThread();
|
||||
}
|
||||
|
||||
unregisterInputDeviceListener();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
|
||||
if (requestCode == REQUEST_CODE_SETTINGS) {
|
||||
if (AndroidHostInterface.getInstance().isEmulationThreadRunning())
|
||||
applySettings();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
showMenu();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConfigurationChanged(@NonNull Configuration newConfig) {
|
||||
super.onConfigurationChanged(newConfig);
|
||||
updateOrientation(newConfig.orientation);
|
||||
}
|
||||
|
||||
private void updateOrientation() {
|
||||
final int orientation = getResources().getConfiguration().orientation;
|
||||
updateOrientation(orientation);
|
||||
}
|
||||
|
||||
private void updateOrientation(int newOrientation) {
|
||||
if (!AndroidHostInterface.getInstance().isEmulationThreadRunning())
|
||||
return;
|
||||
|
||||
if (newOrientation == Configuration.ORIENTATION_PORTRAIT)
|
||||
AndroidHostInterface.getInstance().setDisplayAlignment(AndroidHostInterface.DISPLAY_ALIGNMENT_TOP_OR_LEFT);
|
||||
else
|
||||
AndroidHostInterface.getInstance().setDisplayAlignment(AndroidHostInterface.DISPLAY_ALIGNMENT_CENTER);
|
||||
}
|
||||
|
||||
private void enableFullscreenImmersive()
|
||||
{
|
||||
getWindow().getDecorView().setSystemUiVisibility(
|
||||
View.SYSTEM_UI_FLAG_LAYOUT_STABLE |
|
||||
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION |
|
||||
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN |
|
||||
View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
|
||||
View.SYSTEM_UI_FLAG_FULLSCREEN |
|
||||
View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
|
||||
if (mContentView != null)
|
||||
mContentView.requestFocus();
|
||||
}
|
||||
|
||||
private static final int REQUEST_CODE_SETTINGS = 0;
|
||||
|
||||
private void showMenu() {
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
||||
if (mGameTitle != null && !mGameTitle.isEmpty())
|
||||
builder.setTitle(mGameTitle);
|
||||
|
||||
builder.setItems(R.array.emulation_menu, (dialogInterface, i) -> {
|
||||
switch (i)
|
||||
{
|
||||
case 0: // Quick Load
|
||||
{
|
||||
AndroidHostInterface.getInstance().loadState(false, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
case 1: // Quick Save
|
||||
{
|
||||
AndroidHostInterface.getInstance().saveState(false, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
case 2: // Toggle Speed Limiter
|
||||
{
|
||||
boolean newSetting = !getBooleanSetting("Main/SpeedLimiterEnabled", true);
|
||||
setBooleanSetting("Main/SpeedLimiterEnabled", newSetting);
|
||||
applySettings();
|
||||
return;
|
||||
}
|
||||
|
||||
case 3: // More Options
|
||||
{
|
||||
showMoreMenu();
|
||||
return;
|
||||
}
|
||||
|
||||
case 4: // Quit
|
||||
{
|
||||
mStopRequested = true;
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
builder.setOnDismissListener(dialogInterface -> enableFullscreenImmersive());
|
||||
builder.create().show();
|
||||
}
|
||||
|
||||
private void showMoreMenu() {
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
||||
if (mGameTitle != null && !mGameTitle.isEmpty())
|
||||
builder.setTitle(mGameTitle);
|
||||
|
||||
builder.setItems(R.array.emulation_more_menu, (dialogInterface, i) -> {
|
||||
switch (i)
|
||||
{
|
||||
case 0: // Reset
|
||||
{
|
||||
AndroidHostInterface.getInstance().resetSystem();
|
||||
return;
|
||||
}
|
||||
|
||||
case 1: // Cheats
|
||||
{
|
||||
showCheatsMenu();
|
||||
return;
|
||||
}
|
||||
|
||||
case 2: // Change Disc
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
case 3: // Change Touchscreen Controller
|
||||
{
|
||||
showTouchscreenControllerMenu();
|
||||
return;
|
||||
}
|
||||
|
||||
case 4: // Settings
|
||||
{
|
||||
Intent intent = new Intent(EmulationActivity.this, SettingsActivity.class);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||
startActivityForResult(intent, REQUEST_CODE_SETTINGS);
|
||||
return;
|
||||
}
|
||||
|
||||
case 5: // Quit
|
||||
{
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
builder.setOnDismissListener(dialogInterface -> enableFullscreenImmersive());
|
||||
builder.create().show();
|
||||
}
|
||||
|
||||
private void showTouchscreenControllerMenu() {
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
||||
builder.setItems(R.array.settings_touchscreen_controller_view_entries, (dialogInterface, i) -> {
|
||||
String[] values = getResources().getStringArray(R.array.settings_touchscreen_controller_view_values);
|
||||
setStringSetting("Controller1/TouchscreenControllerView", values[i]);
|
||||
updateControllers();
|
||||
});
|
||||
builder.setOnDismissListener(dialogInterface -> enableFullscreenImmersive());
|
||||
builder.create().show();
|
||||
}
|
||||
|
||||
private void showCheatsMenu() {
|
||||
final CheatCode[] cheats = AndroidHostInterface.getInstance().getCheatList();
|
||||
if (cheats == null) {
|
||||
AndroidHostInterface.getInstance().addOSDMessage("No cheats are loaded.", 5.0f);
|
||||
return;
|
||||
}
|
||||
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
||||
|
||||
CharSequence[] items = new CharSequence[cheats.length];
|
||||
for (int i = 0; i < cheats.length; i++) {
|
||||
final CheatCode cc = cheats[i];
|
||||
items[i] = String.format("%s %s", cc.isEnabled() ? "(ON)" : "(OFF)", cc.getName());
|
||||
}
|
||||
|
||||
builder.setItems(items, (dialogInterface, i) -> AndroidHostInterface.getInstance().setCheatEnabled(i, !cheats[i].isEnabled()));
|
||||
builder.setOnDismissListener(dialogInterface -> enableFullscreenImmersive());
|
||||
builder.create().show();
|
||||
}
|
||||
|
||||
/**
|
||||
* Touchscreen controller overlay
|
||||
*/
|
||||
TouchscreenControllerView mTouchscreenController;
|
||||
|
||||
public void updateControllers() {
|
||||
final String controllerType = getStringSetting("Controller1/Type", "DigitalController");
|
||||
final String viewType = getStringSetting("Controller1/TouchscreenControllerView", "digital");
|
||||
final boolean autoHideTouchscreenController = getBooleanSetting("Controller1/AutoHideTouchscreenController", false);
|
||||
final FrameLayout activityLayout = findViewById(R.id.frameLayout);
|
||||
|
||||
Log.i("EmulationActivity", "Controller type: " + controllerType);
|
||||
Log.i("EmulationActivity", "View type: " + viewType);
|
||||
|
||||
final boolean hasAnyControllers = mContentView.initControllerMapping(controllerType);
|
||||
|
||||
if (controllerType == "none" || viewType == "none" || (hasAnyControllers && autoHideTouchscreenController)) {
|
||||
if (mTouchscreenController != null) {
|
||||
activityLayout.removeView(mTouchscreenController);
|
||||
mTouchscreenController = null;
|
||||
}
|
||||
} else {
|
||||
if (mTouchscreenController == null) {
|
||||
mTouchscreenController = new TouchscreenControllerView(this);
|
||||
activityLayout.addView(mTouchscreenController);
|
||||
}
|
||||
|
||||
mTouchscreenController.init(0, controllerType, viewType);
|
||||
}
|
||||
}
|
||||
|
||||
private InputManager.InputDeviceListener mInputDeviceListener;
|
||||
private void registerInputDeviceListener() {
|
||||
if (mInputDeviceListener != null)
|
||||
return;
|
||||
|
||||
mInputDeviceListener = new InputManager.InputDeviceListener() {
|
||||
@Override
|
||||
public void onInputDeviceAdded(int i) {
|
||||
Log.i("EmulationActivity", String.format("InputDeviceAdded %d", i));
|
||||
updateControllers();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onInputDeviceRemoved(int i) {
|
||||
Log.i("EmulationActivity", String.format("InputDeviceRemoved %d", i));
|
||||
updateControllers();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onInputDeviceChanged(int i) {
|
||||
Log.i("EmulationActivity", String.format("InputDeviceChanged %d", i));
|
||||
updateControllers();
|
||||
}
|
||||
};
|
||||
|
||||
InputManager inputManager = ((InputManager)getSystemService(Context.INPUT_SERVICE));
|
||||
if (inputManager != null)
|
||||
inputManager.registerInputDeviceListener(mInputDeviceListener, null);
|
||||
}
|
||||
private void unregisterInputDeviceListener() {
|
||||
if (mInputDeviceListener == null)
|
||||
return;
|
||||
|
||||
InputManager inputManager = ((InputManager)getSystemService(Context.INPUT_SERVICE));
|
||||
if (inputManager != null)
|
||||
inputManager.unregisterInputDeviceListener(mInputDeviceListener);
|
||||
|
||||
mInputDeviceListener = null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,260 @@
|
||||
package com.github.stenzek.duckstation;
|
||||
|
||||
import android.content.Context;
|
||||
import android.hardware.input.InputManager;
|
||||
import android.util.ArrayMap;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
import android.util.Pair;
|
||||
import android.view.InputDevice;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.SurfaceView;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class EmulationSurfaceView extends SurfaceView {
|
||||
public EmulationSurfaceView(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public EmulationSurfaceView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public EmulationSurfaceView(Context context, AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
}
|
||||
|
||||
private boolean isDPadOrButtonEvent(KeyEvent event) {
|
||||
final int source = event.getSource();
|
||||
return (source & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD ||
|
||||
(source & InputDevice.SOURCE_DPAD) == InputDevice.SOURCE_DPAD ||
|
||||
(source & InputDevice.SOURCE_JOYSTICK) == InputDevice.SOURCE_JOYSTICK;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onKeyDown(int keyCode, KeyEvent event) {
|
||||
if (isDPadOrButtonEvent(event) && event.getRepeatCount() == 0 &&
|
||||
handleControllerKey(event.getDeviceId(), keyCode, true)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return super.onKeyDown(keyCode, event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onKeyUp(int keyCode, KeyEvent event) {
|
||||
if (isDPadOrButtonEvent(event) && event.getRepeatCount() == 0 &&
|
||||
handleControllerKey(event.getDeviceId(), keyCode, false)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return super.onKeyDown(keyCode, event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onGenericMotionEvent(MotionEvent event) {
|
||||
final int source = event.getSource();
|
||||
if ((source & InputDevice.SOURCE_JOYSTICK) == 0)
|
||||
return super.onGenericMotionEvent(event);
|
||||
|
||||
final int deviceId = event.getDeviceId();
|
||||
for (AxisMapping mapping : mControllerAxisMapping) {
|
||||
if (mapping.deviceId != deviceId)
|
||||
continue;
|
||||
|
||||
final float axisValue = event.getAxisValue(mapping.deviceAxisOrButton);
|
||||
float emuValue;
|
||||
|
||||
if (mapping.deviceMotionRange != null) {
|
||||
final float transformedValue = (axisValue - mapping.deviceMotionRange.getMin()) / mapping.deviceMotionRange.getRange();
|
||||
emuValue = (transformedValue * 2.0f) - 1.0f;
|
||||
} else {
|
||||
emuValue = axisValue;
|
||||
}
|
||||
Log.d("EmulationSurfaceView", String.format("axis %d value %f emuvalue %f", mapping.deviceAxisOrButton, axisValue, emuValue));
|
||||
|
||||
if (mapping.axisMapping >= 0) {
|
||||
AndroidHostInterface.getInstance().setControllerAxisState(0, mapping.axisMapping, emuValue);
|
||||
}
|
||||
|
||||
final float DEAD_ZONE = 0.25f;
|
||||
if (mapping.negativeButton >= 0) {
|
||||
AndroidHostInterface.getInstance().setControllerButtonState(0, mapping.negativeButton, (emuValue <= -DEAD_ZONE));
|
||||
}
|
||||
if (mapping.positiveButton >= 0) {
|
||||
AndroidHostInterface.getInstance().setControllerButtonState(0, mapping.positiveButton, (emuValue >= DEAD_ZONE));
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private class ButtonMapping {
|
||||
public ButtonMapping(int deviceId, int deviceButton, int controllerIndex, int button) {
|
||||
this.deviceId = deviceId;
|
||||
this.deviceAxisOrButton = deviceButton;
|
||||
this.controllerIndex = controllerIndex;
|
||||
this.buttonMapping = button;
|
||||
}
|
||||
|
||||
public int deviceId;
|
||||
public int deviceAxisOrButton;
|
||||
public int controllerIndex;
|
||||
public int buttonMapping;
|
||||
}
|
||||
|
||||
private class AxisMapping {
|
||||
public AxisMapping(int deviceId, int deviceAxis, InputDevice.MotionRange motionRange, int controllerIndex, int axis) {
|
||||
this.deviceId = deviceId;
|
||||
this.deviceAxisOrButton = deviceAxis;
|
||||
this.deviceMotionRange = motionRange;
|
||||
this.controllerIndex = controllerIndex;
|
||||
this.axisMapping = axis;
|
||||
this.positiveButton = -1;
|
||||
this.negativeButton = -1;
|
||||
}
|
||||
|
||||
public AxisMapping(int deviceId, int deviceAxis, InputDevice.MotionRange motionRange, int controllerIndex, int positiveButton, int negativeButton) {
|
||||
this.deviceId = deviceId;
|
||||
this.deviceAxisOrButton = deviceAxis;
|
||||
this.deviceMotionRange = motionRange;
|
||||
this.controllerIndex = controllerIndex;
|
||||
this.axisMapping = -1;
|
||||
this.positiveButton = positiveButton;
|
||||
this.negativeButton = negativeButton;
|
||||
}
|
||||
|
||||
public int deviceId;
|
||||
public int deviceAxisOrButton;
|
||||
public InputDevice.MotionRange deviceMotionRange;
|
||||
public int controllerIndex;
|
||||
public int axisMapping;
|
||||
public int positiveButton;
|
||||
public int negativeButton;
|
||||
}
|
||||
|
||||
private ArrayList<ButtonMapping> mControllerKeyMapping;
|
||||
private ArrayList<AxisMapping> mControllerAxisMapping;
|
||||
|
||||
private void addControllerKeyMapping(int deviceId, int keyCode, int controllerIndex, String controllerType, String buttonName) {
|
||||
int mapping = AndroidHostInterface.getControllerButtonCode(controllerType, buttonName);
|
||||
Log.i("EmulationSurfaceView", String.format("Map %d to %d (%s)", keyCode, mapping,
|
||||
buttonName));
|
||||
if (mapping >= 0) {
|
||||
mControllerKeyMapping.add(new ButtonMapping(deviceId, keyCode, controllerIndex, mapping));
|
||||
}
|
||||
}
|
||||
|
||||
private void addControllerAxisMapping(int deviceId, List<InputDevice.MotionRange> motionRanges, int axis, int controllerIndex, String controllerType, String axisName, String negativeButtonName, String positiveButtonName) {
|
||||
InputDevice.MotionRange range = null;
|
||||
for (InputDevice.MotionRange curRange : motionRanges) {
|
||||
if (curRange.getAxis() == axis) {
|
||||
range = curRange;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (range == null)
|
||||
return;
|
||||
|
||||
if (axisName != null) {
|
||||
int mapping = AndroidHostInterface.getControllerAxisCode(controllerType, axisName);
|
||||
Log.i("EmulationSurfaceView", String.format("Map axis %d to %d (%s)", axis, mapping, axisName));
|
||||
if (mapping >= 0) {
|
||||
mControllerAxisMapping.add(new AxisMapping(deviceId, axis, range, controllerIndex, mapping));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (negativeButtonName != null && positiveButtonName != null) {
|
||||
final int negativeMapping = AndroidHostInterface.getControllerButtonCode(controllerType, negativeButtonName);
|
||||
final int positiveMapping = AndroidHostInterface.getControllerButtonCode(controllerType, positiveButtonName);
|
||||
Log.i("EmulationSurfaceView", String.format("Map axis %d to %d %d (button %s %s)", axis, negativeMapping, positiveMapping,
|
||||
negativeButtonName, positiveButtonName));
|
||||
if (negativeMapping >= 0 && positiveMapping >= 0) {
|
||||
mControllerAxisMapping.add(new AxisMapping(deviceId, axis, range, controllerIndex, positiveMapping, negativeMapping));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isJoystickDevice(int deviceId) {
|
||||
if (deviceId < 0)
|
||||
return false;
|
||||
|
||||
final InputDevice dev = InputDevice.getDevice(deviceId);
|
||||
if (dev == null)
|
||||
return false;
|
||||
|
||||
final int sources = dev.getSources();
|
||||
if ((sources & InputDevice.SOURCE_CLASS_JOYSTICK) != 0)
|
||||
return true;
|
||||
|
||||
if ((sources & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD)
|
||||
return true;
|
||||
|
||||
if ((sources & InputDevice.SOURCE_DPAD) == InputDevice.SOURCE_DPAD)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean initControllerMapping(String controllerType) {
|
||||
mControllerKeyMapping = new ArrayList<>();
|
||||
mControllerAxisMapping = new ArrayList<>();
|
||||
|
||||
final int[] deviceIds = InputDevice.getDeviceIds();
|
||||
for (int deviceId : deviceIds) {
|
||||
if (!isJoystickDevice(deviceId))
|
||||
continue;
|
||||
|
||||
InputDevice device = InputDevice.getDevice(deviceId);
|
||||
List<InputDevice.MotionRange> motionRanges = device.getMotionRanges();
|
||||
int controllerIndex = 0;
|
||||
|
||||
addControllerKeyMapping(deviceId, KeyEvent.KEYCODE_DPAD_UP, controllerIndex, controllerType, "Up");
|
||||
addControllerKeyMapping(deviceId, KeyEvent.KEYCODE_DPAD_RIGHT, controllerIndex, controllerType, "Right");
|
||||
addControllerKeyMapping(deviceId, KeyEvent.KEYCODE_DPAD_DOWN, controllerIndex, controllerType, "Down");
|
||||
addControllerKeyMapping(deviceId, KeyEvent.KEYCODE_DPAD_LEFT, controllerIndex, controllerType, "Left");
|
||||
addControllerKeyMapping(deviceId, KeyEvent.KEYCODE_BUTTON_L1, controllerIndex, controllerType, "L1");
|
||||
addControllerKeyMapping(deviceId, KeyEvent.KEYCODE_BUTTON_L2, controllerIndex, controllerType, "L2");
|
||||
addControllerKeyMapping(deviceId, KeyEvent.KEYCODE_BUTTON_SELECT, controllerIndex, controllerType, "Select");
|
||||
addControllerKeyMapping(deviceId, KeyEvent.KEYCODE_BUTTON_START, controllerIndex, controllerType, "Start");
|
||||
addControllerKeyMapping(deviceId, KeyEvent.KEYCODE_BUTTON_Y, controllerIndex, controllerType, "Triangle");
|
||||
addControllerKeyMapping(deviceId, KeyEvent.KEYCODE_BUTTON_B, controllerIndex, controllerType, "Circle");
|
||||
addControllerKeyMapping(deviceId, KeyEvent.KEYCODE_BUTTON_A, controllerIndex, controllerType, "Cross");
|
||||
addControllerKeyMapping(deviceId, KeyEvent.KEYCODE_BUTTON_X, controllerIndex, controllerType, "Square");
|
||||
addControllerKeyMapping(deviceId, KeyEvent.KEYCODE_BUTTON_R1, controllerIndex, controllerType, "R1");
|
||||
addControllerKeyMapping(deviceId, KeyEvent.KEYCODE_BUTTON_R2, controllerIndex, controllerType, "R2");
|
||||
if (motionRanges != null) {
|
||||
addControllerAxisMapping(deviceId, motionRanges, MotionEvent.AXIS_X, controllerIndex, controllerType, "LeftX", null, null);
|
||||
addControllerAxisMapping(deviceId, motionRanges, MotionEvent.AXIS_Y, controllerIndex, controllerType, "LeftY", null, null);
|
||||
addControllerAxisMapping(deviceId, motionRanges, MotionEvent.AXIS_RX, controllerIndex, controllerType, "RightX", null, null);
|
||||
addControllerAxisMapping(deviceId, motionRanges, MotionEvent.AXIS_RY, controllerIndex, controllerType, "RightY", null, null);
|
||||
addControllerAxisMapping(deviceId, motionRanges, MotionEvent.AXIS_Z, controllerIndex, controllerType, "RightX", null, null);
|
||||
addControllerAxisMapping(deviceId, motionRanges, MotionEvent.AXIS_RZ, controllerIndex, controllerType, "RightY", null, null);
|
||||
addControllerAxisMapping(deviceId, motionRanges, MotionEvent.AXIS_LTRIGGER, controllerIndex, controllerType, "L2", "L2", "L2");
|
||||
addControllerAxisMapping(deviceId, motionRanges, MotionEvent.AXIS_RTRIGGER, controllerIndex, controllerType, "R2", "R2", "R2");
|
||||
addControllerAxisMapping(deviceId, motionRanges, MotionEvent.AXIS_HAT_X, controllerIndex, controllerType, null, "Left", "Right");
|
||||
addControllerAxisMapping(deviceId, motionRanges, MotionEvent.AXIS_HAT_Y, controllerIndex, controllerType, null, "Up", "Down");
|
||||
}
|
||||
}
|
||||
|
||||
return !mControllerKeyMapping.isEmpty() || !mControllerKeyMapping.isEmpty();
|
||||
}
|
||||
|
||||
private boolean handleControllerKey(int deviceId, int keyCode, boolean pressed) {
|
||||
boolean result = false;
|
||||
for (ButtonMapping mapping : mControllerKeyMapping) {
|
||||
if (mapping.deviceId != deviceId || mapping.deviceAxisOrButton != keyCode)
|
||||
continue;
|
||||
|
||||
AndroidHostInterface.getInstance().setControllerButtonState(0, mapping.buttonMapping, pressed);
|
||||
Log.d("EmulationSurfaceView", String.format("handleControllerKey %d -> %d %d", keyCode, mapping.buttonMapping, pressed ? 1 : 0));
|
||||
result = true;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
package com.github.stenzek.duckstation;
|
||||
|
||||
// https://stackoverflow.com/questions/34927748/android-5-0-documentfile-from-tree-uri
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.storage.StorageManager;
|
||||
import android.provider.DocumentsContract;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.io.File;
|
||||
import java.lang.reflect.Array;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
public final class FileUtil {
|
||||
static String TAG = "TAG";
|
||||
private static final String PRIMARY_VOLUME_NAME = "primary";
|
||||
|
||||
@Nullable
|
||||
public static String getFullPathFromTreeUri(@Nullable final Uri treeUri, Context con) {
|
||||
if (treeUri == null) return null;
|
||||
String volumePath = getVolumePath(getVolumeIdFromTreeUri(treeUri), con);
|
||||
if (volumePath == null) return File.separator;
|
||||
if (volumePath.endsWith(File.separator))
|
||||
volumePath = volumePath.substring(0, volumePath.length() - 1);
|
||||
|
||||
String documentPath = getDocumentPathFromTreeUri(treeUri);
|
||||
if (documentPath.endsWith(File.separator))
|
||||
documentPath = documentPath.substring(0, documentPath.length() - 1);
|
||||
|
||||
if (documentPath.length() > 0) {
|
||||
if (documentPath.startsWith(File.separator))
|
||||
return volumePath + documentPath;
|
||||
else
|
||||
return volumePath + File.separator + documentPath;
|
||||
} else return volumePath;
|
||||
}
|
||||
|
||||
|
||||
@SuppressLint("ObsoleteSdkInt")
|
||||
private static String getVolumePath(final String volumeId, Context context) {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) return null;
|
||||
try {
|
||||
StorageManager mStorageManager =
|
||||
(StorageManager) context.getSystemService(Context.STORAGE_SERVICE);
|
||||
Class<?> storageVolumeClazz = Class.forName("android.os.storage.StorageVolume");
|
||||
Method getVolumeList = mStorageManager.getClass().getMethod("getVolumeList");
|
||||
Method getUuid = storageVolumeClazz.getMethod("getUuid");
|
||||
Method getPath = storageVolumeClazz.getMethod("getPath");
|
||||
Method isPrimary = storageVolumeClazz.getMethod("isPrimary");
|
||||
Object result = getVolumeList.invoke(mStorageManager);
|
||||
|
||||
final int length = Array.getLength(result);
|
||||
for (int i = 0; i < length; i++) {
|
||||
Object storageVolumeElement = Array.get(result, i);
|
||||
String uuid = (String) getUuid.invoke(storageVolumeElement);
|
||||
Boolean primary = (Boolean) isPrimary.invoke(storageVolumeElement);
|
||||
|
||||
// primary volume?
|
||||
if (primary && PRIMARY_VOLUME_NAME.equals(volumeId))
|
||||
return (String) getPath.invoke(storageVolumeElement);
|
||||
|
||||
// other volumes?
|
||||
if (uuid != null && uuid.equals(volumeId))
|
||||
return (String) getPath.invoke(storageVolumeElement);
|
||||
}
|
||||
// not found.
|
||||
return null;
|
||||
} catch (Exception ex) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
||||
private static String getVolumeIdFromTreeUri(final Uri treeUri) {
|
||||
final String docId = DocumentsContract.getTreeDocumentId(treeUri);
|
||||
final String[] split = docId.split(":");
|
||||
if (split.length > 0) return split[0];
|
||||
else return null;
|
||||
}
|
||||
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
||||
private static String getDocumentPathFromTreeUri(final Uri treeUri) {
|
||||
final String docId = DocumentsContract.getTreeDocumentId(treeUri);
|
||||
final String[] split = docId.split(":");
|
||||
if ((split.length >= 2) && (split[1] != null)) return split[1];
|
||||
else return File.separator;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
package com.github.stenzek.duckstation;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.util.ArraySet;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.BaseAdapter;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.preference.PreferenceManager;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
import java.util.Set;
|
||||
|
||||
public class GameList {
|
||||
private Context mContext;
|
||||
private GameListEntry[] mEntries;
|
||||
private ListViewAdapter mAdapter;
|
||||
|
||||
public GameList(Context context) {
|
||||
mContext = context;
|
||||
mAdapter = new ListViewAdapter();
|
||||
mEntries = new GameListEntry[0];
|
||||
}
|
||||
|
||||
private class GameListEntryComparator implements Comparator<GameListEntry> {
|
||||
@Override
|
||||
public int compare(GameListEntry left, GameListEntry right) {
|
||||
return left.getTitle().compareTo(right.getTitle());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void refresh(boolean invalidateCache, boolean invalidateDatabase) {
|
||||
// Search and get entries from native code
|
||||
AndroidHostInterface.getInstance().refreshGameList(invalidateCache, invalidateDatabase);
|
||||
mEntries = AndroidHostInterface.getInstance().getGameListEntries();
|
||||
Arrays.sort(mEntries, new GameListEntryComparator());
|
||||
mAdapter.notifyDataSetChanged();
|
||||
}
|
||||
|
||||
public int getEntryCount() {
|
||||
return mEntries.length;
|
||||
}
|
||||
|
||||
public GameListEntry getEntry(int index) {
|
||||
return mEntries[index];
|
||||
}
|
||||
|
||||
private class ListViewAdapter extends BaseAdapter {
|
||||
@Override
|
||||
public int getCount() {
|
||||
return mEntries.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getItem(int position) {
|
||||
return mEntries[position];
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getItemId(int position) {
|
||||
return position;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getViewTypeCount() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(int position, View convertView, ViewGroup parent) {
|
||||
if (convertView == null) {
|
||||
convertView = LayoutInflater.from(mContext)
|
||||
.inflate(R.layout.game_list_view_entry, parent, false);
|
||||
}
|
||||
|
||||
mEntries[position].fillView(convertView);
|
||||
return convertView;
|
||||
}
|
||||
}
|
||||
|
||||
public BaseAdapter getListViewAdapter() {
|
||||
return mAdapter;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
package com.github.stenzek.duckstation;
|
||||
|
||||
import android.view.View;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
public class GameListEntry {
|
||||
public enum EntryType {
|
||||
Disc,
|
||||
PSExe
|
||||
}
|
||||
|
||||
public enum CompatibilityRating {
|
||||
Unknown,
|
||||
DoesntBoot,
|
||||
CrashesInIntro,
|
||||
CrashesInGame,
|
||||
GraphicalAudioIssues,
|
||||
NoIssues,
|
||||
}
|
||||
|
||||
private String mPath;
|
||||
private String mCode;
|
||||
private String mTitle;
|
||||
private long mSize;
|
||||
private String mModifiedTime;
|
||||
private DiscRegion mRegion;
|
||||
private EntryType mType;
|
||||
private CompatibilityRating mCompatibilityRating;
|
||||
|
||||
|
||||
public GameListEntry(String path, String code, String title, long size, String modifiedTime, String region,
|
||||
String type, String compatibilityRating) {
|
||||
mPath = path;
|
||||
mCode = code;
|
||||
mTitle = title;
|
||||
mSize = size;
|
||||
mModifiedTime = modifiedTime;
|
||||
|
||||
try {
|
||||
mRegion = DiscRegion.valueOf(region);
|
||||
} catch (IllegalArgumentException e) {
|
||||
mRegion = DiscRegion.NTSC_U;
|
||||
}
|
||||
|
||||
try {
|
||||
mType = EntryType.valueOf(type);
|
||||
} catch (IllegalArgumentException e) {
|
||||
mType = EntryType.Disc;
|
||||
}
|
||||
|
||||
try {
|
||||
mCompatibilityRating = CompatibilityRating.valueOf(compatibilityRating);
|
||||
} catch (IllegalArgumentException e) {
|
||||
mCompatibilityRating = CompatibilityRating.Unknown;
|
||||
}
|
||||
}
|
||||
|
||||
public String getPath() {
|
||||
return mPath;
|
||||
}
|
||||
|
||||
public String getCode() {
|
||||
return mCode;
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return mTitle;
|
||||
}
|
||||
|
||||
public String getModifiedTime() {
|
||||
return mModifiedTime;
|
||||
}
|
||||
|
||||
public DiscRegion getRegion() {
|
||||
return mRegion;
|
||||
}
|
||||
|
||||
public EntryType getType() {
|
||||
return mType;
|
||||
}
|
||||
|
||||
public CompatibilityRating getCompatibilityRating() {
|
||||
return mCompatibilityRating;
|
||||
}
|
||||
|
||||
public void fillView(View view) {
|
||||
((TextView) view.findViewById(R.id.game_list_view_entry_title)).setText(mTitle);
|
||||
((TextView) view.findViewById(R.id.game_list_view_entry_path)).setText(mPath);
|
||||
|
||||
String sizeString = String.format("%.2f MB", (double) mSize / 1048576.0);
|
||||
((TextView) view.findViewById(R.id.game_list_view_entry_size)).setText(sizeString);
|
||||
|
||||
int regionDrawableId;
|
||||
switch (mRegion) {
|
||||
case NTSC_J:
|
||||
regionDrawableId = R.drawable.flag_jp;
|
||||
break;
|
||||
case NTSC_U:
|
||||
default:
|
||||
regionDrawableId = R.drawable.flag_us;
|
||||
break;
|
||||
case PAL:
|
||||
regionDrawableId = R.drawable.flag_eu;
|
||||
break;
|
||||
}
|
||||
|
||||
((ImageView) view.findViewById(R.id.game_list_view_entry_region_icon))
|
||||
.setImageDrawable(ContextCompat.getDrawable(view.getContext(), regionDrawableId));
|
||||
|
||||
int typeDrawableId;
|
||||
switch (mType) {
|
||||
case Disc:
|
||||
default:
|
||||
typeDrawableId = R.drawable.ic_media_cdrom;
|
||||
break;
|
||||
|
||||
case PSExe:
|
||||
typeDrawableId = R.drawable.ic_emblem_system;
|
||||
break;
|
||||
}
|
||||
|
||||
((ImageView) view.findViewById(R.id.game_list_view_entry_type_icon))
|
||||
.setImageDrawable(ContextCompat.getDrawable(view.getContext(), typeDrawableId));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,334 @@
|
||||
package com.github.stenzek.duckstation;
|
||||
|
||||
import android.Manifest;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||
import com.google.android.material.snackbar.Snackbar;
|
||||
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import androidx.core.app.ActivityCompat;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.documentfile.provider.DocumentFile;
|
||||
import androidx.preference.PreferenceManager;
|
||||
|
||||
import android.content.Intent;
|
||||
|
||||
import androidx.collection.ArraySet;
|
||||
|
||||
import android.provider.DocumentsContract;
|
||||
import android.provider.MediaStore;
|
||||
import android.util.Log;
|
||||
import android.view.Gravity;
|
||||
import android.view.View;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ListView;
|
||||
import android.widget.PopupMenu;
|
||||
import android.widget.Toast;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.prefs.Preferences;
|
||||
|
||||
import static com.google.android.material.snackbar.Snackbar.make;
|
||||
|
||||
public class MainActivity extends AppCompatActivity {
|
||||
private static final int REQUEST_EXTERNAL_STORAGE_PERMISSIONS = 1;
|
||||
private static final int REQUEST_ADD_DIRECTORY_TO_GAME_LIST = 2;
|
||||
private static final int REQUEST_IMPORT_BIOS_IMAGE = 3;
|
||||
|
||||
private GameList mGameList;
|
||||
private ListView mGameListView;
|
||||
private boolean mHasExternalStoragePermissions = false;
|
||||
|
||||
private boolean shouldResumeStateByDefault() {
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
|
||||
return prefs.getBoolean("Main/SaveStateOnExit", true);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
setContentView(R.layout.activity_main);
|
||||
Toolbar toolbar = findViewById(R.id.toolbar);
|
||||
setSupportActionBar(toolbar);
|
||||
|
||||
findViewById(R.id.fab_add_game_directory).setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
startAddGameDirectory();
|
||||
}
|
||||
});
|
||||
findViewById(R.id.fab_resume).setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
startEmulation(null, shouldResumeStateByDefault());
|
||||
}
|
||||
});
|
||||
|
||||
// Set up game list view.
|
||||
mGameList = new GameList(this);
|
||||
mGameListView = findViewById(R.id.game_list_view);
|
||||
mGameListView.setAdapter(mGameList.getListViewAdapter());
|
||||
mGameListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
||||
startEmulation(mGameList.getEntry(position).getPath(), shouldResumeStateByDefault());
|
||||
}
|
||||
});
|
||||
mGameListView.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
|
||||
@Override
|
||||
public boolean onItemLongClick(AdapterView<?> parent, View view, int position,
|
||||
long id) {
|
||||
PopupMenu menu = new PopupMenu(MainActivity.this, view,
|
||||
Gravity.RIGHT | Gravity.TOP);
|
||||
menu.getMenuInflater().inflate(R.menu.menu_game_list_entry, menu.getMenu());
|
||||
menu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
|
||||
@Override
|
||||
public boolean onMenuItemClick(MenuItem item) {
|
||||
int id = item.getItemId();
|
||||
if (id == R.id.game_list_entry_menu_start_game) {
|
||||
startEmulation(mGameList.getEntry(position).getPath(), false);
|
||||
return true;
|
||||
} else if (id == R.id.game_list_entry_menu_resume_game) {
|
||||
startEmulation(mGameList.getEntry(position).getPath(), true);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
menu.show();
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
mHasExternalStoragePermissions = checkForExternalStoragePermissions();
|
||||
if (mHasExternalStoragePermissions)
|
||||
completeStartup();
|
||||
}
|
||||
|
||||
private void completeStartup() {
|
||||
if (!AndroidHostInterface.hasInstance() && !AndroidHostInterface.createInstance(this)) {
|
||||
Log.i("MainActivity", "Failed to create host interface");
|
||||
throw new RuntimeException("Failed to create host interface");
|
||||
}
|
||||
|
||||
mGameList.refresh(false, false);
|
||||
}
|
||||
|
||||
private void startAddGameDirectory() {
|
||||
if (!checkForExternalStoragePermissions())
|
||||
return;
|
||||
|
||||
Intent i = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
|
||||
i.addCategory(Intent.CATEGORY_DEFAULT);
|
||||
i.putExtra(Intent.EXTRA_LOCAL_ONLY, true);
|
||||
startActivityForResult(Intent.createChooser(i, "Choose directory"),
|
||||
REQUEST_ADD_DIRECTORY_TO_GAME_LIST);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
// Inflate the menu; this adds items to the action bar if it is present.
|
||||
getMenuInflater().inflate(R.menu.menu_main, menu);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
// Handle action bar item clicks here. The action bar will
|
||||
// automatically handle clicks on the Home/Up button, so long
|
||||
// as you specify a parent activity in AndroidManifest.xml.
|
||||
int id = item.getItemId();
|
||||
|
||||
//noinspection SimplifiableIfStatement
|
||||
if (id == R.id.action_resume) {
|
||||
startEmulation(null, true);
|
||||
} else if (id == R.id.action_start_bios) {
|
||||
startEmulation(null, false);
|
||||
} else if (id == R.id.action_add_game_directory) {
|
||||
startAddGameDirectory();
|
||||
} else if (id == R.id.action_scan_for_new_games) {
|
||||
mGameList.refresh(false, false);
|
||||
} else if (id == R.id.action_rescan_all_games) {
|
||||
mGameList.refresh(true, true);
|
||||
} else if (id == R.id.action_import_bios) {
|
||||
importBIOSImage();
|
||||
} else if (id == R.id.action_settings) {
|
||||
Intent intent = new Intent(this, SettingsActivity.class);
|
||||
startActivity(intent);
|
||||
return true;
|
||||
}
|
||||
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
|
||||
switch (requestCode) {
|
||||
case REQUEST_ADD_DIRECTORY_TO_GAME_LIST: {
|
||||
if (resultCode != RESULT_OK)
|
||||
return;
|
||||
|
||||
Uri treeUri = data.getData();
|
||||
String path = FileUtil.getFullPathFromTreeUri(treeUri, this);
|
||||
if (path.length() < 5) {
|
||||
new AlertDialog.Builder(this)
|
||||
.setTitle("Error")
|
||||
.setMessage("Failed to get path for the selected directory. Please make sure the directory is in internal/external storage.\n\n" +
|
||||
"Tap the overflow button in the directory selector.\nSelect \"Show Internal Storage\".\n" +
|
||||
"Tap the menu button and select your device name or SD card.")
|
||||
.setPositiveButton("OK", (dialog, button) -> {})
|
||||
.create()
|
||||
.show();
|
||||
return;
|
||||
}
|
||||
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
|
||||
Set<String> currentValues = prefs.getStringSet("GameList/RecursivePaths", null);
|
||||
if (currentValues == null)
|
||||
currentValues = new HashSet<String>();
|
||||
|
||||
currentValues.add(path);
|
||||
SharedPreferences.Editor editor = prefs.edit();
|
||||
editor.putStringSet("GameList/RecursivePaths", currentValues);
|
||||
editor.apply();
|
||||
Log.i("MainActivity", "Added path '" + path + "' to game list search directories");
|
||||
mGameList.refresh(false, false);
|
||||
}
|
||||
break;
|
||||
|
||||
case REQUEST_IMPORT_BIOS_IMAGE: {
|
||||
if (resultCode != RESULT_OK)
|
||||
return;
|
||||
|
||||
onImportBIOSImageResult(data.getData());
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean checkForExternalStoragePermissions() {
|
||||
if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) ==
|
||||
PackageManager.PERMISSION_GRANTED &&
|
||||
ContextCompat
|
||||
.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) ==
|
||||
PackageManager.PERMISSION_GRANTED) {
|
||||
return true;
|
||||
}
|
||||
|
||||
ActivityCompat.requestPermissions(this,
|
||||
new String[]{Manifest.permission.READ_EXTERNAL_STORAGE,
|
||||
Manifest.permission.WRITE_EXTERNAL_STORAGE},
|
||||
REQUEST_EXTERNAL_STORAGE_PERMISSIONS);
|
||||
return false;
|
||||
}
|
||||
|
||||
public void onRequestPermissionsResult(int requestCode, String[] permissions,
|
||||
int[] grantResults) {
|
||||
// check that all were successful
|
||||
for (int i = 0; i < grantResults.length; i++) {
|
||||
if (grantResults[i] == PackageManager.PERMISSION_GRANTED) {
|
||||
if (!mHasExternalStoragePermissions) {
|
||||
mHasExternalStoragePermissions = true;
|
||||
completeStartup();
|
||||
}
|
||||
} else {
|
||||
Toast.makeText(this,
|
||||
"External storage permissions are required to use DuckStation.",
|
||||
Toast.LENGTH_LONG);
|
||||
finish();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean startEmulation(String bootPath, boolean resumeState) {
|
||||
if (!doBIOSCheck())
|
||||
return false;
|
||||
|
||||
Intent intent = new Intent(this, EmulationActivity.class);
|
||||
intent.putExtra("bootPath", bootPath);
|
||||
intent.putExtra("resumeState", resumeState);
|
||||
startActivity(intent);
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean doBIOSCheck() {
|
||||
if (AndroidHostInterface.getInstance().hasAnyBIOSImages())
|
||||
return true;
|
||||
|
||||
new AlertDialog.Builder(this)
|
||||
.setTitle("Missing BIOS Image")
|
||||
.setMessage("No BIOS image was found in DuckStation's bios directory. Do you with to locate and import a BIOS image now?")
|
||||
.setPositiveButton("Yes", (dialog, button) -> importBIOSImage())
|
||||
.setNegativeButton("No", (dialog, button) -> {})
|
||||
.create()
|
||||
.show();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void importBIOSImage() {
|
||||
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
|
||||
intent.setType("*/*");
|
||||
intent.addCategory(Intent.CATEGORY_OPENABLE);
|
||||
startActivityForResult(Intent.createChooser(intent, "Choose BIOS Image"), REQUEST_IMPORT_BIOS_IMAGE);
|
||||
}
|
||||
|
||||
private void onImportBIOSImageResult(Uri uri) {
|
||||
// This should really be 512K but just in case we wanted to support the other BIOSes in the future...
|
||||
final int MAX_BIOS_SIZE = 2 * 1024 * 1024;
|
||||
|
||||
InputStream stream = null;
|
||||
try {
|
||||
stream = getContentResolver().openInputStream(uri);
|
||||
} catch (FileNotFoundException e) {
|
||||
Toast.makeText(this, "Failed to open BIOS image.", Toast.LENGTH_LONG);
|
||||
return;
|
||||
}
|
||||
|
||||
ByteArrayOutputStream os = new ByteArrayOutputStream();
|
||||
try {
|
||||
byte[] buffer = new byte[512 * 1024];
|
||||
int len;
|
||||
while ((len = stream.read(buffer)) > 0) {
|
||||
os.write(buffer, 0, len);
|
||||
if (os.size() > MAX_BIOS_SIZE) {
|
||||
throw new IOException("BIOS image is too large.");
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
new AlertDialog.Builder(this)
|
||||
.setMessage("Failed to read BIOS image: " + e.getMessage())
|
||||
.setPositiveButton("OK", (dialog, button) -> {})
|
||||
.create()
|
||||
.show();
|
||||
return;
|
||||
}
|
||||
|
||||
String importResult = AndroidHostInterface.getInstance().importBIOSImage(os.toByteArray());
|
||||
String message = (importResult == null) ? "This BIOS image is invalid, or has already been imported." : ("BIOS '" + importResult + "' imported.");
|
||||
|
||||
new AlertDialog.Builder(this)
|
||||
.setMessage(message)
|
||||
.setPositiveButton("OK", (dialog, button) -> {})
|
||||
.create()
|
||||
.show();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
package com.github.stenzek.duckstation;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.ActionBar;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.preference.PreferenceFragmentCompat;
|
||||
import androidx.viewpager2.adapter.FragmentStateAdapter;
|
||||
import androidx.viewpager2.widget.ViewPager2;
|
||||
|
||||
import com.google.android.material.tabs.TabLayout;
|
||||
import com.google.android.material.tabs.TabLayoutMediator;
|
||||
|
||||
public class SettingsActivity extends AppCompatActivity {
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.settings_activity);
|
||||
getSupportFragmentManager()
|
||||
.beginTransaction()
|
||||
.replace(R.id.settings, new SettingsCollectionFragment())
|
||||
.commit();
|
||||
ActionBar actionBar = getSupportActionBar();
|
||||
if (actionBar != null) {
|
||||
actionBar.setDisplayHomeAsUpEnabled(true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
|
||||
if (item.getItemId() == android.R.id.home) {
|
||||
onBackPressed();
|
||||
return true;
|
||||
}
|
||||
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
public static class SettingsFragment extends PreferenceFragmentCompat {
|
||||
private int resourceId;
|
||||
public SettingsFragment(int resourceId) {
|
||||
this.resourceId = resourceId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
|
||||
setPreferencesFromResource(resourceId, rootKey);
|
||||
}
|
||||
}
|
||||
|
||||
public static class SettingsCollectionFragment extends Fragment {
|
||||
private SettingsCollectionAdapter adapter;
|
||||
private ViewPager2 viewPager;
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
return inflater.inflate(R.layout.fragment_settings_collection, container, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
adapter = new SettingsCollectionAdapter(this);
|
||||
viewPager = view.findViewById(R.id.view_pager);
|
||||
viewPager.setAdapter(adapter);
|
||||
|
||||
TabLayout tabLayout = view.findViewById(R.id.tab_layout);
|
||||
new TabLayoutMediator(tabLayout, viewPager,
|
||||
(tab, position) -> tab.setText(getResources().getStringArray(R.array.settings_tabs)[position])
|
||||
).attach();
|
||||
}
|
||||
}
|
||||
|
||||
public static class SettingsCollectionAdapter extends FragmentStateAdapter {
|
||||
public SettingsCollectionAdapter(@NonNull Fragment fragment) {
|
||||
super(fragment);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Fragment createFragment(int position) {
|
||||
switch (position) {
|
||||
case 0: // General
|
||||
return new SettingsFragment(R.xml.general_preferences);
|
||||
|
||||
case 1: // Display
|
||||
return new SettingsFragment(R.xml.display_preferences);
|
||||
|
||||
case 2: // Audio
|
||||
return new SettingsFragment(R.xml.audio_preferences);
|
||||
|
||||
case 3: // Enhancements
|
||||
return new SettingsFragment(R.xml.enhancements_preferences);
|
||||
|
||||
case 4: // Controllers
|
||||
return new SettingsFragment(R.xml.controllers_preferences);
|
||||
|
||||
case 5: // Advanced
|
||||
return new SettingsFragment(R.xml.advanced_preferences);
|
||||
|
||||
default:
|
||||
return new Fragment();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return 5;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,179 @@
|
||||
package com.github.stenzek.duckstation;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
|
||||
public class TouchscreenControllerAxisView extends View {
|
||||
private Drawable mBaseDrawable;
|
||||
private Drawable mStickUnpressedDrawable;
|
||||
private Drawable mStickPressedDrawable;
|
||||
private boolean mPressed = false;
|
||||
private int mPointerId = 0;
|
||||
private float mXValue = 0.0f;
|
||||
private float mYValue = 0.0f;
|
||||
private int mDrawXPos = 0;
|
||||
private int mDrawYPos = 0;
|
||||
|
||||
private int mControllerIndex = -1;
|
||||
private int mXAxisCode = -1;
|
||||
private int mYAxisCode = -1;
|
||||
private int mLeftButtonCode = -1;
|
||||
private int mRightButtonCode = -1;
|
||||
private int mUpButtonCode = -1;
|
||||
private int mDownButtonCode = -1;
|
||||
|
||||
public TouchscreenControllerAxisView(Context context) {
|
||||
super(context);
|
||||
init();
|
||||
}
|
||||
|
||||
public TouchscreenControllerAxisView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
init();
|
||||
}
|
||||
|
||||
public TouchscreenControllerAxisView(Context context, AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
init();
|
||||
}
|
||||
|
||||
private void init() {
|
||||
mBaseDrawable = getContext().getDrawable(R.drawable.ic_controller_analog_base);
|
||||
mBaseDrawable.setCallback(this);
|
||||
mStickUnpressedDrawable = getContext().getDrawable(R.drawable.ic_controller_analog_stick_unpressed);
|
||||
mStickUnpressedDrawable.setCallback(this);
|
||||
mStickPressedDrawable = getContext().getDrawable(R.drawable.ic_controller_analog_stick_pressed);
|
||||
mStickPressedDrawable.setCallback(this);
|
||||
}
|
||||
|
||||
public void setControllerAxis(int controllerIndex, int xCode, int yCode) {
|
||||
mControllerIndex = controllerIndex;
|
||||
mXAxisCode = xCode;
|
||||
mYAxisCode = yCode;
|
||||
mLeftButtonCode = -1;
|
||||
mRightButtonCode = -1;
|
||||
mUpButtonCode = -1;
|
||||
mDownButtonCode = -1;
|
||||
}
|
||||
|
||||
public void setControllerButtons(int controllerIndex, int leftCode, int rightCode, int upCode, int downCode) {
|
||||
mControllerIndex = controllerIndex;
|
||||
mXAxisCode = -1;
|
||||
mYAxisCode = -1;
|
||||
mLeftButtonCode = leftCode;
|
||||
mRightButtonCode = rightCode;
|
||||
mUpButtonCode = upCode;
|
||||
mDownButtonCode = downCode;
|
||||
}
|
||||
|
||||
public void setUnpressed() {
|
||||
if (!mPressed && mXValue == 0.0f && mYValue == 0.0f)
|
||||
return;
|
||||
|
||||
mPressed = false;
|
||||
mXValue = 0.0f;
|
||||
mYValue = 0.0f;
|
||||
mDrawXPos = 0;
|
||||
mDrawYPos = 0;
|
||||
invalidate();
|
||||
updateControllerState();
|
||||
}
|
||||
|
||||
public void setPressed(int pointerId, float pointerX, float pointerY) {
|
||||
final float dx = pointerX - (float)(getX() + (float)(getWidth() / 2));
|
||||
final float dy = pointerY - (float)(getY() + (float)(getHeight() / 2));
|
||||
// Log.i("SetPressed", String.format("px=%f,py=%f dx=%f,dy=%f", pointerX, pointerY, dx, dy));
|
||||
|
||||
final float pointerDistance = Math.max(Math.abs(dx), Math.abs(dy));
|
||||
final float angle = (float)Math.atan2((double)dy, (double)dx);
|
||||
|
||||
final float maxDistance = (float)Math.min((getWidth() - getPaddingLeft() - getPaddingRight()) / 2, (getHeight() - getPaddingTop() - getPaddingBottom()) / 2);
|
||||
final float length = Math.min(pointerDistance / maxDistance, 1.0f);
|
||||
// Log.i("SetPressed", String.format("pointerDist=%f,angle=%f,w=%d,h=%d,maxDist=%f,length=%f", pointerDistance, angle, getWidth(), getHeight(), maxDistance, length));
|
||||
|
||||
final float xValue = (float)Math.cos((double)angle) * length;
|
||||
final float yValue = (float)Math.sin((double)angle) * length;
|
||||
mDrawXPos = (int)(xValue * maxDistance);
|
||||
mDrawYPos = (int)(yValue * maxDistance);
|
||||
|
||||
boolean doUpdate = (pointerId != mPointerId || !mPressed || (xValue != mXValue || yValue != mYValue));
|
||||
mPointerId = pointerId;
|
||||
mPressed = true;
|
||||
mXValue = xValue;
|
||||
mYValue = yValue;
|
||||
// Log.i("SetPressed", String.format("xval=%f,yval=%f,drawX=%d,drawY=%d", mXValue, mYValue, mDrawXPos, mDrawYPos));
|
||||
|
||||
if (doUpdate) {
|
||||
invalidate();
|
||||
updateControllerState();
|
||||
}
|
||||
}
|
||||
|
||||
private void updateControllerState() {
|
||||
final float BUTTON_THRESHOLD = 0.33f;
|
||||
|
||||
AndroidHostInterface hostInterface = AndroidHostInterface.getInstance();
|
||||
if (mXAxisCode >= 0)
|
||||
hostInterface.setControllerAxisState(mControllerIndex, mXAxisCode, mXValue);
|
||||
if (mYAxisCode >= 0)
|
||||
hostInterface.setControllerAxisState(mControllerIndex, mYAxisCode, mYValue);
|
||||
|
||||
if (mLeftButtonCode >= 0)
|
||||
hostInterface.setControllerButtonState(mControllerIndex, mLeftButtonCode, (mXValue <= -BUTTON_THRESHOLD));
|
||||
if (mRightButtonCode >= 0)
|
||||
hostInterface.setControllerButtonState(mControllerIndex, mRightButtonCode, (mXValue >= BUTTON_THRESHOLD));
|
||||
if (mUpButtonCode >= 0)
|
||||
hostInterface.setControllerButtonState(mControllerIndex, mUpButtonCode, (mYValue <= -BUTTON_THRESHOLD));
|
||||
if (mDownButtonCode >= 0)
|
||||
hostInterface.setControllerButtonState(mControllerIndex, mDownButtonCode, (mYValue >= BUTTON_THRESHOLD));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas) {
|
||||
super.onDraw(canvas);
|
||||
|
||||
final int paddingLeft = getPaddingLeft();
|
||||
final int paddingTop = getPaddingTop();
|
||||
final int paddingRight = getPaddingRight();
|
||||
final int paddingBottom = getPaddingBottom();
|
||||
final int contentWidth = getWidth() - paddingLeft - paddingRight;
|
||||
final int contentHeight = getHeight() - paddingTop - paddingBottom;
|
||||
|
||||
mBaseDrawable.setBounds(paddingLeft, paddingTop,
|
||||
paddingLeft + contentWidth, paddingTop + contentHeight);
|
||||
mBaseDrawable.draw(canvas);
|
||||
|
||||
final int stickWidth = contentWidth / 3;
|
||||
final int stickHeight = contentHeight / 3;
|
||||
final int halfStickWidth = stickWidth / 2;
|
||||
final int halfStickHeight = stickHeight / 2;
|
||||
final int centerX = getWidth() / 2;
|
||||
final int centerY = getHeight() / 2;
|
||||
final int drawX = centerX + mDrawXPos;
|
||||
final int drawY = centerY + mDrawYPos;
|
||||
|
||||
Drawable stickDrawable = mPressed ? mStickPressedDrawable : mStickUnpressedDrawable;
|
||||
stickDrawable.setBounds(drawX - halfStickWidth, drawY - halfStickHeight, drawX + halfStickWidth, drawY + halfStickHeight);
|
||||
stickDrawable.draw(canvas);
|
||||
}
|
||||
|
||||
public boolean isPressed() {
|
||||
return mPressed;
|
||||
}
|
||||
|
||||
public boolean hasPointerId() {
|
||||
return mPointerId >= 0;
|
||||
}
|
||||
|
||||
public int getPointerId() {
|
||||
return mPointerId;
|
||||
}
|
||||
|
||||
public void setPointerId(int mPointerId) {
|
||||
this.mPointerId = mPointerId;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
package com.github.stenzek.duckstation;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
|
||||
/**
|
||||
* TODO: document your custom view class.
|
||||
*/
|
||||
public class TouchscreenControllerButtonView extends View {
|
||||
private Drawable mUnpressedDrawable;
|
||||
private Drawable mPressedDrawable;
|
||||
private boolean mPressed = false;
|
||||
private int mControllerIndex = -1;
|
||||
private int mButtonCode = -1;
|
||||
|
||||
public TouchscreenControllerButtonView(Context context) {
|
||||
super(context);
|
||||
init(context, null, 0);
|
||||
}
|
||||
|
||||
public TouchscreenControllerButtonView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
init(context, attrs, 0);
|
||||
}
|
||||
|
||||
public TouchscreenControllerButtonView(Context context, AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
init(context, attrs, defStyle);
|
||||
}
|
||||
|
||||
private void init(Context context, AttributeSet attrs, int defStyle) {
|
||||
// Load attributes
|
||||
final TypedArray a = getContext().obtainStyledAttributes(
|
||||
attrs, R.styleable.TouchscreenControllerButtonView, defStyle, 0);
|
||||
|
||||
if (a.hasValue(R.styleable.TouchscreenControllerButtonView_unpressedDrawable)) {
|
||||
mUnpressedDrawable = a.getDrawable(R.styleable.TouchscreenControllerButtonView_unpressedDrawable);
|
||||
mUnpressedDrawable.setCallback(this);
|
||||
}
|
||||
|
||||
if (a.hasValue(R.styleable.TouchscreenControllerButtonView_pressedDrawable)) {
|
||||
mPressedDrawable = a.getDrawable(R.styleable.TouchscreenControllerButtonView_pressedDrawable);
|
||||
mPressedDrawable.setCallback(this);
|
||||
}
|
||||
|
||||
a.recycle();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas) {
|
||||
super.onDraw(canvas);
|
||||
|
||||
final int paddingLeft = getPaddingLeft();
|
||||
final int paddingTop = getPaddingTop();
|
||||
final int paddingRight = getPaddingRight();
|
||||
final int paddingBottom = getPaddingBottom();
|
||||
final int contentWidth = getWidth() - paddingLeft - paddingRight;
|
||||
final int contentHeight = getHeight() - paddingTop - paddingBottom;
|
||||
|
||||
// Draw the example drawable on top of the text.
|
||||
Drawable drawable = mPressed ? mPressedDrawable : mUnpressedDrawable;
|
||||
if (drawable != null) {
|
||||
drawable.setBounds(paddingLeft, paddingTop,
|
||||
paddingLeft + contentWidth, paddingTop + contentHeight);
|
||||
drawable.draw(canvas);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isPressed() {
|
||||
return mPressed;
|
||||
}
|
||||
|
||||
public void setPressed(boolean pressed) {
|
||||
if (pressed == mPressed)
|
||||
return;
|
||||
|
||||
mPressed = pressed;
|
||||
invalidate();
|
||||
updateControllerState();
|
||||
}
|
||||
|
||||
public void setButtonCode(int controllerIndex, int code) {
|
||||
mControllerIndex = controllerIndex;
|
||||
mButtonCode = code;
|
||||
}
|
||||
|
||||
private void updateControllerState() {
|
||||
if (mButtonCode >= 0)
|
||||
AndroidHostInterface.getInstance().setControllerButtonState(mControllerIndex, mButtonCode, mPressed);
|
||||
}
|
||||
|
||||
public Drawable getPressedDrawable() {
|
||||
return mPressedDrawable;
|
||||
}
|
||||
|
||||
public void setPressedDrawable(Drawable pressedDrawable) {
|
||||
mPressedDrawable = pressedDrawable;
|
||||
}
|
||||
|
||||
public Drawable getUnpressedDrawable() {
|
||||
return mUnpressedDrawable;
|
||||
}
|
||||
|
||||
public void setUnpressedDrawable(Drawable unpressedDrawable) {
|
||||
mUnpressedDrawable = unpressedDrawable;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,216 @@
|
||||
package com.github.stenzek.duckstation;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Rect;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* TODO: document your custom view class.
|
||||
*/
|
||||
public class TouchscreenControllerView extends FrameLayout {
|
||||
private int mControllerIndex;
|
||||
private String mControllerType;
|
||||
private View mMainView;
|
||||
private ArrayList<TouchscreenControllerButtonView> mButtonViews = new ArrayList<>();
|
||||
private ArrayList<TouchscreenControllerAxisView> mAxisViews = new ArrayList<>();
|
||||
|
||||
public TouchscreenControllerView(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public TouchscreenControllerView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public TouchscreenControllerView(Context context, AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
}
|
||||
|
||||
public void init(int controllerIndex, String controllerType, String viewType) {
|
||||
mControllerIndex = controllerIndex;
|
||||
mControllerType = controllerType;
|
||||
|
||||
mButtonViews.clear();
|
||||
mAxisViews.clear();
|
||||
removeAllViews();
|
||||
|
||||
LayoutInflater inflater = LayoutInflater.from(getContext());
|
||||
switch (viewType)
|
||||
{
|
||||
case "digital":
|
||||
mMainView = inflater.inflate(R.layout.layout_touchscreen_controller_digital, this, true);
|
||||
break;
|
||||
|
||||
case "analog_stick":
|
||||
mMainView = inflater.inflate(R.layout.layout_touchscreen_controller_analog_stick, this, true);
|
||||
break;
|
||||
|
||||
case "analog_sticks":
|
||||
mMainView = inflater.inflate(R.layout.layout_touchscreen_controller_analog_sticks, this, true);
|
||||
break;
|
||||
|
||||
case "none":
|
||||
default:
|
||||
mMainView = null;
|
||||
break;
|
||||
}
|
||||
|
||||
if (mMainView == null)
|
||||
return;
|
||||
|
||||
mMainView.setOnTouchListener((view1, event) -> {
|
||||
return handleTouchEvent(event);
|
||||
});
|
||||
|
||||
linkButton(mMainView, R.id.controller_button_up, "Up");
|
||||
linkButton(mMainView, R.id.controller_button_right, "Right");
|
||||
linkButton(mMainView, R.id.controller_button_down, "Down");
|
||||
linkButton(mMainView, R.id.controller_button_left, "Left");
|
||||
linkButton(mMainView, R.id.controller_button_l1, "L1");
|
||||
linkButton(mMainView, R.id.controller_button_l2, "L2");
|
||||
linkButton(mMainView, R.id.controller_button_select, "Select");
|
||||
linkButton(mMainView, R.id.controller_button_start, "Start");
|
||||
linkButton(mMainView, R.id.controller_button_triangle, "Triangle");
|
||||
linkButton(mMainView, R.id.controller_button_circle, "Circle");
|
||||
linkButton(mMainView, R.id.controller_button_cross, "Cross");
|
||||
linkButton(mMainView, R.id.controller_button_square, "Square");
|
||||
linkButton(mMainView, R.id.controller_button_r1, "R1");
|
||||
linkButton(mMainView, R.id.controller_button_r2, "R2");
|
||||
|
||||
if (!linkAxis(mMainView, R.id.controller_axis_left, "Left"))
|
||||
linkAxisToButtons(mMainView, R.id.controller_axis_left, "");
|
||||
|
||||
linkAxis(mMainView, R.id.controller_axis_right, "Right");
|
||||
}
|
||||
|
||||
private void linkButton(View view, int id, String buttonName) {
|
||||
TouchscreenControllerButtonView buttonView = (TouchscreenControllerButtonView) view.findViewById(id);
|
||||
if (buttonView == null)
|
||||
return;
|
||||
|
||||
int code = AndroidHostInterface.getInstance().getControllerButtonCode(mControllerType, buttonName);
|
||||
Log.i("TouchscreenController", String.format("%s -> %d", buttonName, code));
|
||||
|
||||
if (code >= 0) {
|
||||
buttonView.setButtonCode(mControllerIndex, code);
|
||||
mButtonViews.add(buttonView);
|
||||
} else {
|
||||
Log.e("TouchscreenController", String.format("Unknown button name '%s' " +
|
||||
"for '%s'", buttonName, mControllerType));
|
||||
}
|
||||
}
|
||||
|
||||
private boolean linkAxis(View view, int id, String axisName) {
|
||||
TouchscreenControllerAxisView axisView = (TouchscreenControllerAxisView) view.findViewById(id);
|
||||
if (axisView == null)
|
||||
return false;
|
||||
|
||||
int xCode = AndroidHostInterface.getInstance().getControllerAxisCode(mControllerType, axisName + "X");
|
||||
int yCode = AndroidHostInterface.getInstance().getControllerAxisCode(mControllerType, axisName + "Y");
|
||||
Log.i("TouchscreenController", String.format("%s -> %d/%d", axisName, xCode, yCode));
|
||||
if (xCode < 0 && yCode < 0)
|
||||
return false;
|
||||
|
||||
axisView.setControllerAxis(mControllerIndex, xCode, yCode);
|
||||
mAxisViews.add(axisView);
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean linkAxisToButtons(View view, int id, String buttonPrefix) {
|
||||
TouchscreenControllerAxisView axisView = (TouchscreenControllerAxisView) view.findViewById(id);
|
||||
if (axisView == null)
|
||||
return false;
|
||||
|
||||
int leftCode = AndroidHostInterface.getInstance().getControllerButtonCode(mControllerType, buttonPrefix + "Left");
|
||||
int rightCode = AndroidHostInterface.getInstance().getControllerButtonCode(mControllerType, buttonPrefix + "Right");
|
||||
int upCode = AndroidHostInterface.getInstance().getControllerButtonCode(mControllerType, buttonPrefix + "Up");
|
||||
int downCode = AndroidHostInterface.getInstance().getControllerButtonCode(mControllerType, buttonPrefix + "Down");
|
||||
Log.i("TouchscreenController", String.format("%s(ButtonAxis) -> %d,%d,%d,%d", buttonPrefix, leftCode, rightCode, upCode, downCode));
|
||||
if (leftCode < 0 && rightCode < 0 && upCode < 0 && downCode < 0)
|
||||
return false;
|
||||
|
||||
axisView.setControllerButtons(mControllerIndex, leftCode, rightCode, upCode, downCode);
|
||||
mAxisViews.add(axisView);
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean handleTouchEvent(MotionEvent event) {
|
||||
switch (event.getActionMasked())
|
||||
{
|
||||
case MotionEvent.ACTION_UP:
|
||||
{
|
||||
for (TouchscreenControllerButtonView buttonView : mButtonViews) {
|
||||
buttonView.setPressed(false);
|
||||
}
|
||||
|
||||
for (TouchscreenControllerAxisView axisView : mAxisViews) {
|
||||
axisView.setUnpressed();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
case MotionEvent.ACTION_DOWN:
|
||||
case MotionEvent.ACTION_POINTER_DOWN:
|
||||
case MotionEvent.ACTION_POINTER_UP:
|
||||
case MotionEvent.ACTION_MOVE:
|
||||
{
|
||||
Rect rect = new Rect();
|
||||
final int pointerCount = event.getPointerCount();
|
||||
final int liftedPointerIndex = (event.getActionMasked() == MotionEvent.ACTION_POINTER_UP) ? event.getActionIndex() : -1;
|
||||
for (TouchscreenControllerButtonView buttonView : mButtonViews) {
|
||||
buttonView.getHitRect(rect);
|
||||
boolean pressed = false;
|
||||
for (int i = 0; i < pointerCount; i++) {
|
||||
if (i == liftedPointerIndex)
|
||||
continue;
|
||||
|
||||
final int x = (int) event.getX(i);
|
||||
final int y = (int) event.getY(i);
|
||||
if (rect.contains(x, y)) {
|
||||
buttonView.setPressed(true);
|
||||
pressed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!pressed)
|
||||
buttonView.setPressed(pressed);
|
||||
}
|
||||
|
||||
for (TouchscreenControllerAxisView axisView : mAxisViews) {
|
||||
axisView.getHitRect(rect);
|
||||
boolean pressed = false;
|
||||
for (int i = 0; i < pointerCount; i++) {
|
||||
if (i == liftedPointerIndex)
|
||||
continue;
|
||||
|
||||
final int pointerId = event.getPointerId(i);
|
||||
final int x = (int) event.getX(i);
|
||||
final int y = (int) event.getY(i);
|
||||
|
||||
if ((rect.contains(x, y) && !axisView.isPressed()) ||
|
||||
(axisView.isPressed() && axisView.getPointerId() == pointerId)) {
|
||||
axisView.setPressed(pointerId, x, y);
|
||||
pressed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!pressed)
|
||||
axisView.setUnpressed();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:aapt="http://schemas.android.com/aapt"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108">
|
||||
<path
|
||||
android:fillType="evenOdd"
|
||||
android:pathData="M32,64C32,64 38.39,52.99 44.13,50.95C51.37,48.37 70.14,49.57 70.14,49.57L108.26,87.69L108,109.01L75.97,107.97L32,64Z"
|
||||
android:strokeWidth="1"
|
||||
android:strokeColor="#00000000">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:endX="78.5885"
|
||||
android:endY="90.9159"
|
||||
android:startX="48.7653"
|
||||
android:startY="61.0927"
|
||||
android:type="linear">
|
||||
<item
|
||||
android:color="#44000000"
|
||||
android:offset="0.0" />
|
||||
<item
|
||||
android:color="#00000000"
|
||||
android:offset="1.0" />
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:fillType="nonZero"
|
||||
android:pathData="M66.94,46.02L66.94,46.02C72.44,50.07 76,56.61 76,64L32,64C32,56.61 35.56,50.11 40.98,46.06L36.18,41.19C35.45,40.45 35.45,39.3 36.18,38.56C36.91,37.81 38.05,37.81 38.78,38.56L44.25,44.05C47.18,42.57 50.48,41.71 54,41.71C57.48,41.71 60.78,42.57 63.68,44.05L69.11,38.56C69.84,37.81 70.98,37.81 71.71,38.56C72.44,39.3 72.44,40.45 71.71,41.19L66.94,46.02ZM62.94,56.92C64.08,56.92 65,56.01 65,54.88C65,53.76 64.08,52.85 62.94,52.85C61.8,52.85 60.88,53.76 60.88,54.88C60.88,56.01 61.8,56.92 62.94,56.92ZM45.06,56.92C46.2,56.92 47.13,56.01 47.13,54.88C47.13,53.76 46.2,52.85 45.06,52.85C43.92,52.85 43,53.76 43,54.88C43,56.01 43.92,56.92 45.06,56.92Z"
|
||||
android:strokeWidth="1"
|
||||
android:strokeColor="#00000000" />
|
||||
</vector>
|
||||
71
android/app/src/main/res/drawable/flag_eu.xml
Normal file
71
android/app/src/main/res/drawable/flag_eu.xml
Normal file
@@ -0,0 +1,71 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!-- https://raw.githubusercontent.com/Shusshu/android-flags/master/flags/src/main/res/drawable/flag_us2.xml -->
|
||||
<vector android:height="15dp"
|
||||
android:viewportHeight="15"
|
||||
android:viewportWidth="21"
|
||||
android:width="21dp"
|
||||
xmlns:aapt="http://schemas.android.com/aapt"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path
|
||||
android:fillType="evenOdd"
|
||||
android:pathData="M0,0h21v15h-21z"
|
||||
android:strokeColor="#00000000"
|
||||
android:strokeWidth="1">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:endX="10.5"
|
||||
android:endY="15"
|
||||
android:startX="10.5"
|
||||
android:startY="0"
|
||||
android:type="linear">
|
||||
<item
|
||||
android:color="#FFFFFFFF"
|
||||
android:offset="0" />
|
||||
<item
|
||||
android:color="#FFF0F0F0"
|
||||
android:offset="1" />
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path
|
||||
android:fillType="evenOdd"
|
||||
android:pathData="M0,0h21v15h-21z"
|
||||
android:strokeColor="#00000000"
|
||||
android:strokeWidth="1">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:endX="10.5"
|
||||
android:endY="15"
|
||||
android:startX="10.5"
|
||||
android:startY="0"
|
||||
android:type="linear">
|
||||
<item
|
||||
android:color="#FF043CAE"
|
||||
android:offset="0" />
|
||||
<item
|
||||
android:color="#FF00339A"
|
||||
android:offset="1" />
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path
|
||||
android:fillType="evenOdd"
|
||||
android:pathData="M10.5,3L9.7929,3.2071L10,2.5L9.7929,1.7929L10.5,2L11.2071,1.7929L11,2.5L11.2071,3.2071L10.5,3ZM10.5,13L9.7929,13.2071L10,12.5L9.7929,11.7929L10.5,12L11.2071,11.7929L11,12.5L11.2071,13.2071L10.5,13ZM15.5,8L14.7929,8.2071L15,7.5L14.7929,6.7929L15.5,7L16.2071,6.7929L16,7.5L16.2071,8.2071L15.5,8ZM5.5,8L4.7929,8.2071L5,7.5L4.7929,6.7929L5.5,7L6.2071,6.7929L6,7.5L6.2071,8.2071L5.5,8ZM14.8301,5.5L14.123,5.7071L14.3301,5L14.123,4.2929L14.8301,4.5L15.5372,4.2929L15.3301,5L15.5372,5.7071L14.8301,5.5ZM6.1699,10.5L5.4628,10.7071L5.6699,10L5.4628,9.2929L6.1699,9.5L6.877,9.2929L6.6699,10L6.877,10.7071L6.1699,10.5ZM13,3.6699L12.2929,3.877L12.5,3.1699L12.2929,2.4628L13,2.6699L13.7071,2.4628L13.5,3.1699L13.7071,3.877L13,3.6699ZM8,12.3301L7.2929,12.5372L7.5,11.8301L7.2929,11.123L8,11.3301L8.7071,11.123L8.5,11.8301L8.7071,12.5372L8,12.3301ZM14.8301,10.5L14.123,10.7071L14.3301,10L14.123,9.2929L14.8301,9.5L15.5372,9.2929L15.3301,10L15.5372,10.7071L14.8301,10.5ZM6.1699,5.5L5.4628,5.7071L5.6699,5L5.4628,4.2929L6.1699,4.5L6.877,4.2929L6.6699,5L6.877,5.7071L6.1699,5.5ZM13,12.3301L12.2929,12.5372L12.5,11.8301L12.2929,11.123L13,11.3301L13.7071,11.123L13.5,11.8301L13.7071,12.5372L13,12.3301ZM8,3.6699L7.2929,3.877L7.5,3.1699L7.2929,2.4628L8,2.6699L8.7071,2.4628L8.5,3.1699L8.7071,3.877L8,3.6699Z"
|
||||
android:strokeColor="#00000000"
|
||||
android:strokeWidth="1">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:endX="10.5"
|
||||
android:endY="13.2071"
|
||||
android:startX="10.5"
|
||||
android:startY="1.7929"
|
||||
android:type="linear">
|
||||
<item
|
||||
android:color="#FFFFD429"
|
||||
android:offset="0" />
|
||||
<item
|
||||
android:color="#FFFFCC00"
|
||||
android:offset="1" />
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
</vector>
|
||||
50
android/app/src/main/res/drawable/flag_jp.xml
Normal file
50
android/app/src/main/res/drawable/flag_jp.xml
Normal file
@@ -0,0 +1,50 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!-- https://raw.githubusercontent.com/Shusshu/android-flags/master/flags/src/main/res/drawable/flag_hp.xml -->
|
||||
<vector android:height="15dp"
|
||||
android:viewportHeight="15"
|
||||
android:viewportWidth="21"
|
||||
android:width="21dp"
|
||||
xmlns:aapt="http://schemas.android.com/aapt"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path
|
||||
android:fillType="evenOdd"
|
||||
android:pathData="M0,0h21v15h-21z"
|
||||
android:strokeColor="#00000000"
|
||||
android:strokeWidth="1">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:endX="10.5"
|
||||
android:endY="15"
|
||||
android:startX="10.5"
|
||||
android:startY="0"
|
||||
android:type="linear">
|
||||
<item
|
||||
android:color="#FFFFFFFF"
|
||||
android:offset="0" />
|
||||
<item
|
||||
android:color="#FFF0F0F0"
|
||||
android:offset="1" />
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path
|
||||
android:fillType="evenOdd"
|
||||
android:pathData="M10.5,7.5m-4.5,0a4.5,4.5 0,1 1,9 0a4.5,4.5 0,1 1,-9 0"
|
||||
android:strokeColor="#00000000"
|
||||
android:strokeWidth="1">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:endX="10.5"
|
||||
android:endY="12"
|
||||
android:startX="10.5"
|
||||
android:startY="3"
|
||||
android:type="linear">
|
||||
<item
|
||||
android:color="#FFD81441"
|
||||
android:offset="0" />
|
||||
<item
|
||||
android:color="#FFBB0831"
|
||||
android:offset="1" />
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
</vector>
|
||||
539
android/app/src/main/res/drawable/flag_us.xml
Normal file
539
android/app/src/main/res/drawable/flag_us.xml
Normal file
@@ -0,0 +1,539 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!-- https://raw.githubusercontent.com/Shusshu/android-flags/master/flags/src/main/res/drawable/flag_us2.xml -->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="21dp"
|
||||
android:height="21dp"
|
||||
android:viewportWidth="10"
|
||||
android:viewportHeight="13">
|
||||
<path
|
||||
android:fillColor="#bd3d44"
|
||||
android:pathData="M0 0h13v1h-13Z" />
|
||||
<path
|
||||
android:fillColor="#fff"
|
||||
android:pathData="M0 1h13v1h-13Z" />
|
||||
<path
|
||||
android:fillColor="#bd3d44"
|
||||
android:pathData="M0 2h13v1h-13Z" />
|
||||
<path
|
||||
android:fillColor="#fff"
|
||||
android:pathData="M0 3h13v1h-13Z" />
|
||||
<path
|
||||
android:fillColor="#bd3d44"
|
||||
android:pathData="M0 4h13v1h-13Z" />
|
||||
<path
|
||||
android:fillColor="#fff"
|
||||
android:pathData="M0 5h13v1h-13Z" />
|
||||
<path
|
||||
android:fillColor="#bd3d44"
|
||||
android:pathData="M0 6h13v1h-13Z" />
|
||||
<path
|
||||
android:fillColor="#fff"
|
||||
android:pathData="M0 7h13v1h-13Z" />
|
||||
<path
|
||||
android:fillColor="#bd3d44"
|
||||
android:pathData="M0 8h13v1h-13Z" />
|
||||
<path
|
||||
android:fillColor="#fff"
|
||||
android:pathData="M0 9h13v1h-13Z" />
|
||||
<path
|
||||
android:fillColor="#bd3d44"
|
||||
android:pathData="M0 10h13v1h-13Z" />
|
||||
<path
|
||||
android:fillColor="#fff"
|
||||
android:pathData="M0 11h13v1h-13Z" />
|
||||
<path
|
||||
android:fillColor="#bd3d44"
|
||||
android:pathData="M0 12h13v1h-13Z" />
|
||||
<path
|
||||
android:fillColor="#192f5d"
|
||||
android:pathData="M0 0h5.2v7h-5.2Z" />
|
||||
|
||||
<group
|
||||
android:translateX="0.2"
|
||||
android:translateY="0.2"
|
||||
android:scaleX="0.009"
|
||||
android:scaleY="0.012">
|
||||
<path
|
||||
android:fillColor="#fff"
|
||||
android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" />
|
||||
</group>
|
||||
<group
|
||||
android:translateX="1.0"
|
||||
android:translateY="0.2"
|
||||
android:scaleX="0.009"
|
||||
android:scaleY="0.012">
|
||||
<path
|
||||
android:fillColor="#fff"
|
||||
android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" />
|
||||
</group>
|
||||
<group
|
||||
android:translateX="1.8"
|
||||
android:translateY="0.2"
|
||||
android:scaleX="0.009"
|
||||
android:scaleY="0.012">
|
||||
<path
|
||||
android:fillColor="#fff"
|
||||
android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" />
|
||||
</group>
|
||||
<group
|
||||
android:translateX="2.6"
|
||||
android:translateY="0.2"
|
||||
android:scaleX="0.009"
|
||||
android:scaleY="0.012">
|
||||
<path
|
||||
android:fillColor="#fff"
|
||||
android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" />
|
||||
</group>
|
||||
<group
|
||||
android:translateX="3.4"
|
||||
android:translateY="0.2"
|
||||
android:scaleX="0.009"
|
||||
android:scaleY="0.012">
|
||||
<path
|
||||
android:fillColor="#fff"
|
||||
android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" />
|
||||
</group>
|
||||
<group
|
||||
android:translateX="4.2"
|
||||
android:translateY="0.2"
|
||||
android:scaleX="0.009"
|
||||
android:scaleY="0.012">
|
||||
<path
|
||||
android:fillColor="#fff"
|
||||
android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" />
|
||||
</group>
|
||||
|
||||
|
||||
<group android:translateY="1.4">
|
||||
<group
|
||||
android:translateX="0.2"
|
||||
android:translateY="0.2"
|
||||
android:scaleX="0.009"
|
||||
android:scaleY="0.012">
|
||||
<path
|
||||
android:fillColor="#fff"
|
||||
android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" />
|
||||
</group>
|
||||
<group
|
||||
android:translateX="1.0"
|
||||
android:translateY="0.2"
|
||||
android:scaleX="0.009"
|
||||
android:scaleY="0.012">
|
||||
<path
|
||||
android:fillColor="#fff"
|
||||
android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" />
|
||||
</group>
|
||||
<group
|
||||
android:translateX="1.8"
|
||||
android:translateY="0.2"
|
||||
android:scaleX="0.009"
|
||||
android:scaleY="0.012">
|
||||
<path
|
||||
android:fillColor="#fff"
|
||||
android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" />
|
||||
</group>
|
||||
<group
|
||||
android:translateX="2.6"
|
||||
android:translateY="0.2"
|
||||
android:scaleX="0.009"
|
||||
android:scaleY="0.012">
|
||||
<path
|
||||
android:fillColor="#fff"
|
||||
android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" />
|
||||
</group>
|
||||
<group
|
||||
android:translateX="3.4"
|
||||
android:translateY="0.2"
|
||||
android:scaleX="0.009"
|
||||
android:scaleY="0.012">
|
||||
<path
|
||||
android:fillColor="#fff"
|
||||
android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" />
|
||||
</group>
|
||||
<group
|
||||
android:translateX="4.2"
|
||||
android:translateY="0.2"
|
||||
android:scaleX="0.009"
|
||||
android:scaleY="0.012">
|
||||
<path
|
||||
android:fillColor="#fff"
|
||||
android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" />
|
||||
</group>
|
||||
</group>
|
||||
|
||||
|
||||
<group android:translateY="2.9">
|
||||
<group
|
||||
android:translateX="0.2"
|
||||
android:translateY="0.2"
|
||||
android:scaleX="0.009"
|
||||
android:scaleY="0.012">
|
||||
<path
|
||||
android:fillColor="#fff"
|
||||
android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" />
|
||||
</group>
|
||||
<group
|
||||
android:translateX="1.0"
|
||||
android:translateY="0.2"
|
||||
android:scaleX="0.009"
|
||||
android:scaleY="0.012">
|
||||
<path
|
||||
android:fillColor="#fff"
|
||||
android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" />
|
||||
</group>
|
||||
<group
|
||||
android:translateX="1.8"
|
||||
android:translateY="0.2"
|
||||
android:scaleX="0.009"
|
||||
android:scaleY="0.012">
|
||||
<path
|
||||
android:fillColor="#fff"
|
||||
android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" />
|
||||
</group>
|
||||
<group
|
||||
android:translateX="2.6"
|
||||
android:translateY="0.2"
|
||||
android:scaleX="0.009"
|
||||
android:scaleY="0.012">
|
||||
<path
|
||||
android:fillColor="#fff"
|
||||
android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" />
|
||||
</group>
|
||||
<group
|
||||
android:translateX="3.4"
|
||||
android:translateY="0.2"
|
||||
android:scaleX="0.009"
|
||||
android:scaleY="0.012">
|
||||
<path
|
||||
android:fillColor="#fff"
|
||||
android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" />
|
||||
</group>
|
||||
<group
|
||||
android:translateX="4.2"
|
||||
android:translateY="0.2"
|
||||
android:scaleX="0.009"
|
||||
android:scaleY="0.012">
|
||||
<path
|
||||
android:fillColor="#fff"
|
||||
android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" />
|
||||
</group>
|
||||
</group>
|
||||
|
||||
|
||||
<group android:translateY="4.3">
|
||||
<group
|
||||
android:translateX="0.2"
|
||||
android:translateY="0.2"
|
||||
android:scaleX="0.009"
|
||||
android:scaleY="0.012">
|
||||
<path
|
||||
android:fillColor="#fff"
|
||||
android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" />
|
||||
</group>
|
||||
<group
|
||||
android:translateX="1.0"
|
||||
android:translateY="0.2"
|
||||
android:scaleX="0.009"
|
||||
android:scaleY="0.012">
|
||||
<path
|
||||
android:fillColor="#fff"
|
||||
android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" />
|
||||
</group>
|
||||
<group
|
||||
android:translateX="1.8"
|
||||
android:translateY="0.2"
|
||||
android:scaleX="0.009"
|
||||
android:scaleY="0.012">
|
||||
<path
|
||||
android:fillColor="#fff"
|
||||
android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" />
|
||||
</group>
|
||||
<group
|
||||
android:translateX="2.6"
|
||||
android:translateY="0.2"
|
||||
android:scaleX="0.009"
|
||||
android:scaleY="0.012">
|
||||
<path
|
||||
android:fillColor="#fff"
|
||||
android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" />
|
||||
</group>
|
||||
<group
|
||||
android:translateX="3.4"
|
||||
android:translateY="0.2"
|
||||
android:scaleX="0.009"
|
||||
android:scaleY="0.012">
|
||||
<path
|
||||
android:fillColor="#fff"
|
||||
android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" />
|
||||
</group>
|
||||
<group
|
||||
android:translateX="4.2"
|
||||
android:translateY="0.2"
|
||||
android:scaleX="0.009"
|
||||
android:scaleY="0.012">
|
||||
<path
|
||||
android:fillColor="#fff"
|
||||
android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" />
|
||||
</group>
|
||||
</group>
|
||||
|
||||
|
||||
<group android:translateY="5.6">
|
||||
<group
|
||||
android:translateX="0.2"
|
||||
android:translateY="0.2"
|
||||
android:scaleX="0.009"
|
||||
android:scaleY="0.012">
|
||||
<path
|
||||
android:fillColor="#fff"
|
||||
android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" />
|
||||
</group>
|
||||
<group
|
||||
android:translateX="1.0"
|
||||
android:translateY="0.2"
|
||||
android:scaleX="0.009"
|
||||
android:scaleY="0.012">
|
||||
<path
|
||||
android:fillColor="#fff"
|
||||
android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" />
|
||||
</group>
|
||||
<group
|
||||
android:translateX="1.8"
|
||||
android:translateY="0.2"
|
||||
android:scaleX="0.009"
|
||||
android:scaleY="0.012">
|
||||
<path
|
||||
android:fillColor="#fff"
|
||||
android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" />
|
||||
</group>
|
||||
<group
|
||||
android:translateX="2.6"
|
||||
android:translateY="0.2"
|
||||
android:scaleX="0.009"
|
||||
android:scaleY="0.012">
|
||||
<path
|
||||
android:fillColor="#fff"
|
||||
android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" />
|
||||
</group>
|
||||
<group
|
||||
android:translateX="3.4"
|
||||
android:translateY="0.2"
|
||||
android:scaleX="0.009"
|
||||
android:scaleY="0.012">
|
||||
<path
|
||||
android:fillColor="#fff"
|
||||
android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" />
|
||||
</group>
|
||||
<group
|
||||
android:translateX="4.2"
|
||||
android:translateY="0.2"
|
||||
android:scaleX="0.009"
|
||||
android:scaleY="0.012">
|
||||
<path
|
||||
android:fillColor="#fff"
|
||||
android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" />
|
||||
</group>
|
||||
</group>
|
||||
|
||||
<!-- Odd stars -->
|
||||
|
||||
<group
|
||||
android:translateY="0.7"
|
||||
android:translateX="0.4">
|
||||
<group
|
||||
android:translateX="0.2"
|
||||
android:translateY="0.2"
|
||||
android:scaleX="0.009"
|
||||
android:scaleY="0.012">
|
||||
<path
|
||||
android:fillColor="#fff"
|
||||
android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" />
|
||||
</group>
|
||||
<group
|
||||
android:translateX="1.0"
|
||||
android:translateY="0.2"
|
||||
android:scaleX="0.009"
|
||||
android:scaleY="0.012">
|
||||
<path
|
||||
android:fillColor="#fff"
|
||||
android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" />
|
||||
</group>
|
||||
<group
|
||||
android:translateX="1.8"
|
||||
android:translateY="0.2"
|
||||
android:scaleX="0.009"
|
||||
android:scaleY="0.012">
|
||||
<path
|
||||
android:fillColor="#fff"
|
||||
android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" />
|
||||
</group>
|
||||
<group
|
||||
android:translateX="2.6"
|
||||
android:translateY="0.2"
|
||||
android:scaleX="0.009"
|
||||
android:scaleY="0.012">
|
||||
<path
|
||||
android:fillColor="#fff"
|
||||
android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" />
|
||||
</group>
|
||||
<group
|
||||
android:translateX="3.4"
|
||||
android:translateY="0.2"
|
||||
android:scaleX="0.009"
|
||||
android:scaleY="0.012">
|
||||
<path
|
||||
android:fillColor="#fff"
|
||||
android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" />
|
||||
</group>
|
||||
</group>
|
||||
|
||||
<group
|
||||
android:translateY="2.1"
|
||||
android:translateX="0.4">
|
||||
<group
|
||||
android:translateX="0.2"
|
||||
android:translateY="0.2"
|
||||
android:scaleX="0.009"
|
||||
android:scaleY="0.012">
|
||||
<path
|
||||
android:fillColor="#fff"
|
||||
android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" />
|
||||
</group>
|
||||
<group
|
||||
android:translateX="1.0"
|
||||
android:translateY="0.2"
|
||||
android:scaleX="0.009"
|
||||
android:scaleY="0.012">
|
||||
<path
|
||||
android:fillColor="#fff"
|
||||
android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" />
|
||||
</group>
|
||||
<group
|
||||
android:translateX="1.8"
|
||||
android:translateY="0.2"
|
||||
android:scaleX="0.009"
|
||||
android:scaleY="0.012">
|
||||
<path
|
||||
android:fillColor="#fff"
|
||||
android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" />
|
||||
</group>
|
||||
<group
|
||||
android:translateX="2.6"
|
||||
android:translateY="0.2"
|
||||
android:scaleX="0.009"
|
||||
android:scaleY="0.012">
|
||||
<path
|
||||
android:fillColor="#fff"
|
||||
android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" />
|
||||
</group>
|
||||
<group
|
||||
android:translateX="3.4"
|
||||
android:translateY="0.2"
|
||||
android:scaleX="0.009"
|
||||
android:scaleY="0.012">
|
||||
<path
|
||||
android:fillColor="#fff"
|
||||
android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" />
|
||||
</group>
|
||||
</group>
|
||||
|
||||
<group
|
||||
android:translateY="3.6"
|
||||
android:translateX="0.4">
|
||||
<group
|
||||
android:translateX="0.2"
|
||||
android:translateY="0.2"
|
||||
android:scaleX="0.009"
|
||||
android:scaleY="0.012">
|
||||
<path
|
||||
android:fillColor="#fff"
|
||||
android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" />
|
||||
</group>
|
||||
<group
|
||||
android:translateX="1.0"
|
||||
android:translateY="0.2"
|
||||
android:scaleX="0.009"
|
||||
android:scaleY="0.012">
|
||||
<path
|
||||
android:fillColor="#fff"
|
||||
android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" />
|
||||
</group>
|
||||
<group
|
||||
android:translateX="1.8"
|
||||
android:translateY="0.2"
|
||||
android:scaleX="0.009"
|
||||
android:scaleY="0.012">
|
||||
<path
|
||||
android:fillColor="#fff"
|
||||
android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" />
|
||||
</group>
|
||||
<group
|
||||
android:translateX="2.6"
|
||||
android:translateY="0.2"
|
||||
android:scaleX="0.009"
|
||||
android:scaleY="0.012">
|
||||
<path
|
||||
android:fillColor="#fff"
|
||||
android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" />
|
||||
</group>
|
||||
<group
|
||||
android:translateX="3.4"
|
||||
android:translateY="0.2"
|
||||
android:scaleX="0.009"
|
||||
android:scaleY="0.012">
|
||||
<path
|
||||
android:fillColor="#fff"
|
||||
android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" />
|
||||
</group>
|
||||
</group>
|
||||
|
||||
<group
|
||||
android:translateY="5.0"
|
||||
android:translateX="0.4">
|
||||
<group
|
||||
android:translateX="0.2"
|
||||
android:translateY="0.2"
|
||||
android:scaleX="0.009"
|
||||
android:scaleY="0.012">
|
||||
<path
|
||||
android:fillColor="#fff"
|
||||
android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" />
|
||||
</group>
|
||||
<group
|
||||
android:translateX="1.0"
|
||||
android:translateY="0.2"
|
||||
android:scaleX="0.009"
|
||||
android:scaleY="0.012">
|
||||
<path
|
||||
android:fillColor="#fff"
|
||||
android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" />
|
||||
</group>
|
||||
<group
|
||||
android:translateX="1.8"
|
||||
android:translateY="0.2"
|
||||
android:scaleX="0.009"
|
||||
android:scaleY="0.012">
|
||||
<path
|
||||
android:fillColor="#fff"
|
||||
android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" />
|
||||
</group>
|
||||
<group
|
||||
android:translateX="2.6"
|
||||
android:translateY="0.2"
|
||||
android:scaleX="0.009"
|
||||
android:scaleY="0.012">
|
||||
<path
|
||||
android:fillColor="#fff"
|
||||
android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" />
|
||||
</group>
|
||||
<group
|
||||
android:translateX="3.4"
|
||||
android:translateY="0.2"
|
||||
android:scaleX="0.009"
|
||||
android:scaleY="0.012">
|
||||
<path
|
||||
android:fillColor="#fff"
|
||||
android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z" />
|
||||
</group>
|
||||
</group>
|
||||
|
||||
</vector>
|
||||
@@ -0,0 +1,10 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M8,5v14l11,-7z" />
|
||||
</vector>
|
||||
@@ -0,0 +1,21 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="194.89dp"
|
||||
android:height="194.89dp"
|
||||
android:viewportWidth="194.89"
|
||||
android:viewportHeight="194.89">
|
||||
<path
|
||||
android:pathData="M194.89,97.445A97.445,97.445 0,0 1,97.445 194.89,97.445 97.445,0 0,1 0,97.445 97.445,97.445 0,0 1,97.445 0,97.445 97.445,0 0,1 194.89,97.445Z"
|
||||
android:strokeAlpha="0.50645"
|
||||
android:fillColor="#1a1a1a"
|
||||
android:fillAlpha="0.504414"/>
|
||||
<path
|
||||
android:pathData="M178.82,97.445A81.381,81.381 0,0 1,97.439 178.826,81.381 81.381,0 0,1 16.058,97.445 81.381,81.381 0,0 1,97.439 16.064,81.381 81.381,0 0,1 178.82,97.445Z"
|
||||
android:strokeAlpha="0.50645"
|
||||
android:fillColor="#333333"
|
||||
android:fillAlpha="0.504414"/>
|
||||
<path
|
||||
android:pathData="M159.05,97.445A61.609,61.609 0,0 1,97.441 159.054,61.609 61.609,0 0,1 35.832,97.445 61.609,61.609 0,0 1,97.441 35.836,61.609 61.609,0 0,1 159.05,97.445Z"
|
||||
android:strokeAlpha="0.50645"
|
||||
android:fillColor="#1a1a1a"
|
||||
android:fillAlpha="0.504414"/>
|
||||
</vector>
|
||||
@@ -0,0 +1,11 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="191.756dp"
|
||||
android:height="191.756dp"
|
||||
android:viewportWidth="191.756"
|
||||
android:viewportHeight="191.756">
|
||||
<path
|
||||
android:pathData="M191.756,95.878A95.878,95.878 0,0 1,95.878 191.756,95.878 95.878,0 0,1 0,95.878 95.878,95.878 0,0 1,95.878 0,95.878 95.878,0 0,1 191.756,95.878Z"
|
||||
android:strokeAlpha="0.8"
|
||||
android:fillColor="#666666"
|
||||
android:fillAlpha="0.796784"/>
|
||||
</vector>
|
||||
@@ -0,0 +1,11 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="191.756dp"
|
||||
android:height="191.756dp"
|
||||
android:viewportWidth="191.756"
|
||||
android:viewportHeight="191.756">
|
||||
<path
|
||||
android:pathData="M191.756,95.878A95.878,95.878 0,0 1,95.878 191.756,95.878 95.878,0 0,1 0,95.878 95.878,95.878 0,0 1,95.878 0,95.878 95.878,0 0,1 191.756,95.878Z"
|
||||
android:strokeAlpha="0.50645"
|
||||
android:fillColor="#666666"
|
||||
android:fillAlpha="0.504414"/>
|
||||
</vector>
|
||||
@@ -0,0 +1,19 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="100dp"
|
||||
android:height="100dp"
|
||||
android:viewportWidth="26.458332"
|
||||
android:viewportHeight="26.45833">
|
||||
<path
|
||||
android:pathData="M13.2292,13.2292m-12.5677,0a12.5677,12.5677 0,1 1,25.1354 0a12.5677,12.5677 0,1 1,-25.1354 0"
|
||||
android:strokeWidth="0.26458332"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#ffffff" />
|
||||
<path
|
||||
android:pathData="M13.2292,13.2292m-10.5833,0a10.5833,10.5833 0,1 1,21.1667 0a10.5833,10.5833 0,1 1,-21.1667 0"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="1.05833328"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#ff0000"
|
||||
android:fillAlpha="1"
|
||||
android:strokeLineCap="butt" />
|
||||
</vector>
|
||||
@@ -0,0 +1,19 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="100dp"
|
||||
android:height="100dp"
|
||||
android:viewportWidth="26.458332"
|
||||
android:viewportHeight="26.45833">
|
||||
<path
|
||||
android:pathData="M13.2292,13.2292m-12.5677,0a12.5677,12.5677 0,1 1,25.1354 0a12.5677,12.5677 0,1 1,-25.1354 0"
|
||||
android:strokeWidth="0.26458332"
|
||||
android:fillColor="#4d4d4d"
|
||||
android:strokeColor="#ffffff" />
|
||||
<path
|
||||
android:pathData="M13.2292,13.2292m-10.5833,0a10.5833,10.5833 0,1 1,21.1667 0a10.5833,10.5833 0,1 1,-21.1667 0"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="1.05833328"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#ff0000"
|
||||
android:fillAlpha="1"
|
||||
android:strokeLineCap="butt" />
|
||||
</vector>
|
||||
@@ -0,0 +1,27 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="100dp"
|
||||
android:height="100dp"
|
||||
android:viewportWidth="26.458332"
|
||||
android:viewportHeight="26.45833">
|
||||
<path
|
||||
android:pathData="M13.2292,13.2292m-12.5677,0a12.5677,12.5677 0,1 1,25.1354 0a12.5677,12.5677 0,1 1,-25.1354 0"
|
||||
android:strokeWidth="0.26458332"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#ffffff" />
|
||||
<path
|
||||
android:pathData="m5.2917,5.2917 l15.875,15.875"
|
||||
android:strokeAlpha="1"
|
||||
android:strokeLineJoin="miter"
|
||||
android:strokeWidth="1.05833333"
|
||||
android:fillColor="#ffffff"
|
||||
android:strokeColor="#008080"
|
||||
android:strokeLineCap="butt" />
|
||||
<path
|
||||
android:pathData="m5.2917,21.1667 l15.875,-15.875"
|
||||
android:strokeAlpha="1"
|
||||
android:strokeLineJoin="miter"
|
||||
android:strokeWidth="1.05833333"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#008080"
|
||||
android:strokeLineCap="butt" />
|
||||
</vector>
|
||||
@@ -0,0 +1,27 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="100dp"
|
||||
android:height="100dp"
|
||||
android:viewportWidth="26.458332"
|
||||
android:viewportHeight="26.45833">
|
||||
<path
|
||||
android:pathData="M13.2292,13.2292m-12.5677,0a12.5677,12.5677 0,1 1,25.1354 0a12.5677,12.5677 0,1 1,-25.1354 0"
|
||||
android:strokeWidth="0.26458332"
|
||||
android:fillColor="#4d4d4d"
|
||||
android:strokeColor="#ffffff" />
|
||||
<path
|
||||
android:pathData="m5.2917,5.2917 l15.875,15.875"
|
||||
android:strokeAlpha="1"
|
||||
android:strokeLineJoin="miter"
|
||||
android:strokeWidth="1.05833333"
|
||||
android:fillColor="#ffffff"
|
||||
android:strokeColor="#008080"
|
||||
android:strokeLineCap="butt" />
|
||||
<path
|
||||
android:pathData="m5.2917,21.1667 l15.875,-15.875"
|
||||
android:strokeAlpha="1"
|
||||
android:strokeLineJoin="miter"
|
||||
android:strokeWidth="1.05833333"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#008080"
|
||||
android:strokeLineCap="butt" />
|
||||
</vector>
|
||||
@@ -0,0 +1,14 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="100dp"
|
||||
android:height="100dp"
|
||||
android:viewportWidth="26.458332"
|
||||
android:viewportHeight="26.458332">
|
||||
<path
|
||||
android:pathData="m5.2917,10.5833 l7.9375,-7.9375 7.9375,7.9375v14.5521L5.2917,25.1354Z"
|
||||
android:strokeAlpha="1"
|
||||
android:strokeLineJoin="miter"
|
||||
android:strokeWidth="0.26458332"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#e6e6e6"
|
||||
android:strokeLineCap="butt" />
|
||||
</vector>
|
||||
@@ -0,0 +1,14 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="100dp"
|
||||
android:height="100dp"
|
||||
android:viewportWidth="26.458332"
|
||||
android:viewportHeight="26.458332">
|
||||
<path
|
||||
android:pathData="m5.2917,10.5833 l7.9375,-7.9375 7.9375,7.9375v14.5521L5.2917,25.1354Z"
|
||||
android:strokeAlpha="1"
|
||||
android:strokeLineJoin="miter"
|
||||
android:strokeWidth="0.26458332"
|
||||
android:fillColor="#4d4d4d"
|
||||
android:strokeColor="#e6e6e6"
|
||||
android:strokeLineCap="butt" />
|
||||
</vector>
|
||||
@@ -0,0 +1,28 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="100dp"
|
||||
android:height="50dp"
|
||||
android:viewportWidth="26.458332"
|
||||
android:viewportHeight="13.229165">
|
||||
<path
|
||||
android:pathData="M0.6615,0.6614L25.7969,0.6614v11.9063L0.6615,12.5677Z"
|
||||
android:strokeAlpha="1"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="0.26458332"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#e6e6e6"
|
||||
android:strokeLineCap="round" />
|
||||
<path
|
||||
android:pathData="m9.6928,4.0428h0.6959v4.5579h2.5046v0.5857L9.6928,9.1863Z"
|
||||
android:strokeAlpha="1"
|
||||
android:strokeWidth="0.13229167"
|
||||
android:fillColor="#000000"
|
||||
android:strokeColor="#e6e6e6"
|
||||
android:fillAlpha="0" />
|
||||
<path
|
||||
android:pathData="m13.8028,8.6007h1.1369v-3.924l-1.2368,0.2481v-0.6339l1.2299,-0.248h0.6959v4.5579h1.1369v0.5857h-2.9628z"
|
||||
android:strokeAlpha="1"
|
||||
android:strokeWidth="0.13229167"
|
||||
android:fillColor="#000000"
|
||||
android:strokeColor="#e6e6e6"
|
||||
android:fillAlpha="0" />
|
||||
</vector>
|
||||
@@ -0,0 +1,28 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="100dp"
|
||||
android:height="50dp"
|
||||
android:viewportWidth="26.458332"
|
||||
android:viewportHeight="13.229165">
|
||||
<path
|
||||
android:pathData="M0.6615,0.6614L25.7969,0.6614v11.9063L0.6615,12.5677Z"
|
||||
android:strokeAlpha="1"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="0.26458332"
|
||||
android:fillColor="#4d4d4d"
|
||||
android:strokeColor="#e6e6e6"
|
||||
android:strokeLineCap="round" />
|
||||
<path
|
||||
android:pathData="m9.6928,4.0428h0.6959v4.5579h2.5046v0.5857L9.6928,9.1863Z"
|
||||
android:strokeAlpha="1"
|
||||
android:strokeWidth="0.13229167"
|
||||
android:fillColor="#1a1a1a"
|
||||
android:strokeColor="#000000"
|
||||
android:fillAlpha="1" />
|
||||
<path
|
||||
android:pathData="m13.8028,8.6007h1.1369v-3.924l-1.2368,0.2481v-0.6339l1.2299,-0.248h0.6959v4.5579h1.1369v0.5857h-2.9628z"
|
||||
android:strokeAlpha="1"
|
||||
android:strokeWidth="0.13229167"
|
||||
android:fillColor="#1a1a1a"
|
||||
android:strokeColor="#000000"
|
||||
android:fillAlpha="1" />
|
||||
</vector>
|
||||
@@ -0,0 +1,28 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="100dp"
|
||||
android:height="50dp"
|
||||
android:viewportWidth="26.458332"
|
||||
android:viewportHeight="13.229165">
|
||||
<path
|
||||
android:pathData="M0.6615,0.6614L25.7969,0.6614v11.9063L0.6615,12.5677Z"
|
||||
android:strokeAlpha="1"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="0.26458332"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#e6e6e6"
|
||||
android:strokeLineCap="round" />
|
||||
<path
|
||||
android:pathData="m9.6928,4.0428h0.6959v4.5579h2.5046v0.5857L9.6928,9.1863Z"
|
||||
android:strokeAlpha="1"
|
||||
android:strokeWidth="0.13229167"
|
||||
android:fillColor="#000000"
|
||||
android:strokeColor="#e6e6e6"
|
||||
android:fillAlpha="0" />
|
||||
<path
|
||||
android:pathData="m14.2816,8.6007h2.4288v0.5857h-3.2659v-0.5857q0.3962,-0.41 1.0783,-1.099 0.6856,-0.6925 0.8613,-0.8923 0.3342,-0.3755 0.4651,-0.6339 0.1344,-0.2618 0.1344,-0.5133 0,-0.41 -0.2894,-0.6683 -0.2859,-0.2584 -0.7476,-0.2584 -0.3273,0 -0.6925,0.1137 -0.3617,0.1137 -0.7751,0.3445v-0.7028q0.4203,-0.1688 0.7855,-0.2549 0.3652,-0.0861 0.6683,-0.0861 0.7993,0 1.2747,0.3996 0.4754,0.3996 0.4754,1.068 0,0.3169 -0.1206,0.6029 -0.1171,0.2825 -0.4306,0.6683 -0.0861,0.0999 -0.5478,0.5788 -0.4616,0.4754 -1.3022,1.3333z"
|
||||
android:strokeAlpha="1"
|
||||
android:strokeWidth="0.13229167"
|
||||
android:fillColor="#000000"
|
||||
android:strokeColor="#e6e6e6"
|
||||
android:fillAlpha="0" />
|
||||
</vector>
|
||||
@@ -0,0 +1,28 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="100dp"
|
||||
android:height="50dp"
|
||||
android:viewportWidth="26.458332"
|
||||
android:viewportHeight="13.229165">
|
||||
<path
|
||||
android:pathData="M0.6615,0.6614L25.7969,0.6614v11.9063L0.6615,12.5677Z"
|
||||
android:strokeAlpha="1"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="0.26458332"
|
||||
android:fillColor="#4d4d4d"
|
||||
android:strokeColor="#e6e6e6"
|
||||
android:strokeLineCap="round" />
|
||||
<path
|
||||
android:pathData="m9.6928,4.0428h0.6959v4.5579h2.5046v0.5857L9.6928,9.1863Z"
|
||||
android:strokeAlpha="1"
|
||||
android:strokeWidth="0.13229167"
|
||||
android:fillColor="#1a1a1a"
|
||||
android:strokeColor="#000000"
|
||||
android:fillAlpha="1" />
|
||||
<path
|
||||
android:pathData="m14.2816,8.6007h2.4288v0.5857h-3.2659v-0.5857q0.3962,-0.41 1.0783,-1.099 0.6856,-0.6925 0.8613,-0.8923 0.3342,-0.3755 0.4651,-0.6339 0.1344,-0.2618 0.1344,-0.5133 0,-0.41 -0.2894,-0.6683 -0.2859,-0.2584 -0.7476,-0.2584 -0.3273,0 -0.6925,0.1137 -0.3617,0.1137 -0.7751,0.3445v-0.7028q0.4203,-0.1688 0.7855,-0.2549 0.3652,-0.0861 0.6683,-0.0861 0.7993,0 1.2747,0.3996 0.4754,0.3996 0.4754,1.068 0,0.3169 -0.1206,0.6029 -0.1171,0.2825 -0.4306,0.6683 -0.0861,0.0999 -0.5478,0.5788 -0.4616,0.4754 -1.3022,1.3333z"
|
||||
android:strokeAlpha="1"
|
||||
android:strokeWidth="0.13229167"
|
||||
android:fillColor="#1a1a1a"
|
||||
android:strokeColor="#000000"
|
||||
android:fillAlpha="1" />
|
||||
</vector>
|
||||
@@ -0,0 +1,14 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="100dp"
|
||||
android:height="100dp"
|
||||
android:viewportWidth="26.458332"
|
||||
android:viewportHeight="26.458332">
|
||||
<path
|
||||
android:pathData="m16.5365,5.9531 l7.9375,7.9375 -7.9375,7.9375L1.9844,21.8281v-15.875z"
|
||||
android:strokeAlpha="1"
|
||||
android:strokeLineJoin="miter"
|
||||
android:strokeWidth="0.26458332"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#e6e6e6"
|
||||
android:strokeLineCap="butt" />
|
||||
</vector>
|
||||
@@ -0,0 +1,14 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="100dp"
|
||||
android:height="100dp"
|
||||
android:viewportWidth="26.458332"
|
||||
android:viewportHeight="26.458332">
|
||||
<path
|
||||
android:pathData="m16.5365,5.9531 l7.9375,7.9375 -7.9375,7.9375L1.9844,21.8281v-15.875z"
|
||||
android:strokeAlpha="1"
|
||||
android:strokeLineJoin="miter"
|
||||
android:strokeWidth="0.26458332"
|
||||
android:fillColor="#4d4d4d"
|
||||
android:strokeColor="#e6e6e6"
|
||||
android:strokeLineCap="butt" />
|
||||
</vector>
|
||||
@@ -0,0 +1,28 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="100dp"
|
||||
android:height="50dp"
|
||||
android:viewportWidth="26.458332"
|
||||
android:viewportHeight="13.229165">
|
||||
<path
|
||||
android:pathData="M0.6615,0.6614L25.7969,0.6614v11.9063L0.6615,12.5677Z"
|
||||
android:strokeAlpha="1"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="0.26458332"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#e6e6e6"
|
||||
android:strokeLineCap="round" />
|
||||
<path
|
||||
android:pathData="m11.6703,6.8213q0.2239,0.0758 0.4341,0.3238 0.2136,0.248 0.4272,0.6821l0.7062,1.4056h-0.7476l-0.658,-1.3195q-0.2549,-0.5168 -0.4961,-0.6856 -0.2377,-0.1688 -0.6511,-0.1688L9.927,7.059v2.1739L9.2311,9.2329v-5.1435h1.571q0.8819,0 1.316,0.3686 0.4341,0.3686 0.4341,1.1128 0,0.4858 -0.2274,0.8062 -0.2239,0.3204 -0.6546,0.4444zM9.927,4.6612v1.8259h0.8751q0.503,0 0.7579,-0.2308 0.2584,-0.2343 0.2584,-0.6856 0,-0.4513 -0.2584,-0.6787 -0.2549,-0.2308 -0.7579,-0.2308z"
|
||||
android:strokeAlpha="1"
|
||||
android:strokeWidth="0.13229167"
|
||||
android:fillColor="#000000"
|
||||
android:strokeColor="#e6e6e6"
|
||||
android:fillAlpha="0" />
|
||||
<path
|
||||
android:pathData="m14.3195,8.6472h1.1369v-3.924l-1.2368,0.2481v-0.6339l1.2299,-0.248h0.6959v4.5579h1.1369v0.5857h-2.9628z"
|
||||
android:strokeAlpha="1"
|
||||
android:strokeWidth="0.13229167"
|
||||
android:fillColor="#000000"
|
||||
android:strokeColor="#e6e6e6"
|
||||
android:fillAlpha="0" />
|
||||
</vector>
|
||||
@@ -0,0 +1,28 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="100dp"
|
||||
android:height="50dp"
|
||||
android:viewportWidth="26.458332"
|
||||
android:viewportHeight="13.229165">
|
||||
<path
|
||||
android:pathData="M0.6615,0.6614L25.7969,0.6614v11.9063L0.6615,12.5677Z"
|
||||
android:strokeAlpha="1"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="0.26458332"
|
||||
android:fillColor="#4d4d4d"
|
||||
android:strokeColor="#e6e6e6"
|
||||
android:strokeLineCap="round" />
|
||||
<path
|
||||
android:pathData="m11.6703,6.8213q0.2239,0.0758 0.4341,0.3238 0.2136,0.248 0.4272,0.6821l0.7062,1.4056h-0.7476l-0.658,-1.3195q-0.2549,-0.5168 -0.4961,-0.6856 -0.2377,-0.1688 -0.6511,-0.1688L9.927,7.059v2.1739L9.2311,9.2329v-5.1435h1.571q0.8819,0 1.316,0.3686 0.4341,0.3686 0.4341,1.1128 0,0.4858 -0.2274,0.8062 -0.2239,0.3204 -0.6546,0.4444zM9.927,4.6612v1.8259h0.8751q0.503,0 0.7579,-0.2308 0.2584,-0.2343 0.2584,-0.6856 0,-0.4513 -0.2584,-0.6787 -0.2549,-0.2308 -0.7579,-0.2308z"
|
||||
android:strokeAlpha="1"
|
||||
android:strokeWidth="0.13229167"
|
||||
android:fillColor="#1a1a1a"
|
||||
android:strokeColor="#000000"
|
||||
android:fillAlpha="1" />
|
||||
<path
|
||||
android:pathData="m14.3195,8.6472h1.1369v-3.924l-1.2368,0.2481v-0.6339l1.2299,-0.248h0.6959v4.5579h1.1369v0.5857h-2.9628z"
|
||||
android:strokeAlpha="1"
|
||||
android:strokeWidth="0.13229167"
|
||||
android:fillColor="#1a1a1a"
|
||||
android:strokeColor="#000000"
|
||||
android:fillAlpha="1" />
|
||||
</vector>
|
||||
@@ -0,0 +1,28 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="100dp"
|
||||
android:height="50dp"
|
||||
android:viewportWidth="26.458332"
|
||||
android:viewportHeight="13.229165">
|
||||
<path
|
||||
android:pathData="M0.6615,0.6614L25.7969,0.6614v11.9063L0.6615,12.5677Z"
|
||||
android:strokeAlpha="1"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="0.26458332"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#e6e6e6"
|
||||
android:strokeLineCap="round" />
|
||||
<path
|
||||
android:pathData="m11.6703,6.8213q0.2239,0.0758 0.4341,0.3238 0.2136,0.248 0.4272,0.6821l0.7062,1.4056h-0.7476l-0.658,-1.3195q-0.2549,-0.5168 -0.4961,-0.6856 -0.2377,-0.1688 -0.6511,-0.1688L9.927,7.059v2.1739L9.2311,9.2329v-5.1435h1.571q0.8819,0 1.316,0.3686 0.4341,0.3686 0.4341,1.1128 0,0.4858 -0.2274,0.8062 -0.2239,0.3204 -0.6546,0.4444zM9.927,4.6612v1.8259h0.8751q0.503,0 0.7579,-0.2308 0.2584,-0.2343 0.2584,-0.6856 0,-0.4513 -0.2584,-0.6787 -0.2549,-0.2308 -0.7579,-0.2308z"
|
||||
android:strokeAlpha="1"
|
||||
android:strokeWidth="0.13229167"
|
||||
android:fillColor="#000000"
|
||||
android:strokeColor="#e6e6e6"
|
||||
android:fillAlpha="0" />
|
||||
<path
|
||||
android:pathData="m14.7984,8.6472h2.4288v0.5857h-3.2659v-0.5857q0.3962,-0.41 1.0783,-1.099 0.6856,-0.6925 0.8613,-0.8923 0.3342,-0.3755 0.4651,-0.6339 0.1344,-0.2618 0.1344,-0.5133 0,-0.41 -0.2894,-0.6683 -0.2859,-0.2584 -0.7476,-0.2584 -0.3273,0 -0.6925,0.1137 -0.3617,0.1137 -0.7751,0.3445v-0.7028q0.4203,-0.1688 0.7855,-0.2549 0.3652,-0.0861 0.6683,-0.0861 0.7993,0 1.2747,0.3996 0.4754,0.3996 0.4754,1.068 0,0.3169 -0.1206,0.6029 -0.1171,0.2825 -0.4306,0.6683 -0.0861,0.0999 -0.5478,0.5788 -0.4616,0.4754 -1.3022,1.3333z"
|
||||
android:strokeAlpha="1"
|
||||
android:strokeWidth="0.13229167"
|
||||
android:fillColor="#000000"
|
||||
android:strokeColor="#e6e6e6"
|
||||
android:fillAlpha="0" />
|
||||
</vector>
|
||||
@@ -0,0 +1,28 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="100dp"
|
||||
android:height="50dp"
|
||||
android:viewportWidth="26.458332"
|
||||
android:viewportHeight="13.229165">
|
||||
<path
|
||||
android:pathData="M0.6615,0.6614L25.7969,0.6614v11.9063L0.6615,12.5677Z"
|
||||
android:strokeAlpha="1"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="0.26458332"
|
||||
android:fillColor="#4d4d4d"
|
||||
android:strokeColor="#e6e6e6"
|
||||
android:strokeLineCap="round" />
|
||||
<path
|
||||
android:pathData="m11.6703,6.8213q0.2239,0.0758 0.4341,0.3238 0.2136,0.248 0.4272,0.6821l0.7062,1.4056h-0.7476l-0.658,-1.3195q-0.2549,-0.5168 -0.4961,-0.6856 -0.2377,-0.1688 -0.6511,-0.1688L9.927,7.059v2.1739L9.2311,9.2329v-5.1435h1.571q0.8819,0 1.316,0.3686 0.4341,0.3686 0.4341,1.1128 0,0.4858 -0.2274,0.8062 -0.2239,0.3204 -0.6546,0.4444zM9.927,4.6612v1.8259h0.8751q0.503,0 0.7579,-0.2308 0.2584,-0.2343 0.2584,-0.6856 0,-0.4513 -0.2584,-0.6787 -0.2549,-0.2308 -0.7579,-0.2308z"
|
||||
android:strokeAlpha="1"
|
||||
android:strokeWidth="0.13229167"
|
||||
android:fillColor="#1a1a1a"
|
||||
android:strokeColor="#000000"
|
||||
android:fillAlpha="1" />
|
||||
<path
|
||||
android:pathData="m14.7984,8.6472h2.4288v0.5857h-3.2659v-0.5857q0.3962,-0.41 1.0783,-1.099 0.6856,-0.6925 0.8613,-0.8923 0.3342,-0.3755 0.4651,-0.6339 0.1344,-0.2618 0.1344,-0.5133 0,-0.41 -0.2894,-0.6683 -0.2859,-0.2584 -0.7476,-0.2584 -0.3273,0 -0.6925,0.1137 -0.3617,0.1137 -0.7751,0.3445v-0.7028q0.4203,-0.1688 0.7855,-0.2549 0.3652,-0.0861 0.6683,-0.0861 0.7993,0 1.2747,0.3996 0.4754,0.3996 0.4754,1.068 0,0.3169 -0.1206,0.6029 -0.1171,0.2825 -0.4306,0.6683 -0.0861,0.0999 -0.5478,0.5788 -0.4616,0.4754 -1.3022,1.3333z"
|
||||
android:strokeAlpha="1"
|
||||
android:strokeWidth="0.13229167"
|
||||
android:fillColor="#1a1a1a"
|
||||
android:strokeColor="#000000"
|
||||
android:fillAlpha="1" />
|
||||
</vector>
|
||||
@@ -0,0 +1,14 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="100dp"
|
||||
android:height="100dp"
|
||||
android:viewportWidth="26.458332"
|
||||
android:viewportHeight="26.458332">
|
||||
<path
|
||||
android:pathData="m9.9219,21.8281 l-7.9375,-7.9375 7.9375,-7.9375L24.474,5.9531v15.875z"
|
||||
android:strokeAlpha="1"
|
||||
android:strokeLineJoin="miter"
|
||||
android:strokeWidth="0.26458332"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#e6e6e6"
|
||||
android:strokeLineCap="butt" />
|
||||
</vector>
|
||||
@@ -0,0 +1,14 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="100dp"
|
||||
android:height="100dp"
|
||||
android:viewportWidth="26.458332"
|
||||
android:viewportHeight="26.458332">
|
||||
<path
|
||||
android:pathData="m9.9219,21.8281 l-7.9375,-7.9375 7.9375,-7.9375L24.474,5.9531v15.875z"
|
||||
android:strokeAlpha="1"
|
||||
android:strokeLineJoin="miter"
|
||||
android:strokeWidth="0.26458332"
|
||||
android:fillColor="#4d4d4d"
|
||||
android:strokeColor="#e6e6e6"
|
||||
android:strokeLineCap="butt" />
|
||||
</vector>
|
||||
@@ -0,0 +1,14 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="100dp"
|
||||
android:height="50dp"
|
||||
android:viewportWidth="26.458332"
|
||||
android:viewportHeight="13.229165">
|
||||
<path
|
||||
android:pathData="m0.6615,0.6614v11.9063L25.7969,12.5677v-11.9063z"
|
||||
android:strokeAlpha="1"
|
||||
android:strokeLineJoin="miter"
|
||||
android:strokeWidth="0.26458332"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#6e6e6f"
|
||||
android:strokeLineCap="butt" />
|
||||
</vector>
|
||||
@@ -0,0 +1,14 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="100dp"
|
||||
android:height="50dp"
|
||||
android:viewportWidth="26.458332"
|
||||
android:viewportHeight="13.229165">
|
||||
<path
|
||||
android:pathData="m0.6615,0.6614v11.9063L25.7969,12.5677v-11.9063z"
|
||||
android:strokeAlpha="1"
|
||||
android:strokeLineJoin="miter"
|
||||
android:strokeWidth="0.26458332"
|
||||
android:fillColor="#4d4d4d"
|
||||
android:strokeColor="#6e6e6f"
|
||||
android:strokeLineCap="butt" />
|
||||
</vector>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user