mirror of
https://github.com/citra-emu/citra-nightly.git
synced 2025-09-26 14:17:12 +00:00
Compare commits
46 commits
nightly-20
...
master
Author | SHA1 | Date | |
---|---|---|---|
|
0ff3440232 | ||
|
69e758d738 | ||
|
f4768cd26c | ||
|
e0d2c1308e | ||
|
4f9fc88bb3 | ||
|
d857743075 | ||
|
b5042a5257 | ||
|
e524542a40 | ||
|
3a4ebb1413 | ||
|
cbe8987036 | ||
|
da5aa70fc9 | ||
|
749a721aa2 | ||
|
bb003c2bd4 | ||
|
7638f87f74 | ||
|
aa6809e2a8 | ||
|
5e02be75a3 | ||
|
b9c9beeee5 | ||
|
de993dcfbd | ||
|
3c9157b1ec | ||
|
0c40c10022 | ||
|
2766118e33 | ||
|
06b26691ba | ||
|
d41ce64f7b | ||
|
1165a708d5 | ||
|
19784355f9 | ||
|
aa6a29d7e1 | ||
|
106364e01e | ||
|
d5a1bd07f3 | ||
|
8afa27718c | ||
|
8e2415f455 | ||
|
c978c074db | ||
|
cb92ec278e | ||
|
9f5d5c6ddd | ||
|
480604ec72 | ||
|
63feac6bb3 | ||
|
469f76b075 | ||
|
7a4854c519 | ||
|
d1e3dddf6a | ||
|
265e8193b9 | ||
|
e8c20fa782 | ||
|
95ae46f6a8 | ||
|
41fe75acb7 | ||
|
1744537d85 | ||
|
bea863efff | ||
|
89e13a85a7 | ||
|
549fdd0736 |
65
.github/workflows/build.yml
vendored
65
.github/workflows/build.yml
vendored
|
@ -12,13 +12,13 @@ jobs:
|
||||||
if: ${{ !github.head_ref }}
|
if: ${{ !github.head_ref }}
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
- name: Pack
|
- name: Pack
|
||||||
run: ./.ci/source.sh
|
run: ./.ci/source.sh
|
||||||
- name: Upload
|
- name: Upload
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: source
|
name: source
|
||||||
path: artifacts/
|
path: artifacts/
|
||||||
|
@ -37,11 +37,11 @@ jobs:
|
||||||
OS: linux
|
OS: linux
|
||||||
TARGET: ${{ matrix.target }}
|
TARGET: ${{ matrix.target }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
- name: Set up cache
|
- name: Set up cache
|
||||||
uses: actions/cache@v3
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: ${{ env.CCACHE_DIR }}
|
path: ${{ env.CCACHE_DIR }}
|
||||||
key: ${{ runner.os }}-${{ matrix.target }}-${{ github.sha }}
|
key: ${{ runner.os }}-${{ matrix.target }}-${{ github.sha }}
|
||||||
|
@ -53,13 +53,13 @@ jobs:
|
||||||
run: ./.ci/pack.sh
|
run: ./.ci/pack.sh
|
||||||
if: ${{ matrix.target == 'appimage' }}
|
if: ${{ matrix.target == 'appimage' }}
|
||||||
- name: Upload
|
- name: Upload
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
if: ${{ matrix.target == 'appimage' }}
|
if: ${{ matrix.target == 'appimage' }}
|
||||||
with:
|
with:
|
||||||
name: ${{ env.OS }}-${{ env.TARGET }}
|
name: ${{ env.OS }}-${{ env.TARGET }}
|
||||||
path: artifacts/
|
path: artifacts/
|
||||||
macos:
|
macos:
|
||||||
runs-on: macos-13
|
runs-on: ${{ (matrix.target == 'x86_64' && 'macos-13') || 'macos-14' }}
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
target: ["x86_64", "arm64"]
|
target: ["x86_64", "arm64"]
|
||||||
|
@ -70,43 +70,43 @@ jobs:
|
||||||
OS: macos
|
OS: macos
|
||||||
TARGET: ${{ matrix.target }}
|
TARGET: ${{ matrix.target }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
- name: Set up cache
|
- name: Set up cache
|
||||||
uses: actions/cache@v3
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: ${{ env.CCACHE_DIR }}
|
path: ${{ env.CCACHE_DIR }}
|
||||||
key: ${{ runner.os }}-${{ matrix.target }}-${{ github.sha }}
|
key: ${{ runner.os }}-${{ matrix.target }}-${{ github.sha }}
|
||||||
restore-keys: |
|
restore-keys: |
|
||||||
${{ runner.os }}-${{ matrix.target }}-
|
${{ runner.os }}-${{ matrix.target }}-
|
||||||
- name: Install tools
|
- name: Install tools
|
||||||
run: brew install ccache glslang ninja
|
run: brew install ccache ninja
|
||||||
- name: Build
|
- name: Build
|
||||||
run: ./.ci/macos.sh
|
run: ./.ci/macos.sh
|
||||||
- name: Prepare outputs for caching
|
- name: Prepare outputs for caching
|
||||||
run: mv build/bundle $OS-$TARGET
|
run: mv build/bundle $OS-$TARGET
|
||||||
- name: Cache outputs for universal build
|
- name: Cache outputs for universal build
|
||||||
uses: actions/cache/save@v3
|
uses: actions/cache/save@v4
|
||||||
with:
|
with:
|
||||||
path: ${{ env.OS }}-${{ env.TARGET }}
|
path: ${{ env.OS }}-${{ env.TARGET }}
|
||||||
key: ${{ runner.os }}-${{ matrix.target }}-${{ github.sha }}-${{ github.run_id }}-${{ github.run_attempt }}
|
key: ${{ runner.os }}-${{ matrix.target }}-${{ github.sha }}-${{ github.run_id }}-${{ github.run_attempt }}
|
||||||
macos-universal:
|
macos-universal:
|
||||||
runs-on: macos-13
|
runs-on: macos-14
|
||||||
needs: macos
|
needs: macos
|
||||||
env:
|
env:
|
||||||
OS: macos
|
OS: macos
|
||||||
TARGET: universal
|
TARGET: universal
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- name: Download x86_64 build from cache
|
- name: Download x86_64 build from cache
|
||||||
uses: actions/cache/restore@v3
|
uses: actions/cache/restore@v4
|
||||||
with:
|
with:
|
||||||
path: ${{ env.OS }}-x86_64
|
path: ${{ env.OS }}-x86_64
|
||||||
key: ${{ runner.os }}-x86_64-${{ github.sha }}-${{ github.run_id }}-${{ github.run_attempt }}
|
key: ${{ runner.os }}-x86_64-${{ github.sha }}-${{ github.run_id }}-${{ github.run_attempt }}
|
||||||
fail-on-cache-miss: true
|
fail-on-cache-miss: true
|
||||||
- name: Download ARM64 build from cache
|
- name: Download ARM64 build from cache
|
||||||
uses: actions/cache/restore@v3
|
uses: actions/cache/restore@v4
|
||||||
with:
|
with:
|
||||||
path: ${{ env.OS }}-arm64
|
path: ${{ env.OS }}-arm64
|
||||||
key: ${{ runner.os }}-arm64-${{ github.sha }}-${{ github.run_id }}-${{ github.run_attempt }}
|
key: ${{ runner.os }}-arm64-${{ github.sha }}-${{ github.run_id }}-${{ github.run_attempt }}
|
||||||
|
@ -118,7 +118,7 @@ jobs:
|
||||||
- name: Pack
|
- name: Pack
|
||||||
run: ./.ci/pack.sh
|
run: ./.ci/pack.sh
|
||||||
- name: Upload
|
- name: Upload
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: ${{ env.OS }}-${{ env.TARGET }}
|
name: ${{ env.OS }}-${{ env.TARGET }}
|
||||||
path: artifacts/
|
path: artifacts/
|
||||||
|
@ -137,11 +137,11 @@ jobs:
|
||||||
OS: windows
|
OS: windows
|
||||||
TARGET: ${{ matrix.target }}
|
TARGET: ${{ matrix.target }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
- name: Set up cache
|
- name: Set up cache
|
||||||
uses: actions/cache@v3
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: ${{ env.CCACHE_DIR }}
|
path: ${{ env.CCACHE_DIR }}
|
||||||
key: ${{ runner.os }}-${{ matrix.target }}-${{ github.sha }}
|
key: ${{ runner.os }}-${{ matrix.target }}-${{ github.sha }}
|
||||||
|
@ -153,13 +153,6 @@ jobs:
|
||||||
- name: Install extra tools (MSVC)
|
- name: Install extra tools (MSVC)
|
||||||
run: choco install ccache ninja wget
|
run: choco install ccache ninja wget
|
||||||
if: ${{ matrix.target == 'msvc' }}
|
if: ${{ matrix.target == 'msvc' }}
|
||||||
- name: Set up Vulkan SDK (MSVC)
|
|
||||||
uses: humbletim/setup-vulkan-sdk@v1.2.0
|
|
||||||
if: ${{ matrix.target == 'msvc' }}
|
|
||||||
with:
|
|
||||||
vulkan-query-version: latest
|
|
||||||
vulkan-components: SPIRV-Tools, Glslang
|
|
||||||
vulkan-use-cache: true
|
|
||||||
- name: Set up MSYS2
|
- name: Set up MSYS2
|
||||||
uses: msys2/setup-msys2@v2
|
uses: msys2/setup-msys2@v2
|
||||||
if: ${{ matrix.target == 'msys2' }}
|
if: ${{ matrix.target == 'msys2' }}
|
||||||
|
@ -168,10 +161,8 @@ jobs:
|
||||||
update: true
|
update: true
|
||||||
install: git make p7zip
|
install: git make p7zip
|
||||||
pacboy: >-
|
pacboy: >-
|
||||||
toolchain:p ccache:p cmake:p ninja:p glslang:p
|
toolchain:p ccache:p cmake:p ninja:p
|
||||||
qt6-base:p qt6-multimedia:p qt6-multimedia-wmf:p qt6-tools:p qt6-translations:p
|
qt6-base:p qt6-multimedia:p qt6-multimedia-wmf:p qt6-tools:p qt6-translations:p
|
||||||
- name: Test glslang
|
|
||||||
run: glslang --version || glslangValidator --version
|
|
||||||
- name: Disable line ending translation
|
- name: Disable line ending translation
|
||||||
run: git config --global core.autocrlf input
|
run: git config --global core.autocrlf input
|
||||||
- name: Build
|
- name: Build
|
||||||
|
@ -179,7 +170,7 @@ jobs:
|
||||||
- name: Pack
|
- name: Pack
|
||||||
run: ./.ci/pack.sh
|
run: ./.ci/pack.sh
|
||||||
- name: Upload
|
- name: Upload
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: ${{ env.OS }}-${{ env.TARGET }}
|
name: ${{ env.OS }}-${{ env.TARGET }}
|
||||||
path: artifacts/
|
path: artifacts/
|
||||||
|
@ -192,11 +183,11 @@ jobs:
|
||||||
OS: android
|
OS: android
|
||||||
TARGET: universal
|
TARGET: universal
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
- name: Set up cache
|
- name: Set up cache
|
||||||
uses: actions/cache@v3
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
~/.gradle/caches
|
~/.gradle/caches
|
||||||
|
@ -215,7 +206,7 @@ jobs:
|
||||||
run: |
|
run: |
|
||||||
sudo add-apt-repository -y ppa:theofficialgman/gpu-tools
|
sudo add-apt-repository -y ppa:theofficialgman/gpu-tools
|
||||||
sudo apt-get update -y
|
sudo apt-get update -y
|
||||||
sudo apt-get install ccache glslang-dev glslang-tools apksigner -y
|
sudo apt-get install ccache apksigner -y
|
||||||
- name: Build
|
- name: Build
|
||||||
run: JAVA_HOME=$JAVA_HOME_17_X64 ./.ci/android.sh
|
run: JAVA_HOME=$JAVA_HOME_17_X64 ./.ci/android.sh
|
||||||
env:
|
env:
|
||||||
|
@ -228,12 +219,12 @@ jobs:
|
||||||
env:
|
env:
|
||||||
UNPACKED: 1
|
UNPACKED: 1
|
||||||
- name: Upload
|
- name: Upload
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: ${{ env.OS }}-${{ env.TARGET }}
|
name: ${{ env.OS }}-${{ env.TARGET }}
|
||||||
path: src/android/app/artifacts/
|
path: src/android/app/artifacts/
|
||||||
ios:
|
ios:
|
||||||
runs-on: macos-13
|
runs-on: macos-14
|
||||||
if: ${{ !startsWith(github.ref, 'refs/tags/') }}
|
if: ${{ !startsWith(github.ref, 'refs/tags/') }}
|
||||||
env:
|
env:
|
||||||
CCACHE_DIR: ${{ github.workspace }}/.ccache
|
CCACHE_DIR: ${{ github.workspace }}/.ccache
|
||||||
|
@ -242,18 +233,18 @@ jobs:
|
||||||
OS: ios
|
OS: ios
|
||||||
TARGET: arm64
|
TARGET: arm64
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
- name: Set up cache
|
- name: Set up cache
|
||||||
uses: actions/cache@v3
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: ${{ env.CCACHE_DIR }}
|
path: ${{ env.CCACHE_DIR }}
|
||||||
key: ${{ runner.os }}-ios-${{ github.sha }}
|
key: ${{ runner.os }}-ios-${{ github.sha }}
|
||||||
restore-keys: |
|
restore-keys: |
|
||||||
${{ runner.os }}-ios-
|
${{ runner.os }}-ios-
|
||||||
- name: Install tools
|
- name: Install tools
|
||||||
run: brew install ccache glslang ninja
|
run: brew install ccache ninja
|
||||||
- name: Build
|
- name: Build
|
||||||
run: ./.ci/ios.sh
|
run: ./.ci/ios.sh
|
||||||
release:
|
release:
|
||||||
|
@ -261,7 +252,7 @@ jobs:
|
||||||
needs: [windows, linux, macos-universal, android, source]
|
needs: [windows, linux, macos-universal, android, source]
|
||||||
if: ${{ startsWith(github.ref, 'refs/tags/') }}
|
if: ${{ startsWith(github.ref, 'refs/tags/') }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/download-artifact@v3
|
- uses: actions/download-artifact@v4
|
||||||
- name: Create release
|
- name: Create release
|
||||||
uses: actions/create-release@v1
|
uses: actions/create-release@v1
|
||||||
env:
|
env:
|
||||||
|
|
2
.github/workflows/format.yml
vendored
2
.github/workflows/format.yml
vendored
|
@ -13,7 +13,7 @@ jobs:
|
||||||
image: citraemu/build-environments:linux-fresh
|
image: citraemu/build-environments:linux-fresh
|
||||||
options: -u 1001
|
options: -u 1001
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Build
|
- name: Build
|
||||||
|
|
16
.github/workflows/publish.yml
vendored
16
.github/workflows/publish.yml
vendored
|
@ -20,11 +20,11 @@ jobs:
|
||||||
if: ${{ github.event.inputs.nightly != 'false' && github.repository == 'citra-emu/citra' }}
|
if: ${{ github.event.inputs.nightly != 'false' && github.repository == 'citra-emu/citra' }}
|
||||||
steps:
|
steps:
|
||||||
# this checkout is required to make sure the GitHub Actions scripts are available
|
# this checkout is required to make sure the GitHub Actions scripts are available
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
name: Pre-checkout
|
name: Pre-checkout
|
||||||
with:
|
with:
|
||||||
submodules: false
|
submodules: false
|
||||||
- uses: actions/github-script@v6
|
- uses: actions/github-script@v7
|
||||||
id: check-changes
|
id: check-changes
|
||||||
name: 'Check for new changes'
|
name: 'Check for new changes'
|
||||||
env:
|
env:
|
||||||
|
@ -38,7 +38,7 @@ jobs:
|
||||||
return checkBaseChanges(github, context);
|
return checkBaseChanges(github, context);
|
||||||
- run: npm install execa@5
|
- run: npm install execa@5
|
||||||
if: ${{ steps.check-changes.outputs.result == 'true' }}
|
if: ${{ steps.check-changes.outputs.result == 'true' }}
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
name: Checkout
|
name: Checkout
|
||||||
if: ${{ steps.check-changes.outputs.result == 'true' }}
|
if: ${{ steps.check-changes.outputs.result == 'true' }}
|
||||||
with:
|
with:
|
||||||
|
@ -46,7 +46,7 @@ jobs:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
submodules: true
|
submodules: true
|
||||||
token: ${{ secrets.ALT_GITHUB_TOKEN }}
|
token: ${{ secrets.ALT_GITHUB_TOKEN }}
|
||||||
- uses: actions/github-script@v6
|
- uses: actions/github-script@v7
|
||||||
name: 'Update and tag new commits'
|
name: 'Update and tag new commits'
|
||||||
if: ${{ steps.check-changes.outputs.result == 'true' }}
|
if: ${{ steps.check-changes.outputs.result == 'true' }}
|
||||||
env:
|
env:
|
||||||
|
@ -62,11 +62,11 @@ jobs:
|
||||||
if: ${{ github.event.inputs.canary != 'false' && github.repository == 'citra-emu/citra' }}
|
if: ${{ github.event.inputs.canary != 'false' && github.repository == 'citra-emu/citra' }}
|
||||||
steps:
|
steps:
|
||||||
# this checkout is required to make sure the GitHub Actions scripts are available
|
# this checkout is required to make sure the GitHub Actions scripts are available
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
name: Pre-checkout
|
name: Pre-checkout
|
||||||
with:
|
with:
|
||||||
submodules: false
|
submodules: false
|
||||||
- uses: actions/github-script@v6
|
- uses: actions/github-script@v7
|
||||||
id: check-changes
|
id: check-changes
|
||||||
name: 'Check for new changes'
|
name: 'Check for new changes'
|
||||||
env:
|
env:
|
||||||
|
@ -79,7 +79,7 @@ jobs:
|
||||||
return checkCanaryChanges(github, context);
|
return checkCanaryChanges(github, context);
|
||||||
- run: npm install execa@5
|
- run: npm install execa@5
|
||||||
if: ${{ steps.check-changes.outputs.result == 'true' }}
|
if: ${{ steps.check-changes.outputs.result == 'true' }}
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
name: Checkout
|
name: Checkout
|
||||||
if: ${{ steps.check-changes.outputs.result == 'true' }}
|
if: ${{ steps.check-changes.outputs.result == 'true' }}
|
||||||
with:
|
with:
|
||||||
|
@ -87,7 +87,7 @@ jobs:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
submodules: true
|
submodules: true
|
||||||
token: ${{ secrets.ALT_GITHUB_TOKEN }}
|
token: ${{ secrets.ALT_GITHUB_TOKEN }}
|
||||||
- uses: actions/github-script@v6
|
- uses: actions/github-script@v7
|
||||||
name: 'Check and merge canary changes'
|
name: 'Check and merge canary changes'
|
||||||
if: ${{ steps.check-changes.outputs.result == 'true' }}
|
if: ${{ steps.check-changes.outputs.result == 'true' }}
|
||||||
env:
|
env:
|
||||||
|
|
2
.github/workflows/transifex.yml
vendored
2
.github/workflows/transifex.yml
vendored
|
@ -10,7 +10,7 @@ jobs:
|
||||||
container: citraemu/build-environments:linux-fresh
|
container: citraemu/build-environments:linux-fresh
|
||||||
if: ${{ github.repository == 'citra-emu/citra' }}
|
if: ${{ github.repository == 'citra-emu/citra' }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
|
@ -74,8 +74,7 @@ CMAKE_DEPENDENT_OPTION(ENABLE_DEDICATED_ROOM "Enable generating dedicated room e
|
||||||
option(ENABLE_WEB_SERVICE "Enable web services (telemetry, etc.)" ON)
|
option(ENABLE_WEB_SERVICE "Enable web services (telemetry, etc.)" ON)
|
||||||
option(ENABLE_SCRIPTING "Enable RPC server for scripting" ON)
|
option(ENABLE_SCRIPTING "Enable RPC server for scripting" ON)
|
||||||
|
|
||||||
# TODO: cubeb currently causes issues on macOS, see: https://github.com/mozilla/cubeb/issues/771
|
CMAKE_DEPENDENT_OPTION(ENABLE_CUBEB "Enables the cubeb audio backend" ON "NOT IOS" OFF)
|
||||||
CMAKE_DEPENDENT_OPTION(ENABLE_CUBEB "Enables the cubeb audio backend" ON "NOT APPLE" OFF)
|
|
||||||
option(ENABLE_OPENAL "Enables the OpenAL audio backend" ON)
|
option(ENABLE_OPENAL "Enables the OpenAL audio backend" ON)
|
||||||
|
|
||||||
CMAKE_DEPENDENT_OPTION(ENABLE_LIBUSB "Enable libusb for GameCube Adapter support" ON "NOT IOS" OFF)
|
CMAKE_DEPENDENT_OPTION(ENABLE_LIBUSB "Enable libusb for GameCube Adapter support" ON "NOT IOS" OFF)
|
||||||
|
@ -86,8 +85,6 @@ option(ENABLE_VULKAN "Enables the Vulkan renderer" ON)
|
||||||
|
|
||||||
option(USE_DISCORD_PRESENCE "Enables Discord Rich Presence" OFF)
|
option(USE_DISCORD_PRESENCE "Enables Discord Rich Presence" OFF)
|
||||||
|
|
||||||
CMAKE_DEPENDENT_OPTION(CITRA_ENABLE_BUNDLE_TARGET "Enable the distribution bundling target." ON "NOT ANDROID AND NOT IOS" OFF)
|
|
||||||
|
|
||||||
# Compile options
|
# Compile options
|
||||||
CMAKE_DEPENDENT_OPTION(COMPILE_WITH_DWARF "Add DWARF debugging information" ${IS_DEBUG_BUILD} "MINGW" OFF)
|
CMAKE_DEPENDENT_OPTION(COMPILE_WITH_DWARF "Add DWARF debugging information" ${IS_DEBUG_BUILD} "MINGW" OFF)
|
||||||
option(ENABLE_LTO "Enable link time optimization" ${DEFAULT_ENABLE_LTO})
|
option(ENABLE_LTO "Enable link time optimization" ${DEFAULT_ENABLE_LTO})
|
||||||
|
@ -250,6 +247,26 @@ if (ENABLE_QT)
|
||||||
if (ENABLE_QT_TRANSLATION)
|
if (ENABLE_QT_TRANSLATION)
|
||||||
find_package(Qt6 REQUIRED COMPONENTS LinguistTools)
|
find_package(Qt6 REQUIRED COMPONENTS LinguistTools)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
if (NOT DEFINED QT_TARGET_PATH)
|
||||||
|
# Determine the location of the compile target's Qt.
|
||||||
|
get_target_property(qtcore_path Qt6::Core LOCATION_Release)
|
||||||
|
string(FIND "${qtcore_path}" "/bin/" qtcore_path_bin_pos REVERSE)
|
||||||
|
string(FIND "${qtcore_path}" "/lib/" qtcore_path_lib_pos REVERSE)
|
||||||
|
if (qtcore_path_bin_pos GREATER qtcore_path_lib_pos)
|
||||||
|
string(SUBSTRING "${qtcore_path}" 0 ${qtcore_path_bin_pos} QT_TARGET_PATH)
|
||||||
|
else()
|
||||||
|
string(SUBSTRING "${qtcore_path}" 0 ${qtcore_path_lib_pos} QT_TARGET_PATH)
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if (NOT DEFINED QT_HOST_PATH)
|
||||||
|
# Use the same for host Qt if none is defined.
|
||||||
|
set(QT_HOST_PATH "${QT_TARGET_PATH}")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
message(STATUS "Using target Qt at ${QT_TARGET_PATH}")
|
||||||
|
message(STATUS "Using host Qt at ${QT_HOST_PATH}")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# Use system tsl::robin_map if available (otherwise we fallback to version bundled with dynarmic)
|
# Use system tsl::robin_map if available (otherwise we fallback to version bundled with dynarmic)
|
||||||
|
@ -425,7 +442,8 @@ else()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# Create target for outputting distributable bundles.
|
# Create target for outputting distributable bundles.
|
||||||
if (CITRA_ENABLE_BUNDLE_TARGET)
|
# Not supported for mobile platforms as distributables are built differently.
|
||||||
|
if (NOT ANDROID AND NOT IOS)
|
||||||
include(BundleTarget)
|
include(BundleTarget)
|
||||||
if (ENABLE_SDL2_FRONTEND)
|
if (ENABLE_SDL2_FRONTEND)
|
||||||
bundle_target(citra)
|
bundle_target(citra)
|
||||||
|
|
|
@ -2,37 +2,104 @@
|
||||||
if (BUNDLE_TARGET_EXECUTE)
|
if (BUNDLE_TARGET_EXECUTE)
|
||||||
# --- Bundling method logic ---
|
# --- Bundling method logic ---
|
||||||
|
|
||||||
|
function(symlink_safe_copy from to)
|
||||||
|
if (WIN32)
|
||||||
|
# Use cmake copy for maximum compatibility.
|
||||||
|
execute_process(COMMAND ${CMAKE_COMMAND} -E copy "${from}" "${to}"
|
||||||
|
RESULT_VARIABLE cp_result)
|
||||||
|
else()
|
||||||
|
# Use native copy to turn symlinks into normal files.
|
||||||
|
execute_process(COMMAND cp -L "${from}" "${to}"
|
||||||
|
RESULT_VARIABLE cp_result)
|
||||||
|
endif()
|
||||||
|
if (NOT cp_result EQUAL "0")
|
||||||
|
message(FATAL_ERROR "cp \"${from}\" \"${to}\" failed: ${cp_result}")
|
||||||
|
endif()
|
||||||
|
endfunction()
|
||||||
|
|
||||||
function(bundle_qt executable_path)
|
function(bundle_qt executable_path)
|
||||||
if (WIN32)
|
if (WIN32)
|
||||||
|
# Perform standalone bundling first to copy over all used libraries, as windeployqt does not do this.
|
||||||
|
bundle_standalone("${executable_path}" "${EXECUTABLE_PATH}" "${BUNDLE_LIBRARY_PATHS}")
|
||||||
|
|
||||||
get_filename_component(executable_parent_dir "${executable_path}" DIRECTORY)
|
get_filename_component(executable_parent_dir "${executable_path}" DIRECTORY)
|
||||||
find_program(windeployqt_executable windeployqt6)
|
|
||||||
|
|
||||||
# Create a qt.conf file pointing to the app directory.
|
# Create a qt.conf file pointing to the app directory.
|
||||||
# This ensures Qt can find its plugins.
|
# This ensures Qt can find its plugins.
|
||||||
file(WRITE "${executable_parent_dir}/qt.conf" "[Paths]\nprefix = .")
|
file(WRITE "${executable_parent_dir}/qt.conf" "[Paths]\nPrefix = .")
|
||||||
|
|
||||||
|
find_program(windeployqt_executable windeployqt6 PATHS "${QT_HOST_PATH}/bin")
|
||||||
|
find_program(qtpaths_executable qtpaths6 PATHS "${QT_HOST_PATH}/bin")
|
||||||
|
|
||||||
|
# TODO: Hack around windeployqt's poor cross-compilation support by
|
||||||
|
# TODO: making a local copy with a prefix pointing to the target Qt.
|
||||||
|
if (NOT "${QT_HOST_PATH}" STREQUAL "${QT_TARGET_PATH}")
|
||||||
|
set(windeployqt_dir "${BINARY_PATH}/windeployqt_copy")
|
||||||
|
file(MAKE_DIRECTORY "${windeployqt_dir}")
|
||||||
|
symlink_safe_copy("${windeployqt_executable}" "${windeployqt_dir}/windeployqt.exe")
|
||||||
|
symlink_safe_copy("${qtpaths_executable}" "${windeployqt_dir}/qtpaths.exe")
|
||||||
|
symlink_safe_copy("${QT_HOST_PATH}/bin/Qt6Core.dll" "${windeployqt_dir}")
|
||||||
|
|
||||||
|
if (EXISTS "${QT_TARGET_PATH}/share")
|
||||||
|
# Unix-style Qt; we need to wire up the paths manually.
|
||||||
|
file(WRITE "${windeployqt_dir}/qt.conf" "\
|
||||||
|
[Paths]\n
|
||||||
|
Prefix = ${QT_TARGET_PATH}\n \
|
||||||
|
ArchData = ${QT_TARGET_PATH}/share/qt6\n \
|
||||||
|
Binaries = ${QT_TARGET_PATH}/bin\n \
|
||||||
|
Data = ${QT_TARGET_PATH}/share/qt6\n \
|
||||||
|
Documentation = ${QT_TARGET_PATH}/share/qt6/doc\n \
|
||||||
|
Headers = ${QT_TARGET_PATH}/include/qt6\n \
|
||||||
|
Libraries = ${QT_TARGET_PATH}/lib\n \
|
||||||
|
LibraryExecutables = ${QT_TARGET_PATH}/share/qt6/bin\n \
|
||||||
|
Plugins = ${QT_TARGET_PATH}/share/qt6/plugins\n \
|
||||||
|
QmlImports = ${QT_TARGET_PATH}/share/qt6/qml\n \
|
||||||
|
Translations = ${QT_TARGET_PATH}/share/qt6/translations\n \
|
||||||
|
")
|
||||||
|
else()
|
||||||
|
# Windows-style Qt; the defaults should suffice.
|
||||||
|
file(WRITE "${windeployqt_dir}/qt.conf" "[Paths]\nPrefix = ${QT_TARGET_PATH}")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
set(windeployqt_executable "${windeployqt_dir}/windeployqt.exe")
|
||||||
|
set(qtpaths_executable "${windeployqt_dir}/qtpaths.exe")
|
||||||
|
endif()
|
||||||
|
|
||||||
message(STATUS "Executing windeployqt for executable ${executable_path}")
|
message(STATUS "Executing windeployqt for executable ${executable_path}")
|
||||||
execute_process(COMMAND "${windeployqt_executable}" "${executable_path}"
|
execute_process(COMMAND "${windeployqt_executable}" "${executable_path}"
|
||||||
|
--qtpaths "${qtpaths_executable}"
|
||||||
--no-compiler-runtime --no-system-d3d-compiler --no-opengl-sw --no-translations
|
--no-compiler-runtime --no-system-d3d-compiler --no-opengl-sw --no-translations
|
||||||
--plugindir "${executable_parent_dir}/plugins")
|
--plugindir "${executable_parent_dir}/plugins"
|
||||||
|
RESULT_VARIABLE windeployqt_result)
|
||||||
|
if (NOT windeployqt_result EQUAL "0")
|
||||||
|
message(FATAL_ERROR "windeployqt failed: ${windeployqt_result}")
|
||||||
|
endif()
|
||||||
|
|
||||||
# Remove the FFmpeg multimedia plugin as we don't include FFmpeg.
|
# Remove the FFmpeg multimedia plugin as we don't include FFmpeg.
|
||||||
# We want to use the Windows media plugin instead, which is also included.
|
# We want to use the Windows media plugin instead, which is also included.
|
||||||
file(REMOVE "${executable_parent_dir}/plugins/multimedia/ffmpegmediaplugin.dll")
|
file(REMOVE "${executable_parent_dir}/plugins/multimedia/ffmpegmediaplugin.dll")
|
||||||
elseif (APPLE)
|
elseif (APPLE)
|
||||||
get_filename_component(executable_name "${executable_path}" NAME_WE)
|
get_filename_component(executable_name "${executable_path}" NAME_WE)
|
||||||
find_program(MACDEPLOYQT_EXECUTABLE macdeployqt6)
|
find_program(macdeployqt_executable macdeployqt6 PATHS "${QT_HOST_PATH}/bin")
|
||||||
|
|
||||||
message(STATUS "Executing macdeployqt for executable ${executable_path}")
|
message(STATUS "Executing macdeployqt at \"${macdeployqt_executable}\" for executable \"${executable_path}\"")
|
||||||
execute_process(
|
execute_process(
|
||||||
COMMAND "${MACDEPLOYQT_EXECUTABLE}"
|
COMMAND "${macdeployqt_executable}"
|
||||||
"${executable_path}"
|
"${executable_path}"
|
||||||
"-executable=${executable_path}/Contents/MacOS/${executable_name}"
|
"-executable=${executable_path}/Contents/MacOS/${executable_name}"
|
||||||
-always-overwrite)
|
-always-overwrite
|
||||||
|
RESULT_VARIABLE macdeployqt_result)
|
||||||
|
if (NOT macdeployqt_result EQUAL "0")
|
||||||
|
message(FATAL_ERROR "macdeployqt failed: ${macdeployqt_result}")
|
||||||
|
endif()
|
||||||
|
|
||||||
# Bundling libraries can rewrite path information and break code signatures of system libraries.
|
# Bundling libraries can rewrite path information and break code signatures of system libraries.
|
||||||
# Perform an ad-hoc re-signing on the whole app bundle to fix this.
|
# Perform an ad-hoc re-signing on the whole app bundle to fix this.
|
||||||
execute_process(COMMAND codesign --deep -fs - "${executable_path}")
|
execute_process(COMMAND codesign --deep -fs - "${executable_path}"
|
||||||
|
RESULT_VARIABLE codesign_result)
|
||||||
|
if (NOT codesign_result EQUAL "0")
|
||||||
|
message(FATAL_ERROR "codesign failed: ${codesign_result}")
|
||||||
|
endif()
|
||||||
else()
|
else()
|
||||||
message(FATAL_ERROR "Unsupported OS for Qt bundling.")
|
message(FATAL_ERROR "Unsupported OS for Qt bundling.")
|
||||||
endif()
|
endif()
|
||||||
|
@ -44,9 +111,9 @@ if (BUNDLE_TARGET_EXECUTE)
|
||||||
|
|
||||||
if (enable_qt)
|
if (enable_qt)
|
||||||
# Find qmake to make sure the plugin uses the right version of Qt.
|
# Find qmake to make sure the plugin uses the right version of Qt.
|
||||||
find_program(QMAKE_EXECUTABLE qmake6)
|
find_program(qmake_executable qmake6 PATHS "${QT_HOST_PATH}/bin")
|
||||||
|
|
||||||
set(extra_linuxdeploy_env "QMAKE=${QMAKE_EXECUTABLE}")
|
set(extra_linuxdeploy_env "QMAKE=${qmake_executable}")
|
||||||
set(extra_linuxdeploy_args --plugin qt)
|
set(extra_linuxdeploy_args --plugin qt)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
@ -59,7 +126,11 @@ if (BUNDLE_TARGET_EXECUTE)
|
||||||
--executable "${executable_path}"
|
--executable "${executable_path}"
|
||||||
--icon-file "${source_path}/dist/citra.svg"
|
--icon-file "${source_path}/dist/citra.svg"
|
||||||
--desktop-file "${source_path}/dist/${executable_name}.desktop"
|
--desktop-file "${source_path}/dist/${executable_name}.desktop"
|
||||||
--appdir "${appdir_path}")
|
--appdir "${appdir_path}"
|
||||||
|
RESULT_VARIABLE linuxdeploy_appdir_result)
|
||||||
|
if (NOT linuxdeploy_appdir_result EQUAL "0")
|
||||||
|
message(FATAL_ERROR "linuxdeploy failed to create AppDir: ${linuxdeploy_appdir_result}")
|
||||||
|
endif()
|
||||||
|
|
||||||
if (enable_qt)
|
if (enable_qt)
|
||||||
set(qt_hook_file "${appdir_path}/apprun-hooks/linuxdeploy-plugin-qt-hook.sh")
|
set(qt_hook_file "${appdir_path}/apprun-hooks/linuxdeploy-plugin-qt-hook.sh")
|
||||||
|
@ -82,7 +153,11 @@ if (BUNDLE_TARGET_EXECUTE)
|
||||||
"OUTPUT=${bundle_dir}/${executable_name}.AppImage"
|
"OUTPUT=${bundle_dir}/${executable_name}.AppImage"
|
||||||
"${linuxdeploy_executable}"
|
"${linuxdeploy_executable}"
|
||||||
--output appimage
|
--output appimage
|
||||||
--appdir "${appdir_path}")
|
--appdir "${appdir_path}"
|
||||||
|
RESULT_VARIABLE linuxdeploy_appimage_result)
|
||||||
|
if (NOT linuxdeploy_appimage_result EQUAL "0")
|
||||||
|
message(FATAL_ERROR "linuxdeploy failed to create AppImage: ${linuxdeploy_appimage_result}")
|
||||||
|
endif()
|
||||||
endfunction()
|
endfunction()
|
||||||
|
|
||||||
function(bundle_standalone executable_path original_executable_path bundle_library_paths)
|
function(bundle_standalone executable_path original_executable_path bundle_library_paths)
|
||||||
|
@ -109,16 +184,23 @@ if (BUNDLE_TARGET_EXECUTE)
|
||||||
file(MAKE_DIRECTORY ${lib_dir})
|
file(MAKE_DIRECTORY ${lib_dir})
|
||||||
foreach (lib_file IN LISTS resolved_deps)
|
foreach (lib_file IN LISTS resolved_deps)
|
||||||
message(STATUS "Bundling library ${lib_file}")
|
message(STATUS "Bundling library ${lib_file}")
|
||||||
# Use native copy to turn symlinks into normal files.
|
symlink_safe_copy("${lib_file}" "${lib_dir}")
|
||||||
execute_process(COMMAND cp -L "${lib_file}" "${lib_dir}")
|
|
||||||
endforeach()
|
endforeach()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# Add libs directory to executable rpath where applicable.
|
# Add libs directory to executable rpath where applicable.
|
||||||
if (APPLE)
|
if (APPLE)
|
||||||
execute_process(COMMAND install_name_tool -add_rpath "@loader_path/libs" "${executable_path}")
|
execute_process(COMMAND install_name_tool -add_rpath "@loader_path/libs" "${executable_path}"
|
||||||
|
RESULT_VARIABLE install_name_tool_result)
|
||||||
|
if (NOT install_name_tool_result EQUAL "0")
|
||||||
|
message(FATAL_ERROR "install_name_tool failed: ${install_name_tool_result}")
|
||||||
|
endif()
|
||||||
elseif (UNIX)
|
elseif (UNIX)
|
||||||
execute_process(COMMAND patchelf --set-rpath '$ORIGIN/../libs' "${executable_path}")
|
execute_process(COMMAND patchelf --set-rpath '$ORIGIN/../libs' "${executable_path}"
|
||||||
|
RESULT_VARIABLE patchelf_result)
|
||||||
|
if (NOT patchelf_result EQUAL "0")
|
||||||
|
message(FATAL_ERROR "patchelf failed: ${patchelf_result}")
|
||||||
|
endif()
|
||||||
endif()
|
endif()
|
||||||
endfunction()
|
endfunction()
|
||||||
|
|
||||||
|
@ -127,7 +209,7 @@ if (BUNDLE_TARGET_EXECUTE)
|
||||||
set(bundle_dir ${BINARY_PATH}/bundle)
|
set(bundle_dir ${BINARY_PATH}/bundle)
|
||||||
|
|
||||||
# On Linux, always bundle an AppImage.
|
# On Linux, always bundle an AppImage.
|
||||||
if (DEFINED LINUXDEPLOY)
|
if (CMAKE_HOST_SYSTEM_NAME STREQUAL "Linux")
|
||||||
if (IN_PLACE)
|
if (IN_PLACE)
|
||||||
message(FATAL_ERROR "Cannot bundle for Linux in-place.")
|
message(FATAL_ERROR "Cannot bundle for Linux in-place.")
|
||||||
endif()
|
endif()
|
||||||
|
@ -146,14 +228,12 @@ if (BUNDLE_TARGET_EXECUTE)
|
||||||
|
|
||||||
if (BUNDLE_QT)
|
if (BUNDLE_QT)
|
||||||
bundle_qt("${bundled_executable_path}")
|
bundle_qt("${bundled_executable_path}")
|
||||||
endif()
|
else()
|
||||||
|
|
||||||
if (WIN32 OR NOT BUNDLE_QT)
|
|
||||||
bundle_standalone("${bundled_executable_path}" "${EXECUTABLE_PATH}" "${BUNDLE_LIBRARY_PATHS}")
|
bundle_standalone("${bundled_executable_path}" "${EXECUTABLE_PATH}" "${BUNDLE_LIBRARY_PATHS}")
|
||||||
endif()
|
endif()
|
||||||
endif()
|
endif()
|
||||||
else()
|
elseif (BUNDLE_TARGET_DOWNLOAD_LINUXDEPLOY)
|
||||||
# --- Bundling target creation logic ---
|
# --- linuxdeploy download logic ---
|
||||||
|
|
||||||
# Downloads and extracts a linuxdeploy component.
|
# Downloads and extracts a linuxdeploy component.
|
||||||
function(download_linuxdeploy_component base_dir name executable_name)
|
function(download_linuxdeploy_component base_dir name executable_name)
|
||||||
|
@ -161,7 +241,7 @@ else()
|
||||||
if (NOT EXISTS "${executable_file}")
|
if (NOT EXISTS "${executable_file}")
|
||||||
message(STATUS "Downloading ${executable_name}")
|
message(STATUS "Downloading ${executable_name}")
|
||||||
file(DOWNLOAD
|
file(DOWNLOAD
|
||||||
"https://github.com/linuxdeploy/${name}/releases/download/continuous/${executable_name}"
|
"https://github.com/${name}/releases/download/continuous/${executable_name}"
|
||||||
"${executable_file}" SHOW_PROGRESS)
|
"${executable_file}" SHOW_PROGRESS)
|
||||||
file(CHMOD "${executable_file}" PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE)
|
file(CHMOD "${executable_file}" PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE)
|
||||||
|
|
||||||
|
@ -170,7 +250,11 @@ else()
|
||||||
message(STATUS "Extracting ${executable_name}")
|
message(STATUS "Extracting ${executable_name}")
|
||||||
execute_process(
|
execute_process(
|
||||||
COMMAND "${executable_file}" --appimage-extract
|
COMMAND "${executable_file}" --appimage-extract
|
||||||
WORKING_DIRECTORY "${base_dir}")
|
WORKING_DIRECTORY "${base_dir}"
|
||||||
|
RESULT_VARIABLE extract_result)
|
||||||
|
if (NOT extract_result EQUAL "0")
|
||||||
|
message(FATAL_ERROR "AppImage extract failed: ${extract_result}")
|
||||||
|
endif()
|
||||||
else()
|
else()
|
||||||
message(STATUS "Copying ${executable_name}")
|
message(STATUS "Copying ${executable_name}")
|
||||||
file(COPY "${executable_file}" DESTINATION "${base_dir}/squashfs-root/usr/bin/")
|
file(COPY "${executable_file}" DESTINATION "${base_dir}/squashfs-root/usr/bin/")
|
||||||
|
@ -178,89 +262,102 @@ else()
|
||||||
endif()
|
endif()
|
||||||
endfunction()
|
endfunction()
|
||||||
|
|
||||||
|
# Download plugins first so they don't overwrite linuxdeploy's AppRun file.
|
||||||
|
download_linuxdeploy_component("${LINUXDEPLOY_PATH}" "linuxdeploy/linuxdeploy-plugin-qt" "linuxdeploy-plugin-qt-${LINUXDEPLOY_ARCH}.AppImage")
|
||||||
|
download_linuxdeploy_component("${LINUXDEPLOY_PATH}" "darealshinji/linuxdeploy-plugin-checkrt" "linuxdeploy-plugin-checkrt.sh")
|
||||||
|
download_linuxdeploy_component("${LINUXDEPLOY_PATH}" "linuxdeploy/linuxdeploy" "linuxdeploy-${LINUXDEPLOY_ARCH}.AppImage")
|
||||||
|
else()
|
||||||
|
# --- Bundling target creation logic ---
|
||||||
|
|
||||||
|
# Creates the base bundle target with common files and pre-bundle steps.
|
||||||
|
function(create_base_bundle_target)
|
||||||
|
message(STATUS "Creating base bundle target")
|
||||||
|
|
||||||
|
add_custom_target(bundle)
|
||||||
|
add_custom_command(
|
||||||
|
TARGET bundle
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E make_directory "${CMAKE_BINARY_DIR}/bundle/")
|
||||||
|
add_custom_command(
|
||||||
|
TARGET bundle
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E make_directory "${CMAKE_BINARY_DIR}/bundle/dist/")
|
||||||
|
add_custom_command(
|
||||||
|
TARGET bundle
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_SOURCE_DIR}/dist/icon.png" "${CMAKE_BINARY_DIR}/bundle/dist/citra.png")
|
||||||
|
add_custom_command(
|
||||||
|
TARGET bundle
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_SOURCE_DIR}/license.txt" "${CMAKE_BINARY_DIR}/bundle/")
|
||||||
|
add_custom_command(
|
||||||
|
TARGET bundle
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_SOURCE_DIR}/README.md" "${CMAKE_BINARY_DIR}/bundle/")
|
||||||
|
add_custom_command(
|
||||||
|
TARGET bundle
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E copy_directory "${CMAKE_SOURCE_DIR}/dist/scripting" "${CMAKE_BINARY_DIR}/bundle/scripting")
|
||||||
|
|
||||||
|
# On Linux, add a command to prepare linuxdeploy and any required plugins before any bundling occurs.
|
||||||
|
if (CMAKE_HOST_SYSTEM_NAME STREQUAL "Linux")
|
||||||
|
add_custom_command(
|
||||||
|
TARGET bundle
|
||||||
|
COMMAND ${CMAKE_COMMAND}
|
||||||
|
"-DBUNDLE_TARGET_DOWNLOAD_LINUXDEPLOY=1"
|
||||||
|
"-DLINUXDEPLOY_PATH=${CMAKE_BINARY_DIR}/externals/linuxdeploy"
|
||||||
|
"-DLINUXDEPLOY_ARCH=${CMAKE_HOST_SYSTEM_PROCESSOR}"
|
||||||
|
-P "${CMAKE_SOURCE_DIR}/CMakeModules/BundleTarget.cmake"
|
||||||
|
WORKING_DIRECTORY "${CMAKE_BINARY_DIR}")
|
||||||
|
endif()
|
||||||
|
endfunction()
|
||||||
|
|
||||||
# Adds a target to the bundle target, packing in required libraries.
|
# Adds a target to the bundle target, packing in required libraries.
|
||||||
# If in_place is true, the bundling will be done in-place as part of the specified target.
|
# If in_place is true, the bundling will be done in-place as part of the specified target.
|
||||||
function(bundle_target_internal target_name in_place)
|
function(bundle_target_internal target_name in_place)
|
||||||
# Create base bundle target if it does not exist.
|
# Create base bundle target if it does not exist.
|
||||||
if (NOT in_place AND NOT TARGET bundle)
|
if (NOT in_place AND NOT TARGET bundle)
|
||||||
message(STATUS "Creating base bundle target")
|
create_base_bundle_target()
|
||||||
|
|
||||||
add_custom_target(bundle)
|
|
||||||
add_custom_command(
|
|
||||||
TARGET bundle
|
|
||||||
COMMAND ${CMAKE_COMMAND} -E make_directory "${CMAKE_BINARY_DIR}/bundle/")
|
|
||||||
add_custom_command(
|
|
||||||
TARGET bundle
|
|
||||||
COMMAND ${CMAKE_COMMAND} -E make_directory "${CMAKE_BINARY_DIR}/bundle/dist/")
|
|
||||||
add_custom_command(
|
|
||||||
TARGET bundle
|
|
||||||
COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_SOURCE_DIR}/dist/icon.png" "${CMAKE_BINARY_DIR}/bundle/dist/citra.png")
|
|
||||||
add_custom_command(
|
|
||||||
TARGET bundle
|
|
||||||
COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_SOURCE_DIR}/license.txt" "${CMAKE_BINARY_DIR}/bundle/")
|
|
||||||
add_custom_command(
|
|
||||||
TARGET bundle
|
|
||||||
COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_SOURCE_DIR}/README.md" "${CMAKE_BINARY_DIR}/bundle/")
|
|
||||||
add_custom_command(
|
|
||||||
TARGET bundle
|
|
||||||
COMMAND ${CMAKE_COMMAND} -E copy_directory "${CMAKE_SOURCE_DIR}/dist/scripting" "${CMAKE_BINARY_DIR}/bundle/scripting")
|
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
set(BUNDLE_EXECUTABLE_PATH "$<TARGET_FILE:${target_name}>")
|
set(bundle_executable_path "$<TARGET_FILE:${target_name}>")
|
||||||
if (target_name MATCHES ".*qt")
|
if (target_name MATCHES ".*qt")
|
||||||
set(BUNDLE_QT ON)
|
set(bundle_qt ON)
|
||||||
if (APPLE)
|
if (APPLE)
|
||||||
# For Qt targets on Apple, expect an app bundle.
|
# For Qt targets on Apple, expect an app bundle.
|
||||||
set(BUNDLE_EXECUTABLE_PATH "$<TARGET_BUNDLE_DIR:${target_name}>")
|
set(bundle_executable_path "$<TARGET_BUNDLE_DIR:${target_name}>")
|
||||||
endif()
|
endif()
|
||||||
else()
|
else()
|
||||||
set(BUNDLE_QT OFF)
|
set(bundle_qt OFF)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# Build a list of library search paths from prefix paths.
|
# Build a list of library search paths from prefix paths.
|
||||||
foreach(prefix_path IN LISTS CMAKE_PREFIX_PATH CMAKE_SYSTEM_PREFIX_PATH)
|
foreach(prefix_path IN LISTS CMAKE_FIND_ROOT_PATH CMAKE_PREFIX_PATH CMAKE_SYSTEM_PREFIX_PATH)
|
||||||
if (WIN32)
|
if (WIN32)
|
||||||
list(APPEND BUNDLE_LIBRARY_PATHS "${prefix_path}/bin")
|
list(APPEND bundle_library_paths "${prefix_path}/bin")
|
||||||
endif()
|
endif()
|
||||||
list(APPEND BUNDLE_LIBRARY_PATHS "${prefix_path}/lib")
|
list(APPEND bundle_library_paths "${prefix_path}/lib")
|
||||||
endforeach()
|
endforeach()
|
||||||
foreach(library_path IN LISTS CMAKE_SYSTEM_LIBRARY_PATH)
|
foreach(library_path IN LISTS CMAKE_SYSTEM_LIBRARY_PATH)
|
||||||
list(APPEND BUNDLE_LIBRARY_PATHS "${library_path}")
|
list(APPEND bundle_library_paths "${library_path}")
|
||||||
endforeach()
|
endforeach()
|
||||||
|
|
||||||
# On Linux, prepare linuxdeploy and any required plugins.
|
|
||||||
if (CMAKE_SYSTEM_NAME STREQUAL "Linux")
|
|
||||||
set(LINUXDEPLOY_BASE "${CMAKE_BINARY_DIR}/externals/linuxdeploy")
|
|
||||||
|
|
||||||
# Download plugins first so they don't overwrite linuxdeploy's AppRun file.
|
|
||||||
download_linuxdeploy_component("${LINUXDEPLOY_BASE}" "linuxdeploy-plugin-qt" "linuxdeploy-plugin-qt-x86_64.AppImage")
|
|
||||||
download_linuxdeploy_component("${LINUXDEPLOY_BASE}" "linuxdeploy-plugin-checkrt" "linuxdeploy-plugin-checkrt-x86_64.sh")
|
|
||||||
download_linuxdeploy_component("${LINUXDEPLOY_BASE}" "linuxdeploy" "linuxdeploy-x86_64.AppImage")
|
|
||||||
|
|
||||||
set(EXTRA_BUNDLE_ARGS "-DLINUXDEPLOY=${LINUXDEPLOY_BASE}/squashfs-root/AppRun")
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if (in_place)
|
if (in_place)
|
||||||
message(STATUS "Adding in-place bundling to ${target_name}")
|
message(STATUS "Adding in-place bundling to ${target_name}")
|
||||||
set(DEST_TARGET ${target_name})
|
set(dest_target ${target_name})
|
||||||
else()
|
else()
|
||||||
message(STATUS "Adding ${target_name} to bundle target")
|
message(STATUS "Adding ${target_name} to bundle target")
|
||||||
set(DEST_TARGET bundle)
|
set(dest_target bundle)
|
||||||
add_dependencies(bundle ${target_name})
|
add_dependencies(bundle ${target_name})
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
add_custom_command(TARGET ${DEST_TARGET} POST_BUILD
|
add_custom_command(TARGET ${dest_target} POST_BUILD
|
||||||
COMMAND ${CMAKE_COMMAND}
|
COMMAND ${CMAKE_COMMAND}
|
||||||
"-DCMAKE_PREFIX_PATH=\"${CMAKE_PREFIX_PATH}\""
|
"-DQT_HOST_PATH=\"${QT_HOST_PATH}\""
|
||||||
|
"-DQT_TARGET_PATH=\"${QT_TARGET_PATH}\""
|
||||||
"-DBUNDLE_TARGET_EXECUTE=1"
|
"-DBUNDLE_TARGET_EXECUTE=1"
|
||||||
"-DTARGET=${target_name}"
|
"-DTARGET=${target_name}"
|
||||||
"-DSOURCE_PATH=${CMAKE_SOURCE_DIR}"
|
"-DSOURCE_PATH=${CMAKE_SOURCE_DIR}"
|
||||||
"-DBINARY_PATH=${CMAKE_BINARY_DIR}"
|
"-DBINARY_PATH=${CMAKE_BINARY_DIR}"
|
||||||
"-DEXECUTABLE_PATH=${BUNDLE_EXECUTABLE_PATH}"
|
"-DEXECUTABLE_PATH=${bundle_executable_path}"
|
||||||
"-DBUNDLE_LIBRARY_PATHS=\"${BUNDLE_LIBRARY_PATHS}\""
|
"-DBUNDLE_LIBRARY_PATHS=\"${bundle_library_paths}\""
|
||||||
"-DBUNDLE_QT=${BUNDLE_QT}"
|
"-DBUNDLE_QT=${bundle_qt}"
|
||||||
"-DIN_PLACE=${in_place}"
|
"-DIN_PLACE=${in_place}"
|
||||||
${EXTRA_BUNDLE_ARGS}
|
"-DLINUXDEPLOY=${CMAKE_BINARY_DIR}/externals/linuxdeploy/squashfs-root/AppRun"
|
||||||
-P "${CMAKE_SOURCE_DIR}/CMakeModules/BundleTarget.cmake"
|
-P "${CMAKE_SOURCE_DIR}/CMakeModules/BundleTarget.cmake"
|
||||||
WORKING_DIRECTORY "${CMAKE_BINARY_DIR}")
|
WORKING_DIRECTORY "${CMAKE_BINARY_DIR}")
|
||||||
endfunction()
|
endfunction()
|
||||||
|
|
|
@ -1,21 +1,20 @@
|
||||||
|
|
||||||
set(CURRENT_MODULE_DIR ${CMAKE_CURRENT_LIST_DIR})
|
set(CURRENT_MODULE_DIR ${CMAKE_CURRENT_LIST_DIR})
|
||||||
|
|
||||||
# This function downloads Qt using aqt. The path of the downloaded content will be added to the CMAKE_PREFIX_PATH.
|
# Determines parameters based on the host and target for downloading the right Qt binaries.
|
||||||
# Params:
|
function(determine_qt_parameters target host_out type_out arch_out arch_path_out host_type_out host_arch_out host_arch_path_out)
|
||||||
# target: Qt dependency to install. Specify a version number to download Qt, or "tools_(name)" for a specific build tool.
|
|
||||||
function(download_qt target)
|
|
||||||
if (target MATCHES "tools_.*")
|
if (target MATCHES "tools_.*")
|
||||||
set(DOWNLOAD_QT_TOOL ON)
|
set(tool ON)
|
||||||
else()
|
else()
|
||||||
set(DOWNLOAD_QT_TOOL OFF)
|
set(tool OFF)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# Determine installation parameters for OS, architecture, and compiler
|
# Determine installation parameters for OS, architecture, and compiler
|
||||||
if (WIN32)
|
if (WIN32)
|
||||||
set(host "windows")
|
set(host "windows")
|
||||||
set(type "desktop")
|
set(type "desktop")
|
||||||
if (NOT DOWNLOAD_QT_TOOL)
|
|
||||||
|
if (NOT tool)
|
||||||
if (MINGW)
|
if (MINGW)
|
||||||
set(arch "win64_mingw")
|
set(arch "win64_mingw")
|
||||||
set(arch_path "mingw_64")
|
set(arch_path "mingw_64")
|
||||||
|
@ -28,21 +27,35 @@ function(download_qt target)
|
||||||
message(FATAL_ERROR "Unsupported bundled Qt architecture. Enable USE_SYSTEM_QT and provide your own.")
|
message(FATAL_ERROR "Unsupported bundled Qt architecture. Enable USE_SYSTEM_QT and provide your own.")
|
||||||
endif()
|
endif()
|
||||||
set(arch "win64_${arch_path}")
|
set(arch "win64_${arch_path}")
|
||||||
|
|
||||||
|
# In case we're cross-compiling, prepare to also fetch the correct host Qt tools.
|
||||||
|
if (CMAKE_HOST_SYSTEM_PROCESSOR STREQUAL "AMD64")
|
||||||
|
set(host_arch_path "msvc2019_64")
|
||||||
|
elseif (CMAKE_HOST_SYSTEM_PROCESSOR STREQUAL "ARM64")
|
||||||
|
# TODO: msvc2019_arm64 doesn't include some of the required tools for some reason,
|
||||||
|
# TODO: so until it does, just use msvc2019_64 under x86_64 emulation.
|
||||||
|
# set(host_arch_path "msvc2019_arm64")
|
||||||
|
set(host_arch_path "msvc2019_64")
|
||||||
|
endif()
|
||||||
|
set(host_arch "win64_${host_arch_path}")
|
||||||
else()
|
else()
|
||||||
message(FATAL_ERROR "Unsupported bundled Qt toolchain. Enable USE_SYSTEM_QT and provide your own.")
|
message(FATAL_ERROR "Unsupported bundled Qt toolchain. Enable USE_SYSTEM_QT and provide your own.")
|
||||||
endif()
|
endif()
|
||||||
endif()
|
endif()
|
||||||
elseif (APPLE)
|
elseif (APPLE)
|
||||||
set(host "mac")
|
set(host "mac")
|
||||||
if (IOS AND NOT DOWNLOAD_QT_TOOL)
|
set(type "desktop")
|
||||||
|
set(arch "clang_64")
|
||||||
|
set(arch_path "macos")
|
||||||
|
|
||||||
|
if (IOS AND NOT tool)
|
||||||
|
set(host_type "${type}")
|
||||||
|
set(host_arch "${arch}")
|
||||||
|
set(host_arch_path "${arch_path}")
|
||||||
|
|
||||||
set(type "ios")
|
set(type "ios")
|
||||||
set(arch "ios")
|
set(arch "ios")
|
||||||
set(arch_path "ios")
|
set(arch_path "ios")
|
||||||
set(host_arch_path "macos")
|
|
||||||
else()
|
|
||||||
set(type "desktop")
|
|
||||||
set(arch "clang_64")
|
|
||||||
set(arch_path "macos")
|
|
||||||
endif()
|
endif()
|
||||||
else()
|
else()
|
||||||
set(host "linux")
|
set(host "linux")
|
||||||
|
@ -51,38 +64,64 @@ function(download_qt target)
|
||||||
set(arch_path "linux")
|
set(arch_path "linux")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
get_external_prefix(qt base_path)
|
set(${host_out} "${host}" PARENT_SCOPE)
|
||||||
file(MAKE_DIRECTORY "${base_path}")
|
set(${type_out} "${type}" PARENT_SCOPE)
|
||||||
|
set(${arch_out} "${arch}" PARENT_SCOPE)
|
||||||
|
set(${arch_path_out} "${arch_path}" PARENT_SCOPE)
|
||||||
|
if (DEFINED host_type)
|
||||||
|
set(${host_type_out} "${host_type}" PARENT_SCOPE)
|
||||||
|
else()
|
||||||
|
set(${host_type_out} "${type}" PARENT_SCOPE)
|
||||||
|
endif()
|
||||||
|
if (DEFINED host_arch)
|
||||||
|
set(${host_arch_out} "${host_arch}" PARENT_SCOPE)
|
||||||
|
else()
|
||||||
|
set(${host_arch_out} "${arch}" PARENT_SCOPE)
|
||||||
|
endif()
|
||||||
|
if (DEFINED host_arch_path)
|
||||||
|
set(${host_arch_path_out} "${host_arch_path}" PARENT_SCOPE)
|
||||||
|
else()
|
||||||
|
set(${host_arch_path_out} "${arch_path}" PARENT_SCOPE)
|
||||||
|
endif()
|
||||||
|
endfunction()
|
||||||
|
|
||||||
|
# Download Qt binaries for a specifc configuration.
|
||||||
|
function(download_qt_configuration prefix_out target host type arch arch_path base_path)
|
||||||
|
if (target MATCHES "tools_.*")
|
||||||
|
set(tool ON)
|
||||||
|
else()
|
||||||
|
set(tool OFF)
|
||||||
|
endif()
|
||||||
|
|
||||||
set(install_args -c "${CURRENT_MODULE_DIR}/aqt_config.ini")
|
set(install_args -c "${CURRENT_MODULE_DIR}/aqt_config.ini")
|
||||||
if (DOWNLOAD_QT_TOOL)
|
if (tool)
|
||||||
set(prefix "${base_path}/Tools")
|
set(prefix "${base_path}/Tools")
|
||||||
set(install_args ${install_args} install-tool --outputdir ${base_path} ${host} desktop ${target})
|
set(install_args ${install_args} install-tool --outputdir ${base_path} ${host} desktop ${target})
|
||||||
else()
|
else()
|
||||||
set(prefix "${base_path}/${target}/${arch_path}")
|
set(prefix "${base_path}/${target}/${arch_path}")
|
||||||
if (host_arch_path)
|
set(install_args ${install_args} install-qt --outputdir ${base_path} ${host} ${type} ${target} ${arch}
|
||||||
set(host_flag "--autodesktop")
|
-m qtmultimedia --archives qttranslations qttools qtsvg qtbase)
|
||||||
set(host_prefix "${base_path}/${target}/${host_arch_path}")
|
|
||||||
endif()
|
|
||||||
set(install_args ${install_args} install-qt --outputdir ${base_path} ${host} ${type} ${target} ${arch} ${host_flag}
|
|
||||||
-m qtmultimedia --archives qttranslations qttools qtsvg qtbase)
|
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if (NOT EXISTS "${prefix}")
|
if (NOT EXISTS "${prefix}")
|
||||||
message(STATUS "Downloading binaries for Qt...")
|
message(STATUS "Downloading Qt binaries for ${target}:${host}:${type}:${arch}:${arch_path}")
|
||||||
set(AQT_PREBUILD_BASE_URL "https://github.com/miurahr/aqtinstall/releases/download/v3.1.9")
|
set(AQT_PREBUILD_BASE_URL "https://github.com/miurahr/aqtinstall/releases/download/v3.1.9")
|
||||||
if (WIN32)
|
if (WIN32)
|
||||||
set(aqt_path "${base_path}/aqt.exe")
|
set(aqt_path "${base_path}/aqt.exe")
|
||||||
file(DOWNLOAD
|
if (NOT EXISTS "${aqt_path}")
|
||||||
${AQT_PREBUILD_BASE_URL}/aqt.exe
|
file(DOWNLOAD
|
||||||
${aqt_path} SHOW_PROGRESS)
|
${AQT_PREBUILD_BASE_URL}/aqt.exe
|
||||||
|
${aqt_path} SHOW_PROGRESS)
|
||||||
|
endif()
|
||||||
execute_process(COMMAND ${aqt_path} ${install_args}
|
execute_process(COMMAND ${aqt_path} ${install_args}
|
||||||
WORKING_DIRECTORY ${base_path})
|
WORKING_DIRECTORY ${base_path})
|
||||||
elseif (APPLE)
|
elseif (APPLE)
|
||||||
set(aqt_path "${base_path}/aqt-macos")
|
set(aqt_path "${base_path}/aqt-macos")
|
||||||
file(DOWNLOAD
|
if (NOT EXISTS "${aqt_path}")
|
||||||
${AQT_PREBUILD_BASE_URL}/aqt-macos
|
file(DOWNLOAD
|
||||||
${aqt_path} SHOW_PROGRESS)
|
${AQT_PREBUILD_BASE_URL}/aqt-macos
|
||||||
|
${aqt_path} SHOW_PROGRESS)
|
||||||
|
endif()
|
||||||
execute_process(COMMAND chmod +x ${aqt_path})
|
execute_process(COMMAND chmod +x ${aqt_path})
|
||||||
execute_process(COMMAND ${aqt_path} ${install_args}
|
execute_process(COMMAND ${aqt_path} ${install_args}
|
||||||
WORKING_DIRECTORY ${base_path})
|
WORKING_DIRECTORY ${base_path})
|
||||||
|
@ -96,18 +135,38 @@ function(download_qt target)
|
||||||
execute_process(COMMAND ${CMAKE_COMMAND} -E env PYTHONPATH=${aqt_install_path} python3 -m aqt ${install_args}
|
execute_process(COMMAND ${CMAKE_COMMAND} -E env PYTHONPATH=${aqt_install_path} python3 -m aqt ${install_args}
|
||||||
WORKING_DIRECTORY ${base_path})
|
WORKING_DIRECTORY ${base_path})
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
message(STATUS "Downloaded Qt binaries for ${target}:${host}:${type}:${arch}:${arch_path} to ${prefix}")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
message(STATUS "Using downloaded Qt binaries at ${prefix}")
|
set(${prefix_out} "${prefix}" PARENT_SCOPE)
|
||||||
|
endfunction()
|
||||||
|
|
||||||
# Add the Qt prefix path so CMake can locate it.
|
# This function downloads Qt using aqt.
|
||||||
|
# The path of the downloaded content will be added to the CMAKE_PREFIX_PATH.
|
||||||
|
# QT_TARGET_PATH is set to the Qt for the compile target platform.
|
||||||
|
# QT_HOST_PATH is set to a host-compatible Qt, for running tools.
|
||||||
|
# Params:
|
||||||
|
# target: Qt dependency to install. Specify a version number to download Qt, or "tools_(name)" for a specific build tool.
|
||||||
|
function(download_qt target)
|
||||||
|
determine_qt_parameters("${target}" host type arch arch_path host_type host_arch host_arch_path)
|
||||||
|
|
||||||
|
get_external_prefix(qt base_path)
|
||||||
|
file(MAKE_DIRECTORY "${base_path}")
|
||||||
|
|
||||||
|
download_qt_configuration(prefix "${target}" "${host}" "${type}" "${arch}" "${arch_path}" "${base_path}")
|
||||||
|
if (DEFINED host_arch_path AND NOT "${host_arch_path}" STREQUAL "${arch_path}")
|
||||||
|
download_qt_configuration(host_prefix "${target}" "${host}" "${host_type}" "${host_arch}" "${host_arch_path}" "${base_path}")
|
||||||
|
else()
|
||||||
|
set(host_prefix "${prefix}")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
set(QT_TARGET_PATH "${prefix}" CACHE STRING "")
|
||||||
|
set(QT_HOST_PATH "${host_prefix}" CACHE STRING "")
|
||||||
|
|
||||||
|
# Add the target Qt prefix path so CMake can locate it.
|
||||||
list(APPEND CMAKE_PREFIX_PATH "${prefix}")
|
list(APPEND CMAKE_PREFIX_PATH "${prefix}")
|
||||||
set(CMAKE_PREFIX_PATH ${CMAKE_PREFIX_PATH} PARENT_SCOPE)
|
set(CMAKE_PREFIX_PATH ${CMAKE_PREFIX_PATH} PARENT_SCOPE)
|
||||||
|
|
||||||
if (DEFINED host_prefix)
|
|
||||||
message(STATUS "Using downloaded host Qt binaries at ${host_prefix}")
|
|
||||||
set(QT_HOST_PATH "${host_prefix}" CACHE STRING "")
|
|
||||||
endif()
|
|
||||||
endfunction()
|
endfunction()
|
||||||
|
|
||||||
function(download_moltenvk)
|
function(download_moltenvk)
|
||||||
|
|
8
dist/dumpkeys/DumpKeys.gm9
vendored
8
dist/dumpkeys/DumpKeys.gm9
vendored
|
@ -287,5 +287,13 @@ dumptxt -p $[OUT] "nfcSecret1Seed=$[NFC_SEED_1]"
|
||||||
dumptxt -p $[OUT] "nfcSecret1HmacKey=$[NFC_HMAC_KEY_1]"
|
dumptxt -p $[OUT] "nfcSecret1HmacKey=$[NFC_HMAC_KEY_1]"
|
||||||
dumptxt -p $[OUT] "nfcIv=$[NFC_IV]"
|
dumptxt -p $[OUT] "nfcIv=$[NFC_IV]"
|
||||||
|
|
||||||
|
# Dump seeddb.bin as well
|
||||||
|
|
||||||
|
set SEEDDB_IN "0:/gm9/out/seeddb.bin"
|
||||||
|
set SEEDDB_OUT "0:/gm9/seeddb.bin"
|
||||||
|
|
||||||
|
sdump -w seeddb.bin
|
||||||
|
cp -w $[SEEDDB_IN] $[SEEDDB_OUT]
|
||||||
|
|
||||||
@Exit
|
@Exit
|
||||||
|
|
||||||
|
|
2
dist/dumpkeys/README.md
vendored
2
dist/dumpkeys/README.md
vendored
|
@ -6,5 +6,5 @@ Usage:
|
||||||
1. Copy "DumpKeys.gm9" into the "gm9/scripts/" directory on your SD card.
|
1. Copy "DumpKeys.gm9" into the "gm9/scripts/" directory on your SD card.
|
||||||
2. Launch GodMode9, press the HOME button, select Scripts, and select "DumpKeys" from the list of scripts that appears.
|
2. Launch GodMode9, press the HOME button, select Scripts, and select "DumpKeys" from the list of scripts that appears.
|
||||||
3. Wait for the script to complete and return you to the GodMode9 main menu.
|
3. Wait for the script to complete and return you to the GodMode9 main menu.
|
||||||
4. Power off your system and copy the "gm9/aes_keys.txt" file off of your SD card into "(Citra directory)/sysdata/".
|
4. Power off your system and copy the "gm9/aes_keys.txt" and "gm9/seeddb.bin" files off of your SD card into "(Citra directory)/sysdata/".
|
||||||
|
|
||||||
|
|
1
dist/languages/.tx/config
vendored
1
dist/languages/.tx/config
vendored
|
@ -11,3 +11,4 @@ type = QT
|
||||||
file_filter = ../../src/android/app/src/main/res/values-<lang>/strings.xml
|
file_filter = ../../src/android/app/src/main/res/values-<lang>/strings.xml
|
||||||
source_file = ../../src/android/app/src/main/res/values/strings.xml
|
source_file = ../../src/android/app/src/main/res/values/strings.xml
|
||||||
type = ANDROID
|
type = ANDROID
|
||||||
|
lang_map = es_ES:es, hu_HU:hu, ru_RU:ru, pt_BR:pt, zh_CN:zh
|
||||||
|
|
854
dist/languages/da_DK.ts
vendored
854
dist/languages/da_DK.ts
vendored
File diff suppressed because it is too large
Load diff
1519
dist/languages/de.ts
vendored
1519
dist/languages/de.ts
vendored
File diff suppressed because it is too large
Load diff
854
dist/languages/el.ts
vendored
854
dist/languages/el.ts
vendored
File diff suppressed because it is too large
Load diff
854
dist/languages/es_ES.ts
vendored
854
dist/languages/es_ES.ts
vendored
File diff suppressed because it is too large
Load diff
854
dist/languages/fi.ts
vendored
854
dist/languages/fi.ts
vendored
File diff suppressed because it is too large
Load diff
6235
dist/languages/fi_FI.ts
vendored
6235
dist/languages/fi_FI.ts
vendored
File diff suppressed because it is too large
Load diff
894
dist/languages/fr.ts
vendored
894
dist/languages/fr.ts
vendored
File diff suppressed because it is too large
Load diff
1482
dist/languages/hu_HU.ts
vendored
1482
dist/languages/hu_HU.ts
vendored
File diff suppressed because it is too large
Load diff
854
dist/languages/id.ts
vendored
854
dist/languages/id.ts
vendored
File diff suppressed because it is too large
Load diff
854
dist/languages/it.ts
vendored
854
dist/languages/it.ts
vendored
File diff suppressed because it is too large
Load diff
854
dist/languages/ja_JP.ts
vendored
854
dist/languages/ja_JP.ts
vendored
File diff suppressed because it is too large
Load diff
854
dist/languages/ko_KR.ts
vendored
854
dist/languages/ko_KR.ts
vendored
File diff suppressed because it is too large
Load diff
854
dist/languages/lt_LT.ts
vendored
854
dist/languages/lt_LT.ts
vendored
File diff suppressed because it is too large
Load diff
854
dist/languages/nb.ts
vendored
854
dist/languages/nb.ts
vendored
File diff suppressed because it is too large
Load diff
854
dist/languages/nl.ts
vendored
854
dist/languages/nl.ts
vendored
File diff suppressed because it is too large
Load diff
854
dist/languages/pl_PL.ts
vendored
854
dist/languages/pl_PL.ts
vendored
File diff suppressed because it is too large
Load diff
967
dist/languages/pt_BR.ts
vendored
967
dist/languages/pt_BR.ts
vendored
File diff suppressed because it is too large
Load diff
854
dist/languages/ro_RO.ts
vendored
854
dist/languages/ro_RO.ts
vendored
File diff suppressed because it is too large
Load diff
854
dist/languages/ru_RU.ts
vendored
854
dist/languages/ru_RU.ts
vendored
File diff suppressed because it is too large
Load diff
854
dist/languages/tr_TR.ts
vendored
854
dist/languages/tr_TR.ts
vendored
File diff suppressed because it is too large
Load diff
854
dist/languages/vi_VN.ts
vendored
854
dist/languages/vi_VN.ts
vendored
File diff suppressed because it is too large
Load diff
858
dist/languages/zh_CN.ts
vendored
858
dist/languages/zh_CN.ts
vendored
File diff suppressed because it is too large
Load diff
854
dist/languages/zh_TW.ts
vendored
854
dist/languages/zh_TW.ts
vendored
File diff suppressed because it is too large
Load diff
40
externals/CMakeLists.txt
vendored
40
externals/CMakeLists.txt
vendored
|
@ -57,6 +57,12 @@ if(USE_SYSTEM_CRYPTOPP)
|
||||||
add_library(cryptopp INTERFACE)
|
add_library(cryptopp INTERFACE)
|
||||||
target_link_libraries(cryptopp INTERFACE cryptopp::cryptopp)
|
target_link_libraries(cryptopp INTERFACE cryptopp::cryptopp)
|
||||||
else()
|
else()
|
||||||
|
if (WIN32 AND NOT MSVC AND "arm64" IN_LIST ARCHITECTURE)
|
||||||
|
# TODO: CryptoPP ARM64 ASM does not seem to support Windows unless compiled with MSVC.
|
||||||
|
# TODO: See https://github.com/weidai11/cryptopp/issues/1260
|
||||||
|
set(CRYPTOPP_DISABLE_ASM ON CACHE BOOL "")
|
||||||
|
endif()
|
||||||
|
|
||||||
set(CRYPTOPP_BUILD_DOCUMENTATION OFF CACHE BOOL "")
|
set(CRYPTOPP_BUILD_DOCUMENTATION OFF CACHE BOOL "")
|
||||||
set(CRYPTOPP_BUILD_TESTING OFF CACHE BOOL "")
|
set(CRYPTOPP_BUILD_TESTING OFF CACHE BOOL "")
|
||||||
set(CRYPTOPP_INSTALL OFF CACHE BOOL "")
|
set(CRYPTOPP_INSTALL OFF CACHE BOOL "")
|
||||||
|
@ -235,6 +241,18 @@ endif()
|
||||||
|
|
||||||
# DiscordRPC
|
# DiscordRPC
|
||||||
if (USE_DISCORD_PRESENCE)
|
if (USE_DISCORD_PRESENCE)
|
||||||
|
# rapidjson used by discord-rpc is old and doesn't correctly detect endianness for some platforms.
|
||||||
|
include(TestBigEndian)
|
||||||
|
test_big_endian(RAPIDJSON_BIG_ENDIAN)
|
||||||
|
if(RAPIDJSON_BIG_ENDIAN)
|
||||||
|
add_compile_definitions(RAPIDJSON_ENDIAN=1)
|
||||||
|
else()
|
||||||
|
add_compile_definitions(RAPIDJSON_ENDIAN=0)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# Apply a dummy CLANG_FORMAT_SUFFIX to disable discord-rpc's unnecessary automatic clang-format.
|
||||||
|
set(CLANG_FORMAT_SUFFIX "dummy")
|
||||||
|
|
||||||
add_subdirectory(discord-rpc EXCLUDE_FROM_ALL)
|
add_subdirectory(discord-rpc EXCLUDE_FROM_ALL)
|
||||||
target_include_directories(discord-rpc INTERFACE ./discord-rpc/include)
|
target_include_directories(discord-rpc INTERFACE ./discord-rpc/include)
|
||||||
endif()
|
endif()
|
||||||
|
@ -276,11 +294,20 @@ endif()
|
||||||
add_library(httplib INTERFACE)
|
add_library(httplib INTERFACE)
|
||||||
if(USE_SYSTEM_CPP_HTTPLIB)
|
if(USE_SYSTEM_CPP_HTTPLIB)
|
||||||
find_package(CppHttp 0.14.1)
|
find_package(CppHttp 0.14.1)
|
||||||
if(CppHttp_FOUND)
|
# Detect if system cpphttplib is a shared library
|
||||||
target_link_libraries(httplib INTERFACE httplib::httplib)
|
# this breaks building as Citra relies on functions that are moved
|
||||||
else()
|
# into the shared object.
|
||||||
message(STATUS "Cpp-httplib not found or not suitable version! Falling back to bundled...")
|
get_target_property(HTTP_LIBS httplib::httplib INTERFACE_LINK_LIBRARIES)
|
||||||
|
if(HTTP_LIBS)
|
||||||
|
message(WARNING "Shared cpp-http (${HTTP_LIBS}) not supported. Falling back to bundled...")
|
||||||
target_include_directories(httplib SYSTEM INTERFACE ./httplib)
|
target_include_directories(httplib SYSTEM INTERFACE ./httplib)
|
||||||
|
else()
|
||||||
|
if(CppHttp_FOUND)
|
||||||
|
target_link_libraries(httplib INTERFACE httplib::httplib)
|
||||||
|
else()
|
||||||
|
message(STATUS "Cpp-httplib not found or not suitable version! Falling back to bundled...")
|
||||||
|
target_include_directories(httplib SYSTEM INTERFACE ./httplib)
|
||||||
|
endif()
|
||||||
endif()
|
endif()
|
||||||
else()
|
else()
|
||||||
target_include_directories(httplib SYSTEM INTERFACE ./httplib)
|
target_include_directories(httplib SYSTEM INTERFACE ./httplib)
|
||||||
|
@ -288,11 +315,6 @@ endif()
|
||||||
target_compile_options(httplib INTERFACE -DCPPHTTPLIB_OPENSSL_SUPPORT)
|
target_compile_options(httplib INTERFACE -DCPPHTTPLIB_OPENSSL_SUPPORT)
|
||||||
target_link_libraries(httplib INTERFACE ${OPENSSL_LIBRARIES})
|
target_link_libraries(httplib INTERFACE ${OPENSSL_LIBRARIES})
|
||||||
|
|
||||||
if(ANDROID)
|
|
||||||
add_subdirectory(android-ifaddrs)
|
|
||||||
target_link_libraries(httplib INTERFACE ifaddrs)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if (UNIX AND NOT APPLE)
|
if (UNIX AND NOT APPLE)
|
||||||
add_subdirectory(gamemode)
|
add_subdirectory(gamemode)
|
||||||
endif()
|
endif()
|
||||||
|
|
8
externals/android-ifaddrs/CMakeLists.txt
vendored
8
externals/android-ifaddrs/CMakeLists.txt
vendored
|
@ -1,8 +0,0 @@
|
||||||
add_library(ifaddrs
|
|
||||||
ifaddrs.c
|
|
||||||
ifaddrs.h
|
|
||||||
)
|
|
||||||
|
|
||||||
create_target_directory_groups(ifaddrs)
|
|
||||||
|
|
||||||
target_include_directories(ifaddrs INTERFACE ${CMAKE_CURRENT_SOURCE_DIR})
|
|
600
externals/android-ifaddrs/ifaddrs.c
vendored
600
externals/android-ifaddrs/ifaddrs.c
vendored
|
@ -1,600 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright (c) 2013, Kenneth MacKay
|
|
||||||
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.
|
|
||||||
|
|
||||||
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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "ifaddrs.h"
|
|
||||||
|
|
||||||
#include <string.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <errno.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
#include <sys/socket.h>
|
|
||||||
#include <net/if_arp.h>
|
|
||||||
#include <netinet/in.h>
|
|
||||||
#include <linux/netlink.h>
|
|
||||||
#include <linux/rtnetlink.h>
|
|
||||||
|
|
||||||
typedef struct NetlinkList
|
|
||||||
{
|
|
||||||
struct NetlinkList *m_next;
|
|
||||||
struct nlmsghdr *m_data;
|
|
||||||
unsigned int m_size;
|
|
||||||
} NetlinkList;
|
|
||||||
|
|
||||||
static int netlink_socket(void)
|
|
||||||
{
|
|
||||||
int l_socket = socket(PF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
|
|
||||||
if(l_socket < 0)
|
|
||||||
{
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct sockaddr_nl l_addr;
|
|
||||||
memset(&l_addr, 0, sizeof(l_addr));
|
|
||||||
l_addr.nl_family = AF_NETLINK;
|
|
||||||
if(bind(l_socket, (struct sockaddr *)&l_addr, sizeof(l_addr)) < 0)
|
|
||||||
{
|
|
||||||
close(l_socket);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return l_socket;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int netlink_send(int p_socket, int p_request)
|
|
||||||
{
|
|
||||||
char l_buffer[NLMSG_ALIGN(sizeof(struct nlmsghdr)) + NLMSG_ALIGN(sizeof(struct rtgenmsg))];
|
|
||||||
memset(l_buffer, 0, sizeof(l_buffer));
|
|
||||||
struct nlmsghdr *l_hdr = (struct nlmsghdr *)l_buffer;
|
|
||||||
struct rtgenmsg *l_msg = (struct rtgenmsg *)NLMSG_DATA(l_hdr);
|
|
||||||
|
|
||||||
l_hdr->nlmsg_len = NLMSG_LENGTH(sizeof(*l_msg));
|
|
||||||
l_hdr->nlmsg_type = p_request;
|
|
||||||
l_hdr->nlmsg_flags = NLM_F_ROOT | NLM_F_MATCH | NLM_F_REQUEST;
|
|
||||||
l_hdr->nlmsg_pid = 0;
|
|
||||||
l_hdr->nlmsg_seq = p_socket;
|
|
||||||
l_msg->rtgen_family = AF_UNSPEC;
|
|
||||||
|
|
||||||
struct sockaddr_nl l_addr;
|
|
||||||
memset(&l_addr, 0, sizeof(l_addr));
|
|
||||||
l_addr.nl_family = AF_NETLINK;
|
|
||||||
return (sendto(p_socket, l_hdr, l_hdr->nlmsg_len, 0, (struct sockaddr *)&l_addr, sizeof(l_addr)));
|
|
||||||
}
|
|
||||||
|
|
||||||
static int netlink_recv(int p_socket, void *p_buffer, size_t p_len)
|
|
||||||
{
|
|
||||||
struct msghdr l_msg;
|
|
||||||
struct iovec l_iov = { p_buffer, p_len };
|
|
||||||
struct sockaddr_nl l_addr;
|
|
||||||
int l_result;
|
|
||||||
|
|
||||||
for(;;)
|
|
||||||
{
|
|
||||||
l_msg.msg_name = (void *)&l_addr;
|
|
||||||
l_msg.msg_namelen = sizeof(l_addr);
|
|
||||||
l_msg.msg_iov = &l_iov;
|
|
||||||
l_msg.msg_iovlen = 1;
|
|
||||||
l_msg.msg_control = NULL;
|
|
||||||
l_msg.msg_controllen = 0;
|
|
||||||
l_msg.msg_flags = 0;
|
|
||||||
int l_result = recvmsg(p_socket, &l_msg, 0);
|
|
||||||
|
|
||||||
if(l_result < 0)
|
|
||||||
{
|
|
||||||
if(errno == EINTR)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
return -2;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(l_msg.msg_flags & MSG_TRUNC)
|
|
||||||
{ // buffer was too small
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
return l_result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static struct nlmsghdr *getNetlinkResponse(int p_socket, int *p_size, int *p_done)
|
|
||||||
{
|
|
||||||
size_t l_size = 4096;
|
|
||||||
void *l_buffer = NULL;
|
|
||||||
|
|
||||||
for(;;)
|
|
||||||
{
|
|
||||||
free(l_buffer);
|
|
||||||
l_buffer = malloc(l_size);
|
|
||||||
|
|
||||||
int l_read = netlink_recv(p_socket, l_buffer, l_size);
|
|
||||||
*p_size = l_read;
|
|
||||||
if(l_read == -2)
|
|
||||||
{
|
|
||||||
free(l_buffer);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
if(l_read >= 0)
|
|
||||||
{
|
|
||||||
pid_t l_pid = getpid();
|
|
||||||
struct nlmsghdr *l_hdr;
|
|
||||||
for(l_hdr = (struct nlmsghdr *)l_buffer; NLMSG_OK(l_hdr, (unsigned int)l_read); l_hdr = (struct nlmsghdr *)NLMSG_NEXT(l_hdr, l_read))
|
|
||||||
{
|
|
||||||
if((pid_t)l_hdr->nlmsg_pid != l_pid || (int)l_hdr->nlmsg_seq != p_socket)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(l_hdr->nlmsg_type == NLMSG_DONE)
|
|
||||||
{
|
|
||||||
*p_done = 1;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(l_hdr->nlmsg_type == NLMSG_ERROR)
|
|
||||||
{
|
|
||||||
free(l_buffer);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return l_buffer;
|
|
||||||
}
|
|
||||||
|
|
||||||
l_size *= 2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static NetlinkList *newListItem(struct nlmsghdr *p_data, unsigned int p_size)
|
|
||||||
{
|
|
||||||
NetlinkList *l_item = malloc(sizeof(NetlinkList));
|
|
||||||
l_item->m_next = NULL;
|
|
||||||
l_item->m_data = p_data;
|
|
||||||
l_item->m_size = p_size;
|
|
||||||
return l_item;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void freeResultList(NetlinkList *p_list)
|
|
||||||
{
|
|
||||||
NetlinkList *l_cur;
|
|
||||||
while(p_list)
|
|
||||||
{
|
|
||||||
l_cur = p_list;
|
|
||||||
p_list = p_list->m_next;
|
|
||||||
free(l_cur->m_data);
|
|
||||||
free(l_cur);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static NetlinkList *getResultList(int p_socket, int p_request)
|
|
||||||
{
|
|
||||||
if(netlink_send(p_socket, p_request) < 0)
|
|
||||||
{
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
NetlinkList *l_list = NULL;
|
|
||||||
NetlinkList *l_end = NULL;
|
|
||||||
int l_size;
|
|
||||||
int l_done = 0;
|
|
||||||
while(!l_done)
|
|
||||||
{
|
|
||||||
struct nlmsghdr *l_hdr = getNetlinkResponse(p_socket, &l_size, &l_done);
|
|
||||||
if(!l_hdr)
|
|
||||||
{ // error
|
|
||||||
freeResultList(l_list);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
NetlinkList *l_item = newListItem(l_hdr, l_size);
|
|
||||||
if(!l_list)
|
|
||||||
{
|
|
||||||
l_list = l_item;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
l_end->m_next = l_item;
|
|
||||||
}
|
|
||||||
l_end = l_item;
|
|
||||||
}
|
|
||||||
return l_list;
|
|
||||||
}
|
|
||||||
|
|
||||||
static size_t maxSize(size_t a, size_t b)
|
|
||||||
{
|
|
||||||
return (a > b ? a : b);
|
|
||||||
}
|
|
||||||
|
|
||||||
static size_t calcAddrLen(sa_family_t p_family, int p_dataSize)
|
|
||||||
{
|
|
||||||
switch(p_family)
|
|
||||||
{
|
|
||||||
case AF_INET:
|
|
||||||
return sizeof(struct sockaddr_in);
|
|
||||||
case AF_INET6:
|
|
||||||
return sizeof(struct sockaddr_in6);
|
|
||||||
case AF_PACKET:
|
|
||||||
return maxSize(sizeof(struct sockaddr_ll), offsetof(struct sockaddr_ll, sll_addr) + p_dataSize);
|
|
||||||
default:
|
|
||||||
return maxSize(sizeof(struct sockaddr), offsetof(struct sockaddr, sa_data) + p_dataSize);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void makeSockaddr(sa_family_t p_family, struct sockaddr *p_dest, void *p_data, size_t p_size)
|
|
||||||
{
|
|
||||||
switch(p_family)
|
|
||||||
{
|
|
||||||
case AF_INET:
|
|
||||||
memcpy(&((struct sockaddr_in*)p_dest)->sin_addr, p_data, p_size);
|
|
||||||
break;
|
|
||||||
case AF_INET6:
|
|
||||||
memcpy(&((struct sockaddr_in6*)p_dest)->sin6_addr, p_data, p_size);
|
|
||||||
break;
|
|
||||||
case AF_PACKET:
|
|
||||||
memcpy(((struct sockaddr_ll*)p_dest)->sll_addr, p_data, p_size);
|
|
||||||
((struct sockaddr_ll*)p_dest)->sll_halen = p_size;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
memcpy(p_dest->sa_data, p_data, p_size);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
p_dest->sa_family = p_family;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void addToEnd(struct ifaddrs **p_resultList, struct ifaddrs *p_entry)
|
|
||||||
{
|
|
||||||
if(!*p_resultList)
|
|
||||||
{
|
|
||||||
*p_resultList = p_entry;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
struct ifaddrs *l_cur = *p_resultList;
|
|
||||||
while(l_cur->ifa_next)
|
|
||||||
{
|
|
||||||
l_cur = l_cur->ifa_next;
|
|
||||||
}
|
|
||||||
l_cur->ifa_next = p_entry;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void interpretLink(struct nlmsghdr *p_hdr, struct ifaddrs **p_links, struct ifaddrs **p_resultList)
|
|
||||||
{
|
|
||||||
struct ifinfomsg *l_info = (struct ifinfomsg *)NLMSG_DATA(p_hdr);
|
|
||||||
|
|
||||||
size_t l_nameSize = 0;
|
|
||||||
size_t l_addrSize = 0;
|
|
||||||
size_t l_dataSize = 0;
|
|
||||||
|
|
||||||
size_t l_rtaSize = NLMSG_PAYLOAD(p_hdr, sizeof(struct ifinfomsg));
|
|
||||||
struct rtattr *l_rta;
|
|
||||||
for(l_rta = (struct rtattr *)(((char *)l_info) + NLMSG_ALIGN(sizeof(struct ifinfomsg))); RTA_OK(l_rta, l_rtaSize); l_rta = RTA_NEXT(l_rta, l_rtaSize))
|
|
||||||
{
|
|
||||||
void *l_rtaData = RTA_DATA(l_rta);
|
|
||||||
size_t l_rtaDataSize = RTA_PAYLOAD(l_rta);
|
|
||||||
switch(l_rta->rta_type)
|
|
||||||
{
|
|
||||||
case IFLA_ADDRESS:
|
|
||||||
case IFLA_BROADCAST:
|
|
||||||
l_addrSize += NLMSG_ALIGN(calcAddrLen(AF_PACKET, l_rtaDataSize));
|
|
||||||
break;
|
|
||||||
case IFLA_IFNAME:
|
|
||||||
l_nameSize += NLMSG_ALIGN(l_rtaSize + 1);
|
|
||||||
break;
|
|
||||||
case IFLA_STATS:
|
|
||||||
l_dataSize += NLMSG_ALIGN(l_rtaSize);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct ifaddrs *l_entry = malloc(sizeof(struct ifaddrs) + l_nameSize + l_addrSize + l_dataSize);
|
|
||||||
memset(l_entry, 0, sizeof(struct ifaddrs));
|
|
||||||
l_entry->ifa_name = "";
|
|
||||||
|
|
||||||
char *l_name = ((char *)l_entry) + sizeof(struct ifaddrs);
|
|
||||||
char *l_addr = l_name + l_nameSize;
|
|
||||||
char *l_data = l_addr + l_addrSize;
|
|
||||||
|
|
||||||
l_entry->ifa_flags = l_info->ifi_flags;
|
|
||||||
|
|
||||||
l_rtaSize = NLMSG_PAYLOAD(p_hdr, sizeof(struct ifinfomsg));
|
|
||||||
for(l_rta = (struct rtattr *)(((char *)l_info) + NLMSG_ALIGN(sizeof(struct ifinfomsg))); RTA_OK(l_rta, l_rtaSize); l_rta = RTA_NEXT(l_rta, l_rtaSize))
|
|
||||||
{
|
|
||||||
void *l_rtaData = RTA_DATA(l_rta);
|
|
||||||
size_t l_rtaDataSize = RTA_PAYLOAD(l_rta);
|
|
||||||
switch(l_rta->rta_type)
|
|
||||||
{
|
|
||||||
case IFLA_ADDRESS:
|
|
||||||
case IFLA_BROADCAST:
|
|
||||||
{
|
|
||||||
size_t l_addrLen = calcAddrLen(AF_PACKET, l_rtaDataSize);
|
|
||||||
makeSockaddr(AF_PACKET, (struct sockaddr *)l_addr, l_rtaData, l_rtaDataSize);
|
|
||||||
((struct sockaddr_ll *)l_addr)->sll_ifindex = l_info->ifi_index;
|
|
||||||
((struct sockaddr_ll *)l_addr)->sll_hatype = l_info->ifi_type;
|
|
||||||
if(l_rta->rta_type == IFLA_ADDRESS)
|
|
||||||
{
|
|
||||||
l_entry->ifa_addr = (struct sockaddr *)l_addr;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
l_entry->ifa_broadaddr = (struct sockaddr *)l_addr;
|
|
||||||
}
|
|
||||||
l_addr += NLMSG_ALIGN(l_addrLen);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case IFLA_IFNAME:
|
|
||||||
strncpy(l_name, l_rtaData, l_rtaDataSize);
|
|
||||||
l_name[l_rtaDataSize] = '\0';
|
|
||||||
l_entry->ifa_name = l_name;
|
|
||||||
break;
|
|
||||||
case IFLA_STATS:
|
|
||||||
memcpy(l_data, l_rtaData, l_rtaDataSize);
|
|
||||||
l_entry->ifa_data = l_data;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
addToEnd(p_resultList, l_entry);
|
|
||||||
p_links[l_info->ifi_index - 1] = l_entry;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void interpretAddr(struct nlmsghdr *p_hdr, struct ifaddrs **p_links, struct ifaddrs **p_resultList)
|
|
||||||
{
|
|
||||||
struct ifaddrmsg *l_info = (struct ifaddrmsg *)NLMSG_DATA(p_hdr);
|
|
||||||
|
|
||||||
size_t l_nameSize = 0;
|
|
||||||
size_t l_addrSize = 0;
|
|
||||||
|
|
||||||
int l_addedNetmask = 0;
|
|
||||||
|
|
||||||
size_t l_rtaSize = NLMSG_PAYLOAD(p_hdr, sizeof(struct ifaddrmsg));
|
|
||||||
struct rtattr *l_rta;
|
|
||||||
for(l_rta = (struct rtattr *)(((char *)l_info) + NLMSG_ALIGN(sizeof(struct ifaddrmsg))); RTA_OK(l_rta, l_rtaSize); l_rta = RTA_NEXT(l_rta, l_rtaSize))
|
|
||||||
{
|
|
||||||
void *l_rtaData = RTA_DATA(l_rta);
|
|
||||||
size_t l_rtaDataSize = RTA_PAYLOAD(l_rta);
|
|
||||||
if(l_info->ifa_family == AF_PACKET)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch(l_rta->rta_type)
|
|
||||||
{
|
|
||||||
case IFA_ADDRESS:
|
|
||||||
case IFA_LOCAL:
|
|
||||||
if((l_info->ifa_family == AF_INET || l_info->ifa_family == AF_INET6) && !l_addedNetmask)
|
|
||||||
{ // make room for netmask
|
|
||||||
l_addrSize += NLMSG_ALIGN(calcAddrLen(l_info->ifa_family, l_rtaDataSize));
|
|
||||||
l_addedNetmask = 1;
|
|
||||||
}
|
|
||||||
case IFA_BROADCAST:
|
|
||||||
l_addrSize += NLMSG_ALIGN(calcAddrLen(l_info->ifa_family, l_rtaDataSize));
|
|
||||||
break;
|
|
||||||
case IFA_LABEL:
|
|
||||||
l_nameSize += NLMSG_ALIGN(l_rtaSize + 1);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct ifaddrs *l_entry = malloc(sizeof(struct ifaddrs) + l_nameSize + l_addrSize);
|
|
||||||
memset(l_entry, 0, sizeof(struct ifaddrs));
|
|
||||||
l_entry->ifa_name = p_links[l_info->ifa_index - 1]->ifa_name;
|
|
||||||
|
|
||||||
char *l_name = ((char *)l_entry) + sizeof(struct ifaddrs);
|
|
||||||
char *l_addr = l_name + l_nameSize;
|
|
||||||
|
|
||||||
l_entry->ifa_flags = l_info->ifa_flags | p_links[l_info->ifa_index - 1]->ifa_flags;
|
|
||||||
|
|
||||||
l_rtaSize = NLMSG_PAYLOAD(p_hdr, sizeof(struct ifaddrmsg));
|
|
||||||
for(l_rta = (struct rtattr *)(((char *)l_info) + NLMSG_ALIGN(sizeof(struct ifaddrmsg))); RTA_OK(l_rta, l_rtaSize); l_rta = RTA_NEXT(l_rta, l_rtaSize))
|
|
||||||
{
|
|
||||||
void *l_rtaData = RTA_DATA(l_rta);
|
|
||||||
size_t l_rtaDataSize = RTA_PAYLOAD(l_rta);
|
|
||||||
switch(l_rta->rta_type)
|
|
||||||
{
|
|
||||||
case IFA_ADDRESS:
|
|
||||||
case IFA_BROADCAST:
|
|
||||||
case IFA_LOCAL:
|
|
||||||
{
|
|
||||||
size_t l_addrLen = calcAddrLen(l_info->ifa_family, l_rtaDataSize);
|
|
||||||
makeSockaddr(l_info->ifa_family, (struct sockaddr *)l_addr, l_rtaData, l_rtaDataSize);
|
|
||||||
if(l_info->ifa_family == AF_INET6)
|
|
||||||
{
|
|
||||||
if(IN6_IS_ADDR_LINKLOCAL((struct in6_addr *)l_rtaData) || IN6_IS_ADDR_MC_LINKLOCAL((struct in6_addr *)l_rtaData))
|
|
||||||
{
|
|
||||||
((struct sockaddr_in6 *)l_addr)->sin6_scope_id = l_info->ifa_index;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(l_rta->rta_type == IFA_ADDRESS)
|
|
||||||
{ // apparently in a point-to-point network IFA_ADDRESS contains the dest address and IFA_LOCAL contains the local address
|
|
||||||
if(l_entry->ifa_addr)
|
|
||||||
{
|
|
||||||
l_entry->ifa_dstaddr = (struct sockaddr *)l_addr;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
l_entry->ifa_addr = (struct sockaddr *)l_addr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if(l_rta->rta_type == IFA_LOCAL)
|
|
||||||
{
|
|
||||||
if(l_entry->ifa_addr)
|
|
||||||
{
|
|
||||||
l_entry->ifa_dstaddr = l_entry->ifa_addr;
|
|
||||||
}
|
|
||||||
l_entry->ifa_addr = (struct sockaddr *)l_addr;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
l_entry->ifa_broadaddr = (struct sockaddr *)l_addr;
|
|
||||||
}
|
|
||||||
l_addr += NLMSG_ALIGN(l_addrLen);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case IFA_LABEL:
|
|
||||||
strncpy(l_name, l_rtaData, l_rtaDataSize);
|
|
||||||
l_name[l_rtaDataSize] = '\0';
|
|
||||||
l_entry->ifa_name = l_name;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(l_entry->ifa_addr && (l_entry->ifa_addr->sa_family == AF_INET || l_entry->ifa_addr->sa_family == AF_INET6))
|
|
||||||
{
|
|
||||||
unsigned l_maxPrefix = (l_entry->ifa_addr->sa_family == AF_INET ? 32 : 128);
|
|
||||||
unsigned l_prefix = (l_info->ifa_prefixlen > l_maxPrefix ? l_maxPrefix : l_info->ifa_prefixlen);
|
|
||||||
char l_mask[16] = {0};
|
|
||||||
unsigned i;
|
|
||||||
for(i=0; i<(l_prefix/8); ++i)
|
|
||||||
{
|
|
||||||
l_mask[i] = 0xff;
|
|
||||||
}
|
|
||||||
l_mask[i] = 0xff << (8 - (l_prefix % 8));
|
|
||||||
|
|
||||||
makeSockaddr(l_entry->ifa_addr->sa_family, (struct sockaddr *)l_addr, l_mask, l_maxPrefix / 8);
|
|
||||||
l_entry->ifa_netmask = (struct sockaddr *)l_addr;
|
|
||||||
}
|
|
||||||
|
|
||||||
addToEnd(p_resultList, l_entry);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void interpret(int p_socket, NetlinkList *p_netlinkList, struct ifaddrs **p_links, struct ifaddrs **p_resultList)
|
|
||||||
{
|
|
||||||
pid_t l_pid = getpid();
|
|
||||||
for(; p_netlinkList; p_netlinkList = p_netlinkList->m_next)
|
|
||||||
{
|
|
||||||
unsigned int l_nlsize = p_netlinkList->m_size;
|
|
||||||
struct nlmsghdr *l_hdr;
|
|
||||||
for(l_hdr = p_netlinkList->m_data; NLMSG_OK(l_hdr, l_nlsize); l_hdr = NLMSG_NEXT(l_hdr, l_nlsize))
|
|
||||||
{
|
|
||||||
if((pid_t)l_hdr->nlmsg_pid != l_pid || (int)l_hdr->nlmsg_seq != p_socket)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(l_hdr->nlmsg_type == NLMSG_DONE)
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(l_hdr->nlmsg_type == RTM_NEWLINK)
|
|
||||||
{
|
|
||||||
interpretLink(l_hdr, p_links, p_resultList);
|
|
||||||
}
|
|
||||||
else if(l_hdr->nlmsg_type == RTM_NEWADDR)
|
|
||||||
{
|
|
||||||
interpretAddr(l_hdr, p_links, p_resultList);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static unsigned countLinks(int p_socket, NetlinkList *p_netlinkList)
|
|
||||||
{
|
|
||||||
unsigned l_links = 0;
|
|
||||||
pid_t l_pid = getpid();
|
|
||||||
for(; p_netlinkList; p_netlinkList = p_netlinkList->m_next)
|
|
||||||
{
|
|
||||||
unsigned int l_nlsize = p_netlinkList->m_size;
|
|
||||||
struct nlmsghdr *l_hdr;
|
|
||||||
for(l_hdr = p_netlinkList->m_data; NLMSG_OK(l_hdr, l_nlsize); l_hdr = NLMSG_NEXT(l_hdr, l_nlsize))
|
|
||||||
{
|
|
||||||
if((pid_t)l_hdr->nlmsg_pid != l_pid || (int)l_hdr->nlmsg_seq != p_socket)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(l_hdr->nlmsg_type == NLMSG_DONE)
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(l_hdr->nlmsg_type == RTM_NEWLINK)
|
|
||||||
{
|
|
||||||
++l_links;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return l_links;
|
|
||||||
}
|
|
||||||
|
|
||||||
int getifaddrs(struct ifaddrs **ifap)
|
|
||||||
{
|
|
||||||
if(!ifap)
|
|
||||||
{
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
*ifap = NULL;
|
|
||||||
|
|
||||||
int l_socket = netlink_socket();
|
|
||||||
if(l_socket < 0)
|
|
||||||
{
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
NetlinkList *l_linkResults = getResultList(l_socket, RTM_GETLINK);
|
|
||||||
if(!l_linkResults)
|
|
||||||
{
|
|
||||||
close(l_socket);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
NetlinkList *l_addrResults = getResultList(l_socket, RTM_GETADDR);
|
|
||||||
if(!l_addrResults)
|
|
||||||
{
|
|
||||||
close(l_socket);
|
|
||||||
freeResultList(l_linkResults);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
unsigned l_numLinks = countLinks(l_socket, l_linkResults) + countLinks(l_socket, l_addrResults);
|
|
||||||
struct ifaddrs *l_links[l_numLinks];
|
|
||||||
memset(l_links, 0, l_numLinks * sizeof(struct ifaddrs *));
|
|
||||||
|
|
||||||
interpret(l_socket, l_linkResults, l_links, ifap);
|
|
||||||
interpret(l_socket, l_addrResults, l_links, ifap);
|
|
||||||
|
|
||||||
freeResultList(l_linkResults);
|
|
||||||
freeResultList(l_addrResults);
|
|
||||||
close(l_socket);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void freeifaddrs(struct ifaddrs *ifa)
|
|
||||||
{
|
|
||||||
struct ifaddrs *l_cur;
|
|
||||||
while(ifa)
|
|
||||||
{
|
|
||||||
l_cur = ifa;
|
|
||||||
ifa = ifa->ifa_next;
|
|
||||||
free(l_cur);
|
|
||||||
}
|
|
||||||
}
|
|
54
externals/android-ifaddrs/ifaddrs.h
vendored
54
externals/android-ifaddrs/ifaddrs.h
vendored
|
@ -1,54 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 1995, 1999
|
|
||||||
* Berkeley Software Design, Inc. All rights reserved.
|
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
*
|
|
||||||
* THIS SOFTWARE IS PROVIDED BY Berkeley Software Design, Inc. ``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 Berkeley Software Design, Inc. 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.
|
|
||||||
*
|
|
||||||
* BSDI ifaddrs.h,v 2.5 2000/02/23 14:51:59 dab Exp
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef _IFADDRS_H_
|
|
||||||
#define _IFADDRS_H_
|
|
||||||
|
|
||||||
struct ifaddrs {
|
|
||||||
struct ifaddrs *ifa_next;
|
|
||||||
char *ifa_name;
|
|
||||||
unsigned int ifa_flags;
|
|
||||||
struct sockaddr *ifa_addr;
|
|
||||||
struct sockaddr *ifa_netmask;
|
|
||||||
struct sockaddr *ifa_dstaddr;
|
|
||||||
void *ifa_data;
|
|
||||||
};
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This may have been defined in <net/if.h>. Note that if <net/if.h> is
|
|
||||||
* to be included it must be included before this header file.
|
|
||||||
*/
|
|
||||||
#ifndef ifa_broadaddr
|
|
||||||
#define ifa_broadaddr ifa_dstaddr /* broadcast address interface */
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include <sys/cdefs.h>
|
|
||||||
|
|
||||||
__BEGIN_DECLS
|
|
||||||
extern int getifaddrs(struct ifaddrs **ifap);
|
|
||||||
extern void freeifaddrs(struct ifaddrs *ifa);
|
|
||||||
__END_DECLS
|
|
||||||
|
|
||||||
#endif
|
|
2
externals/cryptopp-cmake
vendored
2
externals/cryptopp-cmake
vendored
|
@ -1 +1 @@
|
||||||
Subproject commit 9327192b0095dc1f420b2082d37bd427b5750d48
|
Subproject commit a99c80c26686e44eddf0432140ae397f3efbd0b3
|
2
externals/cubeb
vendored
2
externals/cubeb
vendored
|
@ -1 +1 @@
|
||||||
Subproject commit 48689ae7a73caeb747953f9ed664dc71d2f918d8
|
Subproject commit 799e775484b8fce7e986ee7a4f4b651fec2bca07
|
2
externals/dynarmic
vendored
2
externals/dynarmic
vendored
|
@ -1 +1 @@
|
||||||
Subproject commit d333a09b3b9152af3cb442902ae8ea18d8416470
|
Subproject commit 30f1a3c6289075ef4af08f5ec502be2fc8627a0c
|
2
externals/oaknut
vendored
2
externals/oaknut
vendored
|
@ -1 +1 @@
|
||||||
Subproject commit e6eecc3f9460728be0a8d3f63e66d31c0362f472
|
Subproject commit 6b1d57ea7ed4882d32a91eeaa6557b0ecb4da152
|
|
@ -10,7 +10,7 @@ plugins {
|
||||||
id("org.jetbrains.kotlin.android")
|
id("org.jetbrains.kotlin.android")
|
||||||
id("de.undercouch.download") version "5.5.0"
|
id("de.undercouch.download") version "5.5.0"
|
||||||
id("kotlin-parcelize")
|
id("kotlin-parcelize")
|
||||||
kotlin("plugin.serialization") version "1.8.21"
|
kotlin("plugin.serialization") version "1.9.22"
|
||||||
id("androidx.navigation.safeargs.kotlin")
|
id("androidx.navigation.safeargs.kotlin")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -173,23 +173,23 @@ android {
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation("androidx.recyclerview:recyclerview:1.3.2")
|
implementation("androidx.recyclerview:recyclerview:1.3.2")
|
||||||
implementation("androidx.activity:activity-ktx:1.8.0")
|
implementation("androidx.activity:activity-ktx:1.8.2")
|
||||||
implementation("androidx.fragment:fragment-ktx:1.6.2")
|
implementation("androidx.fragment:fragment-ktx:1.6.2")
|
||||||
implementation("androidx.appcompat:appcompat:1.6.1")
|
implementation("androidx.appcompat:appcompat:1.6.1")
|
||||||
implementation("androidx.documentfile:documentfile:1.0.1")
|
implementation("androidx.documentfile:documentfile:1.0.1")
|
||||||
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.1")
|
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.7.0")
|
||||||
implementation("androidx.slidingpanelayout:slidingpanelayout:1.2.0")
|
implementation("androidx.slidingpanelayout:slidingpanelayout:1.2.0")
|
||||||
implementation("com.google.android.material:material:1.9.0")
|
implementation("com.google.android.material:material:1.9.0")
|
||||||
implementation("androidx.core:core-splashscreen:1.0.1")
|
implementation("androidx.core:core-splashscreen:1.0.1")
|
||||||
implementation("androidx.work:work-runtime:2.8.1")
|
implementation("androidx.work:work-runtime:2.9.0")
|
||||||
implementation("org.ini4j:ini4j:0.5.4")
|
implementation("org.ini4j:ini4j:0.5.4")
|
||||||
implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0")
|
implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0")
|
||||||
implementation("androidx.navigation:navigation-fragment-ktx:2.7.5")
|
implementation("androidx.navigation:navigation-fragment-ktx:2.7.6")
|
||||||
implementation("androidx.navigation:navigation-ui-ktx:2.7.5")
|
implementation("androidx.navigation:navigation-ui-ktx:2.7.6")
|
||||||
implementation("info.debatty:java-string-similarity:2.0.0")
|
implementation("info.debatty:java-string-similarity:2.0.0")
|
||||||
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0")
|
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.2")
|
||||||
implementation("androidx.preference:preference-ktx:1.2.1")
|
implementation("androidx.preference:preference-ktx:1.2.1")
|
||||||
implementation("io.coil-kt:coil:2.2.2")
|
implementation("io.coil-kt:coil:2.5.0")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Download Vulkan Validation Layers from the KhronosGroup GitHub.
|
// Download Vulkan Validation Layers from the KhronosGroup GitHub.
|
||||||
|
|
|
@ -42,6 +42,9 @@
|
||||||
android:banner="@mipmap/ic_launcher"
|
android:banner="@mipmap/ic_launcher"
|
||||||
android:requestLegacyExternalStorage="true">
|
android:requestLegacyExternalStorage="true">
|
||||||
|
|
||||||
|
<meta-data android:name="android.game_mode_config"
|
||||||
|
android:resource="@xml/game_mode_config" />
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name="org.citra.citra_emu.ui.main.MainActivity"
|
android:name="org.citra.citra_emu.ui.main.MainActivity"
|
||||||
android:theme="@style/Theme.Citra.Splash.Main"
|
android:theme="@style/Theme.Citra.Splash.Main"
|
||||||
|
|
|
@ -9,10 +9,13 @@ import android.app.Application
|
||||||
import android.app.NotificationChannel
|
import android.app.NotificationChannel
|
||||||
import android.app.NotificationManager
|
import android.app.NotificationManager
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.os.Build
|
||||||
import org.citra.citra_emu.utils.DirectoryInitialization
|
import org.citra.citra_emu.utils.DirectoryInitialization
|
||||||
import org.citra.citra_emu.utils.DocumentsTree
|
import org.citra.citra_emu.utils.DocumentsTree
|
||||||
import org.citra.citra_emu.utils.GpuDriverHelper
|
import org.citra.citra_emu.utils.GpuDriverHelper
|
||||||
import org.citra.citra_emu.utils.PermissionsHandler
|
import org.citra.citra_emu.utils.PermissionsHandler
|
||||||
|
import org.citra.citra_emu.utils.Log
|
||||||
|
import org.citra.citra_emu.utils.MemoryUtil
|
||||||
|
|
||||||
class CitraApplication : Application() {
|
class CitraApplication : Application() {
|
||||||
private fun createNotificationChannel() {
|
private fun createNotificationChannel() {
|
||||||
|
@ -53,9 +56,20 @@ class CitraApplication : Application() {
|
||||||
}
|
}
|
||||||
|
|
||||||
NativeLibrary.logDeviceInfo()
|
NativeLibrary.logDeviceInfo()
|
||||||
|
logDeviceInfo()
|
||||||
createNotificationChannel()
|
createNotificationChannel()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun logDeviceInfo() {
|
||||||
|
Log.info("Device Manufacturer - ${Build.MANUFACTURER}")
|
||||||
|
Log.info("Device Model - ${Build.MODEL}")
|
||||||
|
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.R) {
|
||||||
|
Log.info("SoC Manufacturer - ${Build.SOC_MANUFACTURER}")
|
||||||
|
Log.info("SoC Model - ${Build.SOC_MODEL}")
|
||||||
|
}
|
||||||
|
Log.info("Total System Memory - ${MemoryUtil.getDeviceRAM()}")
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private var application: CitraApplication? = null
|
private var application: CitraApplication? = null
|
||||||
|
|
||||||
|
|
|
@ -413,12 +413,12 @@ object NativeLibrary {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setEmulationActivity(emulationActivity: EmulationActivity?) {
|
fun setEmulationActivity(emulationActivity: EmulationActivity?) {
|
||||||
Log.verbose("[NativeLibrary] Registering EmulationActivity.")
|
Log.debug("[NativeLibrary] Registering EmulationActivity.")
|
||||||
sEmulationActivity = WeakReference(emulationActivity)
|
sEmulationActivity = WeakReference(emulationActivity)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun clearEmulationActivity() {
|
fun clearEmulationActivity() {
|
||||||
Log.verbose("[NativeLibrary] Unregistering EmulationActivity.")
|
Log.debug("[NativeLibrary] Unregistering EmulationActivity.")
|
||||||
sEmulationActivity.clear()
|
sEmulationActivity.clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -687,8 +687,8 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
|
||||||
1,
|
1,
|
||||||
10,
|
10,
|
||||||
"x",
|
"x",
|
||||||
IntSetting.GRAPHICS_API.key,
|
IntSetting.RESOLUTION_FACTOR.key,
|
||||||
IntSetting.GRAPHICS_API.defaultValue.toFloat()
|
IntSetting.RESOLUTION_FACTOR.defaultValue.toFloat()
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
add(
|
add(
|
||||||
|
|
|
@ -94,14 +94,14 @@ object DirectoryInitialization {
|
||||||
val dataPath = PermissionsHandler.citraDirectory
|
val dataPath = PermissionsHandler.citraDirectory
|
||||||
if (dataPath.toString().isNotEmpty()) {
|
if (dataPath.toString().isNotEmpty()) {
|
||||||
userPath = dataPath.toString()
|
userPath = dataPath.toString()
|
||||||
Log.debug("[DirectoryInitialization] User Dir: $userPath")
|
android.util.Log.d("[Citra Frontend]", "[DirectoryInitialization] User Dir: $userPath")
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun copyAsset(asset: String, output: File, overwrite: Boolean, context: Context) {
|
private fun copyAsset(asset: String, output: File, overwrite: Boolean, context: Context) {
|
||||||
Log.verbose("[DirectoryInitialization] Copying File $asset to $output")
|
Log.debug("[DirectoryInitialization] Copying File $asset to $output")
|
||||||
try {
|
try {
|
||||||
if (!output.exists() || overwrite) {
|
if (!output.exists() || overwrite) {
|
||||||
val inputStream = context.assets.open(asset)
|
val inputStream = context.assets.open(asset)
|
||||||
|
@ -121,7 +121,7 @@ object DirectoryInitialization {
|
||||||
overwrite: Boolean,
|
overwrite: Boolean,
|
||||||
context: Context
|
context: Context
|
||||||
) {
|
) {
|
||||||
Log.verbose("[DirectoryInitialization] Copying Folder $assetFolder to $outputFolder")
|
Log.debug("[DirectoryInitialization] Copying Folder $assetFolder to $outputFolder")
|
||||||
try {
|
try {
|
||||||
var createdFolder = false
|
var createdFolder = false
|
||||||
for (file in context.assets.list(assetFolder)!!) {
|
for (file in context.assets.list(assetFolder)!!) {
|
||||||
|
|
|
@ -4,34 +4,17 @@
|
||||||
|
|
||||||
package org.citra.citra_emu.utils
|
package org.citra.citra_emu.utils
|
||||||
|
|
||||||
import android.util.Log
|
|
||||||
import org.citra.citra_emu.BuildConfig
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Contains methods that call through to [android.util.Log], but
|
|
||||||
* with the same TAG automatically provided. Also no-ops VERBOSE and DEBUG log
|
|
||||||
* levels in release builds.
|
|
||||||
*/
|
|
||||||
object Log {
|
object Log {
|
||||||
// Tracks whether we should share the old log or the current log
|
// Tracks whether we should share the old log or the current log
|
||||||
var gameLaunched = false
|
var gameLaunched = false
|
||||||
private const val TAG = "Citra Frontend"
|
|
||||||
|
|
||||||
fun verbose(message: String?) {
|
external fun debug(message: String)
|
||||||
if (BuildConfig.DEBUG) {
|
|
||||||
Log.v(TAG, message!!)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun debug(message: String?) {
|
external fun warning(message: String)
|
||||||
if (BuildConfig.DEBUG) {
|
|
||||||
Log.d(TAG, message!!)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun info(message: String?) = Log.i(TAG, message!!)
|
external fun info(message: String)
|
||||||
|
|
||||||
fun warning(message: String?) = Log.w(TAG, message!!)
|
external fun error(message: String)
|
||||||
|
|
||||||
fun error(message: String?) = Log.e(TAG, message!!)
|
external fun critical(message: String)
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,108 @@
|
||||||
|
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
package org.citra.citra_emu.utils
|
||||||
|
|
||||||
|
import android.app.ActivityManager
|
||||||
|
import android.content.Context
|
||||||
|
import android.os.Build
|
||||||
|
import org.citra.citra_emu.CitraApplication
|
||||||
|
import org.citra.citra_emu.R
|
||||||
|
import java.util.Locale
|
||||||
|
import kotlin.math.ceil
|
||||||
|
|
||||||
|
object MemoryUtil {
|
||||||
|
private val context get() = CitraApplication.appContext
|
||||||
|
|
||||||
|
private val Float.hundredths: String
|
||||||
|
get() = String.format(Locale.ROOT, "%.2f", this)
|
||||||
|
|
||||||
|
const val Kb: Float = 1024F
|
||||||
|
const val Mb = Kb * 1024
|
||||||
|
const val Gb = Mb * 1024
|
||||||
|
const val Tb = Gb * 1024
|
||||||
|
const val Pb = Tb * 1024
|
||||||
|
const val Eb = Pb * 1024
|
||||||
|
|
||||||
|
fun bytesToSizeUnit(size: Float, roundUp: Boolean = false): String =
|
||||||
|
when {
|
||||||
|
size < Kb -> {
|
||||||
|
context.getString(
|
||||||
|
R.string.memory_formatted,
|
||||||
|
size.hundredths,
|
||||||
|
context.getString(R.string.memory_byte_shorthand)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
size < Mb -> {
|
||||||
|
context.getString(
|
||||||
|
R.string.memory_formatted,
|
||||||
|
if (roundUp) ceil(size / Kb) else (size / Kb).hundredths,
|
||||||
|
context.getString(R.string.memory_kilobyte)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
size < Gb -> {
|
||||||
|
context.getString(
|
||||||
|
R.string.memory_formatted,
|
||||||
|
if (roundUp) ceil(size / Mb) else (size / Mb).hundredths,
|
||||||
|
context.getString(R.string.memory_megabyte)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
size < Tb -> {
|
||||||
|
context.getString(
|
||||||
|
R.string.memory_formatted,
|
||||||
|
if (roundUp) ceil(size / Gb) else (size / Gb).hundredths,
|
||||||
|
context.getString(R.string.memory_gigabyte)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
size < Pb -> {
|
||||||
|
context.getString(
|
||||||
|
R.string.memory_formatted,
|
||||||
|
if (roundUp) ceil(size / Tb) else (size / Tb).hundredths,
|
||||||
|
context.getString(R.string.memory_terabyte)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
size < Eb -> {
|
||||||
|
context.getString(
|
||||||
|
R.string.memory_formatted,
|
||||||
|
if (roundUp) ceil(size / Pb) else (size / Pb).hundredths,
|
||||||
|
context.getString(R.string.memory_petabyte)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
context.getString(
|
||||||
|
R.string.memory_formatted,
|
||||||
|
if (roundUp) ceil(size / Eb) else (size / Eb).hundredths,
|
||||||
|
context.getString(R.string.memory_exabyte)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val totalMemory: Float
|
||||||
|
get() {
|
||||||
|
val memInfo = ActivityManager.MemoryInfo()
|
||||||
|
with(context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager) {
|
||||||
|
getMemoryInfo(memInfo)
|
||||||
|
}
|
||||||
|
|
||||||
|
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
|
||||||
|
memInfo.advertisedMem.toFloat()
|
||||||
|
} else {
|
||||||
|
memInfo.totalMem.toFloat()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isLessThan(minimum: Int, size: Float): Boolean =
|
||||||
|
when (size) {
|
||||||
|
Kb -> totalMemory < Mb && totalMemory < minimum
|
||||||
|
Mb -> totalMemory < Gb && (totalMemory / Mb) < minimum
|
||||||
|
Gb -> totalMemory < Tb && (totalMemory / Gb) < minimum
|
||||||
|
Tb -> totalMemory < Pb && (totalMemory / Tb) < minimum
|
||||||
|
Pb -> totalMemory < Eb && (totalMemory / Pb) < minimum
|
||||||
|
Eb -> totalMemory / Eb < minimum
|
||||||
|
else -> totalMemory < Kb && totalMemory < minimum
|
||||||
|
}
|
||||||
|
|
||||||
|
// Devices are unlikely to have 0.5GB increments of memory so we'll just round up to account for
|
||||||
|
// the potential error created by memInfo.totalMem
|
||||||
|
fun getDeviceRAM(): String = bytesToSizeUnit(totalMemory, true)
|
||||||
|
}
|
|
@ -28,6 +28,7 @@ add_library(citra-android SHARED
|
||||||
ndk_motion.cpp
|
ndk_motion.cpp
|
||||||
ndk_motion.h
|
ndk_motion.h
|
||||||
system_save_game.cpp
|
system_save_game.cpp
|
||||||
|
native_log.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
target_link_libraries(citra-android PRIVATE audio_core citra_common citra_core input_common network)
|
target_link_libraries(citra-android PRIVATE audio_core citra_common citra_core input_common network)
|
||||||
|
|
30
src/android/app/src/main/jni/native_log.cpp
Normal file
30
src/android/app/src/main/jni/native_log.cpp
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include <common/logging/log.h>
|
||||||
|
#include <jni.h>
|
||||||
|
#include "android_common/android_common.h"
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
|
||||||
|
void Java_org_citra_citra_1emu_utils_Log_debug(JNIEnv* env, jobject obj, jstring jmessage) {
|
||||||
|
LOG_DEBUG(Frontend, "{}", GetJString(env, jmessage));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Java_org_citra_citra_1emu_utils_Log_warning(JNIEnv* env, jobject obj, jstring jmessage) {
|
||||||
|
LOG_WARNING(Frontend, "{}", GetJString(env, jmessage));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Java_org_citra_citra_1emu_utils_Log_info(JNIEnv* env, jobject obj, jstring jmessage) {
|
||||||
|
LOG_INFO(Frontend, "{}", GetJString(env, jmessage));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Java_org_citra_citra_1emu_utils_Log_error(JNIEnv* env, jobject obj, jstring jmessage) {
|
||||||
|
LOG_ERROR(Frontend, "{}", GetJString(env, jmessage));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Java_org_citra_citra_1emu_utils_Log_critical(JNIEnv* env, jobject obj, jstring jmessage) {
|
||||||
|
LOG_CRITICAL(Frontend, "{}", GetJString(env, jmessage));
|
||||||
|
}
|
||||||
|
|
||||||
|
} // extern "C"
|
|
@ -442,6 +442,17 @@
|
||||||
<string name="cia_install_error_encrypted">\"%s\" must be decrypted before being used with Citra.\n A real 3DS is required</string>
|
<string name="cia_install_error_encrypted">\"%s\" must be decrypted before being used with Citra.\n A real 3DS is required</string>
|
||||||
<string name="cia_install_error_unknown">An unknown error occurred while installing \"%s\".\n Please see the log for more details</string>
|
<string name="cia_install_error_unknown">An unknown error occurred while installing \"%s\".\n Please see the log for more details</string>
|
||||||
|
|
||||||
|
<!-- Memory Sizes -->
|
||||||
|
<string name="memory_formatted">%1$s %2$s</string>
|
||||||
|
<string name="memory_byte">Byte</string>
|
||||||
|
<string name="memory_byte_shorthand">B</string>
|
||||||
|
<string name="memory_kilobyte">KB</string>
|
||||||
|
<string name="memory_megabyte">MB</string>
|
||||||
|
<string name="memory_gigabyte">GB</string>
|
||||||
|
<string name="memory_terabyte">TB</string>
|
||||||
|
<string name="memory_petabyte">PB</string>
|
||||||
|
<string name="memory_exabyte">EB</string>
|
||||||
|
|
||||||
<!-- Theme Modes -->
|
<!-- Theme Modes -->
|
||||||
<string name="change_theme_mode">Change Theme Mode</string>
|
<string name="change_theme_mode">Change Theme Mode</string>
|
||||||
<string name="theme_mode_follow_system">Follow System</string>
|
<string name="theme_mode_follow_system">Follow System</string>
|
||||||
|
|
7
src/android/app/src/main/res/xml/game_mode_config.xml
Normal file
7
src/android/app/src/main/res/xml/game_mode_config.xml
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<game-mode-config
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:supportsBatteryGameMode="true"
|
||||||
|
android:supportsPerformanceGameMode="true"
|
||||||
|
android:allowGameDownscaling="false"
|
||||||
|
android:allowGameFpsOverride="false"/>
|
|
@ -4,10 +4,10 @@
|
||||||
|
|
||||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||||
plugins {
|
plugins {
|
||||||
id("com.android.application") version "8.1.2" apply false
|
id("com.android.application") version "8.2.1" apply false
|
||||||
id("com.android.library") version "8.1.2" apply false
|
id("com.android.library") version "8.2.1" apply false
|
||||||
id("org.jetbrains.kotlin.android") version "1.8.21" apply false
|
id("org.jetbrains.kotlin.android") version "1.9.22" apply false
|
||||||
id("org.jetbrains.kotlin.plugin.serialization") version "1.8.21"
|
id("org.jetbrains.kotlin.plugin.serialization") version "1.9.22"
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.register("clean").configure {
|
tasks.register("clean").configure {
|
||||||
|
@ -19,6 +19,6 @@ buildscript {
|
||||||
google()
|
google()
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath("androidx.navigation:navigation-safe-args-gradle-plugin:2.7.5")
|
classpath("androidx.navigation:navigation-safe-args-gradle-plugin:2.7.6")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip
|
||||||
|
|
|
@ -316,7 +316,7 @@ struct SourceStatus {
|
||||||
u16_le sync_count; ///< Is set by the DSP to the value of SourceConfiguration::sync_count
|
u16_le sync_count; ///< Is set by the DSP to the value of SourceConfiguration::sync_count
|
||||||
u32_dsp buffer_position; ///< Number of samples into the current buffer
|
u32_dsp buffer_position; ///< Number of samples into the current buffer
|
||||||
u16_le current_buffer_id; ///< Updated when a buffer finishes playing
|
u16_le current_buffer_id; ///< Updated when a buffer finishes playing
|
||||||
INSERT_PADDING_DSPWORDS(1);
|
u16_le last_buffer_id; ///< Updated when all buffers in the queue finish playing
|
||||||
};
|
};
|
||||||
|
|
||||||
Status status[num_sources];
|
Status status[num_sources];
|
||||||
|
|
|
@ -298,9 +298,9 @@ void Source::ParseConfig(SourceConfiguration::Configuration& config,
|
||||||
b.buffer_id,
|
b.buffer_id,
|
||||||
state.mono_or_stereo,
|
state.mono_or_stereo,
|
||||||
state.format,
|
state.format,
|
||||||
true,
|
true, // from_queue
|
||||||
{}, // 0 in u32_dsp
|
0, // play_position
|
||||||
false,
|
false, // has_played
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
LOG_TRACE(Audio_DSP, "enqueuing queued {} addr={:#010x} len={} id={}", i,
|
LOG_TRACE(Audio_DSP, "enqueuing queued {} addr={:#010x} len={} id={}", i,
|
||||||
|
@ -321,16 +321,19 @@ void Source::ParseConfig(SourceConfiguration::Configuration& config,
|
||||||
void Source::GenerateFrame() {
|
void Source::GenerateFrame() {
|
||||||
current_frame.fill({});
|
current_frame.fill({});
|
||||||
|
|
||||||
if (state.current_buffer.empty() && !DequeueBuffer()) {
|
if (state.current_buffer.empty()) {
|
||||||
|
// TODO(SachinV): Should dequeue happen at the end of the frame generation?
|
||||||
|
if (DequeueBuffer()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
state.enabled = false;
|
state.enabled = false;
|
||||||
state.buffer_update = true;
|
state.buffer_update = true;
|
||||||
|
state.last_buffer_id = state.current_buffer_id;
|
||||||
state.current_buffer_id = 0;
|
state.current_buffer_id = 0;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::size_t frame_position = 0;
|
std::size_t frame_position = 0;
|
||||||
|
|
||||||
state.current_sample_number = state.next_sample_number;
|
|
||||||
while (frame_position < current_frame.size()) {
|
while (frame_position < current_frame.size()) {
|
||||||
if (state.current_buffer.empty() && !DequeueBuffer()) {
|
if (state.current_buffer.empty() && !DequeueBuffer()) {
|
||||||
break;
|
break;
|
||||||
|
@ -357,7 +360,7 @@ void Source::GenerateFrame() {
|
||||||
}
|
}
|
||||||
// TODO(jroweboy): Keep track of frame_position independently so that it doesn't lose precision
|
// TODO(jroweboy): Keep track of frame_position independently so that it doesn't lose precision
|
||||||
// over time
|
// over time
|
||||||
state.next_sample_number += static_cast<u32>(frame_position * state.rate_multiplier);
|
state.current_sample_number += static_cast<u32>(frame_position * state.rate_multiplier);
|
||||||
|
|
||||||
state.filters.ProcessFrame(current_frame);
|
state.filters.ProcessFrame(current_frame);
|
||||||
}
|
}
|
||||||
|
@ -408,9 +411,9 @@ bool Source::DequeueBuffer() {
|
||||||
|
|
||||||
// the first playthrough starts at play_position, loops start at the beginning of the buffer
|
// the first playthrough starts at play_position, loops start at the beginning of the buffer
|
||||||
state.current_sample_number = (!buf.has_played) ? buf.play_position : 0;
|
state.current_sample_number = (!buf.has_played) ? buf.play_position : 0;
|
||||||
state.next_sample_number = state.current_sample_number;
|
|
||||||
state.current_buffer_physical_address = buf.physical_address;
|
state.current_buffer_physical_address = buf.physical_address;
|
||||||
state.current_buffer_id = buf.buffer_id;
|
state.current_buffer_id = buf.buffer_id;
|
||||||
|
state.last_buffer_id = 0;
|
||||||
state.buffer_update = buf.from_queue && !buf.has_played;
|
state.buffer_update = buf.from_queue && !buf.has_played;
|
||||||
|
|
||||||
if (buf.is_looping) {
|
if (buf.is_looping) {
|
||||||
|
@ -418,8 +421,17 @@ bool Source::DequeueBuffer() {
|
||||||
state.input_queue.push(buf);
|
state.input_queue.push(buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
LOG_TRACE(Audio_DSP, "source_id={} buffer_id={} from_queue={} current_buffer.size()={}",
|
// Because our interpolation consumes samples instead of using an index,
|
||||||
source_id, buf.buffer_id, buf.from_queue, state.current_buffer.size());
|
// let's just consume the samples up to the current sample number.
|
||||||
|
state.current_buffer.erase(
|
||||||
|
state.current_buffer.begin(),
|
||||||
|
std::next(state.current_buffer.begin(), state.current_sample_number));
|
||||||
|
|
||||||
|
LOG_TRACE(Audio_DSP,
|
||||||
|
"source_id={} buffer_id={} from_queue={} current_buffer.size()={}, "
|
||||||
|
"buf.has_played={}, buf.play_position={}",
|
||||||
|
source_id, buf.buffer_id, buf.from_queue, state.current_buffer.size(), buf.has_played,
|
||||||
|
buf.play_position);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -432,9 +444,10 @@ SourceStatus::Status Source::GetCurrentStatus() {
|
||||||
ret.is_enabled = state.enabled;
|
ret.is_enabled = state.enabled;
|
||||||
ret.current_buffer_id_dirty = state.buffer_update ? 1 : 0;
|
ret.current_buffer_id_dirty = state.buffer_update ? 1 : 0;
|
||||||
state.buffer_update = false;
|
state.buffer_update = false;
|
||||||
ret.current_buffer_id = state.current_buffer_id;
|
|
||||||
ret.buffer_position = state.current_sample_number;
|
|
||||||
ret.sync_count = state.sync_count;
|
ret.sync_count = state.sync_count;
|
||||||
|
ret.buffer_position = state.current_sample_number;
|
||||||
|
ret.current_buffer_id = state.current_buffer_id;
|
||||||
|
ret.last_buffer_id = state.last_buffer_id;
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
|
@ -87,8 +87,8 @@ private:
|
||||||
Format format;
|
Format format;
|
||||||
|
|
||||||
bool from_queue;
|
bool from_queue;
|
||||||
u32_dsp play_position; // = 0;
|
u32 play_position; // = 0;
|
||||||
bool has_played; // = false;
|
bool has_played; // = false;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
template <class Archive>
|
template <class Archive>
|
||||||
|
@ -136,14 +136,14 @@ private:
|
||||||
// Current buffer
|
// Current buffer
|
||||||
|
|
||||||
u32 current_sample_number = 0;
|
u32 current_sample_number = 0;
|
||||||
u32 next_sample_number = 0;
|
|
||||||
PAddr current_buffer_physical_address = 0;
|
PAddr current_buffer_physical_address = 0;
|
||||||
AudioInterp::StereoBuffer16 current_buffer = {};
|
AudioInterp::StereoBuffer16 current_buffer = {};
|
||||||
|
|
||||||
// buffer_id state
|
// buffer_id state
|
||||||
|
|
||||||
bool buffer_update = false;
|
bool buffer_update = false;
|
||||||
u32 current_buffer_id = 0;
|
u16 last_buffer_id = 0;
|
||||||
|
u16 current_buffer_id = 0;
|
||||||
|
|
||||||
// Decoding state
|
// Decoding state
|
||||||
|
|
||||||
|
@ -170,7 +170,6 @@ private:
|
||||||
ar& mono_or_stereo;
|
ar& mono_or_stereo;
|
||||||
ar& format;
|
ar& format;
|
||||||
ar& current_sample_number;
|
ar& current_sample_number;
|
||||||
ar& next_sample_number;
|
|
||||||
ar& current_buffer_physical_address;
|
ar& current_buffer_physical_address;
|
||||||
ar& current_buffer;
|
ar& current_buffer;
|
||||||
ar& buffer_update;
|
ar& buffer_update;
|
||||||
|
|
|
@ -179,6 +179,8 @@ add_executable(citra-qt
|
||||||
qt_image_interface.h
|
qt_image_interface.h
|
||||||
util/clickable_label.cpp
|
util/clickable_label.cpp
|
||||||
util/clickable_label.h
|
util/clickable_label.h
|
||||||
|
util/graphics_device_info.cpp
|
||||||
|
util/graphics_device_info.h
|
||||||
util/sequence_dialog/sequence_dialog.cpp
|
util/sequence_dialog/sequence_dialog.cpp
|
||||||
util/sequence_dialog/sequence_dialog.h
|
util/sequence_dialog/sequence_dialog.h
|
||||||
util/spinbox.cpp
|
util/spinbox.cpp
|
||||||
|
@ -187,13 +189,6 @@ add_executable(citra-qt
|
||||||
util/util.h
|
util/util.h
|
||||||
)
|
)
|
||||||
|
|
||||||
if (ENABLE_VULKAN)
|
|
||||||
target_sources(citra-qt PRIVATE
|
|
||||||
util/vk_device_info.cpp
|
|
||||||
util/vk_device_info.h
|
|
||||||
)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
file(GLOB COMPAT_LIST
|
file(GLOB COMPAT_LIST
|
||||||
${PROJECT_BINARY_DIR}/dist/compatibility_list/compatibility_list.qrc
|
${PROJECT_BINARY_DIR}/dist/compatibility_list/compatibility_list.qrc
|
||||||
${PROJECT_BINARY_DIR}/dist/compatibility_list/compatibility_list.json)
|
${PROJECT_BINARY_DIR}/dist/compatibility_list/compatibility_list.json)
|
||||||
|
|
|
@ -54,7 +54,7 @@ const std::array<std::array<int, 5>, Settings::NativeAnalog::NumAnalogs> Config:
|
||||||
// This must be in alphabetical order according to action name as it must have the same order as
|
// This must be in alphabetical order according to action name as it must have the same order as
|
||||||
// UISetting::values.shortcuts, which is alphabetically ordered.
|
// UISetting::values.shortcuts, which is alphabetically ordered.
|
||||||
// clang-format off
|
// clang-format off
|
||||||
const std::array<UISettings::Shortcut, 30> Config::default_hotkeys {{
|
const std::array<UISettings::Shortcut, 35> Config::default_hotkeys {{
|
||||||
{QStringLiteral("Advance Frame"), QStringLiteral("Main Window"), {QStringLiteral(""), Qt::ApplicationShortcut}},
|
{QStringLiteral("Advance Frame"), QStringLiteral("Main Window"), {QStringLiteral(""), Qt::ApplicationShortcut}},
|
||||||
{QStringLiteral("Audio Mute/Unmute"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+M"), Qt::WindowShortcut}},
|
{QStringLiteral("Audio Mute/Unmute"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+M"), Qt::WindowShortcut}},
|
||||||
{QStringLiteral("Audio Volume Down"), QStringLiteral("Main Window"), {QStringLiteral(""), Qt::WindowShortcut}},
|
{QStringLiteral("Audio Volume Down"), QStringLiteral("Main Window"), {QStringLiteral(""), Qt::WindowShortcut}},
|
||||||
|
@ -71,6 +71,11 @@ const std::array<UISettings::Shortcut, 30> Config::default_hotkeys {{
|
||||||
{QStringLiteral("Load Amiibo"), QStringLiteral("Main Window"), {QStringLiteral("F2"), Qt::WidgetWithChildrenShortcut}},
|
{QStringLiteral("Load Amiibo"), QStringLiteral("Main Window"), {QStringLiteral("F2"), Qt::WidgetWithChildrenShortcut}},
|
||||||
{QStringLiteral("Load File"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+O"), Qt::WidgetWithChildrenShortcut}},
|
{QStringLiteral("Load File"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+O"), Qt::WidgetWithChildrenShortcut}},
|
||||||
{QStringLiteral("Load from Newest Slot"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+V"), Qt::WindowShortcut}},
|
{QStringLiteral("Load from Newest Slot"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+V"), Qt::WindowShortcut}},
|
||||||
|
{QStringLiteral("Multiplayer Browse Public Game Lobby"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+B"), Qt::ApplicationShortcut}},
|
||||||
|
{QStringLiteral("Multiplayer Create Room"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+N"), Qt::ApplicationShortcut}},
|
||||||
|
{QStringLiteral("Multiplayer Direct Connect to Room"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+Shift"), Qt::ApplicationShortcut}},
|
||||||
|
{QStringLiteral("Multiplayer Leave Room"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+L"), Qt::ApplicationShortcut}},
|
||||||
|
{QStringLiteral("Multiplayer Show Current Room"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+R"), Qt::ApplicationShortcut}},
|
||||||
{QStringLiteral("Remove Amiibo"), QStringLiteral("Main Window"), {QStringLiteral("F3"), Qt::ApplicationShortcut}},
|
{QStringLiteral("Remove Amiibo"), QStringLiteral("Main Window"), {QStringLiteral("F3"), Qt::ApplicationShortcut}},
|
||||||
{QStringLiteral("Restart Emulation"), QStringLiteral("Main Window"), {QStringLiteral("F6"), Qt::WindowShortcut}},
|
{QStringLiteral("Restart Emulation"), QStringLiteral("Main Window"), {QStringLiteral("F6"), Qt::WindowShortcut}},
|
||||||
{QStringLiteral("Rotate Screens Upright"), QStringLiteral("Main Window"), {QStringLiteral("F8"), Qt::WindowShortcut}},
|
{QStringLiteral("Rotate Screens Upright"), QStringLiteral("Main Window"), {QStringLiteral("F8"), Qt::WindowShortcut}},
|
||||||
|
@ -557,6 +562,15 @@ void Config::ReadMultiplayerValues() {
|
||||||
UISettings::values.game_id = ReadSetting(QStringLiteral("game_id"), 0).toULongLong();
|
UISettings::values.game_id = ReadSetting(QStringLiteral("game_id"), 0).toULongLong();
|
||||||
UISettings::values.room_description =
|
UISettings::values.room_description =
|
||||||
ReadSetting(QStringLiteral("room_description"), QString{}).toString();
|
ReadSetting(QStringLiteral("room_description"), QString{}).toString();
|
||||||
|
UISettings::values.multiplayer_filter_text =
|
||||||
|
ReadSetting(QStringLiteral("multiplayer_filter_text"), QString{}).toString();
|
||||||
|
UISettings::values.multiplayer_filter_games_owned =
|
||||||
|
ReadSetting(QStringLiteral("multiplayer_filter_games_owned"), false).toBool();
|
||||||
|
UISettings::values.multiplayer_filter_hide_empty =
|
||||||
|
ReadSetting(QStringLiteral("multiplayer_filter_hide_empty"), false).toBool();
|
||||||
|
UISettings::values.multiplayer_filter_hide_full =
|
||||||
|
ReadSetting(QStringLiteral("multiplayer_filter_hide_full"), false).toBool();
|
||||||
|
|
||||||
// Read ban list back
|
// Read ban list back
|
||||||
int size = qt_config->beginReadArray(QStringLiteral("username_ban_list"));
|
int size = qt_config->beginReadArray(QStringLiteral("username_ban_list"));
|
||||||
UISettings::values.ban_list.first.resize(size);
|
UISettings::values.ban_list.first.resize(size);
|
||||||
|
@ -1074,6 +1088,15 @@ void Config::SaveMultiplayerValues() {
|
||||||
WriteSetting(QStringLiteral("game_id"), UISettings::values.game_id, 0);
|
WriteSetting(QStringLiteral("game_id"), UISettings::values.game_id, 0);
|
||||||
WriteSetting(QStringLiteral("room_description"), UISettings::values.room_description,
|
WriteSetting(QStringLiteral("room_description"), UISettings::values.room_description,
|
||||||
QString{});
|
QString{});
|
||||||
|
WriteSetting(QStringLiteral("multiplayer_filter_text"),
|
||||||
|
UISettings::values.multiplayer_filter_text, QString{});
|
||||||
|
WriteSetting(QStringLiteral("multiplayer_filter_games_owned"),
|
||||||
|
UISettings::values.multiplayer_filter_games_owned, false);
|
||||||
|
WriteSetting(QStringLiteral("multiplayer_filter_hide_empty"),
|
||||||
|
UISettings::values.multiplayer_filter_hide_empty, false);
|
||||||
|
WriteSetting(QStringLiteral("multiplayer_filter_hide_full"),
|
||||||
|
UISettings::values.multiplayer_filter_hide_full, false);
|
||||||
|
|
||||||
// Write ban list
|
// Write ban list
|
||||||
qt_config->beginWriteArray(QStringLiteral("username_ban_list"));
|
qt_config->beginWriteArray(QStringLiteral("username_ban_list"));
|
||||||
for (std::size_t i = 0; i < UISettings::values.ban_list.first.size(); ++i) {
|
for (std::size_t i = 0; i < UISettings::values.ban_list.first.size(); ++i) {
|
||||||
|
|
|
@ -26,7 +26,7 @@ public:
|
||||||
|
|
||||||
static const std::array<int, Settings::NativeButton::NumButtons> default_buttons;
|
static const std::array<int, Settings::NativeButton::NumButtons> default_buttons;
|
||||||
static const std::array<std::array<int, 5>, Settings::NativeAnalog::NumAnalogs> default_analogs;
|
static const std::array<std::array<int, 5>, Settings::NativeAnalog::NumAnalogs> default_analogs;
|
||||||
static const std::array<UISettings::Shortcut, 30> default_hotkeys;
|
static const std::array<UISettings::Shortcut, 35> default_hotkeys;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void Initialize(const std::string& config_name);
|
void Initialize(const std::string& config_name);
|
||||||
|
|
|
@ -23,14 +23,16 @@
|
||||||
#include "ui_configure.h"
|
#include "ui_configure.h"
|
||||||
|
|
||||||
ConfigureDialog::ConfigureDialog(QWidget* parent, HotkeyRegistry& registry_, Core::System& system_,
|
ConfigureDialog::ConfigureDialog(QWidget* parent, HotkeyRegistry& registry_, Core::System& system_,
|
||||||
std::span<const QString> physical_devices, bool enable_web_config)
|
QString gl_renderer, std::span<const QString> physical_devices,
|
||||||
|
bool enable_web_config)
|
||||||
: QDialog(parent), ui{std::make_unique<Ui::ConfigureDialog>()}, registry{registry_},
|
: QDialog(parent), ui{std::make_unique<Ui::ConfigureDialog>()}, registry{registry_},
|
||||||
system{system_}, is_powered_on{system.IsPoweredOn()},
|
system{system_}, is_powered_on{system.IsPoweredOn()},
|
||||||
general_tab{std::make_unique<ConfigureGeneral>(this)},
|
general_tab{std::make_unique<ConfigureGeneral>(this)},
|
||||||
system_tab{std::make_unique<ConfigureSystem>(system, this)},
|
system_tab{std::make_unique<ConfigureSystem>(system, this)},
|
||||||
input_tab{std::make_unique<ConfigureInput>(this)},
|
input_tab{std::make_unique<ConfigureInput>(this)},
|
||||||
hotkeys_tab{std::make_unique<ConfigureHotkeys>(this)},
|
hotkeys_tab{std::make_unique<ConfigureHotkeys>(this)},
|
||||||
graphics_tab{std::make_unique<ConfigureGraphics>(physical_devices, is_powered_on, this)},
|
graphics_tab{
|
||||||
|
std::make_unique<ConfigureGraphics>(gl_renderer, physical_devices, is_powered_on, this)},
|
||||||
enhancements_tab{std::make_unique<ConfigureEnhancements>(this)},
|
enhancements_tab{std::make_unique<ConfigureEnhancements>(this)},
|
||||||
audio_tab{std::make_unique<ConfigureAudio>(is_powered_on, this)},
|
audio_tab{std::make_unique<ConfigureAudio>(is_powered_on, this)},
|
||||||
camera_tab{std::make_unique<ConfigureCamera>(this)},
|
camera_tab{std::make_unique<ConfigureCamera>(this)},
|
||||||
|
|
|
@ -37,7 +37,7 @@ class ConfigureDialog : public QDialog {
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit ConfigureDialog(QWidget* parent, HotkeyRegistry& registry, Core::System& system,
|
explicit ConfigureDialog(QWidget* parent, HotkeyRegistry& registry, Core::System& system,
|
||||||
std::span<const QString> physical_devices,
|
QString gl_renderer, std::span<const QString> physical_devices,
|
||||||
bool enable_web_config = true);
|
bool enable_web_config = true);
|
||||||
~ConfigureDialog() override;
|
~ConfigureDialog() override;
|
||||||
|
|
||||||
|
|
|
@ -12,17 +12,13 @@
|
||||||
#include "video_core/renderer_vulkan/vk_instance.h"
|
#include "video_core/renderer_vulkan/vk_instance.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
ConfigureGraphics::ConfigureGraphics(std::span<const QString> physical_devices, bool is_powered_on,
|
ConfigureGraphics::ConfigureGraphics(QString gl_renderer, std::span<const QString> physical_devices,
|
||||||
QWidget* parent)
|
bool is_powered_on, QWidget* parent)
|
||||||
: QWidget(parent), ui(std::make_unique<Ui::ConfigureGraphics>()) {
|
: QWidget(parent), ui(std::make_unique<Ui::ConfigureGraphics>()) {
|
||||||
ui->setupUi(this);
|
ui->setupUi(this);
|
||||||
|
|
||||||
SetupPerGameUI();
|
SetupPerGameUI();
|
||||||
|
|
||||||
for (const QString& name : physical_devices) {
|
|
||||||
ui->physical_device_combo->addItem(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
ui->graphics_api_combo->setEnabled(!is_powered_on);
|
ui->graphics_api_combo->setEnabled(!is_powered_on);
|
||||||
ui->physical_device_combo->setEnabled(!is_powered_on);
|
ui->physical_device_combo->setEnabled(!is_powered_on);
|
||||||
ui->toggle_async_shaders->setEnabled(!is_powered_on);
|
ui->toggle_async_shaders->setEnabled(!is_powered_on);
|
||||||
|
@ -37,11 +33,15 @@ ConfigureGraphics::ConfigureGraphics(std::span<const QString> physical_devices,
|
||||||
graphics_api_combo_model->item(static_cast<u32>(Settings::GraphicsAPI::Software));
|
graphics_api_combo_model->item(static_cast<u32>(Settings::GraphicsAPI::Software));
|
||||||
software_item->setFlags(software_item->flags() & ~Qt::ItemIsEnabled);
|
software_item->setFlags(software_item->flags() & ~Qt::ItemIsEnabled);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifndef ENABLE_OPENGL
|
#ifndef ENABLE_OPENGL
|
||||||
const auto opengl_item =
|
const auto opengl_item =
|
||||||
graphics_api_combo_model->item(static_cast<u32>(Settings::GraphicsAPI::OpenGL));
|
graphics_api_combo_model->item(static_cast<u32>(Settings::GraphicsAPI::OpenGL));
|
||||||
opengl_item->setFlags(opengl_item->flags() & ~Qt::ItemIsEnabled);
|
opengl_item->setFlags(opengl_item->flags() & ~Qt::ItemIsEnabled);
|
||||||
|
#else
|
||||||
|
ui->opengl_renderer_name_label->setText(gl_renderer);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifndef ENABLE_VULKAN
|
#ifndef ENABLE_VULKAN
|
||||||
const auto vulkan_item =
|
const auto vulkan_item =
|
||||||
graphics_api_combo_model->item(static_cast<u32>(Settings::GraphicsAPI::Vulkan));
|
graphics_api_combo_model->item(static_cast<u32>(Settings::GraphicsAPI::Vulkan));
|
||||||
|
@ -54,6 +54,10 @@ ConfigureGraphics::ConfigureGraphics(std::span<const QString> physical_devices,
|
||||||
|
|
||||||
ui->physical_device_combo->setVisible(false);
|
ui->physical_device_combo->setVisible(false);
|
||||||
ui->spirv_shader_gen->setVisible(false);
|
ui->spirv_shader_gen->setVisible(false);
|
||||||
|
} else {
|
||||||
|
for (const QString& name : physical_devices) {
|
||||||
|
ui->physical_device_combo->addItem(name);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
@ -202,24 +206,24 @@ void ConfigureGraphics::SetupPerGameUI() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void ConfigureGraphics::SetPhysicalDeviceComboVisibility(int index) {
|
void ConfigureGraphics::SetPhysicalDeviceComboVisibility(int index) {
|
||||||
bool is_visible{};
|
Settings::GraphicsAPI effective_api{};
|
||||||
|
|
||||||
// When configuring per-game the physical device combo should be
|
// When configuring per-game the physical device combo should be
|
||||||
// shown either when the global api is used and that is Vulkan or
|
// shown either when the global api is used and that is Vulkan or
|
||||||
// Vulkan is set as the per-game api.
|
// Vulkan is set as the per-game api.
|
||||||
if (!Settings::IsConfiguringGlobal()) {
|
if (!Settings::IsConfiguringGlobal()) {
|
||||||
const auto global_graphics_api = Settings::values.graphics_api.GetValue(true);
|
|
||||||
const bool using_global = index == 0;
|
const bool using_global = index == 0;
|
||||||
if (!using_global) {
|
if (using_global) {
|
||||||
index -= ConfigurationShared::USE_GLOBAL_OFFSET;
|
effective_api = Settings::values.graphics_api.GetValue(true);
|
||||||
|
} else {
|
||||||
|
effective_api =
|
||||||
|
static_cast<Settings::GraphicsAPI>(index - ConfigurationShared::USE_GLOBAL_OFFSET);
|
||||||
}
|
}
|
||||||
const auto graphics_api = static_cast<Settings::GraphicsAPI>(index);
|
|
||||||
is_visible = (using_global && global_graphics_api == Settings::GraphicsAPI::Vulkan) ||
|
|
||||||
graphics_api == Settings::GraphicsAPI::Vulkan;
|
|
||||||
} else {
|
} else {
|
||||||
const auto graphics_api = static_cast<Settings::GraphicsAPI>(index);
|
effective_api = static_cast<Settings::GraphicsAPI>(index);
|
||||||
is_visible = graphics_api == Settings::GraphicsAPI::Vulkan;
|
|
||||||
}
|
}
|
||||||
ui->physical_device_group->setVisible(is_visible);
|
|
||||||
ui->spirv_shader_gen->setVisible(is_visible);
|
ui->physical_device_group->setVisible(effective_api == Settings::GraphicsAPI::Vulkan);
|
||||||
|
ui->spirv_shader_gen->setVisible(effective_api == Settings::GraphicsAPI::Vulkan);
|
||||||
|
ui->opengl_renderer_group->setVisible(effective_api == Settings::GraphicsAPI::OpenGL);
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,8 +21,8 @@ class ConfigureGraphics : public QWidget {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit ConfigureGraphics(std::span<const QString> physical_devices, bool is_powered_on,
|
explicit ConfigureGraphics(QString gl_renderer, std::span<const QString> physical_devices,
|
||||||
QWidget* parent = nullptr);
|
bool is_powered_on, QWidget* parent = nullptr);
|
||||||
~ConfigureGraphics() override;
|
~ConfigureGraphics() override;
|
||||||
|
|
||||||
void ApplyConfiguration();
|
void ApplyConfiguration();
|
||||||
|
|
|
@ -101,6 +101,34 @@
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QWidget" name="opengl_renderer_group" native="true">
|
||||||
|
<layout class="QHBoxLayout" name="opengl_renderer_group_2">
|
||||||
|
<property name="leftMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="topMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="rightMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="bottomMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="opengl_renderer_label">
|
||||||
|
<property name="text">
|
||||||
|
<string>OpenGL Renderer</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="opengl_renderer_name_label"/>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QCheckBox" name="spirv_shader_gen">
|
<widget class="QCheckBox" name="spirv_shader_gen">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
|
|
|
@ -24,7 +24,8 @@
|
||||||
#include "ui_configure_per_game.h"
|
#include "ui_configure_per_game.h"
|
||||||
|
|
||||||
ConfigurePerGame::ConfigurePerGame(QWidget* parent, u64 title_id_, const QString& file_name,
|
ConfigurePerGame::ConfigurePerGame(QWidget* parent, u64 title_id_, const QString& file_name,
|
||||||
std::span<const QString> physical_devices, Core::System& system_)
|
QString gl_renderer, std::span<const QString> physical_devices,
|
||||||
|
Core::System& system_)
|
||||||
: QDialog(parent), ui(std::make_unique<Ui::ConfigurePerGame>()),
|
: QDialog(parent), ui(std::make_unique<Ui::ConfigurePerGame>()),
|
||||||
filename{file_name.toStdString()}, title_id{title_id_}, system{system_} {
|
filename{file_name.toStdString()}, title_id{title_id_}, system{system_} {
|
||||||
const auto config_file_name = title_id == 0 ? std::string(FileUtil::GetFilename(filename))
|
const auto config_file_name = title_id == 0 ? std::string(FileUtil::GetFilename(filename))
|
||||||
|
@ -35,7 +36,8 @@ ConfigurePerGame::ConfigurePerGame(QWidget* parent, u64 title_id_, const QString
|
||||||
audio_tab = std::make_unique<ConfigureAudio>(is_powered_on, this);
|
audio_tab = std::make_unique<ConfigureAudio>(is_powered_on, this);
|
||||||
general_tab = std::make_unique<ConfigureGeneral>(this);
|
general_tab = std::make_unique<ConfigureGeneral>(this);
|
||||||
enhancements_tab = std::make_unique<ConfigureEnhancements>(this);
|
enhancements_tab = std::make_unique<ConfigureEnhancements>(this);
|
||||||
graphics_tab = std::make_unique<ConfigureGraphics>(physical_devices, is_powered_on, this);
|
graphics_tab =
|
||||||
|
std::make_unique<ConfigureGraphics>(gl_renderer, physical_devices, is_powered_on, this);
|
||||||
system_tab = std::make_unique<ConfigureSystem>(system, this);
|
system_tab = std::make_unique<ConfigureSystem>(system, this);
|
||||||
debug_tab = std::make_unique<ConfigureDebug>(is_powered_on, this);
|
debug_tab = std::make_unique<ConfigureDebug>(is_powered_on, this);
|
||||||
cheat_tab = std::make_unique<ConfigureCheats>(system.CheatEngine(), title_id, this);
|
cheat_tab = std::make_unique<ConfigureCheats>(system.CheatEngine(), title_id, this);
|
||||||
|
|
|
@ -38,7 +38,8 @@ class ConfigurePerGame : public QDialog {
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit ConfigurePerGame(QWidget* parent, u64 title_id_, const QString& file_name,
|
explicit ConfigurePerGame(QWidget* parent, u64 title_id_, const QString& file_name,
|
||||||
std::span<const QString> physical_devices, Core::System& system_);
|
QString gl_renderer, std::span<const QString> physical_devices,
|
||||||
|
Core::System& system_);
|
||||||
~ConfigurePerGame() override;
|
~ConfigurePerGame() override;
|
||||||
|
|
||||||
/// Loads all button configurations to settings file
|
/// Loads all button configurations to settings file
|
||||||
|
|
|
@ -31,7 +31,8 @@ void ConfigureUi::InitializeLanguageComboBox() {
|
||||||
locale.truncate(locale.lastIndexOf(QLatin1Char{'.'}));
|
locale.truncate(locale.lastIndexOf(QLatin1Char{'.'}));
|
||||||
locale.remove(0, locale.lastIndexOf(QLatin1Char{'/'}) + 1);
|
locale.remove(0, locale.lastIndexOf(QLatin1Char{'/'}) + 1);
|
||||||
const QString lang = QLocale::languageToString(QLocale(locale).language());
|
const QString lang = QLocale::languageToString(QLocale(locale).language());
|
||||||
ui->language_combobox->addItem(lang, locale);
|
const QString country = QLocale::territoryToString(QLocale(locale).territory());
|
||||||
|
ui->language_combobox->addItem(QStringLiteral("%1 (%2)").arg(lang, country), locale);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unlike other configuration changes, interface language changes need to be reflected on the
|
// Unlike other configuration changes, interface language changes need to be reflected on the
|
||||||
|
|
|
@ -64,7 +64,7 @@
|
||||||
#include "citra_qt/uisettings.h"
|
#include "citra_qt/uisettings.h"
|
||||||
#include "citra_qt/updater/updater.h"
|
#include "citra_qt/updater/updater.h"
|
||||||
#include "citra_qt/util/clickable_label.h"
|
#include "citra_qt/util/clickable_label.h"
|
||||||
#include "citra_qt/util/vk_device_info.h"
|
#include "citra_qt/util/graphics_device_info.h"
|
||||||
#include "common/arch.h"
|
#include "common/arch.h"
|
||||||
#include "common/common_paths.h"
|
#include "common/common_paths.h"
|
||||||
#include "common/detached_tasks.h"
|
#include "common/detached_tasks.h"
|
||||||
|
@ -270,6 +270,18 @@ GMainWindow::GMainWindow(Core::System& system_)
|
||||||
connect(&mouse_hide_timer, &QTimer::timeout, this, &GMainWindow::HideMouseCursor);
|
connect(&mouse_hide_timer, &QTimer::timeout, this, &GMainWindow::HideMouseCursor);
|
||||||
connect(ui->menubar, &QMenuBar::hovered, this, &GMainWindow::OnMouseActivity);
|
connect(ui->menubar, &QMenuBar::hovered, this, &GMainWindow::OnMouseActivity);
|
||||||
|
|
||||||
|
#ifdef ENABLE_OPENGL
|
||||||
|
gl_renderer = GetOpenGLRenderer();
|
||||||
|
#if defined(_WIN32)
|
||||||
|
if (gl_renderer.startsWith(QStringLiteral("D3D12"))) {
|
||||||
|
// OpenGLOn12 supports but does not yet advertise OpenGL 4.0+
|
||||||
|
// We can override the version here to allow Citra to work.
|
||||||
|
// TODO: Remove this when OpenGL 4.0+ is advertised.
|
||||||
|
qputenv("MESA_GL_VERSION_OVERRIDE", "4.6");
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef ENABLE_VULKAN
|
#ifdef ENABLE_VULKAN
|
||||||
physical_devices = GetVulkanPhysicalDevices();
|
physical_devices = GetVulkanPhysicalDevices();
|
||||||
if (physical_devices.empty()) {
|
if (physical_devices.empty()) {
|
||||||
|
@ -635,6 +647,13 @@ void GMainWindow::InitializeHotkeys() {
|
||||||
link_action_shortcut(ui->action_Advance_Frame, QStringLiteral("Advance Frame"));
|
link_action_shortcut(ui->action_Advance_Frame, QStringLiteral("Advance Frame"));
|
||||||
link_action_shortcut(ui->action_Load_from_Newest_Slot, QStringLiteral("Load from Newest Slot"));
|
link_action_shortcut(ui->action_Load_from_Newest_Slot, QStringLiteral("Load from Newest Slot"));
|
||||||
link_action_shortcut(ui->action_Save_to_Oldest_Slot, QStringLiteral("Save to Oldest Slot"));
|
link_action_shortcut(ui->action_Save_to_Oldest_Slot, QStringLiteral("Save to Oldest Slot"));
|
||||||
|
link_action_shortcut(ui->action_View_Lobby,
|
||||||
|
QStringLiteral("Multiplayer Browse Public Game Lobby"));
|
||||||
|
link_action_shortcut(ui->action_Start_Room, QStringLiteral("Multiplayer Create Room"));
|
||||||
|
link_action_shortcut(ui->action_Connect_To_Room,
|
||||||
|
QStringLiteral("Multiplayer Direct Connect to Room"));
|
||||||
|
link_action_shortcut(ui->action_Show_Room, QStringLiteral("Multiplayer Show Current Room"));
|
||||||
|
link_action_shortcut(ui->action_Leave_Room, QStringLiteral("Multiplayer Leave Room"));
|
||||||
|
|
||||||
const auto add_secondary_window_hotkey = [this](QKeySequence hotkey, const char* slot) {
|
const auto add_secondary_window_hotkey = [this](QKeySequence hotkey, const char* slot) {
|
||||||
// This action will fire specifically when secondary_window is in focus
|
// This action will fire specifically when secondary_window is in focus
|
||||||
|
@ -2158,7 +2177,7 @@ void GMainWindow::OnLoadState() {
|
||||||
void GMainWindow::OnConfigure() {
|
void GMainWindow::OnConfigure() {
|
||||||
game_list->SetDirectoryWatcherEnabled(false);
|
game_list->SetDirectoryWatcherEnabled(false);
|
||||||
Settings::SetConfiguringGlobal(true);
|
Settings::SetConfiguringGlobal(true);
|
||||||
ConfigureDialog configureDialog(this, hotkey_registry, system, physical_devices,
|
ConfigureDialog configureDialog(this, hotkey_registry, system, gl_renderer, physical_devices,
|
||||||
!multiplayer_state->IsHostingPublicRoom());
|
!multiplayer_state->IsHostingPublicRoom());
|
||||||
connect(&configureDialog, &ConfigureDialog::LanguageChanged, this,
|
connect(&configureDialog, &ConfigureDialog::LanguageChanged, this,
|
||||||
&GMainWindow::OnLanguageChanged);
|
&GMainWindow::OnLanguageChanged);
|
||||||
|
@ -3006,7 +3025,7 @@ void GMainWindow::OnConfigurePerGame() {
|
||||||
|
|
||||||
void GMainWindow::OpenPerGameConfiguration(u64 title_id, const QString& file_name) {
|
void GMainWindow::OpenPerGameConfiguration(u64 title_id, const QString& file_name) {
|
||||||
Settings::SetConfiguringGlobal(false);
|
Settings::SetConfiguringGlobal(false);
|
||||||
ConfigurePerGame dialog(this, title_id, file_name, physical_devices, system);
|
ConfigurePerGame dialog(this, title_id, file_name, gl_renderer, physical_devices, system);
|
||||||
const auto result = dialog.exec();
|
const auto result = dialog.exec();
|
||||||
|
|
||||||
if (result != QDialog::Accepted) {
|
if (result != QDialog::Accepted) {
|
||||||
|
@ -3178,8 +3197,10 @@ int main(int argc, char* argv[]) {
|
||||||
QApplication::setHighDpiScaleFactorRoundingPolicy(rounding_policy);
|
QApplication::setHighDpiScaleFactorRoundingPolicy(rounding_policy);
|
||||||
|
|
||||||
#ifdef __APPLE__
|
#ifdef __APPLE__
|
||||||
std::string bin_path = FileUtil::GetBundleDirectory() + DIR_SEP + "..";
|
auto bundle_dir = FileUtil::GetBundleDirectory();
|
||||||
chdir(bin_path.c_str());
|
if (bundle_dir) {
|
||||||
|
FileUtil::SetCurrentDir(bundle_dir.value() + "..");
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef ENABLE_OPENGL
|
#ifdef ENABLE_OPENGL
|
||||||
|
|
|
@ -344,6 +344,7 @@ private:
|
||||||
// Whether game was paused due to stopping video dumping
|
// Whether game was paused due to stopping video dumping
|
||||||
bool game_paused_for_dumping = false;
|
bool game_paused_for_dumping = false;
|
||||||
|
|
||||||
|
QString gl_renderer;
|
||||||
std::vector<QString> physical_devices;
|
std::vector<QString> physical_devices;
|
||||||
|
|
||||||
// Debugger panes
|
// Debugger panes
|
||||||
|
|
|
@ -80,9 +80,8 @@ void DirectConnectWindow::Connect() {
|
||||||
// Store settings
|
// Store settings
|
||||||
UISettings::values.nickname = ui->nickname->text();
|
UISettings::values.nickname = ui->nickname->text();
|
||||||
UISettings::values.ip = ui->ip->text();
|
UISettings::values.ip = ui->ip->text();
|
||||||
UISettings::values.port = (ui->port->isModified() && !ui->port->text().isEmpty())
|
UISettings::values.port =
|
||||||
? ui->port->text()
|
!ui->port->text().isEmpty() ? ui->port->text() : UISettings::values.port;
|
||||||
: UISettings::values.port;
|
|
||||||
|
|
||||||
// attempt to connect in a different thread
|
// attempt to connect in a different thread
|
||||||
QFuture<void> f = QtConcurrent::run([&] {
|
QFuture<void> f = QtConcurrent::run([&] {
|
||||||
|
|
|
@ -63,10 +63,10 @@ Lobby::Lobby(Core::System& system_, QWidget* parent, QStandardItemModel* list,
|
||||||
|
|
||||||
// UI Buttons
|
// UI Buttons
|
||||||
connect(ui->refresh_list, &QPushButton::clicked, this, &Lobby::RefreshLobby);
|
connect(ui->refresh_list, &QPushButton::clicked, this, &Lobby::RefreshLobby);
|
||||||
|
connect(ui->search, &QLineEdit::textChanged, proxy, &LobbyFilterProxyModel::SetFilterSearch);
|
||||||
connect(ui->games_owned, &QCheckBox::toggled, proxy, &LobbyFilterProxyModel::SetFilterOwned);
|
connect(ui->games_owned, &QCheckBox::toggled, proxy, &LobbyFilterProxyModel::SetFilterOwned);
|
||||||
connect(ui->hide_empty, &QCheckBox::toggled, proxy, &LobbyFilterProxyModel::SetFilterEmpty);
|
connect(ui->hide_empty, &QCheckBox::toggled, proxy, &LobbyFilterProxyModel::SetFilterEmpty);
|
||||||
connect(ui->hide_full, &QCheckBox::toggled, proxy, &LobbyFilterProxyModel::SetFilterFull);
|
connect(ui->hide_full, &QCheckBox::toggled, proxy, &LobbyFilterProxyModel::SetFilterFull);
|
||||||
connect(ui->search, &QLineEdit::textChanged, proxy, &LobbyFilterProxyModel::SetFilterSearch);
|
|
||||||
connect(ui->room_list, &QTreeView::doubleClicked, this, &Lobby::OnJoinRoom);
|
connect(ui->room_list, &QTreeView::doubleClicked, this, &Lobby::OnJoinRoom);
|
||||||
connect(ui->room_list, &QTreeView::clicked, this, &Lobby::OnExpandRoom);
|
connect(ui->room_list, &QTreeView::clicked, this, &Lobby::OnExpandRoom);
|
||||||
|
|
||||||
|
@ -74,6 +74,12 @@ Lobby::Lobby(Core::System& system_, QWidget* parent, QStandardItemModel* list,
|
||||||
connect(&room_list_watcher, &QFutureWatcher<AnnounceMultiplayerRoom::RoomList>::finished, this,
|
connect(&room_list_watcher, &QFutureWatcher<AnnounceMultiplayerRoom::RoomList>::finished, this,
|
||||||
&Lobby::OnRefreshLobby);
|
&Lobby::OnRefreshLobby);
|
||||||
|
|
||||||
|
// Load persistent filters after events are connected to make sure they apply
|
||||||
|
ui->search->setText(UISettings::values.multiplayer_filter_text);
|
||||||
|
ui->games_owned->setChecked(UISettings::values.multiplayer_filter_games_owned);
|
||||||
|
ui->hide_empty->setChecked(UISettings::values.multiplayer_filter_hide_empty);
|
||||||
|
ui->hide_full->setChecked(UISettings::values.multiplayer_filter_hide_full);
|
||||||
|
|
||||||
// manually start a refresh when the window is opening
|
// manually start a refresh when the window is opening
|
||||||
// TODO(jroweboy): if this refresh is slow for people with bad internet, then don't do it as
|
// TODO(jroweboy): if this refresh is slow for people with bad internet, then don't do it as
|
||||||
// part of the constructor, but offload the refresh until after the window shown. perhaps emit a
|
// part of the constructor, but offload the refresh until after the window shown. perhaps emit a
|
||||||
|
@ -180,6 +186,10 @@ void Lobby::OnJoinRoom(const QModelIndex& source) {
|
||||||
UISettings::values.nickname = ui->nickname->text();
|
UISettings::values.nickname = ui->nickname->text();
|
||||||
UISettings::values.ip = proxy->data(connection_index, LobbyItemHost::HostIPRole).toString();
|
UISettings::values.ip = proxy->data(connection_index, LobbyItemHost::HostIPRole).toString();
|
||||||
UISettings::values.port = proxy->data(connection_index, LobbyItemHost::HostPortRole).toString();
|
UISettings::values.port = proxy->data(connection_index, LobbyItemHost::HostPortRole).toString();
|
||||||
|
UISettings::values.multiplayer_filter_text = ui->search->text();
|
||||||
|
UISettings::values.multiplayer_filter_games_owned = ui->games_owned->isChecked();
|
||||||
|
UISettings::values.multiplayer_filter_hide_empty = ui->hide_empty->isChecked();
|
||||||
|
UISettings::values.multiplayer_filter_hide_full = ui->hide_full->isChecked();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Lobby::ResetModel() {
|
void Lobby::ResetModel() {
|
||||||
|
|
|
@ -188,12 +188,37 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
QVariant data(int role) const override {
|
QVariant data(int role) const override {
|
||||||
if (role != Qt::DisplayRole) {
|
switch (role) {
|
||||||
|
case Qt::DisplayRole: {
|
||||||
|
auto members = data(MemberListRole).toList();
|
||||||
|
return QStringLiteral("%1 / %2").arg(QString::number(members.size()),
|
||||||
|
data(MaxPlayerRole).toString());
|
||||||
|
}
|
||||||
|
case Qt::ForegroundRole: {
|
||||||
|
auto members = data(MemberListRole).toList();
|
||||||
|
auto max_players = data(MaxPlayerRole).toInt();
|
||||||
|
const QColor room_full_color(255, 48, 32);
|
||||||
|
const QColor room_almost_full_color(255, 140, 32);
|
||||||
|
const QColor room_has_players_color(32, 160, 32);
|
||||||
|
const QColor room_empty_color(128, 128, 128);
|
||||||
|
|
||||||
|
if (members.size() >= max_players) {
|
||||||
|
return QBrush(room_full_color);
|
||||||
|
} else if (members.size() == (max_players - 1)) {
|
||||||
|
return QBrush(room_almost_full_color);
|
||||||
|
} else if (members.size() == 0) {
|
||||||
|
return QBrush(room_empty_color);
|
||||||
|
} else if (members.size() > 0 && members.size() < (max_players - 1)) {
|
||||||
|
return QBrush(room_has_players_color);
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME: How to return a value that tells Qt not to modify the
|
||||||
|
// text color from the default (as if Qt::ForegroundRole wasn't overridden)?
|
||||||
|
return QBrush(nullptr);
|
||||||
|
}
|
||||||
|
default:
|
||||||
return LobbyItem::data(role);
|
return LobbyItem::data(role);
|
||||||
}
|
}
|
||||||
auto members = data(MemberListRole).toList();
|
|
||||||
return QStringLiteral("%1 / %2").arg(QString::number(members.size()),
|
|
||||||
data(MaxPlayerRole).toString());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool operator<(const QStandardItem& other) const override {
|
bool operator<(const QStandardItem& other) const override {
|
||||||
|
|
|
@ -138,6 +138,11 @@ struct Values {
|
||||||
QString room_description;
|
QString room_description;
|
||||||
std::pair<std::vector<std::string>, std::vector<std::string>> ban_list;
|
std::pair<std::vector<std::string>, std::vector<std::string>> ban_list;
|
||||||
|
|
||||||
|
QString multiplayer_filter_text;
|
||||||
|
bool multiplayer_filter_games_owned;
|
||||||
|
bool multiplayer_filter_hide_empty;
|
||||||
|
bool multiplayer_filter_hide_full;
|
||||||
|
|
||||||
// logging
|
// logging
|
||||||
Settings::Setting<bool> show_console{false, "showConsole"};
|
Settings::Setting<bool> show_console{false, "showConsole"};
|
||||||
};
|
};
|
||||||
|
|
|
@ -2,9 +2,34 @@
|
||||||
// Licensed under GPLv2 or any later version
|
// Licensed under GPLv2 or any later version
|
||||||
// Refer to the license.txt file included.
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
#include "citra_qt/util/vk_device_info.h"
|
#include "citra_qt/util/graphics_device_info.h"
|
||||||
#include "video_core/renderer_vulkan/vk_instance.h"
|
|
||||||
|
|
||||||
|
#ifdef ENABLE_OPENGL
|
||||||
|
#include <QOffscreenSurface>
|
||||||
|
#include <QOpenGLContext>
|
||||||
|
#include <QOpenGLFunctions>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef ENABLE_VULKAN
|
||||||
|
#include "video_core/renderer_vulkan/vk_instance.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef ENABLE_OPENGL
|
||||||
|
QString GetOpenGLRenderer() {
|
||||||
|
QOffscreenSurface surface;
|
||||||
|
surface.create();
|
||||||
|
|
||||||
|
QOpenGLContext context;
|
||||||
|
if (context.create()) {
|
||||||
|
context.makeCurrent(&surface);
|
||||||
|
return QString::fromUtf8(context.functions()->glGetString(GL_RENDERER));
|
||||||
|
} else {
|
||||||
|
return QStringLiteral("");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef ENABLE_VULKAN
|
||||||
std::vector<QString> GetVulkanPhysicalDevices() {
|
std::vector<QString> GetVulkanPhysicalDevices() {
|
||||||
std::vector<QString> result;
|
std::vector<QString> result;
|
||||||
try {
|
try {
|
||||||
|
@ -21,3 +46,4 @@ std::vector<QString> GetVulkanPhysicalDevices() {
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
#endif
|
|
@ -7,5 +7,12 @@
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
|
|
||||||
|
#ifdef ENABLE_OPENGL
|
||||||
|
/// Returns the name of the OpenGL renderer.
|
||||||
|
QString GetOpenGLRenderer();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef ENABLE_VULKAN
|
||||||
/// Returns a list of all available vulkan GPUs.
|
/// Returns a list of all available vulkan GPUs.
|
||||||
std::vector<QString> GetVulkanPhysicalDevices();
|
std::vector<QString> GetVulkanPhysicalDevices();
|
||||||
|
#endif
|
|
@ -20,7 +20,7 @@ inline bool IsWithin128M(uintptr_t ref, uintptr_t target) {
|
||||||
}
|
}
|
||||||
|
|
||||||
inline bool IsWithin128M(const oaknut::CodeGenerator& code, uintptr_t target) {
|
inline bool IsWithin128M(const oaknut::CodeGenerator& code, uintptr_t target) {
|
||||||
return IsWithin128M(code.ptr<uintptr_t>(), target);
|
return IsWithin128M(code.xptr<uintptr_t>(), target);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
|
|
|
@ -13,7 +13,6 @@
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// The user data dir
|
// The user data dir
|
||||||
#define ROOT_DIR "."
|
|
||||||
#define USERDATA_DIR "user"
|
#define USERDATA_DIR "user"
|
||||||
#ifdef USER_DIR
|
#ifdef USER_DIR
|
||||||
#define EMU_DATA_DIR USER_DIR
|
#define EMU_DATA_DIR USER_DIR
|
||||||
|
|
|
@ -634,6 +634,10 @@ std::optional<std::string> GetCurrentDir() {
|
||||||
std::string strDir = dir;
|
std::string strDir = dir;
|
||||||
#endif
|
#endif
|
||||||
free(dir);
|
free(dir);
|
||||||
|
|
||||||
|
if (!strDir.ends_with(DIR_SEP)) {
|
||||||
|
strDir += DIR_SEP;
|
||||||
|
}
|
||||||
return strDir;
|
return strDir;
|
||||||
} // namespace FileUtil
|
} // namespace FileUtil
|
||||||
|
|
||||||
|
@ -646,17 +650,36 @@ bool SetCurrentDir(const std::string& directory) {
|
||||||
}
|
}
|
||||||
|
|
||||||
#if defined(__APPLE__)
|
#if defined(__APPLE__)
|
||||||
std::string GetBundleDirectory() {
|
std::optional<std::string> GetBundleDirectory() {
|
||||||
CFURLRef BundleRef;
|
|
||||||
char AppBundlePath[MAXPATHLEN];
|
|
||||||
// Get the main bundle for the app
|
// Get the main bundle for the app
|
||||||
BundleRef = CFBundleCopyBundleURL(CFBundleGetMainBundle());
|
CFBundleRef bundle_ref = CFBundleGetMainBundle();
|
||||||
CFStringRef BundlePath = CFURLCopyFileSystemPath(BundleRef, kCFURLPOSIXPathStyle);
|
if (!bundle_ref) {
|
||||||
CFStringGetFileSystemRepresentation(BundlePath, AppBundlePath, sizeof(AppBundlePath));
|
return {};
|
||||||
CFRelease(BundleRef);
|
}
|
||||||
CFRelease(BundlePath);
|
|
||||||
|
|
||||||
return AppBundlePath;
|
CFURLRef bundle_url_ref = CFBundleCopyBundleURL(bundle_ref);
|
||||||
|
if (!bundle_url_ref) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
SCOPE_EXIT({ CFRelease(bundle_url_ref); });
|
||||||
|
|
||||||
|
CFStringRef bundle_path_ref = CFURLCopyFileSystemPath(bundle_url_ref, kCFURLPOSIXPathStyle);
|
||||||
|
if (!bundle_path_ref) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
SCOPE_EXIT({ CFRelease(bundle_path_ref); });
|
||||||
|
|
||||||
|
char app_bundle_path[MAXPATHLEN];
|
||||||
|
if (!CFStringGetFileSystemRepresentation(bundle_path_ref, app_bundle_path,
|
||||||
|
sizeof(app_bundle_path))) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string path_str(app_bundle_path);
|
||||||
|
if (!path_str.ends_with(DIR_SEP)) {
|
||||||
|
path_str += DIR_SEP;
|
||||||
|
}
|
||||||
|
return path_str;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
@ -732,22 +755,6 @@ static const std::string& GetHomeDirectory() {
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
std::string GetSysDirectory() {
|
|
||||||
std::string sysDir;
|
|
||||||
|
|
||||||
#if defined(__APPLE__)
|
|
||||||
sysDir = GetBundleDirectory();
|
|
||||||
sysDir += DIR_SEP;
|
|
||||||
sysDir += SYSDATA_DIR;
|
|
||||||
#else
|
|
||||||
sysDir = SYSDATA_DIR;
|
|
||||||
#endif
|
|
||||||
sysDir += DIR_SEP;
|
|
||||||
|
|
||||||
LOG_DEBUG(Common_Filesystem, "Setting to {}:", sysDir);
|
|
||||||
return sysDir;
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
std::unordered_map<UserPath, std::string> g_paths;
|
std::unordered_map<UserPath, std::string> g_paths;
|
||||||
std::unordered_map<UserPath, std::string> g_default_paths;
|
std::unordered_map<UserPath, std::string> g_default_paths;
|
||||||
|
@ -777,8 +784,10 @@ void SetUserPath(const std::string& path) {
|
||||||
g_paths.emplace(UserPath::ConfigDir, user_path + CONFIG_DIR DIR_SEP);
|
g_paths.emplace(UserPath::ConfigDir, user_path + CONFIG_DIR DIR_SEP);
|
||||||
g_paths.emplace(UserPath::CacheDir, user_path + CACHE_DIR DIR_SEP);
|
g_paths.emplace(UserPath::CacheDir, user_path + CACHE_DIR DIR_SEP);
|
||||||
#else
|
#else
|
||||||
if (FileUtil::Exists(ROOT_DIR DIR_SEP USERDATA_DIR)) {
|
auto current_dir = FileUtil::GetCurrentDir();
|
||||||
user_path = ROOT_DIR DIR_SEP USERDATA_DIR DIR_SEP;
|
if (current_dir.has_value() &&
|
||||||
|
FileUtil::Exists(current_dir.value() + USERDATA_DIR DIR_SEP)) {
|
||||||
|
user_path = current_dir.value() + USERDATA_DIR DIR_SEP;
|
||||||
g_paths.emplace(UserPath::ConfigDir, user_path + CONFIG_DIR DIR_SEP);
|
g_paths.emplace(UserPath::ConfigDir, user_path + CONFIG_DIR DIR_SEP);
|
||||||
g_paths.emplace(UserPath::CacheDir, user_path + CACHE_DIR DIR_SEP);
|
g_paths.emplace(UserPath::CacheDir, user_path + CACHE_DIR DIR_SEP);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -193,11 +193,8 @@ void SetCurrentRomPath(const std::string& path);
|
||||||
// Update the Global Path with the new value
|
// Update the Global Path with the new value
|
||||||
void UpdateUserPath(UserPath path, const std::string& filename);
|
void UpdateUserPath(UserPath path, const std::string& filename);
|
||||||
|
|
||||||
// Returns the path to where the sys file are
|
|
||||||
[[nodiscard]] std::string GetSysDirectory();
|
|
||||||
|
|
||||||
#ifdef __APPLE__
|
#ifdef __APPLE__
|
||||||
[[nodiscard]] std::string GetBundleDirectory();
|
[[nodiscard]] std::optional<std::string> GetBundleDirectory();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
|
|
|
@ -451,7 +451,7 @@ void SetColorConsoleBackendEnabled(bool enabled) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void FmtLogMessageImpl(Class log_class, Level log_level, const char* filename,
|
void FmtLogMessageImpl(Class log_class, Level log_level, const char* filename,
|
||||||
unsigned int line_num, const char* function, const char* format,
|
unsigned int line_num, const char* function, fmt::string_view format,
|
||||||
const fmt::format_args& args) {
|
const fmt::format_args& args) {
|
||||||
if (!initialization_in_progress_suppress_logging) {
|
if (!initialization_in_progress_suppress_logging) {
|
||||||
Impl::Instance().PushEntry(log_class, log_level, filename, line_num, function,
|
Impl::Instance().PushEntry(log_class, log_level, filename, line_num, function,
|
||||||
|
|
|
@ -115,6 +115,7 @@ bool ParseFilterRule(Filter& instance, Iterator begin, Iterator end) {
|
||||||
SUB(Service, Y2R) \
|
SUB(Service, Y2R) \
|
||||||
SUB(Service, PS) \
|
SUB(Service, PS) \
|
||||||
SUB(Service, PLGLDR) \
|
SUB(Service, PLGLDR) \
|
||||||
|
SUB(Service, NEWS) \
|
||||||
CLS(HW) \
|
CLS(HW) \
|
||||||
SUB(HW, Memory) \
|
SUB(HW, Memory) \
|
||||||
SUB(HW, LCD) \
|
SUB(HW, LCD) \
|
||||||
|
|
|
@ -24,12 +24,12 @@ constexpr const char* TrimSourcePath(std::string_view source) {
|
||||||
|
|
||||||
/// Logs a message to the global logger, using fmt
|
/// Logs a message to the global logger, using fmt
|
||||||
void FmtLogMessageImpl(Class log_class, Level log_level, const char* filename,
|
void FmtLogMessageImpl(Class log_class, Level log_level, const char* filename,
|
||||||
unsigned int line_num, const char* function, const char* format,
|
unsigned int line_num, const char* function, fmt::string_view format,
|
||||||
const fmt::format_args& args);
|
const fmt::format_args& args);
|
||||||
|
|
||||||
template <typename... Args>
|
template <typename... Args>
|
||||||
void FmtLogMessage(Class log_class, Level log_level, const char* filename, unsigned int line_num,
|
void FmtLogMessage(Class log_class, Level log_level, const char* filename, unsigned int line_num,
|
||||||
const char* function, const char* format, const Args&... args) {
|
const char* function, fmt::format_string<Args...> format, const Args&... args) {
|
||||||
FmtLogMessageImpl(log_class, log_level, filename, line_num, function, format,
|
FmtLogMessageImpl(log_class, log_level, filename, line_num, function, format,
|
||||||
fmt::make_format_args(args...));
|
fmt::make_format_args(args...));
|
||||||
}
|
}
|
||||||
|
|
|
@ -82,6 +82,7 @@ enum class Class : u8 {
|
||||||
Service_Y2R, ///< The Y2R (YUV to RGB conversion) service
|
Service_Y2R, ///< The Y2R (YUV to RGB conversion) service
|
||||||
Service_PS, ///< The PS (Process) service
|
Service_PS, ///< The PS (Process) service
|
||||||
Service_PLGLDR, ///< The PLGLDR (plugin loader) service
|
Service_PLGLDR, ///< The PLGLDR (plugin loader) service
|
||||||
|
Service_NEWS, ///< The NEWS (Notifications) service
|
||||||
HW, ///< Low-level hardware emulation
|
HW, ///< Low-level hardware emulation
|
||||||
HW_Memory, ///< Memory-map and address translation
|
HW_Memory, ///< Memory-map and address translation
|
||||||
HW_LCD, ///< LCD register emulation
|
HW_LCD, ///< LCD register emulation
|
||||||
|
|
|
@ -327,6 +327,10 @@ add_library(citra_core STATIC
|
||||||
hle/service/ldr_ro/cro_helper.h
|
hle/service/ldr_ro/cro_helper.h
|
||||||
hle/service/ldr_ro/ldr_ro.cpp
|
hle/service/ldr_ro/ldr_ro.cpp
|
||||||
hle/service/ldr_ro/ldr_ro.h
|
hle/service/ldr_ro/ldr_ro.h
|
||||||
|
hle/service/mcu/mcu_hwc.cpp
|
||||||
|
hle/service/mcu/mcu_hwc.h
|
||||||
|
hle/service/mcu/mcu.cpp
|
||||||
|
hle/service/mcu/mcu.h
|
||||||
hle/service/mic/mic_u.cpp
|
hle/service/mic/mic_u.cpp
|
||||||
hle/service/mic/mic_u.h
|
hle/service/mic/mic_u.h
|
||||||
hle/service/mvd/mvd.cpp
|
hle/service/mvd/mvd.cpp
|
||||||
|
|
|
@ -101,7 +101,7 @@ void vfp_put_float(ARMul_State* state, s32 val, unsigned int reg) {
|
||||||
|
|
||||||
u64 vfp_get_double(ARMul_State* state, unsigned int reg) {
|
u64 vfp_get_double(ARMul_State* state, unsigned int reg) {
|
||||||
u64 result = ((u64)state->ExtReg[reg * 2 + 1]) << 32 | state->ExtReg[reg * 2];
|
u64 result = ((u64)state->ExtReg[reg * 2 + 1]) << 32 | state->ExtReg[reg * 2];
|
||||||
LOG_TRACE(Core_ARM11, "VFP get double: s[{}-{}]=[{:016llx}]", reg * 2 + 1, reg * 2, result);
|
LOG_TRACE(Core_ARM11, "VFP get double: s[{}-{}]=[{:016x}]", reg * 2 + 1, reg * 2, result);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -64,7 +64,7 @@ static struct vfp_double vfp_double_default_qnan = {
|
||||||
};
|
};
|
||||||
|
|
||||||
static void vfp_double_dump(const char* str, struct vfp_double* d) {
|
static void vfp_double_dump(const char* str, struct vfp_double* d) {
|
||||||
LOG_TRACE(Core_ARM11, "VFP: {}: sign={} exponent={} significand={:016llx}", str, d->sign != 0,
|
LOG_TRACE(Core_ARM11, "VFP: {}: sign={} exponent={} significand={:016x}", str, d->sign != 0,
|
||||||
d->exponent, d->significand);
|
d->exponent, d->significand);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -166,7 +166,7 @@ u32 vfp_double_normaliseround(ARMul_State* state, int dd, struct vfp_double* vd,
|
||||||
} else if ((rmode == FPSCR_ROUND_PLUSINF) ^ (vd->sign != 0))
|
} else if ((rmode == FPSCR_ROUND_PLUSINF) ^ (vd->sign != 0))
|
||||||
incr = (1ULL << (VFP_DOUBLE_LOW_BITS + 1)) - 1;
|
incr = (1ULL << (VFP_DOUBLE_LOW_BITS + 1)) - 1;
|
||||||
|
|
||||||
LOG_TRACE(Core_ARM11, "VFP: rounding increment = 0x{:08llx}", incr);
|
LOG_TRACE(Core_ARM11, "VFP: rounding increment = 0x{:08x}", incr);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Is our rounding going to overflow?
|
* Is our rounding going to overflow?
|
||||||
|
@ -221,8 +221,7 @@ pack:
|
||||||
vfp_double_dump("pack: final", vd);
|
vfp_double_dump("pack: final", vd);
|
||||||
{
|
{
|
||||||
s64 d = vfp_double_pack(vd);
|
s64 d = vfp_double_pack(vd);
|
||||||
LOG_TRACE(Core_ARM11, "VFP: {}: d(d{})={:016llx} exceptions={:08x}", func, dd, d,
|
LOG_TRACE(Core_ARM11, "VFP: {}: d(d{})={:016x} exceptions={:08x}", func, dd, d, exceptions);
|
||||||
exceptions);
|
|
||||||
vfp_put_double(state, d, dd);
|
vfp_put_double(state, d, dd);
|
||||||
}
|
}
|
||||||
return exceptions;
|
return exceptions;
|
||||||
|
|
|
@ -14,18 +14,18 @@ namespace ConfigMem {
|
||||||
Handler::Handler() {
|
Handler::Handler() {
|
||||||
std::memset(&config_mem, 0, sizeof(config_mem));
|
std::memset(&config_mem, 0, sizeof(config_mem));
|
||||||
|
|
||||||
// Values extracted from firmware 11.2.0-35E
|
// Values extracted from firmware 11.17.0-50E
|
||||||
config_mem.kernel_version_min = 0x34;
|
config_mem.kernel_version_min = 0x3a;
|
||||||
config_mem.kernel_version_maj = 0x2;
|
config_mem.kernel_version_maj = 0x2;
|
||||||
config_mem.ns_tid = 0x0004013000008002;
|
config_mem.ns_tid = 0x0004013000008002;
|
||||||
config_mem.sys_core_ver = 0x2;
|
config_mem.sys_core_ver = 0x2;
|
||||||
config_mem.unit_info = 0x1; // Bit 0 set for Retail
|
config_mem.unit_info = 0x1; // Bit 0 set for Retail
|
||||||
config_mem.prev_firm = 0x1;
|
config_mem.prev_firm = 0x1;
|
||||||
config_mem.ctr_sdk_ver = 0x0000F297;
|
config_mem.ctr_sdk_ver = 0x0000F450;
|
||||||
config_mem.firm_version_min = 0x34;
|
config_mem.firm_version_min = 0x3a;
|
||||||
config_mem.firm_version_maj = 0x2;
|
config_mem.firm_version_maj = 0x2;
|
||||||
config_mem.firm_sys_core_ver = 0x2;
|
config_mem.firm_sys_core_ver = 0x2;
|
||||||
config_mem.firm_ctr_sdk_ver = 0x0000F297;
|
config_mem.firm_ctr_sdk_ver = 0x0000F450;
|
||||||
}
|
}
|
||||||
|
|
||||||
ConfigMemDef& Handler::GetConfigMem() {
|
ConfigMemDef& Handler::GetConfigMem() {
|
||||||
|
|
|
@ -210,10 +210,10 @@ void Process::Set3dsxKernelCaps() {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Similar to Rosalina, we set kernel version to a recent one.
|
// Similar to Rosalina, we set kernel version to a recent one.
|
||||||
// This is 11.2.0, to be consistent with core/hle/kernel/config_mem.cpp
|
// This is 11.17.0, to be consistent with core/hle/kernel/config_mem.cpp
|
||||||
// TODO: refactor kernel version out so it is configurable and consistent
|
// TODO: refactor kernel version out so it is configurable and consistent
|
||||||
// among all relevant places.
|
// among all relevant places.
|
||||||
kernel_version = 0x234;
|
kernel_version = 0x23a;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Process::Run(s32 main_thread_priority, u32 stack_size) {
|
void Process::Run(s32 main_thread_priority, u32 stack_size) {
|
||||||
|
|
|
@ -373,7 +373,10 @@ ResultVal<AppletManager::InitializeResult> AppletManager::Initialize(AppletId ap
|
||||||
if (active_slot == AppletSlot::Error) {
|
if (active_slot == AppletSlot::Error) {
|
||||||
active_slot = slot;
|
active_slot = slot;
|
||||||
|
|
||||||
// Wake up the application.
|
// APT automatically calls enable on the first registered applet.
|
||||||
|
Enable(attributes);
|
||||||
|
|
||||||
|
// Wake up the applet.
|
||||||
SendParameter({
|
SendParameter({
|
||||||
.sender_id = AppletId::None,
|
.sender_id = AppletId::None,
|
||||||
.destination_id = app_id,
|
.destination_id = app_id,
|
||||||
|
@ -398,7 +401,8 @@ Result AppletManager::Enable(AppletAttributes attributes) {
|
||||||
auto slot_data = GetAppletSlot(slot);
|
auto slot_data = GetAppletSlot(slot);
|
||||||
slot_data->registered = true;
|
slot_data->registered = true;
|
||||||
|
|
||||||
if (slot_data->attributes.applet_pos == AppletPos::System &&
|
if (slot_data->applet_id != AppletId::None &&
|
||||||
|
slot_data->attributes.applet_pos == AppletPos::System &&
|
||||||
slot_data->attributes.is_home_menu) {
|
slot_data->attributes.is_home_menu) {
|
||||||
slot_data->attributes.raw |= attributes.raw;
|
slot_data->attributes.raw |= attributes.raw;
|
||||||
LOG_DEBUG(Service_APT, "Updated home menu attributes to {:08X}.",
|
LOG_DEBUG(Service_APT, "Updated home menu attributes to {:08X}.",
|
||||||
|
@ -786,16 +790,23 @@ Result AppletManager::PrepareToStartSystemApplet(AppletId applet_id) {
|
||||||
|
|
||||||
Result AppletManager::StartSystemApplet(AppletId applet_id, std::shared_ptr<Kernel::Object> object,
|
Result AppletManager::StartSystemApplet(AppletId applet_id, std::shared_ptr<Kernel::Object> object,
|
||||||
const std::vector<u8>& buffer) {
|
const std::vector<u8>& buffer) {
|
||||||
auto source_applet_id = AppletId::None;
|
auto source_applet_id = AppletId::Application;
|
||||||
if (last_system_launcher_slot != AppletSlot::Error) {
|
if (last_system_launcher_slot != AppletSlot::Error) {
|
||||||
const auto slot_data = GetAppletSlot(last_system_launcher_slot);
|
const auto launcher_slot_data = GetAppletSlot(last_system_launcher_slot);
|
||||||
source_applet_id = slot_data->applet_id;
|
source_applet_id = launcher_slot_data->applet_id;
|
||||||
|
|
||||||
// If a system applet is launching another system applet, reset the slot to avoid conflicts.
|
// APT generally clears and terminates the caller of StartSystemApplet. This helps in
|
||||||
// This is needed because system applets won't necessarily call CloseSystemApplet before
|
// situations such as a system applet launching another system applet, which would
|
||||||
// exiting.
|
// otherwise deadlock.
|
||||||
if (last_system_launcher_slot == AppletSlot::SystemApplet) {
|
// TODO: In real APT, the check for AppletSlot::Application does not exist; there is
|
||||||
slot_data->Reset();
|
// TODO: something wrong with our implementation somewhere that makes this necessary.
|
||||||
|
// TODO: Otherwise, games that attempt to launch system applets will be cleared and
|
||||||
|
// TODO: emulation will crash.
|
||||||
|
if (!launcher_slot_data->registered ||
|
||||||
|
(last_system_launcher_slot != AppletSlot::Application &&
|
||||||
|
!launcher_slot_data->attributes.no_exit_on_system_applet)) {
|
||||||
|
launcher_slot_data->Reset();
|
||||||
|
// TODO: Implement launcher process termination.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -152,6 +152,7 @@ union AppletAttributes {
|
||||||
u32 raw;
|
u32 raw;
|
||||||
|
|
||||||
BitField<0, 3, AppletPos> applet_pos;
|
BitField<0, 3, AppletPos> applet_pos;
|
||||||
|
BitField<28, 1, u32> no_exit_on_system_applet;
|
||||||
BitField<29, 1, u32> is_home_menu;
|
BitField<29, 1, u32> is_home_menu;
|
||||||
|
|
||||||
AppletAttributes() : raw(0) {}
|
AppletAttributes() : raw(0) {}
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -17,10 +17,8 @@
|
||||||
#include <boost/serialization/unordered_map.hpp>
|
#include <boost/serialization/unordered_map.hpp>
|
||||||
#include <boost/serialization/vector.hpp>
|
#include <boost/serialization/vector.hpp>
|
||||||
#include <boost/serialization/weak_ptr.hpp>
|
#include <boost/serialization/weak_ptr.hpp>
|
||||||
#if defined(__ANDROID__)
|
|
||||||
#include <ifaddrs.h>
|
|
||||||
#endif
|
|
||||||
#include <httplib.h>
|
#include <httplib.h>
|
||||||
|
#include "common/thread.h"
|
||||||
#include "core/hle/ipc_helpers.h"
|
#include "core/hle/ipc_helpers.h"
|
||||||
#include "core/hle/kernel/shared_memory.h"
|
#include "core/hle/kernel/shared_memory.h"
|
||||||
#include "core/hle/service/service.h"
|
#include "core/hle/service/service.h"
|
||||||
|
@ -51,12 +49,25 @@ constexpr u32 TotalRequestMethods = 8;
|
||||||
|
|
||||||
enum class RequestState : u8 {
|
enum class RequestState : u8 {
|
||||||
NotStarted = 0x1, // Request has not started yet.
|
NotStarted = 0x1, // Request has not started yet.
|
||||||
InProgress = 0x5, // Request in progress, sending request over the network.
|
ConnectingToServer = 0x5, // Request in progress, connecting to server.
|
||||||
ReadyToDownloadContent = 0x7, // Ready to download the content. (needs verification)
|
SendingRequest = 0x6, // Request in progress, sending HTTP request.
|
||||||
ReadyToDownload = 0x8, // Ready to download?
|
ReceivingResponse = 0x7, // Request in progress, receiving HTTP response.
|
||||||
|
ReadyToDownloadContent = 0x8, // Ready to download the content.
|
||||||
TimedOut = 0xA, // Request timed out?
|
TimedOut = 0xA, // Request timed out?
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum class PostDataEncoding : u8 {
|
||||||
|
Auto = 0x0,
|
||||||
|
AsciiForm = 0x1,
|
||||||
|
MultipartForm = 0x2,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class PostDataType : u8 {
|
||||||
|
AsciiForm = 0x0,
|
||||||
|
MultipartForm = 0x1,
|
||||||
|
Raw = 0x2,
|
||||||
|
};
|
||||||
|
|
||||||
enum class ClientCertID : u32 {
|
enum class ClientCertID : u32 {
|
||||||
Default = 0x40, // Default client cert
|
Default = 0x40, // Default client cert
|
||||||
};
|
};
|
||||||
|
@ -200,6 +211,41 @@ public:
|
||||||
friend class boost::serialization::access;
|
friend class boost::serialization::access;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct Param {
|
||||||
|
Param(const std::vector<u8>& value)
|
||||||
|
: name(value.begin(), value.end()), value(value.begin(), value.end()){};
|
||||||
|
Param(const std::string& name, const std::string& value) : name(name), value(value){};
|
||||||
|
Param(const std::string& name, const std::vector<u8>& value)
|
||||||
|
: name(name), value(value.begin(), value.end()), is_binary(true){};
|
||||||
|
std::string name;
|
||||||
|
std::string value;
|
||||||
|
bool is_binary = false;
|
||||||
|
|
||||||
|
httplib::MultipartFormData ToMultipartForm() const {
|
||||||
|
httplib::MultipartFormData form;
|
||||||
|
form.name = name;
|
||||||
|
form.content = value;
|
||||||
|
if (is_binary) {
|
||||||
|
form.content_type = "application/octet-stream";
|
||||||
|
// TODO(DaniElectra): httplib doesn't support setting Content-Transfer-Encoding,
|
||||||
|
// while the 3DS sets Content-Transfer-Encoding: binary if a binary value is set
|
||||||
|
}
|
||||||
|
|
||||||
|
return form;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
template <class Archive>
|
||||||
|
void serialize(Archive& ar, const unsigned int) {
|
||||||
|
ar& name;
|
||||||
|
ar& value;
|
||||||
|
ar& is_binary;
|
||||||
|
}
|
||||||
|
friend class boost::serialization::access;
|
||||||
|
};
|
||||||
|
|
||||||
|
using Params = std::multimap<std::string, Param>;
|
||||||
|
|
||||||
Handle handle;
|
Handle handle;
|
||||||
u32 session_id;
|
u32 session_id;
|
||||||
std::string url;
|
std::string url;
|
||||||
|
@ -211,8 +257,14 @@ public:
|
||||||
u32 socket_buffer_size;
|
u32 socket_buffer_size;
|
||||||
std::vector<RequestHeader> headers;
|
std::vector<RequestHeader> headers;
|
||||||
const ClCertAData* clcert_data;
|
const ClCertAData* clcert_data;
|
||||||
httplib::Params post_data;
|
Params post_data;
|
||||||
std::string post_data_raw;
|
std::string post_data_raw;
|
||||||
|
PostDataEncoding post_data_encoding = PostDataEncoding::Auto;
|
||||||
|
PostDataType post_data_type;
|
||||||
|
std::string multipart_boundary;
|
||||||
|
bool force_multipart = false;
|
||||||
|
bool chunked_request = false;
|
||||||
|
u32 chunked_content_length;
|
||||||
|
|
||||||
std::future<void> request_future;
|
std::future<void> request_future;
|
||||||
std::atomic<u64> current_download_size_bytes;
|
std::atomic<u64> current_download_size_bytes;
|
||||||
|
@ -220,12 +272,19 @@ public:
|
||||||
std::size_t current_copied_data;
|
std::size_t current_copied_data;
|
||||||
bool uses_default_client_cert{};
|
bool uses_default_client_cert{};
|
||||||
httplib::Response response;
|
httplib::Response response;
|
||||||
|
Common::Event finish_post_data;
|
||||||
|
|
||||||
|
void ParseAsciiPostData();
|
||||||
|
std::string ParseMultipartFormData();
|
||||||
void MakeRequest();
|
void MakeRequest();
|
||||||
void MakeRequestNonSSL(httplib::Request& request, const URLInfo& url_info,
|
void MakeRequestNonSSL(httplib::Request& request, const URLInfo& url_info,
|
||||||
std::vector<Context::RequestHeader>& pending_headers);
|
std::vector<Context::RequestHeader>& pending_headers);
|
||||||
void MakeRequestSSL(httplib::Request& request, const URLInfo& url_info,
|
void MakeRequestSSL(httplib::Request& request, const URLInfo& url_info,
|
||||||
std::vector<Context::RequestHeader>& pending_headers);
|
std::vector<Context::RequestHeader>& pending_headers);
|
||||||
|
bool ContentProvider(size_t offset, size_t length, httplib::DataSink& sink);
|
||||||
|
bool ChunkedContentProvider(size_t offset, httplib::DataSink& sink);
|
||||||
|
std::size_t HandleHeaderWrite(std::vector<Context::RequestHeader>& pending_headers,
|
||||||
|
httplib::Stream& strm, httplib::Headers& httplib_headers);
|
||||||
};
|
};
|
||||||
|
|
||||||
struct SessionData : public Kernel::SessionRequestHandler::SessionDataBase {
|
struct SessionData : public Kernel::SessionRequestHandler::SessionDataBase {
|
||||||
|
@ -311,6 +370,16 @@ private:
|
||||||
*/
|
*/
|
||||||
void CancelConnection(Kernel::HLERequestContext& ctx);
|
void CancelConnection(Kernel::HLERequestContext& ctx);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HTTP_C::GetRequestState service function
|
||||||
|
* Inputs:
|
||||||
|
* 1 : Context handle
|
||||||
|
* Outputs:
|
||||||
|
* 1 : Result of function, 0 on success, otherwise error code
|
||||||
|
* 2 : Request state
|
||||||
|
*/
|
||||||
|
void GetRequestState(Kernel::HLERequestContext& ctx);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* HTTP_C::GetDownloadSizeState service function
|
* HTTP_C::GetDownloadSizeState service function
|
||||||
* Inputs:
|
* Inputs:
|
||||||
|
@ -421,6 +490,21 @@ private:
|
||||||
*/
|
*/
|
||||||
void AddPostDataAscii(Kernel::HLERequestContext& ctx);
|
void AddPostDataAscii(Kernel::HLERequestContext& ctx);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HTTP_C::AddPostDataBinary service function
|
||||||
|
* Inputs:
|
||||||
|
* 1 : Context handle
|
||||||
|
* 2 : Form name buffer size, including null-terminator.
|
||||||
|
* 3 : Form value buffer size
|
||||||
|
* 4 : (FormNameSize<<14) | 0xC02
|
||||||
|
* 5 : Form name data pointer
|
||||||
|
* 6 : (FormValueSize<<4) | 10
|
||||||
|
* 7 : Form value data pointer
|
||||||
|
* Outputs:
|
||||||
|
* 1 : Result of function, 0 on success, otherwise error code
|
||||||
|
*/
|
||||||
|
void AddPostDataBinary(Kernel::HLERequestContext& ctx);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* HTTP_C::AddPostDataRaw service function
|
* HTTP_C::AddPostDataRaw service function
|
||||||
* Inputs:
|
* Inputs:
|
||||||
|
@ -433,6 +517,140 @@ private:
|
||||||
*/
|
*/
|
||||||
void AddPostDataRaw(Kernel::HLERequestContext& ctx);
|
void AddPostDataRaw(Kernel::HLERequestContext& ctx);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HTTP_C::SetPostDataType service function
|
||||||
|
* Inputs:
|
||||||
|
* 1 : Context handle
|
||||||
|
* 2 : Post data type
|
||||||
|
* Outputs:
|
||||||
|
* 1 : Result of function, 0 on success, otherwise error code
|
||||||
|
*/
|
||||||
|
void SetPostDataType(Kernel::HLERequestContext& ctx);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HTTP_C::SendPostDataAscii service function
|
||||||
|
* Inputs:
|
||||||
|
* 1 : Context handle
|
||||||
|
* 2 : Form name buffer size, including null-terminator.
|
||||||
|
* 3 : Form value buffer size, including null-terminator.
|
||||||
|
* 4 : (FormNameSize<<14) | 0xC02
|
||||||
|
* 5 : Form name data pointer
|
||||||
|
* 6 : (FormValueSize<<4) | 10
|
||||||
|
* 7 : Form value data pointer
|
||||||
|
* Outputs:
|
||||||
|
* 1 : Result of function, 0 on success, otherwise error code
|
||||||
|
*/
|
||||||
|
void SendPostDataAscii(Kernel::HLERequestContext& ctx);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HTTP_C::SendPostDataAsciiTimeout service function
|
||||||
|
* Inputs:
|
||||||
|
* 1 : Context handle
|
||||||
|
* 2 : Form name buffer size, including null-terminator.
|
||||||
|
* 3 : Form value buffer size, including null-terminator.
|
||||||
|
* 4-5 : u64 nanoseconds delay
|
||||||
|
* 6 : (FormNameSize<<14) | 0xC02
|
||||||
|
* 7 : Form name data pointer
|
||||||
|
* 8 : (FormValueSize<<4) | 10
|
||||||
|
* 9 : Form value data pointer
|
||||||
|
* Outputs:
|
||||||
|
* 1 : Result of function, 0 on success, otherwise error code
|
||||||
|
*/
|
||||||
|
void SendPostDataAsciiTimeout(Kernel::HLERequestContext& ctx);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SendPostDataAsciiImpl:
|
||||||
|
* Implements SendPostDataAscii and SendPostDataAsciiTimeout service functions
|
||||||
|
*/
|
||||||
|
void SendPostDataAsciiImpl(Kernel::HLERequestContext& ctx, bool timeout);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HTTP_C::SendPostDataBinary service function
|
||||||
|
* Inputs:
|
||||||
|
* 1 : Context handle
|
||||||
|
* 2 : Form name buffer size, including null-terminator.
|
||||||
|
* 3 : Form value buffer size
|
||||||
|
* 4 : (FormNameSize<<14) | 0xC02
|
||||||
|
* 5 : Form name data pointer
|
||||||
|
* 6 : (FormValueSize<<4) | 10
|
||||||
|
* 7 : Form value data pointer
|
||||||
|
* Outputs:
|
||||||
|
* 1 : Result of function, 0 on success, otherwise error code
|
||||||
|
*/
|
||||||
|
void SendPostDataBinary(Kernel::HLERequestContext& ctx);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HTTP_C::SendPostDataBinaryTimeout service function
|
||||||
|
* Inputs:
|
||||||
|
* 1 : Context handle
|
||||||
|
* 2 : Form name buffer size, including null-terminator.
|
||||||
|
* 3 : Form value buffer size
|
||||||
|
* 4-5 : u64 nanoseconds delay
|
||||||
|
* 6 : (FormNameSize<<14) | 0xC02
|
||||||
|
* 7 : Form name data pointer
|
||||||
|
* 8 : (FormValueSize<<4) | 10
|
||||||
|
* 9 : Form value data pointer
|
||||||
|
* Outputs:
|
||||||
|
* 1 : Result of function, 0 on success, otherwise error code
|
||||||
|
*/
|
||||||
|
void SendPostDataBinaryTimeout(Kernel::HLERequestContext& ctx);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SendPostDataBinaryImpl:
|
||||||
|
* Implements SendPostDataBinary and SendPostDataBinaryTimeout service functions
|
||||||
|
*/
|
||||||
|
void SendPostDataBinaryImpl(Kernel::HLERequestContext& ctx, bool timeout);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HTTP_C::SendPostDataRaw service function
|
||||||
|
* Inputs:
|
||||||
|
* 1 : Context handle
|
||||||
|
* 2 : Post data length
|
||||||
|
* 3-4: (Mapped buffer) Post data
|
||||||
|
* Outputs:
|
||||||
|
* 1 : Result of function, 0 on success, otherwise error code
|
||||||
|
* 2-3: (Mapped buffer) Post data
|
||||||
|
*/
|
||||||
|
void SendPostDataRaw(Kernel::HLERequestContext& ctx);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HTTP_C::SendPostDataRawTimeout service function
|
||||||
|
* Inputs:
|
||||||
|
* 1 : Context handle
|
||||||
|
* 2 : Post data length
|
||||||
|
* 3-4: u64 nanoseconds delay
|
||||||
|
* 5-6: (Mapped buffer) Post data
|
||||||
|
* Outputs:
|
||||||
|
* 1 : Result of function, 0 on success, otherwise error code
|
||||||
|
* 2-3: (Mapped buffer) Post data
|
||||||
|
*/
|
||||||
|
void SendPostDataRawTimeout(Kernel::HLERequestContext& ctx);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SendPostDataRawImpl:
|
||||||
|
* Implements SendPostDataRaw and SendPostDataRawTimeout service functions
|
||||||
|
*/
|
||||||
|
void SendPostDataRawImpl(Kernel::HLERequestContext& ctx, bool timeout);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HTTP_C::NotifyFinishSendPostData service function
|
||||||
|
* Inputs:
|
||||||
|
* 1 : Context handle
|
||||||
|
* Outputs:
|
||||||
|
* 1 : Result of function, 0 on success, otherwise error code
|
||||||
|
*/
|
||||||
|
void NotifyFinishSendPostData(Kernel::HLERequestContext& ctx);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HTTP_C::SetPostDataEncoding service function
|
||||||
|
* Inputs:
|
||||||
|
* 1 : Context handle
|
||||||
|
* 2 : Post data encoding
|
||||||
|
* Outputs:
|
||||||
|
* 1 : Result of function, 0 on success, otherwise error code
|
||||||
|
*/
|
||||||
|
void SetPostDataEncoding(Kernel::HLERequestContext& ctx);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* HTTP_C::GetResponseHeader service function
|
* HTTP_C::GetResponseHeader service function
|
||||||
* Inputs:
|
* Inputs:
|
||||||
|
@ -448,6 +666,28 @@ private:
|
||||||
*/
|
*/
|
||||||
void GetResponseHeader(Kernel::HLERequestContext& ctx);
|
void GetResponseHeader(Kernel::HLERequestContext& ctx);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HTTP_C::GetResponseHeaderTimeout service function
|
||||||
|
* Inputs:
|
||||||
|
* 1 : Context handle
|
||||||
|
* 2 : Header name length
|
||||||
|
* 3 : Return value length
|
||||||
|
* 4-5 : u64 nanoseconds delay
|
||||||
|
* 6-7 : (Static buffer) Header name
|
||||||
|
* 8-9 : (Mapped buffer) Header value
|
||||||
|
* Outputs:
|
||||||
|
* 1 : Result of function, 0 on success, otherwise error code
|
||||||
|
* 2 : Header value copied size
|
||||||
|
* 3-4: (Mapped buffer) Header value
|
||||||
|
*/
|
||||||
|
void GetResponseHeaderTimeout(Kernel::HLERequestContext& ctx);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GetResponseHeaderImpl:
|
||||||
|
* Implements GetResponseHeader and GetResponseHeaderTimeout service functions
|
||||||
|
*/
|
||||||
|
void GetResponseHeaderImpl(Kernel::HLERequestContext& ctx, bool timeout);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* HTTP_C::GetResponseStatusCode service function
|
* HTTP_C::GetResponseStatusCode service function
|
||||||
* Inputs:
|
* Inputs:
|
||||||
|
@ -581,6 +821,17 @@ private:
|
||||||
*/
|
*/
|
||||||
void SetKeepAlive(Kernel::HLERequestContext& ctx);
|
void SetKeepAlive(Kernel::HLERequestContext& ctx);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HTTP_C::SetPostDataTypeSize service function
|
||||||
|
* Inputs:
|
||||||
|
* 1 : Context handle
|
||||||
|
* 2 : Post data type
|
||||||
|
* 3 : Content length size
|
||||||
|
* Outputs:
|
||||||
|
* 1 : Result of function, 0 on success, otherwise error code
|
||||||
|
*/
|
||||||
|
void SetPostDataTypeSize(Kernel::HLERequestContext& ctx);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* HTTP_C::Finalize service function
|
* HTTP_C::Finalize service function
|
||||||
* Outputs:
|
* Outputs:
|
||||||
|
|
16
src/core/hle/service/mcu/mcu.cpp
Normal file
16
src/core/hle/service/mcu/mcu.cpp
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
// Copyright 2024 Citra Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#include "core/core.h"
|
||||||
|
#include "core/hle/service/mcu/mcu.h"
|
||||||
|
#include "core/hle/service/mcu/mcu_hwc.h"
|
||||||
|
|
||||||
|
namespace Service::MCU {
|
||||||
|
|
||||||
|
void InstallInterfaces(Core::System& system) {
|
||||||
|
auto& service_manager = system.ServiceManager();
|
||||||
|
std::make_shared<HWC>()->InstallAsService(service_manager);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Service::MCU
|
15
src/core/hle/service/mcu/mcu.h
Normal file
15
src/core/hle/service/mcu/mcu.h
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
// Copyright 2024 Citra Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace Core {
|
||||||
|
class System;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace Service::MCU {
|
||||||
|
|
||||||
|
void InstallInterfaces(Core::System& system);
|
||||||
|
|
||||||
|
} // namespace Service::MCU
|
36
src/core/hle/service/mcu/mcu_hwc.cpp
Normal file
36
src/core/hle/service/mcu/mcu_hwc.cpp
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
// Copyright 2024 Citra Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#include "common/archives.h"
|
||||||
|
#include "core/hle/service/mcu/mcu_hwc.h"
|
||||||
|
|
||||||
|
SERIALIZE_EXPORT_IMPL(Service::MCU::HWC)
|
||||||
|
|
||||||
|
namespace Service::MCU {
|
||||||
|
|
||||||
|
HWC::HWC() : ServiceFramework("mcu::HWC", 1) {
|
||||||
|
static const FunctionInfo functions[] = {
|
||||||
|
// clang-format off
|
||||||
|
{0x0001, nullptr, "ReadRegister"},
|
||||||
|
{0x0002, nullptr, "WriteRegister"},
|
||||||
|
{0x0003, nullptr, "GetInfoRegisters"},
|
||||||
|
{0x0004, nullptr, "GetBatteryVoltage"},
|
||||||
|
{0x0005, nullptr, "GetBatteryLevel"},
|
||||||
|
{0x0006, nullptr, "SetPowerLEDPattern"},
|
||||||
|
{0x0007, nullptr, "SetWifiLEDState"},
|
||||||
|
{0x0008, nullptr, "SetCameraLEDPattern"},
|
||||||
|
{0x0009, nullptr, "Set3DLEDState"},
|
||||||
|
{0x000A, nullptr, "SetInfoLEDPattern"},
|
||||||
|
{0x000B, nullptr, "GetSoundVolume"},
|
||||||
|
{0x000C, nullptr, "SetTopScreenFlicker"},
|
||||||
|
{0x000D, nullptr, "SetBottomScreenFlicker"},
|
||||||
|
{0x000F, nullptr, "GetRtcTime"},
|
||||||
|
{0x0010, nullptr, "GetMcuFwVerHigh"},
|
||||||
|
{0x0011, nullptr, "GetMcuFwVerLow"},
|
||||||
|
// clang-format on
|
||||||
|
};
|
||||||
|
RegisterHandlers(functions);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Service::MCU
|
21
src/core/hle/service/mcu/mcu_hwc.h
Normal file
21
src/core/hle/service/mcu/mcu_hwc.h
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
// Copyright 2024 Citra Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "core/hle/service/service.h"
|
||||||
|
|
||||||
|
namespace Service::MCU {
|
||||||
|
|
||||||
|
class HWC final : public ServiceFramework<HWC> {
|
||||||
|
public:
|
||||||
|
explicit HWC();
|
||||||
|
|
||||||
|
private:
|
||||||
|
SERVICE_SERIALIZATION_SIMPLE
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Service::MCU
|
||||||
|
|
||||||
|
BOOST_CLASS_EXPORT_KEY(Service::MCU::HWC)
|
|
@ -2,18 +2,765 @@
|
||||||
// Licensed under GPLv2 or any later version
|
// Licensed under GPLv2 or any later version
|
||||||
// Refer to the license.txt file included.
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <fmt/format.h>
|
||||||
|
#include "common/archives.h"
|
||||||
|
#include "common/assert.h"
|
||||||
|
#include "common/file_util.h"
|
||||||
#include "common/logging/log.h"
|
#include "common/logging/log.h"
|
||||||
|
#include "common/scope_exit.h"
|
||||||
|
#include "common/string_util.h"
|
||||||
#include "core/core.h"
|
#include "core/core.h"
|
||||||
|
#include "core/file_sys/archive_systemsavedata.h"
|
||||||
|
#include "core/file_sys/errors.h"
|
||||||
|
#include "core/file_sys/file_backend.h"
|
||||||
|
#include "core/hle/ipc_helpers.h"
|
||||||
|
#include "core/hle/kernel/shared_page.h"
|
||||||
|
#include "core/hle/result.h"
|
||||||
|
#include "core/hle/service/fs/fs_user.h"
|
||||||
|
#include "core/hle/service/news/news.h"
|
||||||
#include "core/hle/service/news/news_s.h"
|
#include "core/hle/service/news/news_s.h"
|
||||||
#include "core/hle/service/news/news_u.h"
|
#include "core/hle/service/news/news_u.h"
|
||||||
#include "core/hle/service/service.h"
|
#include "core/hle/service/service.h"
|
||||||
|
|
||||||
|
SERVICE_CONSTRUCT_IMPL(Service::NEWS::Module)
|
||||||
|
|
||||||
namespace Service::NEWS {
|
namespace Service::NEWS {
|
||||||
|
|
||||||
|
namespace ErrCodes {
|
||||||
|
enum {
|
||||||
|
/// This error is returned if either the NewsDB header or the header for a notification ID is
|
||||||
|
/// invalid
|
||||||
|
InvalidHeader = 5,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr Result ErrorInvalidHeader = // 0xC8A12805
|
||||||
|
Result(ErrCodes::InvalidHeader, ErrorModule::News, ErrorSummary::InvalidState,
|
||||||
|
ErrorLevel::Status);
|
||||||
|
|
||||||
|
constexpr std::array<u8, 8> news_system_savedata_id{
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x35, 0x00, 0x01, 0x00,
|
||||||
|
};
|
||||||
|
|
||||||
|
template <class Archive>
|
||||||
|
void Module::serialize(Archive& ar, const unsigned int) {
|
||||||
|
ar& db;
|
||||||
|
ar& notification_ids;
|
||||||
|
ar& automatic_sync_flag;
|
||||||
|
ar& news_system_save_data_archive;
|
||||||
|
}
|
||||||
|
SERIALIZE_IMPL(Module)
|
||||||
|
|
||||||
|
void Module::Interface::AddNotificationImpl(Kernel::HLERequestContext& ctx, bool news_s) {
|
||||||
|
IPC::RequestParser rp(ctx);
|
||||||
|
const u32 header_size = rp.Pop<u32>();
|
||||||
|
const u32 message_size = rp.Pop<u32>();
|
||||||
|
const u32 image_size = rp.Pop<u32>();
|
||||||
|
|
||||||
|
u32 process_id;
|
||||||
|
if (!news_s) {
|
||||||
|
process_id = rp.PopPID();
|
||||||
|
LOG_INFO(Service_NEWS,
|
||||||
|
"called header_size=0x{:x}, message_size=0x{:x}, image_size=0x{:x}, process_id={}",
|
||||||
|
header_size, message_size, image_size, process_id);
|
||||||
|
} else {
|
||||||
|
LOG_INFO(Service_NEWS, "called header_size=0x{:x}, message_size=0x{:x}, image_size=0x{:x}",
|
||||||
|
header_size, message_size, image_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto header_buffer = rp.PopMappedBuffer();
|
||||||
|
auto message_buffer = rp.PopMappedBuffer();
|
||||||
|
auto image_buffer = rp.PopMappedBuffer();
|
||||||
|
|
||||||
|
NotificationHeader header{};
|
||||||
|
header_buffer.Read(&header, 0,
|
||||||
|
std::min(sizeof(NotificationHeader), static_cast<std::size_t>(header_size)));
|
||||||
|
|
||||||
|
std::vector<u8> message(message_size);
|
||||||
|
message_buffer.Read(message.data(), 0, message.size());
|
||||||
|
|
||||||
|
std::vector<u8> image(image_size);
|
||||||
|
image_buffer.Read(image.data(), 0, image.size());
|
||||||
|
|
||||||
|
IPC::RequestBuilder rb = rp.MakeBuilder(1, 6);
|
||||||
|
SCOPE_EXIT({
|
||||||
|
rb.PushMappedBuffer(header_buffer);
|
||||||
|
rb.PushMappedBuffer(message_buffer);
|
||||||
|
rb.PushMappedBuffer(image_buffer);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!news_s) {
|
||||||
|
// Set the program_id using the input process ID
|
||||||
|
auto fs_user = news->system.ServiceManager().GetService<Service::FS::FS_USER>("fs:USER");
|
||||||
|
ASSERT_MSG(fs_user != nullptr, "fs:USER service is missing.");
|
||||||
|
|
||||||
|
auto program_info_result = fs_user->GetProgramLaunchInfo(process_id);
|
||||||
|
if (program_info_result.Failed()) {
|
||||||
|
rb.Push(program_info_result.Code());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
header.program_id = program_info_result.Unwrap().program_id;
|
||||||
|
|
||||||
|
// The date_time is set by the sysmodule on news:u requests
|
||||||
|
auto& share_page = news->system.Kernel().GetSharedPageHandler();
|
||||||
|
header.date_time = share_page.GetSystemTimeSince2000();
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto save_result = news->SaveNotification(&header, header_size, message, image);
|
||||||
|
if (R_FAILED(save_result)) {
|
||||||
|
rb.Push(save_result);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mark the DB header new notification flag
|
||||||
|
if ((news->db.header.flags & 1) == 0) {
|
||||||
|
news->db.header.flags |= 1;
|
||||||
|
const auto db_result = news->SaveNewsDBSavedata();
|
||||||
|
if (R_FAILED(db_result)) {
|
||||||
|
rb.Push(db_result);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rb.Push(ResultSuccess);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Module::Interface::AddNotification(Kernel::HLERequestContext& ctx) {
|
||||||
|
AddNotificationImpl(ctx, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Module::Interface::AddNotificationSystem(Kernel::HLERequestContext& ctx) {
|
||||||
|
AddNotificationImpl(ctx, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Module::Interface::ResetNotifications(Kernel::HLERequestContext& ctx) {
|
||||||
|
IPC::RequestParser rp(ctx);
|
||||||
|
|
||||||
|
LOG_INFO(Service_NEWS, "called");
|
||||||
|
|
||||||
|
// Cleanup the sorted notification IDs
|
||||||
|
for (u32 i = 0; i < MAX_NOTIFICATIONS; ++i) {
|
||||||
|
news->notification_ids[i] = i;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string& nand_directory = FileUtil::GetUserPath(FileUtil::UserPath::NANDDir);
|
||||||
|
FileSys::ArchiveFactory_SystemSaveData systemsavedata_factory(nand_directory);
|
||||||
|
|
||||||
|
FileSys::Path archive_path(news_system_savedata_id);
|
||||||
|
|
||||||
|
// Format the SystemSaveData archive 0x00010035
|
||||||
|
systemsavedata_factory.Format(archive_path, FileSys::ArchiveFormatInfo(), 0);
|
||||||
|
|
||||||
|
news->news_system_save_data_archive = systemsavedata_factory.Open(archive_path, 0).Unwrap();
|
||||||
|
|
||||||
|
// NOTE: The original sysmodule doesn't clear the News DB in memory
|
||||||
|
|
||||||
|
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
|
||||||
|
rb.Push(ResultSuccess);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Module::Interface::GetTotalNotifications(Kernel::HLERequestContext& ctx) {
|
||||||
|
IPC::RequestParser rp(ctx);
|
||||||
|
|
||||||
|
LOG_INFO(Service_NEWS, "called");
|
||||||
|
|
||||||
|
IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
|
||||||
|
rb.Push(ResultSuccess);
|
||||||
|
rb.Push(static_cast<u32>(news->GetTotalNotifications()));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Module::Interface::SetNewsDBHeader(Kernel::HLERequestContext& ctx) {
|
||||||
|
IPC::RequestParser rp(ctx);
|
||||||
|
const u32 size = rp.Pop<u32>();
|
||||||
|
auto input_buffer = rp.PopMappedBuffer();
|
||||||
|
|
||||||
|
LOG_INFO(Service_NEWS, "called size=0x{:x}", size);
|
||||||
|
|
||||||
|
NewsDBHeader header{};
|
||||||
|
input_buffer.Read(&header, 0, std::min(sizeof(NewsDBHeader), static_cast<std::size_t>(size)));
|
||||||
|
|
||||||
|
IPC::RequestBuilder rb = rp.MakeBuilder(1, 2);
|
||||||
|
rb.Push(news->SetNewsDBHeader(&header, size));
|
||||||
|
rb.PushMappedBuffer(input_buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Module::Interface::SetNotificationHeader(Kernel::HLERequestContext& ctx) {
|
||||||
|
IPC::RequestParser rp(ctx);
|
||||||
|
const u32 notification_index = rp.Pop<u32>();
|
||||||
|
const u32 size = rp.Pop<u32>();
|
||||||
|
auto input_buffer = rp.PopMappedBuffer();
|
||||||
|
|
||||||
|
LOG_INFO(Service_NEWS, "called notification_index={}, size=0x{:x}", notification_index, size);
|
||||||
|
|
||||||
|
NotificationHeader header{};
|
||||||
|
input_buffer.Read(&header, 0,
|
||||||
|
std::min(sizeof(NotificationHeader), static_cast<std::size_t>(size)));
|
||||||
|
|
||||||
|
const auto result = news->SetNotificationHeader(notification_index, &header, size);
|
||||||
|
|
||||||
|
// TODO(DaniElectra): If flag_boss == 1, the original sysmodule updates the optout status of the
|
||||||
|
// source program on SpotPass with the boss:P command 0x00040600C0 (possibly named
|
||||||
|
// SetOptoutFlagPrivileged?) using the program_id and flag_optout as parameter
|
||||||
|
|
||||||
|
IPC::RequestBuilder rb = rp.MakeBuilder(1, 2);
|
||||||
|
rb.Push(result);
|
||||||
|
rb.PushMappedBuffer(input_buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Module::Interface::SetNotificationMessage(Kernel::HLERequestContext& ctx) {
|
||||||
|
IPC::RequestParser rp(ctx);
|
||||||
|
const u32 notification_index = rp.Pop<u32>();
|
||||||
|
const u32 size = rp.Pop<u32>();
|
||||||
|
auto input_buffer = rp.PopMappedBuffer();
|
||||||
|
|
||||||
|
LOG_INFO(Service_NEWS, "called notification_index={}, size=0x{:x}", notification_index, size);
|
||||||
|
|
||||||
|
std::vector<u8> data(size);
|
||||||
|
input_buffer.Read(data.data(), 0, data.size());
|
||||||
|
|
||||||
|
IPC::RequestBuilder rb = rp.MakeBuilder(1, 2);
|
||||||
|
rb.Push(news->SetNotificationMessage(notification_index, data));
|
||||||
|
rb.PushMappedBuffer(input_buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Module::Interface::SetNotificationImage(Kernel::HLERequestContext& ctx) {
|
||||||
|
IPC::RequestParser rp(ctx);
|
||||||
|
const u32 notification_index = rp.Pop<u32>();
|
||||||
|
const u32 size = rp.Pop<u32>();
|
||||||
|
auto input_buffer = rp.PopMappedBuffer();
|
||||||
|
|
||||||
|
LOG_INFO(Service_NEWS, "called notification_index={}, size=0x{:x}", notification_index, size);
|
||||||
|
|
||||||
|
std::vector<u8> data(size);
|
||||||
|
input_buffer.Read(data.data(), 0, data.size());
|
||||||
|
|
||||||
|
IPC::RequestBuilder rb = rp.MakeBuilder(1, 2);
|
||||||
|
rb.Push(news->SetNotificationImage(notification_index, data));
|
||||||
|
rb.PushMappedBuffer(input_buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Module::Interface::GetNewsDBHeader(Kernel::HLERequestContext& ctx) {
|
||||||
|
IPC::RequestParser rp(ctx);
|
||||||
|
const u32 size = rp.Pop<u32>();
|
||||||
|
auto output_buffer = rp.PopMappedBuffer();
|
||||||
|
|
||||||
|
LOG_INFO(Service_NEWS, "called size=0x{:x}", size);
|
||||||
|
|
||||||
|
NewsDBHeader header{};
|
||||||
|
const auto result = news->GetNewsDBHeader(&header, size);
|
||||||
|
|
||||||
|
IPC::RequestBuilder rb = rp.MakeBuilder(2, 2);
|
||||||
|
|
||||||
|
if (result.Failed()) {
|
||||||
|
rb.Push(result.Code());
|
||||||
|
rb.Push<u32>(0);
|
||||||
|
} else {
|
||||||
|
const auto copied_size = result.Unwrap();
|
||||||
|
output_buffer.Write(&header, 0, copied_size);
|
||||||
|
|
||||||
|
rb.Push(ResultSuccess);
|
||||||
|
rb.Push<u32>(static_cast<u32>(copied_size));
|
||||||
|
}
|
||||||
|
|
||||||
|
rb.PushMappedBuffer(output_buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Module::Interface::GetNotificationHeader(Kernel::HLERequestContext& ctx) {
|
||||||
|
IPC::RequestParser rp(ctx);
|
||||||
|
const u32 notification_index = rp.Pop<u32>();
|
||||||
|
const u32 size = rp.Pop<u32>();
|
||||||
|
auto output_buffer = rp.PopMappedBuffer();
|
||||||
|
|
||||||
|
LOG_DEBUG(Service_NEWS, "called notification_index={}, size=0x{:x}", notification_index, size);
|
||||||
|
|
||||||
|
NotificationHeader header{};
|
||||||
|
const auto result = news->GetNotificationHeader(notification_index, &header, size);
|
||||||
|
|
||||||
|
IPC::RequestBuilder rb = rp.MakeBuilder(2, 2);
|
||||||
|
SCOPE_EXIT({ rb.PushMappedBuffer(output_buffer); });
|
||||||
|
|
||||||
|
if (result.Failed()) {
|
||||||
|
rb.Push(result.Code());
|
||||||
|
rb.Push<u32>(0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(DaniElectra): If flag_boss == 1, the original sysmodule updates the optout flag of the
|
||||||
|
// header with the result of boss:P command 0x0004070080 (possibly named
|
||||||
|
// GetOptoutFlagPrivileged?) using the program_id as parameter
|
||||||
|
|
||||||
|
const auto copied_size = result.Unwrap();
|
||||||
|
output_buffer.Write(&header, 0, copied_size);
|
||||||
|
|
||||||
|
rb.Push(ResultSuccess);
|
||||||
|
rb.Push<u32>(static_cast<u32>(copied_size));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Module::Interface::GetNotificationMessage(Kernel::HLERequestContext& ctx) {
|
||||||
|
IPC::RequestParser rp(ctx);
|
||||||
|
const u32 notification_index = rp.Pop<u32>();
|
||||||
|
const u32 size = rp.Pop<u32>();
|
||||||
|
auto output_buffer = rp.PopMappedBuffer();
|
||||||
|
|
||||||
|
LOG_DEBUG(Service_NEWS, "called notification_index={}, size=0x{:x}", notification_index, size);
|
||||||
|
|
||||||
|
std::vector<u8> message(size);
|
||||||
|
const auto result = news->GetNotificationMessage(notification_index, message);
|
||||||
|
|
||||||
|
IPC::RequestBuilder rb = rp.MakeBuilder(2, 2);
|
||||||
|
SCOPE_EXIT({ rb.PushMappedBuffer(output_buffer); });
|
||||||
|
|
||||||
|
if (result.Failed()) {
|
||||||
|
rb.Push(result.Code());
|
||||||
|
rb.Push<u32>(0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto copied_size = result.Unwrap();
|
||||||
|
output_buffer.Write(message.data(), 0, copied_size);
|
||||||
|
|
||||||
|
rb.Push(ResultSuccess);
|
||||||
|
rb.Push<u32>(static_cast<u32>(copied_size));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Module::Interface::GetNotificationImage(Kernel::HLERequestContext& ctx) {
|
||||||
|
IPC::RequestParser rp(ctx);
|
||||||
|
const u32 notification_index = rp.Pop<u32>();
|
||||||
|
const u32 size = rp.Pop<u32>();
|
||||||
|
auto output_buffer = rp.PopMappedBuffer();
|
||||||
|
|
||||||
|
LOG_DEBUG(Service_NEWS, "called notification_index={}, size=0x{:x}", notification_index, size);
|
||||||
|
|
||||||
|
std::vector<u8> image(size);
|
||||||
|
const auto result = news->GetNotificationImage(notification_index, image);
|
||||||
|
|
||||||
|
IPC::RequestBuilder rb = rp.MakeBuilder(2, 2);
|
||||||
|
SCOPE_EXIT({ rb.PushMappedBuffer(output_buffer); });
|
||||||
|
|
||||||
|
if (result.Failed()) {
|
||||||
|
rb.Push(result.Code());
|
||||||
|
rb.Push<u32>(0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto copied_size = result.Unwrap();
|
||||||
|
output_buffer.Write(image.data(), 0, copied_size);
|
||||||
|
|
||||||
|
rb.Push(ResultSuccess);
|
||||||
|
rb.Push<u32>(static_cast<u32>(copied_size));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Module::Interface::SetAutomaticSyncFlag(Kernel::HLERequestContext& ctx) {
|
||||||
|
IPC::RequestParser rp(ctx);
|
||||||
|
const u8 flag = rp.Pop<u8>();
|
||||||
|
|
||||||
|
LOG_INFO(Service_NEWS, "called flag=0x{:x}", flag);
|
||||||
|
|
||||||
|
news->automatic_sync_flag = flag;
|
||||||
|
|
||||||
|
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
|
||||||
|
rb.Push(ResultSuccess);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Module::Interface::SetNotificationHeaderOther(Kernel::HLERequestContext& ctx) {
|
||||||
|
IPC::RequestParser rp(ctx);
|
||||||
|
const u32 notification_index = rp.Pop<u32>();
|
||||||
|
const u32 size = rp.Pop<u32>();
|
||||||
|
auto output_buffer = rp.PopMappedBuffer();
|
||||||
|
|
||||||
|
LOG_INFO(Service_NEWS, "called notification_index={}, size=0x{:x}", notification_index, size);
|
||||||
|
|
||||||
|
NotificationHeader header{};
|
||||||
|
output_buffer.Read(&header, 0,
|
||||||
|
std::min(sizeof(NotificationHeader), static_cast<std::size_t>(size)));
|
||||||
|
|
||||||
|
const auto result = news->SetNotificationHeaderOther(notification_index, &header, size);
|
||||||
|
|
||||||
|
// TODO(DaniElectra): If flag_boss == 1, the original sysmodule updates the optout status of the
|
||||||
|
// source program on SpotPass with the boss:P command 0x00040600C0 (possibly named
|
||||||
|
// SetOptoutFlagPrivileged?) using the program_id and flag_optout as parameter
|
||||||
|
|
||||||
|
IPC::RequestBuilder rb = rp.MakeBuilder(1, 2);
|
||||||
|
rb.Push(result);
|
||||||
|
rb.PushMappedBuffer(output_buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Module::Interface::WriteNewsDBSavedata(Kernel::HLERequestContext& ctx) {
|
||||||
|
IPC::RequestParser rp(ctx);
|
||||||
|
|
||||||
|
LOG_INFO(Service_NEWS, "called");
|
||||||
|
|
||||||
|
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
|
||||||
|
rb.Push(news->SaveNewsDBSavedata());
|
||||||
|
}
|
||||||
|
|
||||||
|
void Module::Interface::GetTotalArrivedNotifications(Kernel::HLERequestContext& ctx) {
|
||||||
|
IPC::RequestParser rp(ctx);
|
||||||
|
|
||||||
|
LOG_WARNING(Service_NEWS, "(STUBBED) called");
|
||||||
|
|
||||||
|
IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
|
||||||
|
rb.Push(ResultSuccess);
|
||||||
|
rb.Push<u32>(0); // Total number of pending BOSS notifications to be synced
|
||||||
|
}
|
||||||
|
|
||||||
|
std::size_t Module::GetTotalNotifications() {
|
||||||
|
return std::count_if(
|
||||||
|
notification_ids.begin(), notification_ids.end(),
|
||||||
|
[this](const u32 notification_id) { return db.notifications[notification_id].IsValid(); });
|
||||||
|
}
|
||||||
|
|
||||||
|
ResultVal<std::size_t> Module::GetNewsDBHeader(NewsDBHeader* header, const std::size_t size) {
|
||||||
|
if (!db.header.IsValid()) {
|
||||||
|
return ErrorInvalidHeader;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::size_t copy_size = std::min(sizeof(NewsDBHeader), size);
|
||||||
|
std::memcpy(header, &db.header, copy_size);
|
||||||
|
return copy_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
ResultVal<std::size_t> Module::GetNotificationHeader(const u32 notification_index,
|
||||||
|
NotificationHeader* header,
|
||||||
|
const std::size_t size) {
|
||||||
|
if (!db.header.IsValid()) {
|
||||||
|
return ErrorInvalidHeader;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (notification_index >= MAX_NOTIFICATIONS) {
|
||||||
|
return ErrorInvalidHeader;
|
||||||
|
}
|
||||||
|
|
||||||
|
const u32 notification_id = notification_ids[notification_index];
|
||||||
|
if (!db.notifications[notification_id].IsValid()) {
|
||||||
|
return ErrorInvalidHeader;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::size_t copy_size = std::min(sizeof(NotificationHeader), size);
|
||||||
|
std::memcpy(header, &db.notifications[notification_id], copy_size);
|
||||||
|
return copy_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
ResultVal<std::size_t> Module::GetNotificationMessage(const u32 notification_index,
|
||||||
|
std::span<u8> message) {
|
||||||
|
if (!db.header.IsValid()) {
|
||||||
|
return ErrorInvalidHeader;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (notification_index >= MAX_NOTIFICATIONS) {
|
||||||
|
return ErrorInvalidHeader;
|
||||||
|
}
|
||||||
|
|
||||||
|
const u32 notification_id = notification_ids[notification_index];
|
||||||
|
if (!db.notifications[notification_id].IsValid()) {
|
||||||
|
return ErrorInvalidHeader;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string message_file = fmt::format("/news{:03d}.txt", notification_id);
|
||||||
|
const auto result = LoadFileFromSavedata(message_file, message);
|
||||||
|
if (result.Failed()) {
|
||||||
|
return result.Code();
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.Unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
ResultVal<std::size_t> Module::GetNotificationImage(const u32 notification_index,
|
||||||
|
std::span<u8> image) {
|
||||||
|
if (!db.header.IsValid()) {
|
||||||
|
return ErrorInvalidHeader;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (notification_index >= MAX_NOTIFICATIONS) {
|
||||||
|
return ErrorInvalidHeader;
|
||||||
|
}
|
||||||
|
|
||||||
|
const u32 notification_id = notification_ids[notification_index];
|
||||||
|
if (!db.notifications[notification_id].IsValid()) {
|
||||||
|
return ErrorInvalidHeader;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string image_file = fmt::format("/news{:03d}.mpo", notification_id);
|
||||||
|
const auto result = LoadFileFromSavedata(image_file, image);
|
||||||
|
if (result.Failed()) {
|
||||||
|
return result.Code();
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.Unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
Result Module::SetNewsDBHeader(const NewsDBHeader* header, const std::size_t size) {
|
||||||
|
const std::size_t copy_size = std::min(sizeof(NewsDBHeader), static_cast<std::size_t>(size));
|
||||||
|
std::memcpy(&db.header, header, copy_size);
|
||||||
|
return SaveNewsDBSavedata();
|
||||||
|
}
|
||||||
|
|
||||||
|
Result Module::SetNotificationHeader(const u32 notification_index, const NotificationHeader* header,
|
||||||
|
const std::size_t size) {
|
||||||
|
if (notification_index >= MAX_NOTIFICATIONS) {
|
||||||
|
return ErrorInvalidHeader;
|
||||||
|
}
|
||||||
|
|
||||||
|
const u32 notification_id = notification_ids[notification_index];
|
||||||
|
const std::size_t copy_size = std::min(sizeof(NotificationHeader), size);
|
||||||
|
std::memcpy(&db.notifications[notification_id], header, copy_size);
|
||||||
|
return SaveNewsDBSavedata();
|
||||||
|
}
|
||||||
|
|
||||||
|
Result Module::SetNotificationHeaderOther(const u32 notification_index,
|
||||||
|
const NotificationHeader* header,
|
||||||
|
const std::size_t size) {
|
||||||
|
if (notification_index >= MAX_NOTIFICATIONS) {
|
||||||
|
return ErrorInvalidHeader;
|
||||||
|
}
|
||||||
|
|
||||||
|
const u32 notification_id = notification_ids[notification_index];
|
||||||
|
const std::size_t copy_size = std::min(sizeof(NotificationHeader), size);
|
||||||
|
std::memcpy(&db.notifications[notification_id], header, copy_size);
|
||||||
|
return ResultSuccess;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result Module::SetNotificationMessage(const u32 notification_index, std::span<const u8> message) {
|
||||||
|
if (notification_index >= MAX_NOTIFICATIONS) {
|
||||||
|
return ErrorInvalidHeader;
|
||||||
|
}
|
||||||
|
|
||||||
|
const u32 notification_id = notification_ids[notification_index];
|
||||||
|
const std::string message_file = fmt::format("/news{:03d}.txt", notification_id);
|
||||||
|
return SaveFileToSavedata(message_file, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result Module::SetNotificationImage(const u32 notification_index, std::span<const u8> image) {
|
||||||
|
if (notification_index >= MAX_NOTIFICATIONS) {
|
||||||
|
return ErrorInvalidHeader;
|
||||||
|
}
|
||||||
|
|
||||||
|
const u32 notification_id = notification_ids[notification_index];
|
||||||
|
const std::string image_file = fmt::format("/news{:03d}.mpo", notification_id);
|
||||||
|
return SaveFileToSavedata(image_file, image);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result Module::SaveNotification(const NotificationHeader* header, const std::size_t header_size,
|
||||||
|
std::span<const u8> message, std::span<const u8> image) {
|
||||||
|
if (!db.header.IsValid()) {
|
||||||
|
return ErrorInvalidHeader;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!header->IsValid()) {
|
||||||
|
return ErrorInvalidHeader;
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 notification_count = static_cast<u32>(GetTotalNotifications());
|
||||||
|
|
||||||
|
// If we have reached the limit of 100 notifications, delete the oldest one
|
||||||
|
if (notification_count >= MAX_NOTIFICATIONS) {
|
||||||
|
LOG_WARNING(Service_NEWS,
|
||||||
|
"Notification limit has been reached. Deleting oldest notification ID: {}",
|
||||||
|
notification_ids[0]);
|
||||||
|
R_TRY(DeleteNotification(notification_ids[0]));
|
||||||
|
|
||||||
|
notification_count--;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if there is enough space for storing the new notification data. The header is already
|
||||||
|
// allocated with the News DB
|
||||||
|
const u64 needed_space = static_cast<u64>(message.size() + image.size());
|
||||||
|
while (notification_count > 0) {
|
||||||
|
const u64 free_space = news_system_save_data_archive->GetFreeBytes();
|
||||||
|
if (needed_space <= free_space) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_WARNING(Service_NEWS, "Not enough space available. Deleting oldest notification ID: {}",
|
||||||
|
notification_ids[0]);
|
||||||
|
|
||||||
|
// If we don't have space, delete old notifications until we do
|
||||||
|
R_TRY(DeleteNotification(notification_ids[0]));
|
||||||
|
|
||||||
|
notification_count--;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_DEBUG(Service_NEWS, "New notification: notification_id={}, title={}",
|
||||||
|
notification_ids[notification_count], Common::UTF16BufferToUTF8(header->title));
|
||||||
|
|
||||||
|
if (!image.empty()) {
|
||||||
|
R_TRY(SetNotificationImage(notification_count, image));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!message.empty()) {
|
||||||
|
R_TRY(SetNotificationMessage(notification_count, message));
|
||||||
|
}
|
||||||
|
|
||||||
|
R_TRY(SetNotificationHeader(notification_count, header, header_size));
|
||||||
|
|
||||||
|
// Sort the notifications after saving
|
||||||
|
std::sort(notification_ids.begin(), notification_ids.end(),
|
||||||
|
[this](const u32 first_id, const u32 second_id) -> bool {
|
||||||
|
return CompareNotifications(first_id, second_id);
|
||||||
|
});
|
||||||
|
|
||||||
|
return ResultSuccess;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result Module::DeleteNotification(const u32 notification_id) {
|
||||||
|
bool deleted = false;
|
||||||
|
|
||||||
|
// Check if the input notification ID exists, and clear it
|
||||||
|
if (db.notifications[notification_id].IsValid()) {
|
||||||
|
db.notifications[notification_id] = {};
|
||||||
|
|
||||||
|
R_TRY(SaveNewsDBSavedata());
|
||||||
|
|
||||||
|
deleted = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cleanup images and messages for invalid notifications
|
||||||
|
for (u32 i = 0; i < MAX_NOTIFICATIONS; ++i) {
|
||||||
|
if (!db.notifications[i].IsValid()) {
|
||||||
|
const std::string image_file = fmt::format("/news{:03d}.mpo", i);
|
||||||
|
auto result = news_system_save_data_archive->DeleteFile(image_file);
|
||||||
|
if (R_FAILED(result) && result != FileSys::ResultFileNotFound) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string message_file = fmt::format("/news{:03d}.txt", i);
|
||||||
|
result = news_system_save_data_archive->DeleteFile(message_file);
|
||||||
|
if (R_FAILED(result) && result != FileSys::ResultFileNotFound) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the input notification ID was deleted, reorder the notification IDs list
|
||||||
|
if (deleted) {
|
||||||
|
std::sort(notification_ids.begin(), notification_ids.end(),
|
||||||
|
[this](const u32 first_id, const u32 second_id) -> bool {
|
||||||
|
return CompareNotifications(first_id, second_id);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return ResultSuccess;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result Module::LoadNewsDBSavedata() {
|
||||||
|
const std::string& nand_directory = FileUtil::GetUserPath(FileUtil::UserPath::NANDDir);
|
||||||
|
FileSys::ArchiveFactory_SystemSaveData systemsavedata_factory(nand_directory);
|
||||||
|
|
||||||
|
// Open the SystemSaveData archive 0x00010035
|
||||||
|
FileSys::Path archive_path(news_system_savedata_id);
|
||||||
|
auto archive_result = systemsavedata_factory.Open(archive_path, 0);
|
||||||
|
|
||||||
|
// If the archive didn't exist, create the files inside
|
||||||
|
if (archive_result.Code() == FileSys::ResultNotFound) {
|
||||||
|
// Format the archive to create the directories
|
||||||
|
systemsavedata_factory.Format(archive_path, FileSys::ArchiveFormatInfo(), 0);
|
||||||
|
|
||||||
|
// Open it again to get a valid archive now that the folder exists
|
||||||
|
news_system_save_data_archive = systemsavedata_factory.Open(archive_path, 0).Unwrap();
|
||||||
|
} else {
|
||||||
|
ASSERT_MSG(archive_result.Succeeded(), "Could not open the NEWS SystemSaveData archive!");
|
||||||
|
|
||||||
|
news_system_save_data_archive = std::move(archive_result).Unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string news_db_file = "/news.db";
|
||||||
|
auto news_result =
|
||||||
|
LoadFileFromSavedata(news_db_file, std::span{reinterpret_cast<u8*>(&db), sizeof(NewsDB)});
|
||||||
|
|
||||||
|
// Read the file if it already exists
|
||||||
|
if (news_result.Failed()) {
|
||||||
|
// Create the file immediately if it doesn't exist
|
||||||
|
db.header = {.valid = 1};
|
||||||
|
news_result = SaveFileToSavedata(
|
||||||
|
news_db_file, std::span{reinterpret_cast<const u8*>(&db), sizeof(NewsDB)});
|
||||||
|
} else {
|
||||||
|
// Sort the notifications from the file
|
||||||
|
std::sort(notification_ids.begin(), notification_ids.end(),
|
||||||
|
[this](const u32 first_id, const u32 second_id) -> bool {
|
||||||
|
return CompareNotifications(first_id, second_id);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return news_result.Code();
|
||||||
|
}
|
||||||
|
|
||||||
|
Result Module::SaveNewsDBSavedata() {
|
||||||
|
return SaveFileToSavedata("/news.db",
|
||||||
|
std::span{reinterpret_cast<const u8*>(&db), sizeof(NewsDB)});
|
||||||
|
}
|
||||||
|
|
||||||
|
ResultVal<std::size_t> Module::LoadFileFromSavedata(std::string filename, std::span<u8> buffer) {
|
||||||
|
FileSys::Mode mode = {};
|
||||||
|
mode.read_flag.Assign(1);
|
||||||
|
|
||||||
|
FileSys::Path path(filename);
|
||||||
|
|
||||||
|
auto result = news_system_save_data_archive->OpenFile(path, mode);
|
||||||
|
if (result.Failed()) {
|
||||||
|
return result.Code();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto file = std::move(result).Unwrap();
|
||||||
|
const auto bytes_read = file->Read(0, buffer.size(), buffer.data());
|
||||||
|
file->Close();
|
||||||
|
|
||||||
|
ASSERT_MSG(bytes_read.Succeeded(), "could not read file");
|
||||||
|
|
||||||
|
return bytes_read.Unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
Result Module::SaveFileToSavedata(std::string filename, std::span<const u8> buffer) {
|
||||||
|
FileSys::Mode mode = {};
|
||||||
|
mode.write_flag.Assign(1);
|
||||||
|
mode.create_flag.Assign(1);
|
||||||
|
|
||||||
|
FileSys::Path path(filename);
|
||||||
|
|
||||||
|
auto result = news_system_save_data_archive->OpenFile(path, mode);
|
||||||
|
ASSERT_MSG(result.Succeeded(), "could not open file");
|
||||||
|
|
||||||
|
auto file = std::move(result).Unwrap();
|
||||||
|
file->Write(0, buffer.size(), 1, buffer.data());
|
||||||
|
file->Close();
|
||||||
|
|
||||||
|
return ResultSuccess;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Module::CompareNotifications(const u32 first_id, const u32 second_id) {
|
||||||
|
// Notification IDs are sorted by date time, with valid notifications being first.
|
||||||
|
// This is done so that other system applications like the News applet can easily
|
||||||
|
// iterate over the notifications with an incrementing index.
|
||||||
|
ASSERT(first_id < MAX_NOTIFICATIONS && second_id < MAX_NOTIFICATIONS);
|
||||||
|
|
||||||
|
if (!db.notifications[first_id].IsValid()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!db.notifications[second_id].IsValid()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return db.notifications[first_id].date_time < db.notifications[second_id].date_time;
|
||||||
|
}
|
||||||
|
|
||||||
|
Module::Interface::Interface(std::shared_ptr<Module> news, const char* name, u32 max_session)
|
||||||
|
: ServiceFramework(name, max_session), news(std::move(news)) {}
|
||||||
|
|
||||||
|
Module::Module(Core::System& system_) : system(system_) {
|
||||||
|
for (u32 i = 0; i < MAX_NOTIFICATIONS; ++i) {
|
||||||
|
notification_ids[i] = i;
|
||||||
|
}
|
||||||
|
|
||||||
|
LoadNewsDBSavedata();
|
||||||
|
}
|
||||||
|
|
||||||
void InstallInterfaces(Core::System& system) {
|
void InstallInterfaces(Core::System& system) {
|
||||||
auto& service_manager = system.ServiceManager();
|
auto& service_manager = system.ServiceManager();
|
||||||
std::make_shared<NEWS_S>()->InstallAsService(service_manager);
|
auto news = std::make_shared<Module>(system);
|
||||||
std::make_shared<NEWS_U>()->InstallAsService(service_manager);
|
std::make_shared<NEWS_S>(news)->InstallAsService(service_manager);
|
||||||
|
std::make_shared<NEWS_U>(news)->InstallAsService(service_manager);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace Service::NEWS
|
} // namespace Service::NEWS
|
||||||
|
|
|
@ -4,12 +4,482 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "core/file_sys/archive_backend.h"
|
||||||
|
#include "core/hle/service/service.h"
|
||||||
|
|
||||||
namespace Core {
|
namespace Core {
|
||||||
class System;
|
class System;
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace Service::NEWS {
|
namespace Service::NEWS {
|
||||||
|
constexpr u32 MAX_NOTIFICATIONS = 100;
|
||||||
|
|
||||||
|
struct NewsDBHeader {
|
||||||
|
u8 valid;
|
||||||
|
u8 flags;
|
||||||
|
INSERT_PADDING_BYTES(0xE);
|
||||||
|
|
||||||
|
bool IsValid() const {
|
||||||
|
return valid == 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
template <class Archive>
|
||||||
|
void serialize(Archive& ar, const unsigned int) {
|
||||||
|
ar& valid;
|
||||||
|
ar& flags;
|
||||||
|
}
|
||||||
|
friend class boost::serialization::access;
|
||||||
|
};
|
||||||
|
static_assert(sizeof(NewsDBHeader) == 0x10, "News DB Header structure size is wrong");
|
||||||
|
|
||||||
|
struct NotificationHeader {
|
||||||
|
u8 flag_valid;
|
||||||
|
u8 flag_read;
|
||||||
|
u8 flag_jpeg;
|
||||||
|
u8 flag_boss;
|
||||||
|
u8 flag_optout;
|
||||||
|
u8 flag_url;
|
||||||
|
u8 flag_unk0x6;
|
||||||
|
INSERT_PADDING_BYTES(0x1);
|
||||||
|
u64_le program_id;
|
||||||
|
u32_le ns_data_id; // Only used in BOSS notifications
|
||||||
|
u32_le version; // Only used in BOSS notifications
|
||||||
|
u64_le jump_param;
|
||||||
|
INSERT_PADDING_BYTES(0x8);
|
||||||
|
u64_le date_time;
|
||||||
|
std::array<u16_le, 0x20> title;
|
||||||
|
|
||||||
|
bool IsValid() const {
|
||||||
|
return flag_valid == 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
template <class Archive>
|
||||||
|
void serialize(Archive& ar, const unsigned int) {
|
||||||
|
ar& flag_valid;
|
||||||
|
ar& flag_read;
|
||||||
|
ar& flag_jpeg;
|
||||||
|
ar& flag_boss;
|
||||||
|
ar& flag_optout;
|
||||||
|
ar& flag_url;
|
||||||
|
ar& flag_unk0x6;
|
||||||
|
ar& program_id;
|
||||||
|
ar& ns_data_id;
|
||||||
|
ar& version;
|
||||||
|
ar& jump_param;
|
||||||
|
ar& date_time;
|
||||||
|
ar& title;
|
||||||
|
}
|
||||||
|
friend class boost::serialization::access;
|
||||||
|
};
|
||||||
|
static_assert(sizeof(NotificationHeader) == 0x70, "Notification Header structure size is wrong");
|
||||||
|
|
||||||
|
struct NewsDB {
|
||||||
|
NewsDBHeader header;
|
||||||
|
std::array<NotificationHeader, MAX_NOTIFICATIONS> notifications;
|
||||||
|
|
||||||
|
private:
|
||||||
|
template <class Archive>
|
||||||
|
void serialize(Archive& ar, const unsigned int) {
|
||||||
|
ar& header;
|
||||||
|
ar& notifications;
|
||||||
|
}
|
||||||
|
friend class boost::serialization::access;
|
||||||
|
};
|
||||||
|
static_assert(sizeof(NewsDB) == 0x2BD0, "News DB structure size is wrong");
|
||||||
|
|
||||||
|
class Module final {
|
||||||
|
public:
|
||||||
|
explicit Module(Core::System& system_);
|
||||||
|
~Module() = default;
|
||||||
|
|
||||||
|
class Interface : public ServiceFramework<Interface> {
|
||||||
|
public:
|
||||||
|
Interface(std::shared_ptr<Module> news, const char* name, u32 max_session);
|
||||||
|
~Interface() = default;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void AddNotificationImpl(Kernel::HLERequestContext& ctx, bool news_s);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
/**
|
||||||
|
* AddNotification NEWS:U service function.
|
||||||
|
* Inputs:
|
||||||
|
* 0 : 0x000100C8
|
||||||
|
* 1 : Header size
|
||||||
|
* 2 : Message size
|
||||||
|
* 3 : Image size
|
||||||
|
* 4 : PID Translation Header (0x20)
|
||||||
|
* 5 : Caller PID
|
||||||
|
* 6 : Header Buffer Mapping Translation Header ((Size << 4) | 0xA)
|
||||||
|
* 7 : Header Buffer Pointer
|
||||||
|
* 8 : Message Buffer Mapping Translation Header ((Size << 4) | 0xA)
|
||||||
|
* 9 : Message Buffer Pointer
|
||||||
|
* 10 : Image Buffer Mapping Translation Header ((Size << 4) | 0xA)
|
||||||
|
* 11 : Image Buffer Pointer
|
||||||
|
* Outputs:
|
||||||
|
* 0 : 0x00010046
|
||||||
|
* 1 : Result of function, 0 on success, otherwise error code
|
||||||
|
*/
|
||||||
|
void AddNotification(Kernel::HLERequestContext& ctx);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AddNotification NEWS:S service function.
|
||||||
|
* Inputs:
|
||||||
|
* 0 : 0x000100C6
|
||||||
|
* 1 : Header size
|
||||||
|
* 2 : Message size
|
||||||
|
* 3 : Image size
|
||||||
|
* 4 : Header Buffer Mapping Translation Header ((Size << 4) | 0xA)
|
||||||
|
* 5 : Header Buffer Pointer
|
||||||
|
* 6 : Message Buffer Mapping Translation Header ((Size << 4) | 0xA)
|
||||||
|
* 7 : Message Buffer Pointer
|
||||||
|
* 8 : Image Buffer Mapping Translation Header ((Size << 4) | 0xA)
|
||||||
|
* 9 : Image Buffer Pointer
|
||||||
|
* Outputs:
|
||||||
|
* 0 : 0x00010046
|
||||||
|
* 1 : Result of function, 0 on success, otherwise error code
|
||||||
|
*/
|
||||||
|
void AddNotificationSystem(Kernel::HLERequestContext& ctx);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ResetNotifications service function.
|
||||||
|
* Inputs:
|
||||||
|
* 0 : 0x00040000
|
||||||
|
* Outputs:
|
||||||
|
* 0 : 0x00040040
|
||||||
|
* 1 : Result of function, 0 on success, otherwise error code
|
||||||
|
*/
|
||||||
|
void ResetNotifications(Kernel::HLERequestContext& ctx);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GetTotalNotifications service function.
|
||||||
|
* Inputs:
|
||||||
|
* 0 : 0x00050000
|
||||||
|
* Outputs:
|
||||||
|
* 0 : 0x00050080
|
||||||
|
* 1 : Result of function, 0 on success, otherwise error code
|
||||||
|
* 2 : Number of notifications
|
||||||
|
*/
|
||||||
|
void GetTotalNotifications(Kernel::HLERequestContext& ctx);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SetNewsDBHeader service function.
|
||||||
|
* Inputs:
|
||||||
|
* 0 : 0x00060042
|
||||||
|
* 1 : Size
|
||||||
|
* 2 : Input Buffer Mapping Translation Header ((Size << 4) | 0xA)
|
||||||
|
* 3 : Input Buffer Pointer
|
||||||
|
* Outputs:
|
||||||
|
* 0 : 0x00060042
|
||||||
|
* 1 : Result of function, 0 on success, otherwise error code
|
||||||
|
*/
|
||||||
|
void SetNewsDBHeader(Kernel::HLERequestContext& ctx);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SetNotificationHeader service function.
|
||||||
|
* Inputs:
|
||||||
|
* 0 : 0x00070082
|
||||||
|
* 1 : Notification index
|
||||||
|
* 2 : Size
|
||||||
|
* 3 : Input Buffer Mapping Translation Header ((Size << 4) | 0xA)
|
||||||
|
* 4 : Input Buffer Pointer
|
||||||
|
* Outputs:
|
||||||
|
* 0 : 0x00070042
|
||||||
|
* 1 : Result of function, 0 on success, otherwise error code
|
||||||
|
*/
|
||||||
|
void SetNotificationHeader(Kernel::HLERequestContext& ctx);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SetNotificationMessage service function.
|
||||||
|
* Inputs:
|
||||||
|
* 0 : 0x00080082
|
||||||
|
* 1 : Notification index
|
||||||
|
* 2 : Size
|
||||||
|
* 3 : Input Buffer Mapping Translation Header ((Size << 4) | 0xA)
|
||||||
|
* 4 : Input Buffer Pointer
|
||||||
|
* Outputs:
|
||||||
|
* 0 : 0x00080042
|
||||||
|
* 1 : Result of function, 0 on success, otherwise error code
|
||||||
|
*/
|
||||||
|
void SetNotificationMessage(Kernel::HLERequestContext& ctx);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SetNotificationImage service function.
|
||||||
|
* Inputs:
|
||||||
|
* 0 : 0x00090082
|
||||||
|
* 1 : Notification index
|
||||||
|
* 2 : Size
|
||||||
|
* 3 : Input Buffer Mapping Translation Header ((Size << 4) | 0xA)
|
||||||
|
* 4 : Input Buffer Pointer
|
||||||
|
* Outputs:
|
||||||
|
* 0 : 0x00090042
|
||||||
|
* 1 : Result of function, 0 on success, otherwise error code
|
||||||
|
*/
|
||||||
|
void SetNotificationImage(Kernel::HLERequestContext& ctx);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GetNewsDBHeader service function.
|
||||||
|
* Inputs:
|
||||||
|
* 0 : 0x000A0042
|
||||||
|
* 1 : Size
|
||||||
|
* 2 : Output Buffer Mapping Translation Header ((Size << 4) | 0xC)
|
||||||
|
* 3 : Output Buffer Pointer
|
||||||
|
* Outputs:
|
||||||
|
* 0 : 0x000A0082
|
||||||
|
* 1 : Result of function, 0 on success, otherwise error code
|
||||||
|
* 2 : Actual Size
|
||||||
|
*/
|
||||||
|
void GetNewsDBHeader(Kernel::HLERequestContext& ctx);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GetNotificationHeader service function.
|
||||||
|
* Inputs:
|
||||||
|
* 0 : 0x000B0082
|
||||||
|
* 1 : Notification index
|
||||||
|
* 2 : Size
|
||||||
|
* 3 : Output Buffer Mapping Translation Header ((Size << 4) | 0xC)
|
||||||
|
* 4 : Output Buffer Pointer
|
||||||
|
* Outputs:
|
||||||
|
* 0 : 0x000B0082
|
||||||
|
* 1 : Result of function, 0 on success, otherwise error code
|
||||||
|
* 2 : Actual Size
|
||||||
|
*/
|
||||||
|
void GetNotificationHeader(Kernel::HLERequestContext& ctx);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GetNotificationMessage service function.
|
||||||
|
* Inputs:
|
||||||
|
* 0 : 0x000C0082
|
||||||
|
* 1 : Notification index
|
||||||
|
* 2 : Size
|
||||||
|
* 3 : Output Buffer Mapping Translation Header ((Size << 4) | 0xC)
|
||||||
|
* 4 : Output Buffer Pointer
|
||||||
|
* Outputs:
|
||||||
|
* 0 : 0x000C0082
|
||||||
|
* 1 : Result of function, 0 on success, otherwise error code
|
||||||
|
* 2 : Actual Size
|
||||||
|
*/
|
||||||
|
void GetNotificationMessage(Kernel::HLERequestContext& ctx);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GetNotificationImage service function.
|
||||||
|
* Inputs:
|
||||||
|
* 0 : 0x000D0082
|
||||||
|
* 1 : Notification index
|
||||||
|
* 2 : Size
|
||||||
|
* 3 : Output Buffer Mapping Translation Header ((Size << 4) | 0xC)
|
||||||
|
* 4 : Output Buffer Pointer
|
||||||
|
* Outputs:
|
||||||
|
* 0 : 0x000D0082
|
||||||
|
* 1 : Result of function, 0 on success, otherwise error code
|
||||||
|
* 2 : Actual Size
|
||||||
|
*/
|
||||||
|
void GetNotificationImage(Kernel::HLERequestContext& ctx);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SetAutomaticSyncFlag service function.
|
||||||
|
* Inputs:
|
||||||
|
* 0 : 0x00110040
|
||||||
|
* 1 : Flag
|
||||||
|
* Outputs:
|
||||||
|
* 0 : 0x00110040
|
||||||
|
* 1 : Result of function, 0 on success, otherwise error code
|
||||||
|
*/
|
||||||
|
void SetAutomaticSyncFlag(Kernel::HLERequestContext& ctx);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SetNotificationHeaderOther service function.
|
||||||
|
* Inputs:
|
||||||
|
* 0 : 0x00120082
|
||||||
|
* 1 : Notification index
|
||||||
|
* 2 : Size
|
||||||
|
* 3 : Input Buffer Mapping Translation Header ((Size << 4) | 0xA)
|
||||||
|
* 4 : Input Buffer Pointer
|
||||||
|
* Outputs:
|
||||||
|
* 0 : 0x00120042
|
||||||
|
* 1 : Result of function, 0 on success, otherwise error code
|
||||||
|
*/
|
||||||
|
void SetNotificationHeaderOther(Kernel::HLERequestContext& ctx);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* WriteNewsDBSavedata service function.
|
||||||
|
* Inputs:
|
||||||
|
* 0 : 0x00130000
|
||||||
|
* Outputs:
|
||||||
|
* 0 : 0x00130040
|
||||||
|
* 1 : Result of function, 0 on success, otherwise error code
|
||||||
|
*/
|
||||||
|
void WriteNewsDBSavedata(Kernel::HLERequestContext& ctx);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GetTotalArrivedNotifications service function.
|
||||||
|
* Inputs:
|
||||||
|
* 0 : 0x00140000
|
||||||
|
* Outputs:
|
||||||
|
* 0 : 0x00140080
|
||||||
|
* 1 : Result of function, 0 on success, otherwise error code
|
||||||
|
* 2 : Number of pending notifications to be synced
|
||||||
|
*/
|
||||||
|
void GetTotalArrivedNotifications(Kernel::HLERequestContext& ctx);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
std::shared_ptr<Module> news;
|
||||||
|
};
|
||||||
|
|
||||||
|
private:
|
||||||
|
/**
|
||||||
|
* Gets the total number of notifications
|
||||||
|
* @returns Number of notifications
|
||||||
|
*/
|
||||||
|
std::size_t GetTotalNotifications();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads the News DB into the given buffer
|
||||||
|
* @param header The header buffer
|
||||||
|
* @param size The size of the header buffer
|
||||||
|
* @returns Number of bytes read, or error code
|
||||||
|
*/
|
||||||
|
ResultVal<std::size_t> GetNewsDBHeader(NewsDBHeader* header, const std::size_t size);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads the header for a notification ID into the given buffer
|
||||||
|
* @param notification_index The index of the notification ID
|
||||||
|
* @param header The header buffer
|
||||||
|
* @param size The size of the header buffer
|
||||||
|
* @returns Number of bytes read, or error code
|
||||||
|
*/
|
||||||
|
ResultVal<std::size_t> GetNotificationHeader(const u32 notification_index,
|
||||||
|
NotificationHeader* header,
|
||||||
|
const std::size_t size);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opens the message file for a notification ID and loads it to the message buffer
|
||||||
|
* @param notification_index The index of the notification ID
|
||||||
|
* @param mesasge The message buffer
|
||||||
|
* @returns Number of bytes read, or error code
|
||||||
|
*/
|
||||||
|
ResultVal<std::size_t> GetNotificationMessage(const u32 notification_index,
|
||||||
|
std::span<u8> message);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opens the image file for a notification ID and loads it to the image buffer
|
||||||
|
* @param notification_index The index of the notification ID
|
||||||
|
* @param image The image buffer
|
||||||
|
* @returns Number of bytes read, or error code
|
||||||
|
*/
|
||||||
|
ResultVal<std::size_t> GetNotificationImage(const u32 notification_index, std::span<u8> image);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Modifies the header for the News DB in memory and saves the News DB file
|
||||||
|
* @param header The database header
|
||||||
|
* @param size The amount of bytes to copy from the header
|
||||||
|
* @returns Result indicating the result of the operation, 0 on success
|
||||||
|
*/
|
||||||
|
Result SetNewsDBHeader(const NewsDBHeader* header, const std::size_t size);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Modifies the header for a notification ID on memory and saves the News DB file
|
||||||
|
* @param notification_index The index of the notification ID
|
||||||
|
* @param header The notification header
|
||||||
|
* @param size The amount of bytes to copy from the header
|
||||||
|
* @returns Result indicating the result of the operation, 0 on success
|
||||||
|
*/
|
||||||
|
Result SetNotificationHeader(const u32 notification_index, const NotificationHeader* header,
|
||||||
|
const std::size_t size);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Modifies the header for a notification ID on memory. The News DB file isn't updated
|
||||||
|
* @param notification_index The index of the notification ID
|
||||||
|
* @param header The notification header
|
||||||
|
* @param size The amount of bytes to copy from the header
|
||||||
|
* @returns Result indicating the result of the operation, 0 on success
|
||||||
|
*/
|
||||||
|
Result SetNotificationHeaderOther(const u32 notification_index,
|
||||||
|
const NotificationHeader* header, const std::size_t size);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets a given message to a notification ID
|
||||||
|
* @param notification_index The index of the notification ID
|
||||||
|
* @param message The notification message
|
||||||
|
* @returns Result indicating the result of the operation, 0 on success
|
||||||
|
*/
|
||||||
|
Result SetNotificationMessage(const u32 notification_index, std::span<const u8> message);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets a given image to a notification ID
|
||||||
|
* @param notification_index The index of the notification ID
|
||||||
|
* @param image The notification image
|
||||||
|
* @returns Result indicating the result of the operation, 0 on success
|
||||||
|
*/
|
||||||
|
Result SetNotificationImage(const u32 notification_index, std::span<const u8> image);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new notification with the given data and saves all the contents
|
||||||
|
* @param header The notification header
|
||||||
|
* @param header_size The amount of bytes to copy from the header
|
||||||
|
* @param message The notification message
|
||||||
|
* @param image The notification image
|
||||||
|
* @returns Result indicating the result of the operation, 0 on success
|
||||||
|
*/
|
||||||
|
Result SaveNotification(const NotificationHeader* header, const std::size_t header_size,
|
||||||
|
std::span<const u8> message, std::span<const u8> image);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes the given notification ID from the database
|
||||||
|
* @param notification_id The notification ID to delete
|
||||||
|
* @returns Result indicating the result of the operation, 0 on success
|
||||||
|
*/
|
||||||
|
Result DeleteNotification(const u32 notification_id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opens the news.db savedata file and load it to the memory buffer. If the file or the savedata
|
||||||
|
* don't exist, they are created
|
||||||
|
* @returns Result indicating the result of the operation, 0 on success
|
||||||
|
*/
|
||||||
|
Result LoadNewsDBSavedata();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes the news.db savedata file to the the NEWS system savedata
|
||||||
|
* @returns Result indicating the result of the operation, 0 on success
|
||||||
|
*/
|
||||||
|
Result SaveNewsDBSavedata();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opens the file with the given filename inside the NEWS system savedata
|
||||||
|
* @param filename The file to open
|
||||||
|
* @param buffer The buffer to output the contents on
|
||||||
|
* @returns Number of bytes read, or error code
|
||||||
|
*/
|
||||||
|
ResultVal<std::size_t> LoadFileFromSavedata(std::string filename, std::span<u8> buffer);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes the file with the given filename inside the NEWS system savedata
|
||||||
|
* @param filename The output file
|
||||||
|
* @param buffer The buffer to read the contents from
|
||||||
|
* @returns Result indicating the result of the operation, 0 on success
|
||||||
|
*/
|
||||||
|
Result SaveFileToSavedata(std::string filename, std::span<const u8> buffer);
|
||||||
|
|
||||||
|
bool CompareNotifications(const u32 first_id, const u32 second_id);
|
||||||
|
|
||||||
|
private:
|
||||||
|
Core::System& system;
|
||||||
|
|
||||||
|
NewsDB db{};
|
||||||
|
std::array<u32, MAX_NOTIFICATIONS> notification_ids; // Notifications ordered by date time
|
||||||
|
u8 automatic_sync_flag;
|
||||||
|
std::unique_ptr<FileSys::ArchiveBackend> news_system_save_data_archive;
|
||||||
|
|
||||||
|
template <class Archive>
|
||||||
|
void serialize(Archive& ar, const unsigned int);
|
||||||
|
friend class boost::serialization::access;
|
||||||
|
};
|
||||||
|
|
||||||
void InstallInterfaces(Core::System& system);
|
void InstallInterfaces(Core::System& system);
|
||||||
|
|
||||||
} // namespace Service::NEWS
|
} // namespace Service::NEWS
|
||||||
|
|
||||||
|
SERVICE_CONSTRUCT(Service::NEWS::Module)
|
||||||
|
BOOST_CLASS_EXPORT_KEY(Service::NEWS::Module)
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue