Compare commits
44 Commits
Author | SHA1 | Date |
---|---|---|
liushuyu | 69e758d738 | |
Steveice10 | f4768cd26c | |
Théo B | e0d2c1308e | |
Steveice10 | 4f9fc88bb3 | |
GPUCode | d857743075 | |
kylon | b5042a5257 | |
Wunk | e524542a40 | |
Steveice10 | 3a4ebb1413 | |
Steveice10 | cbe8987036 | |
Charles Lombardo | da5aa70fc9 | |
Castor215 | 749a721aa2 | |
SachinVin | bb003c2bd4 | |
Tobias | 7638f87f74 | |
Steveice10 | aa6809e2a8 | |
Steveice10 | 5e02be75a3 | |
Tobias | b9c9beeee5 | |
GPUCode | de993dcfbd | |
oltolm | 3c9157b1ec | |
Ishan09811 | 0c40c10022 | |
Daniel López Guimaraes | 2766118e33 | |
Steveice10 | 06b26691ba | |
PabloMK7 | d41ce64f7b | |
Tobias | 1165a708d5 | |
Steveice10 | 19784355f9 | |
SachinVin | aa6a29d7e1 | |
GPUCode | 106364e01e | |
GPUCode | d5a1bd07f3 | |
Steveice10 | 8afa27718c | |
zhaobot | 8e2415f455 | |
Steveice10 | c978c074db | |
Steveice10 | cb92ec278e | |
Steveice10 | 9f5d5c6ddd | |
GPUCode | 480604ec72 | |
merry | 63feac6bb3 | |
Steveice10 | 469f76b075 | |
SachinVin | 7a4854c519 | |
Steveice10 | d1e3dddf6a | |
Charles Lombardo | 265e8193b9 | |
Amanda Watson | e8c20fa782 | |
PabloMK7 | 95ae46f6a8 | |
Steveice10 | 41fe75acb7 | |
Tobias | 1744537d85 | |
GPUCode | bea863efff | |
Daniel López Guimaraes | 89e13a85a7 |
|
@ -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:
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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/".
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -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()
|
||||||
|
|
|
@ -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})
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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
|
|
|
@ -1 +1 @@
|
||||||
Subproject commit 9327192b0095dc1f420b2082d37bd427b5750d48
|
Subproject commit a99c80c26686e44eddf0432140ae397f3efbd0b3
|
|
@ -1 +1 @@
|
||||||
Subproject commit 48689ae7a73caeb747953f9ed664dc71d2f918d8
|
Subproject commit 799e775484b8fce7e986ee7a4f4b651fec2bca07
|
|
@ -1 +1 @@
|
||||||
Subproject commit d333a09b3b9152af3cb442902ae8ea18d8416470
|
Subproject commit 30f1a3c6289075ef4af08f5ec502be2fc8627a0c
|
|
@ -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)
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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 New Issue