Compare commits
63 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
b5126f979c | ||
|
a8e601ae7d | ||
|
d063f26efc | ||
|
b1e5485058 | ||
|
de65b15dde | ||
|
e26ceabfd1 | ||
|
9dfe3eb4bc | ||
|
f5cf180cee | ||
|
db3da09d85 | ||
|
5691cf7e91 | ||
|
0c2f076dc4 | ||
|
775ceac27d | ||
|
7fc382479d | ||
|
8e35df1a6c | ||
|
a442389a60 | ||
|
12b30be45e | ||
|
b90569700c | ||
|
0500b993e0 | ||
|
69eba0d295 | ||
|
8433057909 | ||
|
c4c90f0190 | ||
|
813d0c2a30 | ||
|
f112d975b9 | ||
|
bf0127d0ae | ||
|
c710c0009f | ||
|
f5316532e6 | ||
|
cb75e1d637 | ||
|
06b3bed49a | ||
|
0ff3440232 | ||
|
69e758d738 | ||
|
f4768cd26c | ||
|
e0d2c1308e | ||
|
4f9fc88bb3 | ||
|
d857743075 | ||
|
b5042a5257 | ||
|
e524542a40 | ||
|
3a4ebb1413 | ||
|
cbe8987036 | ||
|
da5aa70fc9 | ||
|
749a721aa2 | ||
|
bb003c2bd4 | ||
|
7638f87f74 | ||
|
aa6809e2a8 | ||
|
5e02be75a3 | ||
|
b9c9beeee5 | ||
|
de993dcfbd | ||
|
3c9157b1ec | ||
|
0c40c10022 | ||
|
2766118e33 | ||
|
06b26691ba | ||
|
d41ce64f7b | ||
|
1165a708d5 | ||
|
19784355f9 | ||
|
aa6a29d7e1 | ||
|
106364e01e | ||
|
d5a1bd07f3 | ||
|
8afa27718c | ||
|
8e2415f455 | ||
|
c978c074db | ||
|
cb92ec278e | ||
|
9f5d5c6ddd | ||
|
480604ec72 | ||
|
63feac6bb3 |
@ -9,7 +9,7 @@ COMPAT_LIST='dist/compatibility_list/compatibility_list.json'
|
||||
mkdir artifacts
|
||||
|
||||
pip3 install git-archive-all
|
||||
wget -q https://api.citra-emu.org/gamedb -O "${COMPAT_LIST}"
|
||||
touch "${COMPAT_LIST}"
|
||||
git describe --abbrev=0 --always HEAD > GIT-COMMIT
|
||||
git describe --tags HEAD > GIT-TAG || echo 'unknown' > GIT-TAG
|
||||
git archive-all --include "${COMPAT_LIST}" --include GIT-COMMIT --include GIT-TAG --force-submodules artifacts/"${REV_NAME}.tar"
|
||||
|
67
.github/workflows/build.yml
vendored
67
.github/workflows/build.yml
vendored
@ -12,13 +12,13 @@ jobs:
|
||||
if: ${{ !github.head_ref }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Pack
|
||||
run: ./.ci/source.sh
|
||||
- name: Upload
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: source
|
||||
path: artifacts/
|
||||
@ -28,7 +28,7 @@ jobs:
|
||||
matrix:
|
||||
target: ["appimage", "fresh"]
|
||||
container:
|
||||
image: citraemu/build-environments:linux-${{ matrix.target }}
|
||||
image: pablomk7/build-environments:linux-${{ matrix.target }}
|
||||
options: -u 1001
|
||||
env:
|
||||
CCACHE_DIR: ${{ github.workspace }}/.ccache
|
||||
@ -37,11 +37,11 @@ jobs:
|
||||
OS: linux
|
||||
TARGET: ${{ matrix.target }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Set up cache
|
||||
uses: actions/cache@v3
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ env.CCACHE_DIR }}
|
||||
key: ${{ runner.os }}-${{ matrix.target }}-${{ github.sha }}
|
||||
@ -53,13 +53,13 @@ jobs:
|
||||
run: ./.ci/pack.sh
|
||||
if: ${{ matrix.target == 'appimage' }}
|
||||
- name: Upload
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
if: ${{ matrix.target == 'appimage' }}
|
||||
with:
|
||||
name: ${{ env.OS }}-${{ env.TARGET }}
|
||||
path: artifacts/
|
||||
macos:
|
||||
runs-on: macos-13
|
||||
runs-on: ${{ (matrix.target == 'x86_64' && 'macos-13') || 'macos-14' }}
|
||||
strategy:
|
||||
matrix:
|
||||
target: ["x86_64", "arm64"]
|
||||
@ -70,43 +70,43 @@ jobs:
|
||||
OS: macos
|
||||
TARGET: ${{ matrix.target }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Set up cache
|
||||
uses: actions/cache@v3
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ env.CCACHE_DIR }}
|
||||
key: ${{ runner.os }}-${{ matrix.target }}-${{ github.sha }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-${{ matrix.target }}-
|
||||
- name: Install tools
|
||||
run: brew install ccache glslang ninja
|
||||
run: brew install ccache ninja
|
||||
- name: Build
|
||||
run: ./.ci/macos.sh
|
||||
- name: Prepare outputs for caching
|
||||
run: mv build/bundle $OS-$TARGET
|
||||
- name: Cache outputs for universal build
|
||||
uses: actions/cache/save@v3
|
||||
uses: actions/cache/save@v4
|
||||
with:
|
||||
path: ${{ env.OS }}-${{ env.TARGET }}
|
||||
key: ${{ runner.os }}-${{ matrix.target }}-${{ github.sha }}-${{ github.run_id }}-${{ github.run_attempt }}
|
||||
macos-universal:
|
||||
runs-on: macos-13
|
||||
runs-on: macos-14
|
||||
needs: macos
|
||||
env:
|
||||
OS: macos
|
||||
TARGET: universal
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Download x86_64 build from cache
|
||||
uses: actions/cache/restore@v3
|
||||
uses: actions/cache/restore@v4
|
||||
with:
|
||||
path: ${{ env.OS }}-x86_64
|
||||
key: ${{ runner.os }}-x86_64-${{ github.sha }}-${{ github.run_id }}-${{ github.run_attempt }}
|
||||
fail-on-cache-miss: true
|
||||
- name: Download ARM64 build from cache
|
||||
uses: actions/cache/restore@v3
|
||||
uses: actions/cache/restore@v4
|
||||
with:
|
||||
path: ${{ env.OS }}-arm64
|
||||
key: ${{ runner.os }}-arm64-${{ github.sha }}-${{ github.run_id }}-${{ github.run_attempt }}
|
||||
@ -118,7 +118,7 @@ jobs:
|
||||
- name: Pack
|
||||
run: ./.ci/pack.sh
|
||||
- name: Upload
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ env.OS }}-${{ env.TARGET }}
|
||||
path: artifacts/
|
||||
@ -137,11 +137,11 @@ jobs:
|
||||
OS: windows
|
||||
TARGET: ${{ matrix.target }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Set up cache
|
||||
uses: actions/cache@v3
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ env.CCACHE_DIR }}
|
||||
key: ${{ runner.os }}-${{ matrix.target }}-${{ github.sha }}
|
||||
@ -153,13 +153,6 @@ jobs:
|
||||
- name: Install extra tools (MSVC)
|
||||
run: choco install ccache ninja wget
|
||||
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
|
||||
uses: msys2/setup-msys2@v2
|
||||
if: ${{ matrix.target == 'msys2' }}
|
||||
@ -168,10 +161,8 @@ jobs:
|
||||
update: true
|
||||
install: git make p7zip
|
||||
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
|
||||
- name: Test glslang
|
||||
run: glslang --version || glslangValidator --version
|
||||
- name: Disable line ending translation
|
||||
run: git config --global core.autocrlf input
|
||||
- name: Build
|
||||
@ -179,7 +170,7 @@ jobs:
|
||||
- name: Pack
|
||||
run: ./.ci/pack.sh
|
||||
- name: Upload
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ env.OS }}-${{ env.TARGET }}
|
||||
path: artifacts/
|
||||
@ -192,11 +183,11 @@ jobs:
|
||||
OS: android
|
||||
TARGET: universal
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Set up cache
|
||||
uses: actions/cache@v3
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.gradle/caches
|
||||
@ -215,7 +206,7 @@ jobs:
|
||||
run: |
|
||||
sudo add-apt-repository -y ppa:theofficialgman/gpu-tools
|
||||
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
|
||||
run: JAVA_HOME=$JAVA_HOME_17_X64 ./.ci/android.sh
|
||||
env:
|
||||
@ -228,12 +219,12 @@ jobs:
|
||||
env:
|
||||
UNPACKED: 1
|
||||
- name: Upload
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ env.OS }}-${{ env.TARGET }}
|
||||
path: src/android/app/artifacts/
|
||||
ios:
|
||||
runs-on: macos-13
|
||||
runs-on: macos-14
|
||||
if: ${{ !startsWith(github.ref, 'refs/tags/') }}
|
||||
env:
|
||||
CCACHE_DIR: ${{ github.workspace }}/.ccache
|
||||
@ -242,18 +233,18 @@ jobs:
|
||||
OS: ios
|
||||
TARGET: arm64
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Set up cache
|
||||
uses: actions/cache@v3
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ env.CCACHE_DIR }}
|
||||
key: ${{ runner.os }}-ios-${{ github.sha }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-ios-
|
||||
- name: Install tools
|
||||
run: brew install ccache glslang ninja
|
||||
run: brew install ccache ninja
|
||||
- name: Build
|
||||
run: ./.ci/ios.sh
|
||||
release:
|
||||
@ -261,7 +252,7 @@ jobs:
|
||||
needs: [windows, linux, macos-universal, android, source]
|
||||
if: ${{ startsWith(github.ref, 'refs/tags/') }}
|
||||
steps:
|
||||
- uses: actions/download-artifact@v3
|
||||
- uses: actions/download-artifact@v4
|
||||
- name: Create release
|
||||
uses: actions/create-release@v1
|
||||
env:
|
||||
|
2
.github/workflows/ci-merge.js
vendored
2
.github/workflows/ci-merge.js
vendored
@ -200,7 +200,7 @@ async function mergebot(github, context, execa) {
|
||||
}
|
||||
console.info("The following pull requests will be merged:");
|
||||
console.table(displayList);
|
||||
await fetchPullRequests(pulls, "https://github.com/citra-emu/citra", execa);
|
||||
await fetchPullRequests(pulls, "https://github.com/PabloMK7/citra", execa);
|
||||
const mergeResults = await mergePullRequests(pulls, execa);
|
||||
await generateReadme(pulls, context, mergeResults, execa);
|
||||
await tagAndPush(github, context.repo.owner, `${context.repo.repo}-canary`, execa, true);
|
||||
|
4
.github/workflows/format.yml
vendored
4
.github/workflows/format.yml
vendored
@ -10,10 +10,10 @@ jobs:
|
||||
clang-format:
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: citraemu/build-environments:linux-fresh
|
||||
image: pablomk7/build-environments:linux-fresh
|
||||
options: -u 1001
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Build
|
||||
|
100
.github/workflows/publish.yml
vendored
100
.github/workflows/publish.yml
vendored
@ -1,100 +0,0 @@
|
||||
name: citra-publish
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '7 0 * * *'
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
nightly:
|
||||
description: 'Whether to trigger a nightly build (true/false/auto)'
|
||||
required: false
|
||||
default: 'true'
|
||||
canary:
|
||||
description: 'Whether to trigger a canary build (true/false/auto)'
|
||||
required: false
|
||||
default: 'true'
|
||||
|
||||
jobs:
|
||||
nightly:
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.event.inputs.nightly != 'false' && github.repository == 'citra-emu/citra' }}
|
||||
steps:
|
||||
# this checkout is required to make sure the GitHub Actions scripts are available
|
||||
- uses: actions/checkout@v3
|
||||
name: Pre-checkout
|
||||
with:
|
||||
submodules: false
|
||||
- uses: actions/github-script@v6
|
||||
id: check-changes
|
||||
name: 'Check for new changes'
|
||||
env:
|
||||
# 24 hours
|
||||
DETECTION_TIME_FRAME: 86400000
|
||||
with:
|
||||
result-encoding: string
|
||||
script: |
|
||||
if (context.payload.inputs && context.payload.inputs.nightly === 'true') return true;
|
||||
const checkBaseChanges = require('./.github/workflows/ci-merge.js').checkBaseChanges;
|
||||
return checkBaseChanges(github, context);
|
||||
- run: npm install execa@5
|
||||
if: ${{ steps.check-changes.outputs.result == 'true' }}
|
||||
- uses: actions/checkout@v3
|
||||
name: Checkout
|
||||
if: ${{ steps.check-changes.outputs.result == 'true' }}
|
||||
with:
|
||||
path: 'citra-merge'
|
||||
fetch-depth: 0
|
||||
submodules: true
|
||||
token: ${{ secrets.ALT_GITHUB_TOKEN }}
|
||||
- uses: actions/github-script@v6
|
||||
name: 'Update and tag new commits'
|
||||
if: ${{ steps.check-changes.outputs.result == 'true' }}
|
||||
env:
|
||||
ALT_GITHUB_TOKEN: ${{ secrets.ALT_GITHUB_TOKEN }}
|
||||
with:
|
||||
script: |
|
||||
const execa = require("execa");
|
||||
const tagAndPush = require('./.github/workflows/ci-merge.js').tagAndPush;
|
||||
process.chdir('${{ github.workspace }}/citra-merge');
|
||||
tagAndPush(github, context.repo.owner, `${context.repo.repo}-nightly`, execa);
|
||||
canary:
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.event.inputs.canary != 'false' && github.repository == 'citra-emu/citra' }}
|
||||
steps:
|
||||
# this checkout is required to make sure the GitHub Actions scripts are available
|
||||
- uses: actions/checkout@v3
|
||||
name: Pre-checkout
|
||||
with:
|
||||
submodules: false
|
||||
- uses: actions/github-script@v6
|
||||
id: check-changes
|
||||
name: 'Check for new changes'
|
||||
env:
|
||||
# 24 hours
|
||||
DETECTION_TIME_FRAME: 86400000
|
||||
with:
|
||||
script: |
|
||||
if (context.payload.inputs && context.payload.inputs.canary === 'true') return true;
|
||||
const checkCanaryChanges = require('./.github/workflows/ci-merge.js').checkCanaryChanges;
|
||||
return checkCanaryChanges(github, context);
|
||||
- run: npm install execa@5
|
||||
if: ${{ steps.check-changes.outputs.result == 'true' }}
|
||||
- uses: actions/checkout@v3
|
||||
name: Checkout
|
||||
if: ${{ steps.check-changes.outputs.result == 'true' }}
|
||||
with:
|
||||
path: 'citra-merge'
|
||||
fetch-depth: 0
|
||||
submodules: true
|
||||
token: ${{ secrets.ALT_GITHUB_TOKEN }}
|
||||
- uses: actions/github-script@v6
|
||||
name: 'Check and merge canary changes'
|
||||
if: ${{ steps.check-changes.outputs.result == 'true' }}
|
||||
env:
|
||||
ALT_GITHUB_TOKEN: ${{ secrets.ALT_GITHUB_TOKEN }}
|
||||
with:
|
||||
script: |
|
||||
const execa = require("execa");
|
||||
const mergebot = require('./.github/workflows/ci-merge.js').mergebot;
|
||||
process.chdir('${{ github.workspace }}/citra-merge');
|
||||
mergebot(github, context, execa);
|
4
.github/workflows/transifex.yml
vendored
4
.github/workflows/transifex.yml
vendored
@ -7,10 +7,10 @@ on:
|
||||
jobs:
|
||||
transifex:
|
||||
runs-on: ubuntu-latest
|
||||
container: citraemu/build-environments:linux-fresh
|
||||
container: pablomk7/build-environments:linux-fresh
|
||||
if: ${{ github.repository == 'citra-emu/citra' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
fetch-depth: 0
|
||||
|
12
.gitmodules
vendored
12
.gitmodules
vendored
@ -1,6 +1,6 @@
|
||||
[submodule "boost"]
|
||||
path = externals/boost
|
||||
url = https://github.com/citra-emu/ext-boost.git
|
||||
url = https://github.com/PabloMK7/ext-boost.git
|
||||
[submodule "nihstro"]
|
||||
path = externals/nihstro
|
||||
url = https://github.com/neobrain/nihstro.git
|
||||
@ -12,7 +12,7 @@
|
||||
url = https://github.com/catchorg/Catch2
|
||||
[submodule "dynarmic"]
|
||||
path = externals/dynarmic
|
||||
url = https://github.com/merryhime/dynarmic.git
|
||||
url = https://github.com/PabloMK7/dynarmic.git
|
||||
[submodule "xbyak"]
|
||||
path = externals/xbyak
|
||||
url = https://github.com/herumi/xbyak.git
|
||||
@ -27,7 +27,7 @@
|
||||
url = https://github.com/benhoyt/inih.git
|
||||
[submodule "libressl"]
|
||||
path = externals/libressl
|
||||
url = https://github.com/citra-emu/ext-libressl-portable.git
|
||||
url = https://github.com/PabloMK7/ext-libressl-portable.git
|
||||
[submodule "libusb"]
|
||||
path = externals/libusb/libusb
|
||||
url = https://github.com/libusb/libusb.git
|
||||
@ -36,7 +36,7 @@
|
||||
url = https://github.com/mozilla/cubeb
|
||||
[submodule "discord-rpc"]
|
||||
path = externals/discord-rpc
|
||||
url = https://github.com/yuzu-emu/discord-rpc.git
|
||||
url = https://github.com/PabloMK7/discord-rpc.git
|
||||
[submodule "cpp-jwt"]
|
||||
path = externals/cpp-jwt
|
||||
url = https://github.com/arun11299/cpp-jwt.git
|
||||
@ -78,13 +78,13 @@
|
||||
url = https://github.com/KhronosGroup/Vulkan-Headers
|
||||
[submodule "sirit"]
|
||||
path = externals/sirit
|
||||
url = https://github.com/yuzu-emu/sirit
|
||||
url = https://github.com/PabloMK7/sirit
|
||||
[submodule "faad2"]
|
||||
path = externals/faad2/faad2
|
||||
url = https://github.com/knik0/faad2
|
||||
[submodule "library-headers"]
|
||||
path = externals/library-headers
|
||||
url = https://github.com/citra-emu/ext-library-headers.git
|
||||
url = https://github.com/PabloMK7/ext-library-headers.git
|
||||
[submodule "libadrenotools"]
|
||||
path = externals/libadrenotools
|
||||
url = https://github.com/bylaws/libadrenotools
|
||||
|
@ -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_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 APPLE" OFF)
|
||||
CMAKE_DEPENDENT_OPTION(ENABLE_CUBEB "Enables the cubeb audio backend" ON "NOT IOS" OFF)
|
||||
option(ENABLE_OPENAL "Enables the OpenAL audio backend" ON)
|
||||
|
||||
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)
|
||||
|
||||
CMAKE_DEPENDENT_OPTION(CITRA_ENABLE_BUNDLE_TARGET "Enable the distribution bundling target." ON "NOT ANDROID AND NOT IOS" OFF)
|
||||
|
||||
# Compile options
|
||||
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})
|
||||
@ -250,6 +247,26 @@ if (ENABLE_QT)
|
||||
if (ENABLE_QT_TRANSLATION)
|
||||
find_package(Qt6 REQUIRED COMPONENTS LinguistTools)
|
||||
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()
|
||||
|
||||
# Use system tsl::robin_map if available (otherwise we fallback to version bundled with dynarmic)
|
||||
@ -425,7 +442,8 @@ else()
|
||||
endif()
|
||||
|
||||
# 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)
|
||||
if (ENABLE_SDL2_FRONTEND)
|
||||
bundle_target(citra)
|
||||
|
@ -2,37 +2,104 @@
|
||||
if (BUNDLE_TARGET_EXECUTE)
|
||||
# --- 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)
|
||||
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)
|
||||
find_program(windeployqt_executable windeployqt6)
|
||||
|
||||
# Create a qt.conf file pointing to the app directory.
|
||||
# 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}")
|
||||
execute_process(COMMAND "${windeployqt_executable}" "${executable_path}"
|
||||
--qtpaths "${qtpaths_executable}"
|
||||
--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.
|
||||
# We want to use the Windows media plugin instead, which is also included.
|
||||
file(REMOVE "${executable_parent_dir}/plugins/multimedia/ffmpegmediaplugin.dll")
|
||||
elseif (APPLE)
|
||||
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(
|
||||
COMMAND "${MACDEPLOYQT_EXECUTABLE}"
|
||||
COMMAND "${macdeployqt_executable}"
|
||||
"${executable_path}"
|
||||
"-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.
|
||||
# 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()
|
||||
message(FATAL_ERROR "Unsupported OS for Qt bundling.")
|
||||
endif()
|
||||
@ -44,9 +111,9 @@ if (BUNDLE_TARGET_EXECUTE)
|
||||
|
||||
if (enable_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)
|
||||
endif()
|
||||
|
||||
@ -59,7 +126,11 @@ if (BUNDLE_TARGET_EXECUTE)
|
||||
--executable "${executable_path}"
|
||||
--icon-file "${source_path}/dist/citra.svg"
|
||||
--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)
|
||||
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"
|
||||
"${linuxdeploy_executable}"
|
||||
--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()
|
||||
|
||||
function(bundle_standalone executable_path original_executable_path bundle_library_paths)
|
||||
@ -109,16 +184,23 @@ if (BUNDLE_TARGET_EXECUTE)
|
||||
file(MAKE_DIRECTORY ${lib_dir})
|
||||
foreach (lib_file IN LISTS resolved_deps)
|
||||
message(STATUS "Bundling library ${lib_file}")
|
||||
# Use native copy to turn symlinks into normal files.
|
||||
execute_process(COMMAND cp -L "${lib_file}" "${lib_dir}")
|
||||
symlink_safe_copy("${lib_file}" "${lib_dir}")
|
||||
endforeach()
|
||||
endif()
|
||||
|
||||
# Add libs directory to executable rpath where applicable.
|
||||
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)
|
||||
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()
|
||||
endfunction()
|
||||
|
||||
@ -127,7 +209,7 @@ if (BUNDLE_TARGET_EXECUTE)
|
||||
set(bundle_dir ${BINARY_PATH}/bundle)
|
||||
|
||||
# On Linux, always bundle an AppImage.
|
||||
if (DEFINED LINUXDEPLOY)
|
||||
if (CMAKE_HOST_SYSTEM_NAME STREQUAL "Linux")
|
||||
if (IN_PLACE)
|
||||
message(FATAL_ERROR "Cannot bundle for Linux in-place.")
|
||||
endif()
|
||||
@ -146,14 +228,12 @@ if (BUNDLE_TARGET_EXECUTE)
|
||||
|
||||
if (BUNDLE_QT)
|
||||
bundle_qt("${bundled_executable_path}")
|
||||
endif()
|
||||
|
||||
if (WIN32 OR NOT BUNDLE_QT)
|
||||
else()
|
||||
bundle_standalone("${bundled_executable_path}" "${EXECUTABLE_PATH}" "${BUNDLE_LIBRARY_PATHS}")
|
||||
endif()
|
||||
endif()
|
||||
else()
|
||||
# --- Bundling target creation logic ---
|
||||
elseif (BUNDLE_TARGET_DOWNLOAD_LINUXDEPLOY)
|
||||
# --- linuxdeploy download logic ---
|
||||
|
||||
# Downloads and extracts a linuxdeploy component.
|
||||
function(download_linuxdeploy_component base_dir name executable_name)
|
||||
@ -161,7 +241,7 @@ else()
|
||||
if (NOT EXISTS "${executable_file}")
|
||||
message(STATUS "Downloading ${executable_name}")
|
||||
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)
|
||||
file(CHMOD "${executable_file}" PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE)
|
||||
|
||||
@ -170,7 +250,11 @@ else()
|
||||
message(STATUS "Extracting ${executable_name}")
|
||||
execute_process(
|
||||
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()
|
||||
message(STATUS "Copying ${executable_name}")
|
||||
file(COPY "${executable_file}" DESTINATION "${base_dir}/squashfs-root/usr/bin/")
|
||||
@ -178,89 +262,102 @@ else()
|
||||
endif()
|
||||
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.
|
||||
# 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)
|
||||
# Create base bundle target if it does not exist.
|
||||
if (NOT in_place AND NOT TARGET bundle)
|
||||
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")
|
||||
create_base_bundle_target()
|
||||
endif()
|
||||
|
||||
set(BUNDLE_EXECUTABLE_PATH "$<TARGET_FILE:${target_name}>")
|
||||
set(bundle_executable_path "$<TARGET_FILE:${target_name}>")
|
||||
if (target_name MATCHES ".*qt")
|
||||
set(BUNDLE_QT ON)
|
||||
set(bundle_qt ON)
|
||||
if (APPLE)
|
||||
# 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()
|
||||
else()
|
||||
set(BUNDLE_QT OFF)
|
||||
set(bundle_qt OFF)
|
||||
endif()
|
||||
|
||||
# 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)
|
||||
list(APPEND BUNDLE_LIBRARY_PATHS "${prefix_path}/bin")
|
||||
list(APPEND bundle_library_paths "${prefix_path}/bin")
|
||||
endif()
|
||||
list(APPEND BUNDLE_LIBRARY_PATHS "${prefix_path}/lib")
|
||||
list(APPEND bundle_library_paths "${prefix_path}/lib")
|
||||
endforeach()
|
||||
foreach(library_path IN LISTS CMAKE_SYSTEM_LIBRARY_PATH)
|
||||
list(APPEND BUNDLE_LIBRARY_PATHS "${library_path}")
|
||||
list(APPEND bundle_library_paths "${library_path}")
|
||||
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)
|
||||
message(STATUS "Adding in-place bundling to ${target_name}")
|
||||
set(DEST_TARGET ${target_name})
|
||||
set(dest_target ${target_name})
|
||||
else()
|
||||
message(STATUS "Adding ${target_name} to bundle target")
|
||||
set(DEST_TARGET bundle)
|
||||
set(dest_target bundle)
|
||||
add_dependencies(bundle ${target_name})
|
||||
endif()
|
||||
|
||||
add_custom_command(TARGET ${DEST_TARGET} POST_BUILD
|
||||
add_custom_command(TARGET ${dest_target} POST_BUILD
|
||||
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"
|
||||
"-DTARGET=${target_name}"
|
||||
"-DSOURCE_PATH=${CMAKE_SOURCE_DIR}"
|
||||
"-DBINARY_PATH=${CMAKE_BINARY_DIR}"
|
||||
"-DEXECUTABLE_PATH=${BUNDLE_EXECUTABLE_PATH}"
|
||||
"-DBUNDLE_LIBRARY_PATHS=\"${BUNDLE_LIBRARY_PATHS}\""
|
||||
"-DBUNDLE_QT=${BUNDLE_QT}"
|
||||
"-DEXECUTABLE_PATH=${bundle_executable_path}"
|
||||
"-DBUNDLE_LIBRARY_PATHS=\"${bundle_library_paths}\""
|
||||
"-DBUNDLE_QT=${bundle_qt}"
|
||||
"-DIN_PLACE=${in_place}"
|
||||
${EXTRA_BUNDLE_ARGS}
|
||||
"-DLINUXDEPLOY=${CMAKE_BINARY_DIR}/externals/linuxdeploy/squashfs-root/AppRun"
|
||||
-P "${CMAKE_SOURCE_DIR}/CMakeModules/BundleTarget.cmake"
|
||||
WORKING_DIRECTORY "${CMAKE_BINARY_DIR}")
|
||||
endfunction()
|
||||
|
@ -1,21 +1,20 @@
|
||||
|
||||
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.
|
||||
# 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)
|
||||
# Determines parameters based on the host and target for downloading the right Qt binaries.
|
||||
function(determine_qt_parameters target host_out type_out arch_out arch_path_out host_type_out host_arch_out host_arch_path_out)
|
||||
if (target MATCHES "tools_.*")
|
||||
set(DOWNLOAD_QT_TOOL ON)
|
||||
set(tool ON)
|
||||
else()
|
||||
set(DOWNLOAD_QT_TOOL OFF)
|
||||
set(tool OFF)
|
||||
endif()
|
||||
|
||||
# Determine installation parameters for OS, architecture, and compiler
|
||||
if (WIN32)
|
||||
set(host "windows")
|
||||
set(type "desktop")
|
||||
if (NOT DOWNLOAD_QT_TOOL)
|
||||
|
||||
if (NOT tool)
|
||||
if (MINGW)
|
||||
set(arch "win64_mingw")
|
||||
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.")
|
||||
endif()
|
||||
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()
|
||||
message(FATAL_ERROR "Unsupported bundled Qt toolchain. Enable USE_SYSTEM_QT and provide your own.")
|
||||
endif()
|
||||
endif()
|
||||
elseif (APPLE)
|
||||
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(arch "ios")
|
||||
set(arch_path "ios")
|
||||
set(host_arch_path "macos")
|
||||
else()
|
||||
set(type "desktop")
|
||||
set(arch "clang_64")
|
||||
set(arch_path "macos")
|
||||
endif()
|
||||
else()
|
||||
set(host "linux")
|
||||
@ -51,38 +64,64 @@ function(download_qt target)
|
||||
set(arch_path "linux")
|
||||
endif()
|
||||
|
||||
get_external_prefix(qt base_path)
|
||||
file(MAKE_DIRECTORY "${base_path}")
|
||||
set(${host_out} "${host}" PARENT_SCOPE)
|
||||
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")
|
||||
if (DOWNLOAD_QT_TOOL)
|
||||
if (tool)
|
||||
set(prefix "${base_path}/Tools")
|
||||
set(install_args ${install_args} install-tool --outputdir ${base_path} ${host} desktop ${target})
|
||||
else()
|
||||
set(prefix "${base_path}/${target}/${arch_path}")
|
||||
if (host_arch_path)
|
||||
set(host_flag "--autodesktop")
|
||||
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)
|
||||
set(install_args ${install_args} install-qt --outputdir ${base_path} ${host} ${type} ${target} ${arch}
|
||||
-m qtmultimedia --archives qttranslations qttools qtsvg qtbase)
|
||||
endif()
|
||||
|
||||
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")
|
||||
if (WIN32)
|
||||
set(aqt_path "${base_path}/aqt.exe")
|
||||
file(DOWNLOAD
|
||||
${AQT_PREBUILD_BASE_URL}/aqt.exe
|
||||
${aqt_path} SHOW_PROGRESS)
|
||||
if (NOT EXISTS "${aqt_path}")
|
||||
file(DOWNLOAD
|
||||
${AQT_PREBUILD_BASE_URL}/aqt.exe
|
||||
${aqt_path} SHOW_PROGRESS)
|
||||
endif()
|
||||
execute_process(COMMAND ${aqt_path} ${install_args}
|
||||
WORKING_DIRECTORY ${base_path})
|
||||
elseif (APPLE)
|
||||
set(aqt_path "${base_path}/aqt-macos")
|
||||
file(DOWNLOAD
|
||||
${AQT_PREBUILD_BASE_URL}/aqt-macos
|
||||
${aqt_path} SHOW_PROGRESS)
|
||||
if (NOT EXISTS "${aqt_path}")
|
||||
file(DOWNLOAD
|
||||
${AQT_PREBUILD_BASE_URL}/aqt-macos
|
||||
${aqt_path} SHOW_PROGRESS)
|
||||
endif()
|
||||
execute_process(COMMAND chmod +x ${aqt_path})
|
||||
execute_process(COMMAND ${aqt_path} ${install_args}
|
||||
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}
|
||||
WORKING_DIRECTORY ${base_path})
|
||||
endif()
|
||||
|
||||
message(STATUS "Downloaded Qt binaries for ${target}:${host}:${type}:${arch}:${arch_path} to ${prefix}")
|
||||
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}")
|
||||
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()
|
||||
|
||||
function(download_moltenvk)
|
||||
|
99
README.md
99
README.md
@ -1,98 +1 @@
|
||||
<h1 align="center">
|
||||
<br>
|
||||
<a href="https://citra-emu.org/"><img src="https://raw.githubusercontent.com/citra-emu/citra-assets/master/Main/citra_logo.svg" alt="Citra" width="200"></a>
|
||||
<br>
|
||||
<b>Citra</b>
|
||||
<br>
|
||||
</h1>
|
||||
|
||||
<h4 align="center"><b>Citra</b> is the world's most popular, open-source, Nintendo 3DS emulator.
|
||||
<br>
|
||||
It is written in C++ with portability in mind and builds are actively maintained for Windows, Linux, Android and macOS.
|
||||
</h4>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/citra-emu/citra/actions/">
|
||||
<img src="https://github.com/citra-emu/citra/workflows/citra-ci/badge.svg"
|
||||
alt="GitHub Actions Build Status">
|
||||
</a>
|
||||
<a href="https://discord.gg/FAXfZV9">
|
||||
<img src="https://img.shields.io/discord/220740965957107713?color=%237289DA&label=Citra&logo=discord&logoColor=white"
|
||||
alt="Discord">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="#compatibility">Compatibility</a> |
|
||||
<a href="#releases">Releases</a> |
|
||||
<a href="#development">Development</a> |
|
||||
<a href="#building">Building</a> |
|
||||
<a href="#support">Support</a> |
|
||||
<a href="#license">License</a>
|
||||
</p>
|
||||
|
||||
|
||||
## Compatibility
|
||||
|
||||
The emulator is capable of running most commercial games at full speed, provided you meet the necessary hardware requirements.
|
||||
|
||||
For a full list of games Citra supports, please visit our [Compatibility page](https://citra-emu.org/game/)
|
||||
|
||||
Check out our [website](https://citra-emu.org/) for the latest news on exciting features, progress reports, and more!
|
||||
Please read the [FAQ](https://citra-emu.org/wiki/faq/) before getting started with the project.
|
||||
|
||||
Need help? Check out our [asking for help](https://citra-emu.org/help/reference/asking/) guide.
|
||||
|
||||
## Releases
|
||||
|
||||
Citra has two main release channels: Nightly and Canary.
|
||||
|
||||
The [Nightly](https://github.com/citra-emu/citra-nightly) build is based on the master branch, and contains already reviewed and tested features.
|
||||
|
||||
The [Canary](https://github.com/citra-emu/citra-canary) build is based on the master branch, but with additional features still under review. PRs tagged `canary-merge` are merged only into the Canary builds.
|
||||
|
||||
Both builds can be installed with the installer provided on the [website](https://citra-emu.org/download/), but those looking for specific versions or standalone releases can find them in the release tabs of the [Nightly](https://github.com/citra-emu/citra-nightly/releases) and [Canary](https://github.com/citra-emu/citra-canary/releases) repositories.
|
||||
|
||||
Android builds can be downloaded from the Google Play Store.
|
||||
|
||||
A Flatpak for Citra is available on [Flathub](https://flathub.org/apps/details/org.citra_emu.citra). Details on the build process can be found in [our Flathub repository](https://github.com/flathub/org.citra_emu.citra).
|
||||
|
||||
## Development
|
||||
|
||||
Most of the development happens on GitHub. It's also where [our central repository](https://github.com/citra-emu/citra) is hosted.
|
||||
For development discussion, please join us on our [Discord server](https://citra-emu.org/discord/) or at #citra-dev on libera.
|
||||
|
||||
If you want to contribute please take a look at the [Contributor's Guide](https://github.com/citra-emu/citra/wiki/Contributing) and [Developer Information](https://github.com/citra-emu/citra/wiki/Developer-Information). You can also contact any of the developers on Discord in order to know about the current state of the emulator.
|
||||
|
||||
If you want to contribute to the user interface translation, please check out the [Citra project on transifex](https://www.transifex.com/citra/citra). We centralize the translation work there, and periodically upstream translations.
|
||||
|
||||
## Building
|
||||
|
||||
* __Windows__: [Windows Build](https://github.com/citra-emu/citra/wiki/Building-For-Windows)
|
||||
* __Linux__: [Linux Build](https://github.com/citra-emu/citra/wiki/Building-For-Linux)
|
||||
* __macOS__: [macOS Build](https://github.com/citra-emu/citra/wiki/Building-for-macOS)
|
||||
* __Android__: [Android Build](https://github.com/citra-emu/citra/wiki/Building-for-Android)
|
||||
|
||||
|
||||
## Support
|
||||
|
||||
If you enjoy the project and want to support us financially, check out our Patreon!
|
||||
|
||||
<a href="https://www.patreon.com/citraemu">
|
||||
<img src="https://c5.patreon.com/external/logo/become_a_patron_button@2x.png" width="160">
|
||||
</a>
|
||||
|
||||
We also happily accept donated games and hardware.
|
||||
Please see our [donations page](https://citra-emu.org/donate/) for more information on how you can contribute to Citra.
|
||||
Any donations received will go towards things like:
|
||||
* 3DS consoles for developers to explore the hardware
|
||||
* 3DS games for testing
|
||||
* Any equipment required for homebrew
|
||||
* Infrastructure setup
|
||||
|
||||
We also more than gladly accept used 3DS consoles! If you would like to give yours away, don't hesitate to join our [Discord server](https://citra-emu.org/discord/) and talk to bunnei.
|
||||
|
||||
|
||||
## License
|
||||
|
||||
Citra is licensed under the GPLv2 (or any later version). Refer to the [LICENSE.txt](https://github.com/citra-emu/citra/blob/master/license.txt) file.
|
||||
ñ
|
||||
|
8
dist/dumpkeys/DumpKeys.gm9
vendored
8
dist/dumpkeys/DumpKeys.gm9
vendored
@ -287,5 +287,13 @@ dumptxt -p $[OUT] "nfcSecret1Seed=$[NFC_SEED_1]"
|
||||
dumptxt -p $[OUT] "nfcSecret1HmacKey=$[NFC_HMAC_KEY_1]"
|
||||
dumptxt -p $[OUT] "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
|
||||
|
||||
|
2
dist/dumpkeys/README.md
vendored
2
dist/dumpkeys/README.md
vendored
@ -6,5 +6,5 @@ Usage:
|
||||
1. Copy "DumpKeys.gm9" into the "gm9/scripts/" directory on your SD card.
|
||||
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.
|
||||
4. Power off your system and copy the "gm9/aes_keys.txt" file off of your SD card into "(Citra directory)/sysdata/".
|
||||
4. Power off your system and copy the "gm9/aes_keys.txt" and "gm9/seeddb.bin" files off of your SD card into "(Citra directory)/sysdata/".
|
||||
|
||||
|
1
dist/languages/.tx/config
vendored
1
dist/languages/.tx/config
vendored
@ -11,3 +11,4 @@ type = QT
|
||||
file_filter = ../../src/android/app/src/main/res/values-<lang>/strings.xml
|
||||
source_file = ../../src/android/app/src/main/res/values/strings.xml
|
||||
type = ANDROID
|
||||
lang_map = es_ES:es, hu_HU:hu, ru_RU:ru, pt_BR:pt, zh_CN:zh
|
||||
|
992
dist/languages/da_DK.ts
vendored
992
dist/languages/da_DK.ts
vendored
File diff suppressed because it is too large
Load Diff
1655
dist/languages/de.ts
vendored
1655
dist/languages/de.ts
vendored
File diff suppressed because it is too large
Load Diff
992
dist/languages/el.ts
vendored
992
dist/languages/el.ts
vendored
File diff suppressed because it is too large
Load Diff
992
dist/languages/es_ES.ts
vendored
992
dist/languages/es_ES.ts
vendored
File diff suppressed because it is too large
Load Diff
992
dist/languages/fi.ts
vendored
992
dist/languages/fi.ts
vendored
File diff suppressed because it is too large
Load Diff
1030
dist/languages/fr.ts
vendored
1030
dist/languages/fr.ts
vendored
File diff suppressed because it is too large
Load Diff
1658
dist/languages/hu_HU.ts
vendored
1658
dist/languages/hu_HU.ts
vendored
File diff suppressed because it is too large
Load Diff
993
dist/languages/id.ts
vendored
993
dist/languages/id.ts
vendored
File diff suppressed because it is too large
Load Diff
992
dist/languages/it.ts
vendored
992
dist/languages/it.ts
vendored
File diff suppressed because it is too large
Load Diff
992
dist/languages/ja_JP.ts
vendored
992
dist/languages/ja_JP.ts
vendored
File diff suppressed because it is too large
Load Diff
992
dist/languages/ko_KR.ts
vendored
992
dist/languages/ko_KR.ts
vendored
File diff suppressed because it is too large
Load Diff
992
dist/languages/lt_LT.ts
vendored
992
dist/languages/lt_LT.ts
vendored
File diff suppressed because it is too large
Load Diff
992
dist/languages/nb.ts
vendored
992
dist/languages/nb.ts
vendored
File diff suppressed because it is too large
Load Diff
992
dist/languages/nl.ts
vendored
992
dist/languages/nl.ts
vendored
File diff suppressed because it is too large
Load Diff
992
dist/languages/pl_PL.ts
vendored
992
dist/languages/pl_PL.ts
vendored
File diff suppressed because it is too large
Load Diff
1105
dist/languages/pt_BR.ts
vendored
1105
dist/languages/pt_BR.ts
vendored
File diff suppressed because it is too large
Load Diff
992
dist/languages/ro_RO.ts
vendored
992
dist/languages/ro_RO.ts
vendored
File diff suppressed because it is too large
Load Diff
993
dist/languages/ru_RU.ts
vendored
993
dist/languages/ru_RU.ts
vendored
File diff suppressed because it is too large
Load Diff
992
dist/languages/tr_TR.ts
vendored
992
dist/languages/tr_TR.ts
vendored
File diff suppressed because it is too large
Load Diff
992
dist/languages/vi_VN.ts
vendored
992
dist/languages/vi_VN.ts
vendored
File diff suppressed because it is too large
Load Diff
996
dist/languages/zh_CN.ts
vendored
996
dist/languages/zh_CN.ts
vendored
File diff suppressed because it is too large
Load Diff
992
dist/languages/zh_TW.ts
vendored
992
dist/languages/zh_TW.ts
vendored
File diff suppressed because it is too large
Load Diff
40
externals/CMakeLists.txt
vendored
40
externals/CMakeLists.txt
vendored
@ -57,6 +57,12 @@ if(USE_SYSTEM_CRYPTOPP)
|
||||
add_library(cryptopp INTERFACE)
|
||||
target_link_libraries(cryptopp INTERFACE cryptopp::cryptopp)
|
||||
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_TESTING OFF CACHE BOOL "")
|
||||
set(CRYPTOPP_INSTALL OFF CACHE BOOL "")
|
||||
@ -235,6 +241,18 @@ endif()
|
||||
|
||||
# DiscordRPC
|
||||
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)
|
||||
target_include_directories(discord-rpc INTERFACE ./discord-rpc/include)
|
||||
endif()
|
||||
@ -276,11 +294,20 @@ endif()
|
||||
add_library(httplib INTERFACE)
|
||||
if(USE_SYSTEM_CPP_HTTPLIB)
|
||||
find_package(CppHttp 0.14.1)
|
||||
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...")
|
||||
# Detect if system cpphttplib is a shared library
|
||||
# this breaks building as Citra relies on functions that are moved
|
||||
# into the shared object.
|
||||
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)
|
||||
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()
|
||||
else()
|
||||
target_include_directories(httplib SYSTEM INTERFACE ./httplib)
|
||||
@ -288,11 +315,6 @@ endif()
|
||||
target_compile_options(httplib INTERFACE -DCPPHTTPLIB_OPENSSL_SUPPORT)
|
||||
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)
|
||||
add_subdirectory(gamemode)
|
||||
endif()
|
||||
|
8
externals/android-ifaddrs/CMakeLists.txt
vendored
8
externals/android-ifaddrs/CMakeLists.txt
vendored
@ -1,8 +0,0 @@
|
||||
add_library(ifaddrs
|
||||
ifaddrs.c
|
||||
ifaddrs.h
|
||||
)
|
||||
|
||||
create_target_directory_groups(ifaddrs)
|
||||
|
||||
target_include_directories(ifaddrs INTERFACE ${CMAKE_CURRENT_SOURCE_DIR})
|
600
externals/android-ifaddrs/ifaddrs.c
vendored
600
externals/android-ifaddrs/ifaddrs.c
vendored
@ -1,600 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2013, Kenneth MacKay
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
|
||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include "ifaddrs.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <errno.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/socket.h>
|
||||
#include <net/if_arp.h>
|
||||
#include <netinet/in.h>
|
||||
#include <linux/netlink.h>
|
||||
#include <linux/rtnetlink.h>
|
||||
|
||||
typedef struct NetlinkList
|
||||
{
|
||||
struct NetlinkList *m_next;
|
||||
struct nlmsghdr *m_data;
|
||||
unsigned int m_size;
|
||||
} NetlinkList;
|
||||
|
||||
static int netlink_socket(void)
|
||||
{
|
||||
int l_socket = socket(PF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
|
||||
if(l_socket < 0)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct sockaddr_nl l_addr;
|
||||
memset(&l_addr, 0, sizeof(l_addr));
|
||||
l_addr.nl_family = AF_NETLINK;
|
||||
if(bind(l_socket, (struct sockaddr *)&l_addr, sizeof(l_addr)) < 0)
|
||||
{
|
||||
close(l_socket);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return l_socket;
|
||||
}
|
||||
|
||||
static int netlink_send(int p_socket, int p_request)
|
||||
{
|
||||
char l_buffer[NLMSG_ALIGN(sizeof(struct nlmsghdr)) + NLMSG_ALIGN(sizeof(struct rtgenmsg))];
|
||||
memset(l_buffer, 0, sizeof(l_buffer));
|
||||
struct nlmsghdr *l_hdr = (struct nlmsghdr *)l_buffer;
|
||||
struct rtgenmsg *l_msg = (struct rtgenmsg *)NLMSG_DATA(l_hdr);
|
||||
|
||||
l_hdr->nlmsg_len = NLMSG_LENGTH(sizeof(*l_msg));
|
||||
l_hdr->nlmsg_type = p_request;
|
||||
l_hdr->nlmsg_flags = NLM_F_ROOT | NLM_F_MATCH | NLM_F_REQUEST;
|
||||
l_hdr->nlmsg_pid = 0;
|
||||
l_hdr->nlmsg_seq = p_socket;
|
||||
l_msg->rtgen_family = AF_UNSPEC;
|
||||
|
||||
struct sockaddr_nl l_addr;
|
||||
memset(&l_addr, 0, sizeof(l_addr));
|
||||
l_addr.nl_family = AF_NETLINK;
|
||||
return (sendto(p_socket, l_hdr, l_hdr->nlmsg_len, 0, (struct sockaddr *)&l_addr, sizeof(l_addr)));
|
||||
}
|
||||
|
||||
static int netlink_recv(int p_socket, void *p_buffer, size_t p_len)
|
||||
{
|
||||
struct msghdr l_msg;
|
||||
struct iovec l_iov = { p_buffer, p_len };
|
||||
struct sockaddr_nl l_addr;
|
||||
int l_result;
|
||||
|
||||
for(;;)
|
||||
{
|
||||
l_msg.msg_name = (void *)&l_addr;
|
||||
l_msg.msg_namelen = sizeof(l_addr);
|
||||
l_msg.msg_iov = &l_iov;
|
||||
l_msg.msg_iovlen = 1;
|
||||
l_msg.msg_control = NULL;
|
||||
l_msg.msg_controllen = 0;
|
||||
l_msg.msg_flags = 0;
|
||||
int l_result = recvmsg(p_socket, &l_msg, 0);
|
||||
|
||||
if(l_result < 0)
|
||||
{
|
||||
if(errno == EINTR)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
return -2;
|
||||
}
|
||||
|
||||
if(l_msg.msg_flags & MSG_TRUNC)
|
||||
{ // buffer was too small
|
||||
return -1;
|
||||
}
|
||||
return l_result;
|
||||
}
|
||||
}
|
||||
|
||||
static struct nlmsghdr *getNetlinkResponse(int p_socket, int *p_size, int *p_done)
|
||||
{
|
||||
size_t l_size = 4096;
|
||||
void *l_buffer = NULL;
|
||||
|
||||
for(;;)
|
||||
{
|
||||
free(l_buffer);
|
||||
l_buffer = malloc(l_size);
|
||||
|
||||
int l_read = netlink_recv(p_socket, l_buffer, l_size);
|
||||
*p_size = l_read;
|
||||
if(l_read == -2)
|
||||
{
|
||||
free(l_buffer);
|
||||
return NULL;
|
||||
}
|
||||
if(l_read >= 0)
|
||||
{
|
||||
pid_t l_pid = getpid();
|
||||
struct nlmsghdr *l_hdr;
|
||||
for(l_hdr = (struct nlmsghdr *)l_buffer; NLMSG_OK(l_hdr, (unsigned int)l_read); l_hdr = (struct nlmsghdr *)NLMSG_NEXT(l_hdr, l_read))
|
||||
{
|
||||
if((pid_t)l_hdr->nlmsg_pid != l_pid || (int)l_hdr->nlmsg_seq != p_socket)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if(l_hdr->nlmsg_type == NLMSG_DONE)
|
||||
{
|
||||
*p_done = 1;
|
||||
break;
|
||||
}
|
||||
|
||||
if(l_hdr->nlmsg_type == NLMSG_ERROR)
|
||||
{
|
||||
free(l_buffer);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
return l_buffer;
|
||||
}
|
||||
|
||||
l_size *= 2;
|
||||
}
|
||||
}
|
||||
|
||||
static NetlinkList *newListItem(struct nlmsghdr *p_data, unsigned int p_size)
|
||||
{
|
||||
NetlinkList *l_item = malloc(sizeof(NetlinkList));
|
||||
l_item->m_next = NULL;
|
||||
l_item->m_data = p_data;
|
||||
l_item->m_size = p_size;
|
||||
return l_item;
|
||||
}
|
||||
|
||||
static void freeResultList(NetlinkList *p_list)
|
||||
{
|
||||
NetlinkList *l_cur;
|
||||
while(p_list)
|
||||
{
|
||||
l_cur = p_list;
|
||||
p_list = p_list->m_next;
|
||||
free(l_cur->m_data);
|
||||
free(l_cur);
|
||||
}
|
||||
}
|
||||
|
||||
static NetlinkList *getResultList(int p_socket, int p_request)
|
||||
{
|
||||
if(netlink_send(p_socket, p_request) < 0)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
NetlinkList *l_list = NULL;
|
||||
NetlinkList *l_end = NULL;
|
||||
int l_size;
|
||||
int l_done = 0;
|
||||
while(!l_done)
|
||||
{
|
||||
struct nlmsghdr *l_hdr = getNetlinkResponse(p_socket, &l_size, &l_done);
|
||||
if(!l_hdr)
|
||||
{ // error
|
||||
freeResultList(l_list);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
NetlinkList *l_item = newListItem(l_hdr, l_size);
|
||||
if(!l_list)
|
||||
{
|
||||
l_list = l_item;
|
||||
}
|
||||
else
|
||||
{
|
||||
l_end->m_next = l_item;
|
||||
}
|
||||
l_end = l_item;
|
||||
}
|
||||
return l_list;
|
||||
}
|
||||
|
||||
static size_t maxSize(size_t a, size_t b)
|
||||
{
|
||||
return (a > b ? a : b);
|
||||
}
|
||||
|
||||
static size_t calcAddrLen(sa_family_t p_family, int p_dataSize)
|
||||
{
|
||||
switch(p_family)
|
||||
{
|
||||
case AF_INET:
|
||||
return sizeof(struct sockaddr_in);
|
||||
case AF_INET6:
|
||||
return sizeof(struct sockaddr_in6);
|
||||
case AF_PACKET:
|
||||
return maxSize(sizeof(struct sockaddr_ll), offsetof(struct sockaddr_ll, sll_addr) + p_dataSize);
|
||||
default:
|
||||
return maxSize(sizeof(struct sockaddr), offsetof(struct sockaddr, sa_data) + p_dataSize);
|
||||
}
|
||||
}
|
||||
|
||||
static void makeSockaddr(sa_family_t p_family, struct sockaddr *p_dest, void *p_data, size_t p_size)
|
||||
{
|
||||
switch(p_family)
|
||||
{
|
||||
case AF_INET:
|
||||
memcpy(&((struct sockaddr_in*)p_dest)->sin_addr, p_data, p_size);
|
||||
break;
|
||||
case AF_INET6:
|
||||
memcpy(&((struct sockaddr_in6*)p_dest)->sin6_addr, p_data, p_size);
|
||||
break;
|
||||
case AF_PACKET:
|
||||
memcpy(((struct sockaddr_ll*)p_dest)->sll_addr, p_data, p_size);
|
||||
((struct sockaddr_ll*)p_dest)->sll_halen = p_size;
|
||||
break;
|
||||
default:
|
||||
memcpy(p_dest->sa_data, p_data, p_size);
|
||||
break;
|
||||
}
|
||||
p_dest->sa_family = p_family;
|
||||
}
|
||||
|
||||
static void addToEnd(struct ifaddrs **p_resultList, struct ifaddrs *p_entry)
|
||||
{
|
||||
if(!*p_resultList)
|
||||
{
|
||||
*p_resultList = p_entry;
|
||||
}
|
||||
else
|
||||
{
|
||||
struct ifaddrs *l_cur = *p_resultList;
|
||||
while(l_cur->ifa_next)
|
||||
{
|
||||
l_cur = l_cur->ifa_next;
|
||||
}
|
||||
l_cur->ifa_next = p_entry;
|
||||
}
|
||||
}
|
||||
|
||||
static void interpretLink(struct nlmsghdr *p_hdr, struct ifaddrs **p_links, struct ifaddrs **p_resultList)
|
||||
{
|
||||
struct ifinfomsg *l_info = (struct ifinfomsg *)NLMSG_DATA(p_hdr);
|
||||
|
||||
size_t l_nameSize = 0;
|
||||
size_t l_addrSize = 0;
|
||||
size_t l_dataSize = 0;
|
||||
|
||||
size_t l_rtaSize = NLMSG_PAYLOAD(p_hdr, sizeof(struct ifinfomsg));
|
||||
struct rtattr *l_rta;
|
||||
for(l_rta = (struct rtattr *)(((char *)l_info) + NLMSG_ALIGN(sizeof(struct ifinfomsg))); RTA_OK(l_rta, l_rtaSize); l_rta = RTA_NEXT(l_rta, l_rtaSize))
|
||||
{
|
||||
void *l_rtaData = RTA_DATA(l_rta);
|
||||
size_t l_rtaDataSize = RTA_PAYLOAD(l_rta);
|
||||
switch(l_rta->rta_type)
|
||||
{
|
||||
case IFLA_ADDRESS:
|
||||
case IFLA_BROADCAST:
|
||||
l_addrSize += NLMSG_ALIGN(calcAddrLen(AF_PACKET, l_rtaDataSize));
|
||||
break;
|
||||
case IFLA_IFNAME:
|
||||
l_nameSize += NLMSG_ALIGN(l_rtaSize + 1);
|
||||
break;
|
||||
case IFLA_STATS:
|
||||
l_dataSize += NLMSG_ALIGN(l_rtaSize);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
struct ifaddrs *l_entry = malloc(sizeof(struct ifaddrs) + l_nameSize + l_addrSize + l_dataSize);
|
||||
memset(l_entry, 0, sizeof(struct ifaddrs));
|
||||
l_entry->ifa_name = "";
|
||||
|
||||
char *l_name = ((char *)l_entry) + sizeof(struct ifaddrs);
|
||||
char *l_addr = l_name + l_nameSize;
|
||||
char *l_data = l_addr + l_addrSize;
|
||||
|
||||
l_entry->ifa_flags = l_info->ifi_flags;
|
||||
|
||||
l_rtaSize = NLMSG_PAYLOAD(p_hdr, sizeof(struct ifinfomsg));
|
||||
for(l_rta = (struct rtattr *)(((char *)l_info) + NLMSG_ALIGN(sizeof(struct ifinfomsg))); RTA_OK(l_rta, l_rtaSize); l_rta = RTA_NEXT(l_rta, l_rtaSize))
|
||||
{
|
||||
void *l_rtaData = RTA_DATA(l_rta);
|
||||
size_t l_rtaDataSize = RTA_PAYLOAD(l_rta);
|
||||
switch(l_rta->rta_type)
|
||||
{
|
||||
case IFLA_ADDRESS:
|
||||
case IFLA_BROADCAST:
|
||||
{
|
||||
size_t l_addrLen = calcAddrLen(AF_PACKET, l_rtaDataSize);
|
||||
makeSockaddr(AF_PACKET, (struct sockaddr *)l_addr, l_rtaData, l_rtaDataSize);
|
||||
((struct sockaddr_ll *)l_addr)->sll_ifindex = l_info->ifi_index;
|
||||
((struct sockaddr_ll *)l_addr)->sll_hatype = l_info->ifi_type;
|
||||
if(l_rta->rta_type == IFLA_ADDRESS)
|
||||
{
|
||||
l_entry->ifa_addr = (struct sockaddr *)l_addr;
|
||||
}
|
||||
else
|
||||
{
|
||||
l_entry->ifa_broadaddr = (struct sockaddr *)l_addr;
|
||||
}
|
||||
l_addr += NLMSG_ALIGN(l_addrLen);
|
||||
break;
|
||||
}
|
||||
case IFLA_IFNAME:
|
||||
strncpy(l_name, l_rtaData, l_rtaDataSize);
|
||||
l_name[l_rtaDataSize] = '\0';
|
||||
l_entry->ifa_name = l_name;
|
||||
break;
|
||||
case IFLA_STATS:
|
||||
memcpy(l_data, l_rtaData, l_rtaDataSize);
|
||||
l_entry->ifa_data = l_data;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
addToEnd(p_resultList, l_entry);
|
||||
p_links[l_info->ifi_index - 1] = l_entry;
|
||||
}
|
||||
|
||||
static void interpretAddr(struct nlmsghdr *p_hdr, struct ifaddrs **p_links, struct ifaddrs **p_resultList)
|
||||
{
|
||||
struct ifaddrmsg *l_info = (struct ifaddrmsg *)NLMSG_DATA(p_hdr);
|
||||
|
||||
size_t l_nameSize = 0;
|
||||
size_t l_addrSize = 0;
|
||||
|
||||
int l_addedNetmask = 0;
|
||||
|
||||
size_t l_rtaSize = NLMSG_PAYLOAD(p_hdr, sizeof(struct ifaddrmsg));
|
||||
struct rtattr *l_rta;
|
||||
for(l_rta = (struct rtattr *)(((char *)l_info) + NLMSG_ALIGN(sizeof(struct ifaddrmsg))); RTA_OK(l_rta, l_rtaSize); l_rta = RTA_NEXT(l_rta, l_rtaSize))
|
||||
{
|
||||
void *l_rtaData = RTA_DATA(l_rta);
|
||||
size_t l_rtaDataSize = RTA_PAYLOAD(l_rta);
|
||||
if(l_info->ifa_family == AF_PACKET)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
switch(l_rta->rta_type)
|
||||
{
|
||||
case IFA_ADDRESS:
|
||||
case IFA_LOCAL:
|
||||
if((l_info->ifa_family == AF_INET || l_info->ifa_family == AF_INET6) && !l_addedNetmask)
|
||||
{ // make room for netmask
|
||||
l_addrSize += NLMSG_ALIGN(calcAddrLen(l_info->ifa_family, l_rtaDataSize));
|
||||
l_addedNetmask = 1;
|
||||
}
|
||||
case IFA_BROADCAST:
|
||||
l_addrSize += NLMSG_ALIGN(calcAddrLen(l_info->ifa_family, l_rtaDataSize));
|
||||
break;
|
||||
case IFA_LABEL:
|
||||
l_nameSize += NLMSG_ALIGN(l_rtaSize + 1);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
struct ifaddrs *l_entry = malloc(sizeof(struct ifaddrs) + l_nameSize + l_addrSize);
|
||||
memset(l_entry, 0, sizeof(struct ifaddrs));
|
||||
l_entry->ifa_name = p_links[l_info->ifa_index - 1]->ifa_name;
|
||||
|
||||
char *l_name = ((char *)l_entry) + sizeof(struct ifaddrs);
|
||||
char *l_addr = l_name + l_nameSize;
|
||||
|
||||
l_entry->ifa_flags = l_info->ifa_flags | p_links[l_info->ifa_index - 1]->ifa_flags;
|
||||
|
||||
l_rtaSize = NLMSG_PAYLOAD(p_hdr, sizeof(struct ifaddrmsg));
|
||||
for(l_rta = (struct rtattr *)(((char *)l_info) + NLMSG_ALIGN(sizeof(struct ifaddrmsg))); RTA_OK(l_rta, l_rtaSize); l_rta = RTA_NEXT(l_rta, l_rtaSize))
|
||||
{
|
||||
void *l_rtaData = RTA_DATA(l_rta);
|
||||
size_t l_rtaDataSize = RTA_PAYLOAD(l_rta);
|
||||
switch(l_rta->rta_type)
|
||||
{
|
||||
case IFA_ADDRESS:
|
||||
case IFA_BROADCAST:
|
||||
case IFA_LOCAL:
|
||||
{
|
||||
size_t l_addrLen = calcAddrLen(l_info->ifa_family, l_rtaDataSize);
|
||||
makeSockaddr(l_info->ifa_family, (struct sockaddr *)l_addr, l_rtaData, l_rtaDataSize);
|
||||
if(l_info->ifa_family == AF_INET6)
|
||||
{
|
||||
if(IN6_IS_ADDR_LINKLOCAL((struct in6_addr *)l_rtaData) || IN6_IS_ADDR_MC_LINKLOCAL((struct in6_addr *)l_rtaData))
|
||||
{
|
||||
((struct sockaddr_in6 *)l_addr)->sin6_scope_id = l_info->ifa_index;
|
||||
}
|
||||
}
|
||||
|
||||
if(l_rta->rta_type == IFA_ADDRESS)
|
||||
{ // apparently in a point-to-point network IFA_ADDRESS contains the dest address and IFA_LOCAL contains the local address
|
||||
if(l_entry->ifa_addr)
|
||||
{
|
||||
l_entry->ifa_dstaddr = (struct sockaddr *)l_addr;
|
||||
}
|
||||
else
|
||||
{
|
||||
l_entry->ifa_addr = (struct sockaddr *)l_addr;
|
||||
}
|
||||
}
|
||||
else if(l_rta->rta_type == IFA_LOCAL)
|
||||
{
|
||||
if(l_entry->ifa_addr)
|
||||
{
|
||||
l_entry->ifa_dstaddr = l_entry->ifa_addr;
|
||||
}
|
||||
l_entry->ifa_addr = (struct sockaddr *)l_addr;
|
||||
}
|
||||
else
|
||||
{
|
||||
l_entry->ifa_broadaddr = (struct sockaddr *)l_addr;
|
||||
}
|
||||
l_addr += NLMSG_ALIGN(l_addrLen);
|
||||
break;
|
||||
}
|
||||
case IFA_LABEL:
|
||||
strncpy(l_name, l_rtaData, l_rtaDataSize);
|
||||
l_name[l_rtaDataSize] = '\0';
|
||||
l_entry->ifa_name = l_name;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(l_entry->ifa_addr && (l_entry->ifa_addr->sa_family == AF_INET || l_entry->ifa_addr->sa_family == AF_INET6))
|
||||
{
|
||||
unsigned l_maxPrefix = (l_entry->ifa_addr->sa_family == AF_INET ? 32 : 128);
|
||||
unsigned l_prefix = (l_info->ifa_prefixlen > l_maxPrefix ? l_maxPrefix : l_info->ifa_prefixlen);
|
||||
char l_mask[16] = {0};
|
||||
unsigned i;
|
||||
for(i=0; i<(l_prefix/8); ++i)
|
||||
{
|
||||
l_mask[i] = 0xff;
|
||||
}
|
||||
l_mask[i] = 0xff << (8 - (l_prefix % 8));
|
||||
|
||||
makeSockaddr(l_entry->ifa_addr->sa_family, (struct sockaddr *)l_addr, l_mask, l_maxPrefix / 8);
|
||||
l_entry->ifa_netmask = (struct sockaddr *)l_addr;
|
||||
}
|
||||
|
||||
addToEnd(p_resultList, l_entry);
|
||||
}
|
||||
|
||||
static void interpret(int p_socket, NetlinkList *p_netlinkList, struct ifaddrs **p_links, struct ifaddrs **p_resultList)
|
||||
{
|
||||
pid_t l_pid = getpid();
|
||||
for(; p_netlinkList; p_netlinkList = p_netlinkList->m_next)
|
||||
{
|
||||
unsigned int l_nlsize = p_netlinkList->m_size;
|
||||
struct nlmsghdr *l_hdr;
|
||||
for(l_hdr = p_netlinkList->m_data; NLMSG_OK(l_hdr, l_nlsize); l_hdr = NLMSG_NEXT(l_hdr, l_nlsize))
|
||||
{
|
||||
if((pid_t)l_hdr->nlmsg_pid != l_pid || (int)l_hdr->nlmsg_seq != p_socket)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if(l_hdr->nlmsg_type == NLMSG_DONE)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if(l_hdr->nlmsg_type == RTM_NEWLINK)
|
||||
{
|
||||
interpretLink(l_hdr, p_links, p_resultList);
|
||||
}
|
||||
else if(l_hdr->nlmsg_type == RTM_NEWADDR)
|
||||
{
|
||||
interpretAddr(l_hdr, p_links, p_resultList);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static unsigned countLinks(int p_socket, NetlinkList *p_netlinkList)
|
||||
{
|
||||
unsigned l_links = 0;
|
||||
pid_t l_pid = getpid();
|
||||
for(; p_netlinkList; p_netlinkList = p_netlinkList->m_next)
|
||||
{
|
||||
unsigned int l_nlsize = p_netlinkList->m_size;
|
||||
struct nlmsghdr *l_hdr;
|
||||
for(l_hdr = p_netlinkList->m_data; NLMSG_OK(l_hdr, l_nlsize); l_hdr = NLMSG_NEXT(l_hdr, l_nlsize))
|
||||
{
|
||||
if((pid_t)l_hdr->nlmsg_pid != l_pid || (int)l_hdr->nlmsg_seq != p_socket)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if(l_hdr->nlmsg_type == NLMSG_DONE)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if(l_hdr->nlmsg_type == RTM_NEWLINK)
|
||||
{
|
||||
++l_links;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return l_links;
|
||||
}
|
||||
|
||||
int getifaddrs(struct ifaddrs **ifap)
|
||||
{
|
||||
if(!ifap)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
*ifap = NULL;
|
||||
|
||||
int l_socket = netlink_socket();
|
||||
if(l_socket < 0)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
NetlinkList *l_linkResults = getResultList(l_socket, RTM_GETLINK);
|
||||
if(!l_linkResults)
|
||||
{
|
||||
close(l_socket);
|
||||
return -1;
|
||||
}
|
||||
|
||||
NetlinkList *l_addrResults = getResultList(l_socket, RTM_GETADDR);
|
||||
if(!l_addrResults)
|
||||
{
|
||||
close(l_socket);
|
||||
freeResultList(l_linkResults);
|
||||
return -1;
|
||||
}
|
||||
|
||||
unsigned l_numLinks = countLinks(l_socket, l_linkResults) + countLinks(l_socket, l_addrResults);
|
||||
struct ifaddrs *l_links[l_numLinks];
|
||||
memset(l_links, 0, l_numLinks * sizeof(struct ifaddrs *));
|
||||
|
||||
interpret(l_socket, l_linkResults, l_links, ifap);
|
||||
interpret(l_socket, l_addrResults, l_links, ifap);
|
||||
|
||||
freeResultList(l_linkResults);
|
||||
freeResultList(l_addrResults);
|
||||
close(l_socket);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void freeifaddrs(struct ifaddrs *ifa)
|
||||
{
|
||||
struct ifaddrs *l_cur;
|
||||
while(ifa)
|
||||
{
|
||||
l_cur = ifa;
|
||||
ifa = ifa->ifa_next;
|
||||
free(l_cur);
|
||||
}
|
||||
}
|
54
externals/android-ifaddrs/ifaddrs.h
vendored
54
externals/android-ifaddrs/ifaddrs.h
vendored
@ -1,54 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 1995, 1999
|
||||
* Berkeley Software Design, Inc. All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY Berkeley Software Design, Inc. ``AS IS'' AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL Berkeley Software Design, Inc. BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
* SUCH DAMAGE.
|
||||
*
|
||||
* BSDI ifaddrs.h,v 2.5 2000/02/23 14:51:59 dab Exp
|
||||
*/
|
||||
|
||||
#ifndef _IFADDRS_H_
|
||||
#define _IFADDRS_H_
|
||||
|
||||
struct ifaddrs {
|
||||
struct ifaddrs *ifa_next;
|
||||
char *ifa_name;
|
||||
unsigned int ifa_flags;
|
||||
struct sockaddr *ifa_addr;
|
||||
struct sockaddr *ifa_netmask;
|
||||
struct sockaddr *ifa_dstaddr;
|
||||
void *ifa_data;
|
||||
};
|
||||
|
||||
/*
|
||||
* This may have been defined in <net/if.h>. Note that if <net/if.h> is
|
||||
* to be included it must be included before this header file.
|
||||
*/
|
||||
#ifndef ifa_broadaddr
|
||||
#define ifa_broadaddr ifa_dstaddr /* broadcast address interface */
|
||||
#endif
|
||||
|
||||
#include <sys/cdefs.h>
|
||||
|
||||
__BEGIN_DECLS
|
||||
extern int getifaddrs(struct ifaddrs **ifap);
|
||||
extern void freeifaddrs(struct ifaddrs *ifa);
|
||||
__END_DECLS
|
||||
|
||||
#endif
|
2
externals/cryptopp-cmake
vendored
2
externals/cryptopp-cmake
vendored
@ -1 +1 @@
|
||||
Subproject commit 9327192b0095dc1f420b2082d37bd427b5750d48
|
||||
Subproject commit a99c80c26686e44eddf0432140ae397f3efbd0b3
|
2
externals/cubeb
vendored
2
externals/cubeb
vendored
@ -1 +1 @@
|
||||
Subproject commit 48689ae7a73caeb747953f9ed664dc71d2f918d8
|
||||
Subproject commit 799e775484b8fce7e986ee7a4f4b651fec2bca07
|
2
externals/dynarmic
vendored
2
externals/dynarmic
vendored
@ -1 +1 @@
|
||||
Subproject commit d333a09b3b9152af3cb442902ae8ea18d8416470
|
||||
Subproject commit 30f1a3c6289075ef4af08f5ec502be2fc8627a0c
|
2
externals/oaknut
vendored
2
externals/oaknut
vendored
@ -1 +1 @@
|
||||
Subproject commit e6eecc3f9460728be0a8d3f63e66d31c0362f472
|
||||
Subproject commit 6b1d57ea7ed4882d32a91eeaa6557b0ecb4da152
|
2
externals/vulkan-headers
vendored
2
externals/vulkan-headers
vendored
@ -1 +1 @@
|
||||
Subproject commit 217e93c664ec6704ec2d8c36fa116c1a4a1e2d40
|
||||
Subproject commit 5a5c9a643484d888873e32c5d7d484fae8e71d3d
|
@ -10,7 +10,7 @@ plugins {
|
||||
id("org.jetbrains.kotlin.android")
|
||||
id("de.undercouch.download") version "5.5.0"
|
||||
id("kotlin-parcelize")
|
||||
kotlin("plugin.serialization") version "1.8.21"
|
||||
kotlin("plugin.serialization") version "1.9.22"
|
||||
id("androidx.navigation.safeargs.kotlin")
|
||||
}
|
||||
|
||||
@ -173,23 +173,23 @@ android {
|
||||
|
||||
dependencies {
|
||||
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.appcompat:appcompat:1.6.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("com.google.android.material:material:1.9.0")
|
||||
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("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0")
|
||||
implementation("androidx.navigation:navigation-fragment-ktx:2.7.5")
|
||||
implementation("androidx.navigation:navigation-ui-ktx:2.7.5")
|
||||
implementation("androidx.navigation:navigation-fragment-ktx:2.7.6")
|
||||
implementation("androidx.navigation:navigation-ui-ktx:2.7.6")
|
||||
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("io.coil-kt:coil:2.2.2")
|
||||
implementation("io.coil-kt:coil:2.5.0")
|
||||
}
|
||||
|
||||
// Download Vulkan Validation Layers from the KhronosGroup GitHub.
|
||||
|
@ -42,6 +42,9 @@
|
||||
android:banner="@mipmap/ic_launcher"
|
||||
android:requestLegacyExternalStorage="true">
|
||||
|
||||
<meta-data android:name="android.game_mode_config"
|
||||
android:resource="@xml/game_mode_config" />
|
||||
|
||||
<activity
|
||||
android:name="org.citra.citra_emu.ui.main.MainActivity"
|
||||
android:theme="@style/Theme.Citra.Splash.Main"
|
||||
|
@ -9,10 +9,13 @@ import android.app.Application
|
||||
import android.app.NotificationChannel
|
||||
import android.app.NotificationManager
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import org.citra.citra_emu.utils.DirectoryInitialization
|
||||
import org.citra.citra_emu.utils.DocumentsTree
|
||||
import org.citra.citra_emu.utils.GpuDriverHelper
|
||||
import org.citra.citra_emu.utils.PermissionsHandler
|
||||
import org.citra.citra_emu.utils.Log
|
||||
import org.citra.citra_emu.utils.MemoryUtil
|
||||
|
||||
class CitraApplication : Application() {
|
||||
private fun createNotificationChannel() {
|
||||
@ -53,9 +56,20 @@ class CitraApplication : Application() {
|
||||
}
|
||||
|
||||
NativeLibrary.logDeviceInfo()
|
||||
logDeviceInfo()
|
||||
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 {
|
||||
private var application: CitraApplication? = null
|
||||
|
||||
|
@ -413,12 +413,12 @@ object NativeLibrary {
|
||||
}
|
||||
|
||||
fun setEmulationActivity(emulationActivity: EmulationActivity?) {
|
||||
Log.verbose("[NativeLibrary] Registering EmulationActivity.")
|
||||
Log.debug("[NativeLibrary] Registering EmulationActivity.")
|
||||
sEmulationActivity = WeakReference(emulationActivity)
|
||||
}
|
||||
|
||||
fun clearEmulationActivity() {
|
||||
Log.verbose("[NativeLibrary] Unregistering EmulationActivity.")
|
||||
Log.debug("[NativeLibrary] Unregistering EmulationActivity.")
|
||||
sEmulationActivity.clear()
|
||||
}
|
||||
|
||||
|
@ -94,14 +94,14 @@ object DirectoryInitialization {
|
||||
val dataPath = PermissionsHandler.citraDirectory
|
||||
if (dataPath.toString().isNotEmpty()) {
|
||||
userPath = dataPath.toString()
|
||||
Log.debug("[DirectoryInitialization] User Dir: $userPath")
|
||||
android.util.Log.d("[Citra Frontend]", "[DirectoryInitialization] User Dir: $userPath")
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
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 {
|
||||
if (!output.exists() || overwrite) {
|
||||
val inputStream = context.assets.open(asset)
|
||||
@ -121,7 +121,7 @@ object DirectoryInitialization {
|
||||
overwrite: Boolean,
|
||||
context: Context
|
||||
) {
|
||||
Log.verbose("[DirectoryInitialization] Copying Folder $assetFolder to $outputFolder")
|
||||
Log.debug("[DirectoryInitialization] Copying Folder $assetFolder to $outputFolder")
|
||||
try {
|
||||
var createdFolder = false
|
||||
for (file in context.assets.list(assetFolder)!!) {
|
||||
|
@ -4,34 +4,17 @@
|
||||
|
||||
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 {
|
||||
// Tracks whether we should share the old log or the current log
|
||||
var gameLaunched = false
|
||||
private const val TAG = "Citra Frontend"
|
||||
|
||||
fun verbose(message: String?) {
|
||||
if (BuildConfig.DEBUG) {
|
||||
Log.v(TAG, message!!)
|
||||
}
|
||||
}
|
||||
external fun debug(message: String)
|
||||
|
||||
fun debug(message: String?) {
|
||||
if (BuildConfig.DEBUG) {
|
||||
Log.d(TAG, message!!)
|
||||
}
|
||||
}
|
||||
external fun warning(message: String)
|
||||
|
||||
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.h
|
||||
system_save_game.cpp
|
||||
native_log.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(citra-android PRIVATE audio_core citra_common citra_core input_common network)
|
||||
|
@ -266,8 +266,6 @@ void Config::ReadValues() {
|
||||
}
|
||||
|
||||
// Web Service
|
||||
NetSettings::values.enable_telemetry =
|
||||
sdl2_config->GetBoolean("WebService", "enable_telemetry", false);
|
||||
NetSettings::values.web_api_url =
|
||||
sdl2_config->GetString("WebService", "web_api_url", "https://api.citra-emu.org");
|
||||
NetSettings::values.citra_username = sdl2_config->GetString("WebService", "citra_username", "");
|
||||
|
@ -353,9 +353,6 @@ gdbstub_port=24689
|
||||
# To LLE a service module add "LLE\<module name>=true"
|
||||
|
||||
[WebService]
|
||||
# Whether or not to enable telemetry
|
||||
# 0 (default): No, 1: Yes
|
||||
enable_telemetry =
|
||||
# URL for Web API
|
||||
web_api_url = https://api.citra-emu.org
|
||||
# Username and token for Citra Web Service
|
||||
|
@ -38,7 +38,6 @@
|
||||
#include "core/hle/service/nfc/nfc.h"
|
||||
#include "core/loader/loader.h"
|
||||
#include "core/savestate.h"
|
||||
#include "core/telemetry_session.h"
|
||||
#include "jni/android_common/android_common.h"
|
||||
#include "jni/applets/mii_selector.h"
|
||||
#include "jni/applets/swkbd.h"
|
||||
@ -207,9 +206,6 @@ static Core::System::ResultStatus RunCitra(const std::string& filepath) {
|
||||
return load_result;
|
||||
}
|
||||
|
||||
auto& telemetry_session = system.TelemetrySession();
|
||||
telemetry_session.AddField(Common::Telemetry::FieldType::App, "Frontend", "Android");
|
||||
|
||||
stop_run = false;
|
||||
pause_emulation = false;
|
||||
|
||||
|
30
src/android/app/src/main/jni/native_log.cpp
Normal file
30
src/android/app/src/main/jni/native_log.cpp
Normal file
@ -0,0 +1,30 @@
|
||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <common/logging/log.h>
|
||||
#include <jni.h>
|
||||
#include "android_common/android_common.h"
|
||||
|
||||
extern "C" {
|
||||
|
||||
void Java_org_citra_citra_1emu_utils_Log_debug(JNIEnv* env, jobject obj, jstring jmessage) {
|
||||
LOG_DEBUG(Frontend, "{}", GetJString(env, jmessage));
|
||||
}
|
||||
|
||||
void Java_org_citra_citra_1emu_utils_Log_warning(JNIEnv* env, jobject obj, jstring jmessage) {
|
||||
LOG_WARNING(Frontend, "{}", GetJString(env, jmessage));
|
||||
}
|
||||
|
||||
void Java_org_citra_citra_1emu_utils_Log_info(JNIEnv* env, jobject obj, jstring jmessage) {
|
||||
LOG_INFO(Frontend, "{}", GetJString(env, jmessage));
|
||||
}
|
||||
|
||||
void Java_org_citra_citra_1emu_utils_Log_error(JNIEnv* env, jobject obj, jstring jmessage) {
|
||||
LOG_ERROR(Frontend, "{}", GetJString(env, jmessage));
|
||||
}
|
||||
|
||||
void Java_org_citra_citra_1emu_utils_Log_critical(JNIEnv* env, jobject obj, jstring jmessage) {
|
||||
LOG_CRITICAL(Frontend, "{}", GetJString(env, jmessage));
|
||||
}
|
||||
|
||||
} // extern "C"
|
@ -442,6 +442,17 @@
|
||||
<string name="cia_install_error_encrypted">\"%s\" must be decrypted before being used with Citra.\n A real 3DS is required</string>
|
||||
<string name="cia_install_error_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 -->
|
||||
<string name="change_theme_mode">Change Theme Mode</string>
|
||||
<string name="theme_mode_follow_system">Follow System</string>
|
||||
|
7
src/android/app/src/main/res/xml/game_mode_config.xml
Normal file
7
src/android/app/src/main/res/xml/game_mode_config.xml
Normal file
@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<game-mode-config
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:supportsBatteryGameMode="true"
|
||||
android:supportsPerformanceGameMode="true"
|
||||
android:allowGameDownscaling="false"
|
||||
android:allowGameFpsOverride="false"/>
|
@ -4,10 +4,10 @@
|
||||
|
||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
plugins {
|
||||
id("com.android.application") version "8.1.2" apply false
|
||||
id("com.android.library") version "8.1.2" apply false
|
||||
id("org.jetbrains.kotlin.android") version "1.8.21" apply false
|
||||
id("org.jetbrains.kotlin.plugin.serialization") version "1.8.21"
|
||||
id("com.android.application") version "8.2.1" apply false
|
||||
id("com.android.library") version "8.2.1" apply false
|
||||
id("org.jetbrains.kotlin.android") version "1.9.22" apply false
|
||||
id("org.jetbrains.kotlin.plugin.serialization") version "1.9.22"
|
||||
}
|
||||
|
||||
tasks.register("clean").configure {
|
||||
@ -19,6 +19,6 @@ buildscript {
|
||||
google()
|
||||
}
|
||||
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
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
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
|
||||
u32_dsp buffer_position; ///< Number of samples into the current buffer
|
||||
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];
|
||||
|
@ -298,9 +298,9 @@ void Source::ParseConfig(SourceConfiguration::Configuration& config,
|
||||
b.buffer_id,
|
||||
state.mono_or_stereo,
|
||||
state.format,
|
||||
true,
|
||||
{}, // 0 in u32_dsp
|
||||
false,
|
||||
true, // from_queue
|
||||
0, // play_position
|
||||
false, // has_played
|
||||
});
|
||||
}
|
||||
LOG_TRACE(Audio_DSP, "enqueuing queued {} addr={:#010x} len={} id={}", i,
|
||||
@ -321,16 +321,19 @@ void Source::ParseConfig(SourceConfiguration::Configuration& config,
|
||||
void Source::GenerateFrame() {
|
||||
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.buffer_update = true;
|
||||
state.last_buffer_id = state.current_buffer_id;
|
||||
state.current_buffer_id = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
std::size_t frame_position = 0;
|
||||
|
||||
state.current_sample_number = state.next_sample_number;
|
||||
while (frame_position < current_frame.size()) {
|
||||
if (state.current_buffer.empty() && !DequeueBuffer()) {
|
||||
break;
|
||||
@ -357,7 +360,7 @@ void Source::GenerateFrame() {
|
||||
}
|
||||
// TODO(jroweboy): Keep track of frame_position independently so that it doesn't lose precision
|
||||
// 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);
|
||||
}
|
||||
@ -408,9 +411,9 @@ bool Source::DequeueBuffer() {
|
||||
|
||||
// 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.next_sample_number = state.current_sample_number;
|
||||
state.current_buffer_physical_address = buf.physical_address;
|
||||
state.current_buffer_id = buf.buffer_id;
|
||||
state.last_buffer_id = 0;
|
||||
state.buffer_update = buf.from_queue && !buf.has_played;
|
||||
|
||||
if (buf.is_looping) {
|
||||
@ -418,8 +421,17 @@ bool Source::DequeueBuffer() {
|
||||
state.input_queue.push(buf);
|
||||
}
|
||||
|
||||
LOG_TRACE(Audio_DSP, "source_id={} buffer_id={} from_queue={} current_buffer.size()={}",
|
||||
source_id, buf.buffer_id, buf.from_queue, state.current_buffer.size());
|
||||
// Because our interpolation consumes samples instead of using an index,
|
||||
// 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;
|
||||
}
|
||||
|
||||
@ -432,9 +444,10 @@ SourceStatus::Status Source::GetCurrentStatus() {
|
||||
ret.is_enabled = state.enabled;
|
||||
ret.current_buffer_id_dirty = state.buffer_update ? 1 : 0;
|
||||
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.buffer_position = state.current_sample_number;
|
||||
ret.current_buffer_id = state.current_buffer_id;
|
||||
ret.last_buffer_id = state.last_buffer_id;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
@ -87,8 +87,8 @@ private:
|
||||
Format format;
|
||||
|
||||
bool from_queue;
|
||||
u32_dsp play_position; // = 0;
|
||||
bool has_played; // = false;
|
||||
u32 play_position; // = 0;
|
||||
bool has_played; // = false;
|
||||
|
||||
private:
|
||||
template <class Archive>
|
||||
@ -136,14 +136,14 @@ private:
|
||||
// Current buffer
|
||||
|
||||
u32 current_sample_number = 0;
|
||||
u32 next_sample_number = 0;
|
||||
PAddr current_buffer_physical_address = 0;
|
||||
AudioInterp::StereoBuffer16 current_buffer = {};
|
||||
|
||||
// buffer_id state
|
||||
|
||||
bool buffer_update = false;
|
||||
u32 current_buffer_id = 0;
|
||||
u16 last_buffer_id = 0;
|
||||
u16 current_buffer_id = 0;
|
||||
|
||||
// Decoding state
|
||||
|
||||
@ -170,7 +170,6 @@ private:
|
||||
ar& mono_or_stereo;
|
||||
ar& format;
|
||||
ar& current_sample_number;
|
||||
ar& next_sample_number;
|
||||
ar& current_buffer_physical_address;
|
||||
ar& current_buffer;
|
||||
ar& buffer_update;
|
||||
|
@ -73,8 +73,9 @@ OpenALSink::OpenALSink(std::string device_name) : impl(std::make_unique<Impl>())
|
||||
|
||||
auto alBufferCallbackSOFT =
|
||||
reinterpret_cast<LPALBUFFERCALLBACKSOFT>(alGetProcAddress("alBufferCallbackSOFT"));
|
||||
alBufferCallbackSOFT(impl->buffer, AL_FORMAT_STEREO16, native_sample_rate, &Impl::Callback,
|
||||
impl.get());
|
||||
alBufferCallbackSOFT(impl->buffer, AL_FORMAT_STEREO16, native_sample_rate,
|
||||
reinterpret_cast<ALBUFFERCALLBACKTYPESOFT>(&Impl::Callback), impl.get());
|
||||
|
||||
if (alGetError() != AL_NO_ERROR) {
|
||||
LOG_CRITICAL(Audio_Sink, "alBufferCallbackSOFT failed: {}", alGetError());
|
||||
Close();
|
||||
|
@ -39,7 +39,6 @@
|
||||
#include "core/hle/service/am/am.h"
|
||||
#include "core/hle/service/cfg/cfg.h"
|
||||
#include "core/movie.h"
|
||||
#include "core/telemetry_session.h"
|
||||
#include "input_common/main.h"
|
||||
#include "network/network.h"
|
||||
#include "video_core/gpu.h"
|
||||
@ -436,8 +435,6 @@ int main(int argc, char** argv) {
|
||||
break;
|
||||
}
|
||||
|
||||
system.TelemetrySession().AddField(Common::Telemetry::FieldType::App, "Frontend", "SDL");
|
||||
|
||||
if (use_multiplayer) {
|
||||
if (auto member = Network::GetRoomMember().lock()) {
|
||||
member->BindOnChatMessageRecieved(OnMessageReceived);
|
||||
|
@ -328,8 +328,6 @@ void Config::ReadValues() {
|
||||
}
|
||||
|
||||
// Web Service
|
||||
NetSettings::values.enable_telemetry =
|
||||
sdl2_config->GetBoolean("WebService", "enable_telemetry", false);
|
||||
NetSettings::values.web_api_url =
|
||||
sdl2_config->GetString("WebService", "web_api_url", "https://api.citra-emu.org");
|
||||
NetSettings::values.citra_username = sdl2_config->GetString("WebService", "citra_username", "");
|
||||
|
@ -361,9 +361,6 @@ renderer_debug =
|
||||
# To LLE a service module add "LLE\<module name>=true"
|
||||
|
||||
[WebService]
|
||||
# Whether or not to enable telemetry
|
||||
# 0 (default): No, 1: Yes
|
||||
enable_telemetry =
|
||||
# URL for Web API
|
||||
web_api_url = https://api.citra-emu.org
|
||||
# Username and token for Citra Web Service
|
||||
|
@ -81,9 +81,6 @@ add_executable(citra-qt
|
||||
configuration/configure_ui.cpp
|
||||
configuration/configure_ui.h
|
||||
configuration/configure_ui.ui
|
||||
configuration/configure_web.cpp
|
||||
configuration/configure_web.h
|
||||
configuration/configure_web.ui
|
||||
configuration/configure_cheats.cpp
|
||||
configuration/configure_cheats.h
|
||||
configuration/configure_cheats.ui
|
||||
|
@ -7,14 +7,12 @@
|
||||
#include <QPushButton>
|
||||
#include <QtConcurrent/qtconcurrentrun.h>
|
||||
#include "citra_qt/compatdb.h"
|
||||
#include "common/telemetry.h"
|
||||
#include "core/core.h"
|
||||
#include "core/telemetry_session.h"
|
||||
#include "ui_compatdb.h"
|
||||
|
||||
CompatDB::CompatDB(Core::TelemetrySession& telemetry_session_, QWidget* parent)
|
||||
CompatDB::CompatDB(QWidget* parent)
|
||||
: QWizard(parent, Qt::WindowTitleHint | Qt::WindowCloseButtonHint | Qt::WindowSystemMenuHint),
|
||||
ui{std::make_unique<Ui::CompatDB>()}, telemetry_session{telemetry_session_} {
|
||||
ui{std::make_unique<Ui::CompatDB>()} {
|
||||
ui->setupUi(this);
|
||||
connect(ui->radioButton_Perfect, &QRadioButton::clicked, this, &CompatDB::EnableNext);
|
||||
connect(ui->radioButton_Great, &QRadioButton::clicked, this, &CompatDB::EnableNext);
|
||||
@ -52,15 +50,11 @@ void CompatDB::Submit() {
|
||||
case CompatDBPage::Final:
|
||||
back();
|
||||
LOG_DEBUG(Frontend, "Compatibility Rating: {}", compatibility->checkedId());
|
||||
telemetry_session.AddField(Common::Telemetry::FieldType::UserFeedback, "Compatibility",
|
||||
compatibility->checkedId());
|
||||
|
||||
button(NextButton)->setEnabled(false);
|
||||
button(NextButton)->setText(tr("Submitting"));
|
||||
button(CancelButton)->setVisible(false);
|
||||
|
||||
testcase_watcher.setFuture(
|
||||
QtConcurrent::run([this] { return telemetry_session.SubmitTestcase(); }));
|
||||
break;
|
||||
default:
|
||||
LOG_ERROR(Frontend, "Unexpected page: {}", currentId());
|
||||
|
@ -8,10 +8,6 @@
|
||||
#include <QFutureWatcher>
|
||||
#include <QWizard>
|
||||
|
||||
namespace Core {
|
||||
class TelemetrySession;
|
||||
}
|
||||
|
||||
namespace Ui {
|
||||
class CompatDB;
|
||||
}
|
||||
@ -20,7 +16,7 @@ class CompatDB : public QWizard {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit CompatDB(Core::TelemetrySession& telemetry_session_, QWidget* parent = nullptr);
|
||||
explicit CompatDB(QWidget* parent = nullptr);
|
||||
~CompatDB();
|
||||
|
||||
private:
|
||||
@ -31,6 +27,4 @@ private:
|
||||
void Submit();
|
||||
void OnTestcaseSubmitted();
|
||||
void EnableNext();
|
||||
|
||||
Core::TelemetrySession& telemetry_session;
|
||||
};
|
||||
|
@ -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
|
||||
// UISetting::values.shortcuts, which is alphabetically ordered.
|
||||
// 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("Audio Mute/Unmute"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+M"), 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 File"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+O"), Qt::WidgetWithChildrenShortcut}},
|
||||
{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("Restart Emulation"), QStringLiteral("Main Window"), {QStringLiteral("F6"), Qt::WindowShortcut}},
|
||||
{QStringLiteral("Rotate Screens Upright"), QStringLiteral("Main Window"), {QStringLiteral("F8"), Qt::WindowShortcut}},
|
||||
@ -558,6 +563,15 @@ void Config::ReadMultiplayerValues() {
|
||||
UISettings::values.game_id = ReadSetting(QStringLiteral("game_id"), 0).toULongLong();
|
||||
UISettings::values.room_description =
|
||||
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
|
||||
int size = qt_config->beginReadArray(QStringLiteral("username_ban_list"));
|
||||
UISettings::values.ban_list.first.resize(size);
|
||||
@ -823,8 +837,6 @@ void Config::ReadUpdaterValues() {
|
||||
void Config::ReadWebServiceValues() {
|
||||
qt_config->beginGroup(QStringLiteral("WebService"));
|
||||
|
||||
NetSettings::values.enable_telemetry =
|
||||
ReadSetting(QStringLiteral("enable_telemetry"), false).toBool();
|
||||
NetSettings::values.web_api_url =
|
||||
ReadSetting(QStringLiteral("web_api_url"), QStringLiteral("https://api.citra-emu.org"))
|
||||
.toString()
|
||||
@ -1076,6 +1088,15 @@ void Config::SaveMultiplayerValues() {
|
||||
WriteSetting(QStringLiteral("game_id"), UISettings::values.game_id, 0);
|
||||
WriteSetting(QStringLiteral("room_description"), UISettings::values.room_description,
|
||||
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
|
||||
qt_config->beginWriteArray(QStringLiteral("username_ban_list"));
|
||||
for (std::size_t i = 0; i < UISettings::values.ban_list.first.size(); ++i) {
|
||||
@ -1296,7 +1317,6 @@ void Config::SaveUpdaterValues() {
|
||||
void Config::SaveWebServiceValues() {
|
||||
qt_config->beginGroup(QStringLiteral("WebService"));
|
||||
|
||||
WriteSetting(QStringLiteral("enable_telemetry"), NetSettings::values.enable_telemetry, false);
|
||||
WriteSetting(QStringLiteral("web_api_url"),
|
||||
QString::fromStdString(NetSettings::values.web_api_url),
|
||||
QStringLiteral("https://api.citra-emu.org"));
|
||||
|
@ -26,7 +26,7 @@ public:
|
||||
|
||||
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<UISettings::Shortcut, 30> default_hotkeys;
|
||||
static const std::array<UISettings::Shortcut, 35> default_hotkeys;
|
||||
|
||||
private:
|
||||
void Initialize(const std::string& config_name);
|
||||
|
@ -97,12 +97,6 @@
|
||||
<header>configuration/configure_enhancements.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>ConfigureWeb</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>configuration/configure_web.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>ConfigureUi</class>
|
||||
<extends>QWidget</extends>
|
||||
|
@ -16,7 +16,6 @@
|
||||
#include "citra_qt/configuration/configure_storage.h"
|
||||
#include "citra_qt/configuration/configure_system.h"
|
||||
#include "citra_qt/configuration/configure_ui.h"
|
||||
#include "citra_qt/configuration/configure_web.h"
|
||||
#include "citra_qt/hotkeys.h"
|
||||
#include "common/settings.h"
|
||||
#include "core/core.h"
|
||||
@ -38,7 +37,7 @@ ConfigureDialog::ConfigureDialog(QWidget* parent, HotkeyRegistry& registry_, Cor
|
||||
camera_tab{std::make_unique<ConfigureCamera>(this)},
|
||||
debug_tab{std::make_unique<ConfigureDebug>(is_powered_on, this)},
|
||||
storage_tab{std::make_unique<ConfigureStorage>(is_powered_on, this)},
|
||||
web_tab{std::make_unique<ConfigureWeb>(this)}, ui_tab{std::make_unique<ConfigureUi>(this)} {
|
||||
ui_tab{std::make_unique<ConfigureUi>(this)} {
|
||||
Settings::SetConfiguringGlobal(true);
|
||||
|
||||
ui->setupUi(this);
|
||||
@ -53,11 +52,9 @@ ConfigureDialog::ConfigureDialog(QWidget* parent, HotkeyRegistry& registry_, Cor
|
||||
ui->tabWidget->addTab(camera_tab.get(), tr("Camera"));
|
||||
ui->tabWidget->addTab(debug_tab.get(), tr("Debug"));
|
||||
ui->tabWidget->addTab(storage_tab.get(), tr("Storage"));
|
||||
ui->tabWidget->addTab(web_tab.get(), tr("Web"));
|
||||
ui->tabWidget->addTab(ui_tab.get(), tr("UI"));
|
||||
|
||||
hotkeys_tab->Populate(registry);
|
||||
web_tab->SetWebServiceConfigEnabled(enable_web_config);
|
||||
|
||||
PopulateSelectionList();
|
||||
|
||||
@ -90,7 +87,6 @@ void ConfigureDialog::SetConfiguration() {
|
||||
audio_tab->SetConfiguration();
|
||||
camera_tab->SetConfiguration();
|
||||
debug_tab->SetConfiguration();
|
||||
web_tab->SetConfiguration();
|
||||
ui_tab->SetConfiguration();
|
||||
storage_tab->SetConfiguration();
|
||||
}
|
||||
@ -106,7 +102,6 @@ void ConfigureDialog::ApplyConfiguration() {
|
||||
audio_tab->ApplyConfiguration();
|
||||
camera_tab->ApplyConfiguration();
|
||||
debug_tab->ApplyConfiguration();
|
||||
web_tab->ApplyConfiguration();
|
||||
ui_tab->ApplyConfiguration();
|
||||
storage_tab->ApplyConfiguration();
|
||||
system.ApplySettings();
|
||||
@ -119,7 +114,7 @@ void ConfigureDialog::PopulateSelectionList() {
|
||||
ui->selectorList->clear();
|
||||
|
||||
const std::array<std::pair<QString, QList<QWidget*>>, 5> items{
|
||||
{{tr("General"), {general_tab.get(), web_tab.get(), debug_tab.get(), ui_tab.get()}},
|
||||
{{tr("General"), {general_tab.get(), debug_tab.get(), ui_tab.get()}},
|
||||
{tr("System"), {system_tab.get(), camera_tab.get(), storage_tab.get()}},
|
||||
{tr("Graphics"), {enhancements_tab.get(), graphics_tab.get()}},
|
||||
{tr("Audio"), {audio_tab.get()}},
|
||||
@ -159,7 +154,6 @@ void ConfigureDialog::RetranslateUI() {
|
||||
audio_tab->RetranslateUI();
|
||||
camera_tab->RetranslateUI();
|
||||
debug_tab->RetranslateUI();
|
||||
web_tab->RetranslateUI();
|
||||
ui_tab->RetranslateUI();
|
||||
storage_tab->RetranslateUI();
|
||||
}
|
||||
@ -179,7 +173,6 @@ void ConfigureDialog::UpdateVisibleTabs() {
|
||||
{camera_tab.get(), tr("Camera")},
|
||||
{debug_tab.get(), tr("Debug")},
|
||||
{storage_tab.get(), tr("Storage")},
|
||||
{web_tab.get(), tr("Web")},
|
||||
{ui_tab.get(), tr("UI")}};
|
||||
|
||||
ui->tabWidget->clear();
|
||||
|
@ -29,7 +29,6 @@ class ConfigureAudio;
|
||||
class ConfigureCamera;
|
||||
class ConfigureDebug;
|
||||
class ConfigureStorage;
|
||||
class ConfigureWeb;
|
||||
class ConfigureUi;
|
||||
|
||||
class ConfigureDialog : public QDialog {
|
||||
@ -70,6 +69,5 @@ private:
|
||||
std::unique_ptr<ConfigureCamera> camera_tab;
|
||||
std::unique_ptr<ConfigureDebug> debug_tab;
|
||||
std::unique_ptr<ConfigureStorage> storage_tab;
|
||||
std::unique_ptr<ConfigureWeb> web_tab;
|
||||
std::unique_ptr<ConfigureUi> ui_tab;
|
||||
};
|
||||
|
@ -3,12 +3,14 @@
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <cstring>
|
||||
#include <QFileDialog>
|
||||
#include <QFutureWatcher>
|
||||
#include <QMessageBox>
|
||||
#include <QProgressDialog>
|
||||
#include <QtConcurrent/QtConcurrentMap>
|
||||
#include "citra_qt/configuration/configuration_shared.h"
|
||||
#include "citra_qt/configuration/configure_system.h"
|
||||
#include "common/file_util.h"
|
||||
#include "common/settings.h"
|
||||
#include "core/core.h"
|
||||
#include "core/hle/service/am/am.h"
|
||||
@ -236,6 +238,32 @@ ConfigureSystem::ConfigureSystem(Core::System& system_, QWidget* parent)
|
||||
&ConfigureSystem::RefreshConsoleID);
|
||||
connect(ui->button_start_download, &QPushButton::clicked, this,
|
||||
&ConfigureSystem::DownloadFromNUS);
|
||||
|
||||
connect(ui->button_secure_info, &QPushButton::clicked, this, [this] {
|
||||
ui->button_secure_info->setEnabled(false);
|
||||
const QString file_path_qtstr = QFileDialog::getOpenFileName(
|
||||
this, tr("Select SecureInfo_A/B"), QString(),
|
||||
tr("SecureInfo_A/B (SecureInfo_A SecureInfo_B);;All Files (*.*)"));
|
||||
ui->button_secure_info->setEnabled(true);
|
||||
InstallSecureData(file_path_qtstr.toStdString(), cfg->GetSecureInfoAPath());
|
||||
});
|
||||
connect(ui->button_friend_code_seed, &QPushButton::clicked, this, [this] {
|
||||
ui->button_friend_code_seed->setEnabled(false);
|
||||
const QString file_path_qtstr =
|
||||
QFileDialog::getOpenFileName(this, tr("Select LocalFriendCodeSeed_A/B"), QString(),
|
||||
tr("LocalFriendCodeSeed_A/B (LocalFriendCodeSeed_A "
|
||||
"LocalFriendCodeSeed_B);;All Files (*.*)"));
|
||||
ui->button_friend_code_seed->setEnabled(true);
|
||||
InstallSecureData(file_path_qtstr.toStdString(), cfg->GetLocalFriendCodeSeedBPath());
|
||||
});
|
||||
connect(ui->button_ct_cert, &QPushButton::clicked, this, [this] {
|
||||
ui->button_ct_cert->setEnabled(false);
|
||||
const QString file_path_qtstr = QFileDialog::getOpenFileName(
|
||||
this, tr("Select CTCert"), QString(), tr("CTCert.bin (*.bin);;All Files (*.*)"));
|
||||
ui->button_ct_cert->setEnabled(true);
|
||||
InstallCTCert(file_path_qtstr.toStdString());
|
||||
});
|
||||
|
||||
for (u8 i = 0; i < country_names.size(); i++) {
|
||||
if (std::strcmp(country_names.at(i), "") != 0) {
|
||||
ui->combo_country->addItem(tr(country_names.at(i)), i);
|
||||
@ -304,6 +332,7 @@ void ConfigureSystem::SetConfiguration() {
|
||||
ReadSystemSettings();
|
||||
|
||||
ui->group_system_settings->setEnabled(enabled);
|
||||
ui->group_real_console_unique_data->setEnabled(enabled);
|
||||
if (enabled) {
|
||||
ui->label_disable_info->hide();
|
||||
}
|
||||
@ -354,6 +383,9 @@ void ConfigureSystem::ReadSystemSettings() {
|
||||
|
||||
// set firmware download region
|
||||
ui->combo_download_region->setCurrentIndex(static_cast<int>(cfg->GetRegionValue()));
|
||||
|
||||
// Refresh secure data status
|
||||
RefreshSecureDataStatus();
|
||||
}
|
||||
|
||||
void ConfigureSystem::ApplyConfiguration() {
|
||||
@ -522,6 +554,59 @@ void ConfigureSystem::RefreshConsoleID() {
|
||||
tr("Console ID: 0x%1").arg(QString::number(console_id, 16).toUpper()));
|
||||
}
|
||||
|
||||
void ConfigureSystem::InstallSecureData(const std::string& from_path, const std::string& to_path) {
|
||||
std::string from =
|
||||
FileUtil::SanitizePath(from_path, FileUtil::DirectorySeparator::PlatformDefault);
|
||||
std::string to = FileUtil::SanitizePath(to_path, FileUtil::DirectorySeparator::PlatformDefault);
|
||||
if (from.empty() || from == to) {
|
||||
return;
|
||||
}
|
||||
FileUtil::CreateFullPath(to_path);
|
||||
FileUtil::Copy(from, to);
|
||||
cfg->InvalidateSecureData();
|
||||
RefreshSecureDataStatus();
|
||||
}
|
||||
|
||||
void ConfigureSystem::InstallCTCert(const std::string& from_path) {
|
||||
std::string from =
|
||||
FileUtil::SanitizePath(from_path, FileUtil::DirectorySeparator::PlatformDefault);
|
||||
std::string to = FileUtil::SanitizePath(Service::AM::Module::GetCTCertPath(),
|
||||
FileUtil::DirectorySeparator::PlatformDefault);
|
||||
if (from.empty() || from == to) {
|
||||
return;
|
||||
}
|
||||
FileUtil::Copy(from, to);
|
||||
RefreshSecureDataStatus();
|
||||
}
|
||||
|
||||
void ConfigureSystem::RefreshSecureDataStatus() {
|
||||
auto status_to_str = [](Service::CFG::SecureDataLoadStatus status) {
|
||||
switch (status) {
|
||||
case Service::CFG::SecureDataLoadStatus::Loaded:
|
||||
return "Loaded";
|
||||
case Service::CFG::SecureDataLoadStatus::NotFound:
|
||||
return "Not Found";
|
||||
case Service::CFG::SecureDataLoadStatus::Invalid:
|
||||
return "Invalid";
|
||||
case Service::CFG::SecureDataLoadStatus::IOError:
|
||||
return "IO Error";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
};
|
||||
|
||||
Service::AM::CTCert ct_cert;
|
||||
|
||||
ui->label_secure_info_status->setText(
|
||||
tr((std::string("Status: ") + status_to_str(cfg->LoadSecureInfoAFile())).c_str()));
|
||||
ui->label_friend_code_seed_status->setText(
|
||||
tr((std::string("Status: ") + status_to_str(cfg->LoadLocalFriendCodeSeedBFile())).c_str()));
|
||||
ui->label_ct_cert_status->setText(
|
||||
tr((std::string("Status: ") + status_to_str(static_cast<Service::CFG::SecureDataLoadStatus>(
|
||||
Service::AM::Module::LoadCTCertFile(ct_cert))))
|
||||
.c_str()));
|
||||
}
|
||||
|
||||
void ConfigureSystem::RetranslateUI() {
|
||||
ui->retranslateUi(this);
|
||||
}
|
||||
|
@ -20,6 +20,12 @@ namespace Core {
|
||||
class System;
|
||||
}
|
||||
|
||||
namespace Service {
|
||||
namespace AM {
|
||||
class Module;
|
||||
} // namespace AM
|
||||
} // namespace Service
|
||||
|
||||
namespace Service {
|
||||
namespace CFG {
|
||||
class Module;
|
||||
@ -46,6 +52,10 @@ private:
|
||||
void UpdateInitTicks(int init_ticks_type);
|
||||
void RefreshConsoleID();
|
||||
|
||||
void InstallSecureData(const std::string& from_path, const std::string& to_path);
|
||||
void InstallCTCert(const std::string& from_path);
|
||||
void RefreshSecureDataStatus();
|
||||
|
||||
void SetupPerGameUI();
|
||||
|
||||
void DownloadFromNUS();
|
||||
|
@ -506,6 +506,123 @@
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="group_real_console_unique_data">
|
||||
<property name="title">
|
||||
<string>Real Console Unique Data</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout1">
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_secure_info">
|
||||
<property name="text">
|
||||
<string>SecureInfo_A/B</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QWidget" name="secure_info">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_secure_info">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_secure_info_status">
|
||||
<property name="text">
|
||||
<string></string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="button_secure_info">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="layoutDirection">
|
||||
<enum>Qt::RightToLeft</enum>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Choose</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_friend_code_seed">
|
||||
<property name="text">
|
||||
<string>LocalFriendCodeSeed_A/B</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QWidget" name="friend_code_seed">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_friend_code_seed">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_friend_code_seed_status">
|
||||
<property name="text">
|
||||
<string></string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="button_friend_code_seed">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="layoutDirection">
|
||||
<enum>Qt::RightToLeft</enum>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Choose</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="label_ct_cert">
|
||||
<property name="text">
|
||||
<string>CTCert</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QWidget" name="ct_cert">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_ct_cert">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_ct_cert_status">
|
||||
<property name="text">
|
||||
<string></string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="button_ct_cert">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="layoutDirection">
|
||||
<enum>Qt::RightToLeft</enum>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Choose</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_disable_info">
|
||||
<property name="text">
|
||||
|
@ -1,165 +0,0 @@
|
||||
// Copyright 2017 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <QIcon>
|
||||
#include <QMessageBox>
|
||||
#include <QtConcurrent/QtConcurrentRun>
|
||||
#include "citra_qt/configuration/configure_web.h"
|
||||
#include "citra_qt/uisettings.h"
|
||||
#include "core/telemetry_session.h"
|
||||
#include "network/network_settings.h"
|
||||
#include "ui_configure_web.h"
|
||||
|
||||
static constexpr char token_delimiter{':'};
|
||||
|
||||
static std::string GenerateDisplayToken(const std::string& username, const std::string& token) {
|
||||
if (username.empty() || token.empty()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const std::string unencoded_display_token{username + token_delimiter + token};
|
||||
QByteArray b{unencoded_display_token.c_str()};
|
||||
QByteArray b64 = b.toBase64();
|
||||
return b64.toStdString();
|
||||
}
|
||||
|
||||
static std::string UsernameFromDisplayToken(const std::string& display_token) {
|
||||
const std::string unencoded_display_token{
|
||||
QByteArray::fromBase64(display_token.c_str()).toStdString()};
|
||||
return unencoded_display_token.substr(0, unencoded_display_token.find(token_delimiter));
|
||||
}
|
||||
|
||||
static std::string TokenFromDisplayToken(const std::string& display_token) {
|
||||
const std::string unencoded_display_token{
|
||||
QByteArray::fromBase64(display_token.c_str()).toStdString()};
|
||||
return unencoded_display_token.substr(unencoded_display_token.find(token_delimiter) + 1);
|
||||
}
|
||||
|
||||
ConfigureWeb::ConfigureWeb(QWidget* parent)
|
||||
: QWidget(parent), ui(std::make_unique<Ui::ConfigureWeb>()) {
|
||||
ui->setupUi(this);
|
||||
connect(ui->button_regenerate_telemetry_id, &QPushButton::clicked, this,
|
||||
&ConfigureWeb::RefreshTelemetryID);
|
||||
connect(ui->button_verify_login, &QPushButton::clicked, this, &ConfigureWeb::VerifyLogin);
|
||||
connect(&verify_watcher, &QFutureWatcher<bool>::finished, this, &ConfigureWeb::OnLoginVerified);
|
||||
|
||||
#ifndef USE_DISCORD_PRESENCE
|
||||
ui->discord_group->setVisible(false);
|
||||
#endif
|
||||
SetConfiguration();
|
||||
}
|
||||
|
||||
ConfigureWeb::~ConfigureWeb() = default;
|
||||
|
||||
void ConfigureWeb::SetConfiguration() {
|
||||
ui->web_credentials_disclaimer->setWordWrap(true);
|
||||
ui->telemetry_learn_more->setOpenExternalLinks(true);
|
||||
ui->telemetry_learn_more->setText(tr("<a "
|
||||
"href='https://citra-emu.org/entry/"
|
||||
"telemetry-and-why-thats-a-good-thing/'><span "
|
||||
"style=\"text-decoration: underline; "
|
||||
"color:#039be5;\">Learn more</span></a>"));
|
||||
|
||||
ui->web_signup_link->setOpenExternalLinks(true);
|
||||
ui->web_signup_link->setText(
|
||||
tr("<a href='https://profile.citra-emu.org/'><span style=\"text-decoration: underline; "
|
||||
"color:#039be5;\">Sign up</span></a>"));
|
||||
ui->web_token_info_link->setOpenExternalLinks(true);
|
||||
ui->web_token_info_link->setText(
|
||||
tr("<a href='https://citra-emu.org/wiki/citra-web-service/'><span style=\"text-decoration: "
|
||||
"underline; color:#039be5;\">What is my token?</span></a>"));
|
||||
|
||||
ui->toggle_telemetry->setChecked(NetSettings::values.enable_telemetry);
|
||||
|
||||
if (NetSettings::values.citra_username.empty()) {
|
||||
ui->username->setText(tr("Unspecified"));
|
||||
} else {
|
||||
ui->username->setText(QString::fromStdString(NetSettings::values.citra_username));
|
||||
}
|
||||
|
||||
ui->edit_token->setText(QString::fromStdString(
|
||||
GenerateDisplayToken(NetSettings::values.citra_username, NetSettings::values.citra_token)));
|
||||
|
||||
// Connect after setting the values, to avoid calling OnLoginChanged now
|
||||
connect(ui->edit_token, &QLineEdit::textChanged, this, &ConfigureWeb::OnLoginChanged);
|
||||
ui->label_telemetry_id->setText(
|
||||
tr("Telemetry ID: 0x%1").arg(QString::number(Core::GetTelemetryId(), 16).toUpper()));
|
||||
user_verified = true;
|
||||
|
||||
ui->toggle_discordrpc->setChecked(UISettings::values.enable_discord_presence.GetValue());
|
||||
}
|
||||
|
||||
void ConfigureWeb::ApplyConfiguration() {
|
||||
NetSettings::values.enable_telemetry = ui->toggle_telemetry->isChecked();
|
||||
UISettings::values.enable_discord_presence = ui->toggle_discordrpc->isChecked();
|
||||
if (user_verified) {
|
||||
NetSettings::values.citra_username =
|
||||
UsernameFromDisplayToken(ui->edit_token->text().toStdString());
|
||||
NetSettings::values.citra_token =
|
||||
TokenFromDisplayToken(ui->edit_token->text().toStdString());
|
||||
} else {
|
||||
QMessageBox::warning(
|
||||
this, tr("Token not verified"),
|
||||
tr("Token was not verified. The change to your token has not been saved."));
|
||||
}
|
||||
}
|
||||
|
||||
void ConfigureWeb::RefreshTelemetryID() {
|
||||
const u64 new_telemetry_id{Core::RegenerateTelemetryId()};
|
||||
ui->label_telemetry_id->setText(
|
||||
tr("Telemetry ID: 0x%1").arg(QString::number(new_telemetry_id, 16).toUpper()));
|
||||
}
|
||||
|
||||
void ConfigureWeb::OnLoginChanged() {
|
||||
if (ui->edit_token->text().isEmpty()) {
|
||||
user_verified = true;
|
||||
|
||||
const QPixmap pixmap = QIcon::fromTheme(QStringLiteral("checked")).pixmap(16);
|
||||
ui->label_token_verified->setPixmap(pixmap);
|
||||
} else {
|
||||
user_verified = false;
|
||||
|
||||
const QPixmap pixmap = QIcon::fromTheme(QStringLiteral("failed")).pixmap(16);
|
||||
ui->label_token_verified->setPixmap(pixmap);
|
||||
}
|
||||
}
|
||||
|
||||
void ConfigureWeb::VerifyLogin() {
|
||||
ui->button_verify_login->setDisabled(true);
|
||||
ui->button_verify_login->setText(tr("Verifying..."));
|
||||
verify_watcher.setFuture(QtConcurrent::run(
|
||||
[username = UsernameFromDisplayToken(ui->edit_token->text().toStdString()),
|
||||
token = TokenFromDisplayToken(ui->edit_token->text().toStdString())] {
|
||||
return Core::VerifyLogin(username, token);
|
||||
}));
|
||||
}
|
||||
|
||||
void ConfigureWeb::OnLoginVerified() {
|
||||
ui->button_verify_login->setEnabled(true);
|
||||
ui->button_verify_login->setText(tr("Verify"));
|
||||
if (verify_watcher.result()) {
|
||||
user_verified = true;
|
||||
|
||||
const QPixmap pixmap = QIcon::fromTheme(QStringLiteral("checked")).pixmap(16);
|
||||
ui->label_token_verified->setPixmap(pixmap);
|
||||
ui->username->setText(
|
||||
QString::fromStdString(UsernameFromDisplayToken(ui->edit_token->text().toStdString())));
|
||||
} else {
|
||||
const QPixmap pixmap = QIcon::fromTheme(QStringLiteral("failed")).pixmap(16);
|
||||
ui->label_token_verified->setPixmap(pixmap);
|
||||
ui->username->setText(tr("Unspecified"));
|
||||
QMessageBox::critical(this, tr("Verification failed"),
|
||||
tr("Verification failed. Check that you have entered your token "
|
||||
"correctly, and that your internet connection is working."));
|
||||
}
|
||||
}
|
||||
|
||||
void ConfigureWeb::RetranslateUI() {
|
||||
ui->retranslateUi(this);
|
||||
}
|
||||
|
||||
void ConfigureWeb::SetWebServiceConfigEnabled(bool enabled) {
|
||||
ui->label_disable_info->setVisible(!enabled);
|
||||
ui->groupBoxWebConfig->setEnabled(enabled);
|
||||
}
|
@ -1,37 +0,0 @@
|
||||
// Copyright 2017 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <QFutureWatcher>
|
||||
#include <QWidget>
|
||||
|
||||
namespace Ui {
|
||||
class ConfigureWeb;
|
||||
}
|
||||
|
||||
class ConfigureWeb : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit ConfigureWeb(QWidget* parent = nullptr);
|
||||
~ConfigureWeb() override;
|
||||
|
||||
void ApplyConfiguration();
|
||||
void RetranslateUI();
|
||||
void SetConfiguration();
|
||||
void SetWebServiceConfigEnabled(bool enabled);
|
||||
|
||||
private:
|
||||
void RefreshTelemetryID();
|
||||
void OnLoginChanged();
|
||||
void VerifyLogin();
|
||||
void OnLoginVerified();
|
||||
|
||||
bool user_verified = true;
|
||||
QFutureWatcher<bool> verify_watcher;
|
||||
|
||||
std::unique_ptr<Ui::ConfigureWeb> ui;
|
||||
};
|
@ -1,214 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>ConfigureWeb</class>
|
||||
<widget class="QWidget" name="ConfigureWeb">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>996</width>
|
||||
<height>561</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Form</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBoxWebConfig">
|
||||
<property name="title">
|
||||
<string>Citra Web Service</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayoutCitraWebService">
|
||||
<item>
|
||||
<widget class="QLabel" name="web_credentials_disclaimer">
|
||||
<property name="text">
|
||||
<string>By providing your username and token, you agree to allow Citra to collect additional usage data, which may include user identifying information.</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QGridLayout" name="gridLayoutCitraUsername">
|
||||
<item row="2" column="3">
|
||||
<widget class="QPushButton" name="button_verify_login">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="layoutDirection">
|
||||
<enum>Qt::RightToLeft</enum>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Verify</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="web_signup_link">
|
||||
<property name="text">
|
||||
<string>Sign up</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1" colspan="3">
|
||||
<widget class="QLabel" name="username"/>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_token">
|
||||
<property name="text">
|
||||
<string>Token: </string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="4">
|
||||
<widget class="QLabel" name="label_token_verified"/>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_username">
|
||||
<property name="text">
|
||||
<string>Username: </string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1" colspan="3">
|
||||
<widget class="QLineEdit" name="edit_token">
|
||||
<property name="maxLength">
|
||||
<number>80</number>
|
||||
</property>
|
||||
<property name="echoMode">
|
||||
<enum>QLineEdit::Password</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QLabel" name="web_token_info_link">
|
||||
<property name="text">
|
||||
<string>What is my token?</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="2">
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_disable_info">
|
||||
<property name="text">
|
||||
<string>Web Service configuration can only be changed when a public room isn't being hosted.</string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox">
|
||||
<property name="title">
|
||||
<string>Telemetry</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<item>
|
||||
<widget class="QCheckBox" name="toggle_telemetry">
|
||||
<property name="text">
|
||||
<string>Share anonymous usage data with the Citra team</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="telemetry_learn_more">
|
||||
<property name="text">
|
||||
<string>Learn more</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QGridLayout" name="gridLayoutTelemetryId">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_telemetry_id">
|
||||
<property name="text">
|
||||
<string>Telemetry ID:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QPushButton" name="button_regenerate_telemetry_id">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="layoutDirection">
|
||||
<enum>Qt::RightToLeft</enum>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Regenerate</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="discord_group">
|
||||
<property name="title">
|
||||
<string>Discord Presence</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_21">
|
||||
<item>
|
||||
<widget class="QCheckBox" name="toggle_discordrpc">
|
||||
<property name="text">
|
||||
<string>Show Current Game in your Discord Status</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<tabstops>
|
||||
<tabstop>edit_token</tabstop>
|
||||
<tabstop>button_verify_login</tabstop>
|
||||
<tabstop>toggle_telemetry</tabstop>
|
||||
<tabstop>button_regenerate_telemetry_id</tabstop>
|
||||
<tabstop>toggle_discordrpc</tabstop>
|
||||
</tabstops>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
@ -25,7 +25,14 @@ void ToggleConsole() {
|
||||
#ifdef _WIN32
|
||||
FILE* temp;
|
||||
if (UISettings::values.show_console) {
|
||||
if (AllocConsole()) {
|
||||
BOOL alloc_console_res = AllocConsole();
|
||||
DWORD last_error = 0;
|
||||
if (!alloc_console_res) {
|
||||
last_error = GetLastError();
|
||||
}
|
||||
// If the windows debugger already opened a console, calling AllocConsole again
|
||||
// will cause ERROR_ACCESS_DENIED. If that's the case assume a console is open.
|
||||
if (alloc_console_res || last_error == ERROR_ACCESS_DENIED) {
|
||||
// The first parameter for freopen_s is a out parameter, so we can just ignore it
|
||||
freopen_s(&temp, "CONIN$", "r", stdin);
|
||||
freopen_s(&temp, "CONOUT$", "w", stdout);
|
||||
|
@ -15,7 +15,6 @@
|
||||
#include <QtGui>
|
||||
#include <QtWidgets>
|
||||
#include <fmt/format.h>
|
||||
#include "core/telemetry_session.h"
|
||||
#ifdef __APPLE__
|
||||
#include <unistd.h> // for chdir
|
||||
#endif
|
||||
@ -128,27 +127,6 @@ constexpr int default_mouse_timeout = 2500;
|
||||
* is a bitfield "callout_flags" options, used to track if a message has already been shown to the
|
||||
* user. This is 32-bits - if we have more than 32 callouts, we should retire and recycle old ones.
|
||||
*/
|
||||
enum class CalloutFlag : uint32_t {
|
||||
Telemetry = 0x1,
|
||||
};
|
||||
|
||||
void GMainWindow::ShowTelemetryCallout() {
|
||||
if (UISettings::values.callout_flags.GetValue() &
|
||||
static_cast<uint32_t>(CalloutFlag::Telemetry)) {
|
||||
return;
|
||||
}
|
||||
|
||||
UISettings::values.callout_flags =
|
||||
UISettings::values.callout_flags.GetValue() | static_cast<uint32_t>(CalloutFlag::Telemetry);
|
||||
const QString telemetry_message =
|
||||
tr("<a href='https://citra-emu.org/entry/telemetry-and-why-thats-a-good-thing/'>Anonymous "
|
||||
"data is collected</a> to help improve Citra. "
|
||||
"<br/><br/>Would you like to share your usage data with us?");
|
||||
if (QMessageBox::question(this, tr("Telemetry"), telemetry_message) == QMessageBox::Yes) {
|
||||
NetSettings::values.enable_telemetry = true;
|
||||
system.ApplySettings();
|
||||
}
|
||||
}
|
||||
|
||||
const int GMainWindow::max_recent_files_item;
|
||||
|
||||
@ -263,9 +241,6 @@ GMainWindow::GMainWindow(Core::System& system_)
|
||||
game_list->LoadCompatibilityList();
|
||||
game_list->PopulateAsync(UISettings::values.game_dirs);
|
||||
|
||||
// Show one-time "callout" messages to the user
|
||||
ShowTelemetryCallout();
|
||||
|
||||
mouse_hide_timer.setInterval(default_mouse_timeout);
|
||||
connect(&mouse_hide_timer, &QTimer::timeout, this, &GMainWindow::HideMouseCursor);
|
||||
connect(ui->menubar, &QMenuBar::hovered, this, &GMainWindow::OnMouseActivity);
|
||||
@ -647,6 +622,13 @@ void GMainWindow::InitializeHotkeys() {
|
||||
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_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) {
|
||||
// This action will fire specifically when secondary_window is in focus
|
||||
@ -1237,7 +1219,6 @@ bool GMainWindow::LoadROM(const QString& filename) {
|
||||
|
||||
game_path = filename;
|
||||
|
||||
system.TelemetrySession().AddField(Common::Telemetry::FieldType::App, "Frontend", "Qt");
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -1981,7 +1962,7 @@ void GMainWindow::OnLoadComplete() {
|
||||
|
||||
void GMainWindow::OnMenuReportCompatibility() {
|
||||
if (!NetSettings::values.citra_token.empty() && !NetSettings::values.citra_username.empty()) {
|
||||
CompatDB compatdb{system.TelemetrySession(), this};
|
||||
CompatDB compatdb{this};
|
||||
compatdb.exec();
|
||||
} else {
|
||||
QMessageBox::critical(this, tr("Missing Citra Account"),
|
||||
@ -3190,8 +3171,10 @@ int main(int argc, char* argv[]) {
|
||||
QApplication::setHighDpiScaleFactorRoundingPolicy(rounding_policy);
|
||||
|
||||
#ifdef __APPLE__
|
||||
std::string bin_path = FileUtil::GetBundleDirectory() + DIR_SEP + "..";
|
||||
chdir(bin_path.c_str());
|
||||
auto bundle_dir = FileUtil::GetBundleDirectory();
|
||||
if (bundle_dir) {
|
||||
FileUtil::SetCurrentDir(bundle_dir.value() + "..");
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_OPENGL
|
||||
|
@ -152,7 +152,6 @@ private:
|
||||
void BootGame(const QString& filename);
|
||||
void ShutdownGame();
|
||||
|
||||
void ShowTelemetryCallout();
|
||||
void SetDiscordEnabled(bool state);
|
||||
void LoadAmiibo(const QString& filename);
|
||||
|
||||
|
@ -80,9 +80,8 @@ void DirectConnectWindow::Connect() {
|
||||
// Store settings
|
||||
UISettings::values.nickname = ui->nickname->text();
|
||||
UISettings::values.ip = ui->ip->text();
|
||||
UISettings::values.port = (ui->port->isModified() && !ui->port->text().isEmpty())
|
||||
? ui->port->text()
|
||||
: UISettings::values.port;
|
||||
UISettings::values.port =
|
||||
!ui->port->text().isEmpty() ? ui->port->text() : UISettings::values.port;
|
||||
|
||||
// attempt to connect in a different thread
|
||||
QFuture<void> f = QtConcurrent::run([&] {
|
||||
|
@ -63,10 +63,10 @@ Lobby::Lobby(Core::System& system_, QWidget* parent, QStandardItemModel* list,
|
||||
|
||||
// UI Buttons
|
||||
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->hide_empty, &QCheckBox::toggled, proxy, &LobbyFilterProxyModel::SetFilterEmpty);
|
||||
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::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,
|
||||
&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
|
||||
// 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
|
||||
@ -180,6 +186,10 @@ void Lobby::OnJoinRoom(const QModelIndex& source) {
|
||||
UISettings::values.nickname = ui->nickname->text();
|
||||
UISettings::values.ip = proxy->data(connection_index, LobbyItemHost::HostIPRole).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() {
|
||||
|
@ -188,12 +188,37 @@ public:
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
auto members = data(MemberListRole).toList();
|
||||
return QStringLiteral("%1 / %2").arg(QString::number(members.size()),
|
||||
data(MaxPlayerRole).toString());
|
||||
}
|
||||
|
||||
bool operator<(const QStandardItem& other) const override {
|
||||
|
@ -138,6 +138,11 @@ struct Values {
|
||||
QString room_description;
|
||||
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
|
||||
Settings::Setting<bool> show_console{false, "showConsole"};
|
||||
};
|
||||
|
@ -22,7 +22,8 @@ QString GetOpenGLRenderer() {
|
||||
QOpenGLContext context;
|
||||
if (context.create()) {
|
||||
context.makeCurrent(&surface);
|
||||
return QString::fromUtf8(context.functions()->glGetString(GL_RENDERER));
|
||||
return QString::fromUtf8(
|
||||
reinterpret_cast<const char*>(context.functions()->glGetString(GL_RENDERER)));
|
||||
} else {
|
||||
return QStringLiteral("");
|
||||
}
|
||||
|
@ -130,8 +130,6 @@ add_library(citra_common STATIC
|
||||
string_util.cpp
|
||||
string_util.h
|
||||
swap.h
|
||||
telemetry.cpp
|
||||
telemetry.h
|
||||
texture.cpp
|
||||
texture.h
|
||||
thread.cpp
|
||||
|
@ -20,7 +20,7 @@ inline bool IsWithin128M(uintptr_t ref, 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>
|
||||
|
@ -13,7 +13,6 @@
|
||||
#endif
|
||||
|
||||
// The user data dir
|
||||
#define ROOT_DIR "."
|
||||
#define USERDATA_DIR "user"
|
||||
#ifdef USER_DIR
|
||||
#define EMU_DATA_DIR USER_DIR
|
||||
|
@ -12,10 +12,10 @@ namespace Common {
|
||||
/**
|
||||
* A background manager which ensures that all detached task is finished before program exits.
|
||||
*
|
||||
* Some tasks, telemetry submission for example, prefer executing asynchronously and don't care
|
||||
* about the result. These tasks are suitable for std::thread::detach(). However, this is unsafe if
|
||||
* the task is launched just before the program exits (which is a common case for telemetry), so we
|
||||
* need to block on these tasks on program exit.
|
||||
* Some tasks prefer executing asynchronously and don't care
|
||||
* about the result. These tasks are suitable for std::thread::detach().
|
||||
* However, this is unsafe if the task is launched just before the program exits
|
||||
* so we need to block on these tasks on program exit.
|
||||
*
|
||||
* To make detached task safe, a single DetachedTasks object should be placed in the main(), and
|
||||
* call WaitForAllTasks() after all program execution but before global/static variable destruction.
|
||||
|
@ -634,6 +634,10 @@ std::optional<std::string> GetCurrentDir() {
|
||||
std::string strDir = dir;
|
||||
#endif
|
||||
free(dir);
|
||||
|
||||
if (!strDir.ends_with(DIR_SEP)) {
|
||||
strDir += DIR_SEP;
|
||||
}
|
||||
return strDir;
|
||||
} // namespace FileUtil
|
||||
|
||||
@ -646,17 +650,36 @@ bool SetCurrentDir(const std::string& directory) {
|
||||
}
|
||||
|
||||
#if defined(__APPLE__)
|
||||
std::string GetBundleDirectory() {
|
||||
CFURLRef BundleRef;
|
||||
char AppBundlePath[MAXPATHLEN];
|
||||
std::optional<std::string> GetBundleDirectory() {
|
||||
// Get the main bundle for the app
|
||||
BundleRef = CFBundleCopyBundleURL(CFBundleGetMainBundle());
|
||||
CFStringRef BundlePath = CFURLCopyFileSystemPath(BundleRef, kCFURLPOSIXPathStyle);
|
||||
CFStringGetFileSystemRepresentation(BundlePath, AppBundlePath, sizeof(AppBundlePath));
|
||||
CFRelease(BundleRef);
|
||||
CFRelease(BundlePath);
|
||||
CFBundleRef bundle_ref = CFBundleGetMainBundle();
|
||||
if (!bundle_ref) {
|
||||
return {};
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
@ -732,22 +755,6 @@ static const std::string& GetHomeDirectory() {
|
||||
}
|
||||
#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 {
|
||||
std::unordered_map<UserPath, std::string> g_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::CacheDir, user_path + CACHE_DIR DIR_SEP);
|
||||
#else
|
||||
if (FileUtil::Exists(ROOT_DIR DIR_SEP USERDATA_DIR)) {
|
||||
user_path = ROOT_DIR DIR_SEP USERDATA_DIR DIR_SEP;
|
||||
auto current_dir = FileUtil::GetCurrentDir();
|
||||
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::CacheDir, user_path + CACHE_DIR DIR_SEP);
|
||||
} else {
|
||||
|
@ -193,11 +193,8 @@ void SetCurrentRomPath(const std::string& path);
|
||||
// Update the Global Path with the new value
|
||||
void UpdateUserPath(UserPath path, const std::string& filename);
|
||||
|
||||
// Returns the path to where the sys file are
|
||||
[[nodiscard]] std::string GetSysDirectory();
|
||||
|
||||
#ifdef __APPLE__
|
||||
[[nodiscard]] std::string GetBundleDirectory();
|
||||
[[nodiscard]] std::optional<std::string> GetBundleDirectory();
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
|
@ -475,7 +475,7 @@ void SetColorConsoleBackendEnabled(bool enabled) {
|
||||
}
|
||||
|
||||
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) {
|
||||
if (!initialization_in_progress_suppress_logging) {
|
||||
Impl::Instance().PushEntry(log_class, log_level, filename, line_num, function,
|
||||
|
@ -24,12 +24,12 @@ constexpr const char* TrimSourcePath(std::string_view source) {
|
||||
|
||||
/// Logs a message to the global logger, using fmt
|
||||
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);
|
||||
|
||||
template <typename... Args>
|
||||
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,
|
||||
fmt::make_format_args(args...));
|
||||
}
|
||||
|
@ -1,92 +0,0 @@
|
||||
// Copyright 2017 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include "common/arch.h"
|
||||
#include "common/assert.h"
|
||||
#include "common/scm_rev.h"
|
||||
#include "common/telemetry.h"
|
||||
|
||||
#if CITRA_ARCH(x86_64)
|
||||
#include "common/x64/cpu_detect.h"
|
||||
#endif
|
||||
|
||||
namespace Common::Telemetry {
|
||||
|
||||
void FieldCollection::Accept(VisitorInterface& visitor) const {
|
||||
for (const auto& field : fields) {
|
||||
field.second->Accept(visitor);
|
||||
}
|
||||
}
|
||||
|
||||
void FieldCollection::AddField(std::unique_ptr<FieldInterface> field) {
|
||||
fields[field->GetName()] = std::move(field);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
void Field<T>::Accept(VisitorInterface& visitor) const {
|
||||
visitor.Visit(*this);
|
||||
}
|
||||
|
||||
template class Field<bool>;
|
||||
template class Field<double>;
|
||||
template class Field<float>;
|
||||
template class Field<u8>;
|
||||
template class Field<u16>;
|
||||
template class Field<u32>;
|
||||
template class Field<u64>;
|
||||
template class Field<s8>;
|
||||
template class Field<s16>;
|
||||
template class Field<s32>;
|
||||
template class Field<s64>;
|
||||
template class Field<std::string>;
|
||||
template class Field<const char*>;
|
||||
template class Field<std::chrono::microseconds>;
|
||||
|
||||
void AppendBuildInfo(FieldCollection& fc) {
|
||||
const bool is_git_dirty{std::strstr(Common::g_scm_desc, "dirty") != nullptr};
|
||||
fc.AddField(FieldType::App, "Git_IsDirty", is_git_dirty);
|
||||
fc.AddField(FieldType::App, "Git_Branch", Common::g_scm_branch);
|
||||
fc.AddField(FieldType::App, "Git_Revision", Common::g_scm_rev);
|
||||
fc.AddField(FieldType::App, "BuildDate", Common::g_build_date);
|
||||
fc.AddField(FieldType::App, "BuildName", Common::g_build_name);
|
||||
}
|
||||
|
||||
void AppendCPUInfo(FieldCollection& fc) {
|
||||
#if CITRA_ARCH(x86_64)
|
||||
fc.AddField(FieldType::UserSystem, "CPU_Model", Common::GetCPUCaps().cpu_string);
|
||||
fc.AddField(FieldType::UserSystem, "CPU_BrandString", Common::GetCPUCaps().brand_string);
|
||||
fc.AddField(FieldType::UserSystem, "CPU_Extension_x64_AES", Common::GetCPUCaps().aes);
|
||||
fc.AddField(FieldType::UserSystem, "CPU_Extension_x64_AVX", Common::GetCPUCaps().avx);
|
||||
fc.AddField(FieldType::UserSystem, "CPU_Extension_x64_AVX2", Common::GetCPUCaps().avx2);
|
||||
fc.AddField(FieldType::UserSystem, "CPU_Extension_x64_AVX512", Common::GetCPUCaps().avx512);
|
||||
fc.AddField(FieldType::UserSystem, "CPU_Extension_x64_BMI1", Common::GetCPUCaps().bmi1);
|
||||
fc.AddField(FieldType::UserSystem, "CPU_Extension_x64_BMI2", Common::GetCPUCaps().bmi2);
|
||||
fc.AddField(FieldType::UserSystem, "CPU_Extension_x64_FMA", Common::GetCPUCaps().fma);
|
||||
fc.AddField(FieldType::UserSystem, "CPU_Extension_x64_FMA4", Common::GetCPUCaps().fma4);
|
||||
fc.AddField(FieldType::UserSystem, "CPU_Extension_x64_SSE", Common::GetCPUCaps().sse);
|
||||
fc.AddField(FieldType::UserSystem, "CPU_Extension_x64_SSE2", Common::GetCPUCaps().sse2);
|
||||
fc.AddField(FieldType::UserSystem, "CPU_Extension_x64_SSE3", Common::GetCPUCaps().sse3);
|
||||
fc.AddField(FieldType::UserSystem, "CPU_Extension_x64_SSSE3", Common::GetCPUCaps().ssse3);
|
||||
fc.AddField(FieldType::UserSystem, "CPU_Extension_x64_SSE41", Common::GetCPUCaps().sse4_1);
|
||||
fc.AddField(FieldType::UserSystem, "CPU_Extension_x64_SSE42", Common::GetCPUCaps().sse4_2);
|
||||
#else
|
||||
fc.AddField(FieldType::UserSystem, "CPU_Model", "Other");
|
||||
#endif
|
||||
}
|
||||
|
||||
void AppendOSInfo(FieldCollection& fc) {
|
||||
#ifdef __APPLE__
|
||||
fc.AddField(FieldType::UserSystem, "OsPlatform", "Apple");
|
||||
#elif defined(_WIN32)
|
||||
fc.AddField(FieldType::UserSystem, "OsPlatform", "Windows");
|
||||
#elif defined(__linux__) || defined(linux) || defined(__linux)
|
||||
fc.AddField(FieldType::UserSystem, "OsPlatform", "Linux");
|
||||
#else
|
||||
fc.AddField(FieldType::UserSystem, "OsPlatform", "Unknown");
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace Common::Telemetry
|
@ -1,199 +0,0 @@
|
||||
// Copyright 2017 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <chrono>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace Common::Telemetry {
|
||||
|
||||
/// Field type, used for grouping fields together in the final submitted telemetry log
|
||||
enum class FieldType : u8 {
|
||||
None = 0, ///< No specified field group
|
||||
App, ///< Citra application fields (e.g. version, branch, etc.)
|
||||
Session, ///< Emulated session fields (e.g. title ID, log, etc.)
|
||||
Performance, ///< Emulated performance (e.g. fps, emulated CPU speed, etc.)
|
||||
UserFeedback, ///< User submitted feedback (e.g. star rating, user notes, etc.)
|
||||
UserConfig, ///< User configuration fields (e.g. emulated CPU core, renderer, etc.)
|
||||
UserSystem, ///< User system information (e.g. host CPU type, RAM, etc.)
|
||||
};
|
||||
|
||||
struct VisitorInterface;
|
||||
|
||||
/**
|
||||
* Interface class for telemetry data fields.
|
||||
*/
|
||||
class FieldInterface : NonCopyable {
|
||||
public:
|
||||
virtual ~FieldInterface() = default;
|
||||
|
||||
/**
|
||||
* Accept method for the visitor pattern.
|
||||
* @param visitor Reference to the visitor that will visit this field.
|
||||
*/
|
||||
virtual void Accept(VisitorInterface& visitor) const = 0;
|
||||
|
||||
/**
|
||||
* Gets the name of this field.
|
||||
* @returns Name of this field as a string.
|
||||
*/
|
||||
virtual const std::string& GetName() const = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* Represents a telemetry data field, i.e. a unit of data that gets logged and submitted to our
|
||||
* telemetry web service.
|
||||
*/
|
||||
template <typename T>
|
||||
class Field : public FieldInterface {
|
||||
public:
|
||||
Field(FieldType type, std::string name, T value)
|
||||
: name(std::move(name)), type(type), value(std::move(value)) {}
|
||||
|
||||
Field(const Field&) = default;
|
||||
Field& operator=(const Field&) = default;
|
||||
|
||||
Field(Field&&) = default;
|
||||
Field& operator=(Field&& other) = default;
|
||||
|
||||
void Accept(VisitorInterface& visitor) const override;
|
||||
|
||||
[[nodiscard]] const std::string& GetName() const override {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the type of the field.
|
||||
*/
|
||||
[[nodiscard]] FieldType GetType() const {
|
||||
return type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value of the field.
|
||||
*/
|
||||
[[nodiscard]] const T& GetValue() const {
|
||||
return value;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool operator==(const Field& other) const {
|
||||
return (type == other.type) && (name == other.name) && (value == other.value);
|
||||
}
|
||||
|
||||
[[nodiscard]] bool operator!=(const Field& other) const {
|
||||
return !operator==(other);
|
||||
}
|
||||
|
||||
private:
|
||||
std::string name; ///< Field name, must be unique
|
||||
FieldType type{}; ///< Field type, used for grouping fields together
|
||||
T value; ///< Field value
|
||||
};
|
||||
|
||||
/**
|
||||
* Collection of data fields that have been logged.
|
||||
*/
|
||||
class FieldCollection final : NonCopyable {
|
||||
public:
|
||||
FieldCollection() = default;
|
||||
|
||||
/**
|
||||
* Accept method for the visitor pattern, visits each field in the collection.
|
||||
* @param visitor Reference to the visitor that will visit each field.
|
||||
*/
|
||||
void Accept(VisitorInterface& visitor) const;
|
||||
|
||||
/**
|
||||
* Creates a new field and adds it to the field collection.
|
||||
* @param type Type of the field to add.
|
||||
* @param name Name of the field to add.
|
||||
* @param value Value for the field to add.
|
||||
*/
|
||||
template <typename T>
|
||||
void AddField(FieldType type, const char* name, T value) {
|
||||
return AddField(std::make_unique<Field<T>>(type, name, std::move(value)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new field to the field collection.
|
||||
* @param field Field to add to the field collection.
|
||||
*/
|
||||
void AddField(std::unique_ptr<FieldInterface> field);
|
||||
|
||||
private:
|
||||
std::map<std::string, std::unique_ptr<FieldInterface>> fields;
|
||||
};
|
||||
|
||||
/**
|
||||
* Telemetry fields visitor interface class. A backend to log to a web service should implement
|
||||
* this interface.
|
||||
*/
|
||||
struct VisitorInterface : NonCopyable {
|
||||
virtual ~VisitorInterface() = default;
|
||||
|
||||
virtual void Visit(const Field<bool>& field) = 0;
|
||||
virtual void Visit(const Field<double>& field) = 0;
|
||||
virtual void Visit(const Field<float>& field) = 0;
|
||||
virtual void Visit(const Field<u8>& field) = 0;
|
||||
virtual void Visit(const Field<u16>& field) = 0;
|
||||
virtual void Visit(const Field<u32>& field) = 0;
|
||||
virtual void Visit(const Field<u64>& field) = 0;
|
||||
virtual void Visit(const Field<s8>& field) = 0;
|
||||
virtual void Visit(const Field<s16>& field) = 0;
|
||||
virtual void Visit(const Field<s32>& field) = 0;
|
||||
virtual void Visit(const Field<s64>& field) = 0;
|
||||
virtual void Visit(const Field<std::string>& field) = 0;
|
||||
virtual void Visit(const Field<const char*>& field) = 0;
|
||||
virtual void Visit(const Field<std::chrono::microseconds>& field) = 0;
|
||||
|
||||
/// Completion method, called once all fields have been visited
|
||||
virtual void Complete() = 0;
|
||||
virtual bool SubmitTestcase() = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* Empty implementation of VisitorInterface that drops all fields. Used when a functional
|
||||
* backend implementation is not available.
|
||||
*/
|
||||
struct NullVisitor : public VisitorInterface {
|
||||
~NullVisitor() = default;
|
||||
|
||||
void Visit(const Field<bool>& /*field*/) override {}
|
||||
void Visit(const Field<double>& /*field*/) override {}
|
||||
void Visit(const Field<float>& /*field*/) override {}
|
||||
void Visit(const Field<u8>& /*field*/) override {}
|
||||
void Visit(const Field<u16>& /*field*/) override {}
|
||||
void Visit(const Field<u32>& /*field*/) override {}
|
||||
void Visit(const Field<u64>& /*field*/) override {}
|
||||
void Visit(const Field<s8>& /*field*/) override {}
|
||||
void Visit(const Field<s16>& /*field*/) override {}
|
||||
void Visit(const Field<s32>& /*field*/) override {}
|
||||
void Visit(const Field<s64>& /*field*/) override {}
|
||||
void Visit(const Field<std::string>& /*field*/) override {}
|
||||
void Visit(const Field<const char*>& /*field*/) override {}
|
||||
void Visit(const Field<std::chrono::microseconds>& /*field*/) override {}
|
||||
|
||||
void Complete() override {}
|
||||
bool SubmitTestcase() override {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
/// Appends build-specific information to the given FieldCollection,
|
||||
/// such as branch name, revision hash, etc.
|
||||
void AppendBuildInfo(FieldCollection& fc);
|
||||
|
||||
/// Appends CPU-specific information to the given FieldCollection,
|
||||
/// such as instruction set extensions, etc.
|
||||
void AppendCPUInfo(FieldCollection& fc);
|
||||
|
||||
/// Appends OS-specific information to the given FieldCollection,
|
||||
/// such as platform name, etc.
|
||||
void AppendOSInfo(FieldCollection& fc);
|
||||
|
||||
} // namespace Common::Telemetry
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user