forked from suyu/suyu
Compare commits
2 commits
dev
...
niansa-app
Author | SHA1 | Date | |
---|---|---|---|
|
6febb82917 | ||
|
b76ad05a53 |
79 changed files with 1369 additions and 828 deletions
|
@ -1,6 +1,6 @@
|
|||
#!/bin/bash -ex
|
||||
# SPDX-FileCopyrightText: 2019 yuzu Emulator Project
|
||||
# SPDX-FileCopyrightText: 2024 suyu Emulator Project
|
||||
|
||||
# SPDX-FileCopyrightText: 2019 yuzu Emulator Project & 2024 suyu Emulator Project
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
# Exit on error, rather than continuing with the rest of the script.
|
||||
|
@ -12,8 +12,6 @@ mkdir build || true && cd build
|
|||
cmake .. \
|
||||
-DBoost_USE_STATIC_LIBS=ON \
|
||||
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
|
||||
-DSUYU_USE_PRECOMPILED_HEADERS=OFF \
|
||||
-DDYNARMIC_USE_PRECOMPILED_HEADERS=OFF \
|
||||
-DCMAKE_CXX_FLAGS="-march=x86-64-v2" \
|
||||
-DCMAKE_CXX_COMPILER=/usr/lib/ccache/g++ \
|
||||
-DCMAKE_C_COMPILER=/usr/lib/ccache/gcc \
|
||||
|
@ -26,7 +24,6 @@ cmake .. \
|
|||
-DSUYU_USE_BUNDLED_FFMPEG=ON \
|
||||
-DSUYU_ENABLE_LTO=ON \
|
||||
-DSUYU_CRASH_DUMPS=ON \
|
||||
-DSUYU_USE_FASTER_LD=ON \
|
||||
-GNinja
|
||||
|
||||
ninja
|
||||
|
|
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -4,7 +4,6 @@
|
|||
# Build directory
|
||||
[Bb]uild*/
|
||||
doc-build/
|
||||
cmake-build*/
|
||||
|
||||
# Generated source files
|
||||
src/common/scm_rev.cpp
|
||||
|
@ -38,5 +37,3 @@ CMakeSettings.json
|
|||
# Windows global filetypes
|
||||
Thumbs.db
|
||||
|
||||
# Local Gitlab CI Runner
|
||||
.gitlab-ci-local/
|
||||
|
|
68
.gitlab-ci.yml
Executable file → Normal file
68
.gitlab-ci.yml
Executable file → Normal file
|
@ -2,82 +2,26 @@ stages:
|
|||
- format
|
||||
- build
|
||||
|
||||
variables:
|
||||
# https://docs.gitlab.com/ee/ci/runners/configure_runners.html
|
||||
TRANSFER_METER_FREQUENCY: "2s"
|
||||
ARTIFACT_COMPRESSION_LEVEL: "fast"
|
||||
CACHE_COMPRESSION_LEVEL: "fastest"
|
||||
CACHE_REQUEST_TIMEOUT: 5
|
||||
# Use FASTZIP for faster compression in cache and artifacts (boolean)
|
||||
# https://docs.gitlab.com/runner/configuration/feature-flags.html#available-feature-flags
|
||||
FF_USE_FASTZIP: 1
|
||||
|
||||
# Our Variables
|
||||
CACHE_DIR: "$CI_PROJECT_DIR/ccache"
|
||||
CCACHE_DIR: $CACHE_DIR
|
||||
|
||||
|
||||
#CLANG FORMAT - CHECKS CODE FOR FORMATTING ISSUES
|
||||
clang-format:
|
||||
stage: format
|
||||
image: suyuemu/cibuild:clangformat
|
||||
#THIS HAS TO BE FALSE. IT KEEPS RESOURCES AVAILABLE - EG RUNNERS WONT TRY BUILDING IF CODEBASE IS WRONG
|
||||
#MR's NEED TO BE CORRECTLY CLANG FORMATTED
|
||||
allow_failure: false
|
||||
image: suyuemu/cibuild:linux-x64
|
||||
allow_failure: true
|
||||
variables:
|
||||
RELEASE_NAME: mainline
|
||||
script:
|
||||
- git submodule update --init --depth 1 --recursive
|
||||
- bash .ci/scripts/format/script.sh
|
||||
tags:
|
||||
# - Linux
|
||||
# - Windows
|
||||
- Parallelized
|
||||
- Format
|
||||
|
||||
#LINUX BUILD - BUILDS LINUX APPIMAGE
|
||||
build-linux:
|
||||
stage: build
|
||||
image: suyuemu/cibuild:linux-x64
|
||||
resource_group: linux-ci
|
||||
cache:
|
||||
key: "$CI_COMMIT_REF_NAME-ccache"
|
||||
paths:
|
||||
- $CACHE_DIR
|
||||
before_script:
|
||||
- mkdir -p $CACHE_DIR
|
||||
- chmod -R 777 $CACHE_DIR
|
||||
- ls -la $CACHE_DIR
|
||||
variables:
|
||||
GIT_SUBMODULE_STRATEGY: recursive
|
||||
GIT_SUBMODULE_DEPTH: 1
|
||||
RELEASE_NAME: mainline
|
||||
RELEASE_NAME: mainline
|
||||
script:
|
||||
- git submodule update --init --depth 1 --recursive
|
||||
- bash .ci/scripts/linux/docker.sh
|
||||
- bash .ci/scripts/linux/upload.sh
|
||||
artifacts:
|
||||
paths:
|
||||
- artifacts/*
|
||||
tags:
|
||||
- Linux
|
||||
- Parallelized
|
||||
|
||||
#ANDROID BUILD - BUILDS APK
|
||||
android:
|
||||
stage: build
|
||||
image: suyuemu/cibuild:android-x64
|
||||
script:
|
||||
- apt-get update -y
|
||||
- git submodule update --init --recursive
|
||||
- cd externals/vcpkg
|
||||
- git fetch --unshallow || true
|
||||
- cd ../..
|
||||
- export ANDROID_HOME="/usr/lib/android-sdk/"
|
||||
- echo y | sdkmanager --sdk_root=/usr/lib/android-sdk --licenses
|
||||
- bash ./.ci/scripts/android/build.sh
|
||||
- bash ./.ci/scripts/android/upload.sh
|
||||
artifacts:
|
||||
paths:
|
||||
- artifacts/*
|
||||
tags:
|
||||
- Android
|
||||
- Parallelized
|
||||
|
||||
|
|
7
.gitmodules
vendored
7
.gitmodules
vendored
|
@ -7,9 +7,6 @@
|
|||
[submodule "cubeb"]
|
||||
path = externals/cubeb
|
||||
url = https://github.com/mozilla/cubeb.git
|
||||
[submodule "dynarmic"]
|
||||
path = externals/dynarmic
|
||||
url = https://gitlab.com/suyu-emu/dynarmic.git
|
||||
[submodule "libusb"]
|
||||
path = externals/libusb/libusb
|
||||
url = https://github.com/libusb/libusb.git
|
||||
|
@ -67,3 +64,7 @@
|
|||
[submodule "Vulkan-Utility-Libraries"]
|
||||
path = externals/Vulkan-Utility-Libraries
|
||||
url = https://github.com/KhronosGroup/Vulkan-Utility-Libraries.git
|
||||
[submodule "externals/dynarmic"]
|
||||
path = externals/dynarmic
|
||||
url = https://gitlab.com/suyu-emu/dynarmic.git
|
||||
branch = stable
|
||||
|
|
|
@ -3,9 +3,6 @@
|
|||
|
||||
cmake_minimum_required(VERSION 3.22)
|
||||
|
||||
set(CMAKE_XCODE_GENERATE_TOP_LEVEL_PROJECT OFF)
|
||||
set(CMAKE_XCODE_EMIT_RELATIVE_PATH YES)
|
||||
|
||||
project(suyu)
|
||||
|
||||
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/CMakeModules")
|
||||
|
@ -73,8 +70,6 @@ CMAKE_DEPENDENT_OPTION(SUYU_USE_FASTER_LD "Check if a faster linker is available
|
|||
|
||||
CMAKE_DEPENDENT_OPTION(USE_SYSTEM_MOLTENVK "Use the system MoltenVK lib (instead of the bundled one)" OFF "APPLE" OFF)
|
||||
|
||||
option(USE_CCACHE "Use CCache for faster building" ON)
|
||||
|
||||
set(DEFAULT_ENABLE_OPENSSL ON)
|
||||
if (ANDROID OR WIN32 OR APPLE)
|
||||
# - Windows defaults to the Schannel backend.
|
||||
|
@ -738,17 +733,3 @@ if(ENABLE_QT AND UNIX AND NOT APPLE)
|
|||
install(FILES "dist/org.suyu_emu.suyu.metainfo.xml"
|
||||
DESTINATION "share/metainfo")
|
||||
endif()
|
||||
|
||||
# Enable CCACHE. Linux only for now.
|
||||
if (USE_CCACHE)
|
||||
find_program(CCACHE_PROGRAM ccache)
|
||||
if(CCACHE_PROGRAM)
|
||||
set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE "${CCACHE_PROGRAM}")
|
||||
if (SUYU_USE_PRECOMPILED_HEADERS)
|
||||
message(NOTICE "To make use of CCache, set SUYU_USE_PRECOMPILED_HEADERS to OFF or else, it will barely be functional")
|
||||
endif()
|
||||
message(STATUS "CCache found.")
|
||||
else()
|
||||
message(STATUS "CCache has not been found and it will not be used. Install CCache or set USE_CCACHE to OFF")
|
||||
endif()
|
||||
endif()
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# SPDX-FileCopyrightText: 2017 yuzu Emulator Project & 2024 suyu Emulator Project
|
||||
# SPDX-FileCopyrightText: 2017 yuzu Emulator Project
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
# This function downloads a binary library package from our external repo.
|
||||
|
@ -17,9 +17,6 @@ if (WIN32)
|
|||
elseif (${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
|
||||
set(package_repo "ext-linux-bin/-/raw/main/")
|
||||
set(package_extension ".tar.xz")
|
||||
# elseif (APPLE)
|
||||
# set(package_repo "ext-osx-bin/-/raw/main/")
|
||||
# set(package_extension ".dmg")
|
||||
elseif (ANDROID)
|
||||
set(package_repo "ext-android-bin/-/raw/main/")
|
||||
set(package_extension ".tar.xz")
|
||||
|
|
|
@ -5,5 +5,5 @@ SPDX-License-Identifier: GPL-2.0-or-later
|
|||
|
||||
Please check out the
|
||||
|
||||
* [Contributors's guide](https://gitlab.com/suyu-emu/suyu/-/wikis/Contributing).
|
||||
* [Conributors's guide](https://gitlab.com/suyu-emu/suyu/-/wikis/Contributing).
|
||||
* [Merge request guidelines](https://gitlab.com/suyu-emu/suyu/-/wikis/Merge-requests)
|
||||
|
|
12
README.md
12
README.md
|
@ -18,9 +18,9 @@ This repo is based on Yuzu EA 4176.
|
|||
<br>
|
||||
</h1>
|
||||
|
||||
<h4 align="center"><b>suyu</b> is the continuation of the world's most popular, open-source, Nintendo Switch emulator, yuzu.
|
||||
<h4 align="center"><b>suyu</b>, pronounced "sue-you" (wink wink) is the continuation of the world's most popular, open-source, Nintendo Switch emulator, yuzu.
|
||||
<br>
|
||||
It is written in C++ with portability in mind, and we're actively working on builds for Windows, Linux and Android.
|
||||
It is written in C++ with portability in mind, and we actively maintain builds for Windows, Linux and Android.
|
||||
</h4>
|
||||
|
||||
<p align="center">
|
||||
|
@ -51,12 +51,10 @@ You can also contact any of the developers on Discord to learn more about the cu
|
|||
|
||||
## Downloads
|
||||
|
||||
* __Windows__: [Releases](https://gitlab.com/suyu-emu/suyu/-/releases)
|
||||
* __Linux__: [Releases](https://gitlab.com/suyu-emu/suyu/-/releases)
|
||||
* __macOS__: [Releases](https://gitlab.com/suyu-emu/suyu/-/releases)
|
||||
* __Android__: [Releases](https://gitlab.com/suyu-emu/suyu/-/releases)
|
||||
* __Windows__: WIP
|
||||
* __Linux__: WIP
|
||||
|
||||
We have official builds [here.](https://gitlab.com/suyu-emu/suyu/-/releases) If any website or person is claiming to have a build for suyu, take that with a grain of salt.
|
||||
We don't have any official builds yet! If any website or person is claiming to have a build for suyu, take that with a grain of salt, because it might contain malware. Until we do have an official build, it might be a better idea to keep using the last version of yuzu.
|
||||
|
||||
## Building
|
||||
|
||||
|
|
84
dist/icns_generator.sh
vendored
84
dist/icns_generator.sh
vendored
|
@ -1,72 +1,14 @@
|
|||
#!/bin/bash
|
||||
# icns_generator.sh GNU GPLv3 License
|
||||
# Run this script when a new logo is made and the svg file inside.
|
||||
# You should install Imagemagick to make the conversions: $brew install imagemagick
|
||||
mkdir suyu.iconset
|
||||
convert -background none -resize 16x16 suyu.svg suyu.iconset/icon_16x16.png;
|
||||
convert -background none -resize 32x32 suyu.svg suyu.iconset/icon_16x16@2x.png;
|
||||
convert -background none -resize 32x32 suyu.svg suyu.iconset/icon_32x32.png;
|
||||
convert -background none -resize 64x64 suyu.svg suyu.iconset/icon_32x32@2x.png;
|
||||
convert -background none -resize 128x128 suyu.svg suyu.iconset/icon_128x128.png;
|
||||
convert -background none -resize 256x256 suyu.svg suyu.iconset/icon_256x256.png;
|
||||
convert -background none -resize 256x256 suyu.svg suyu.iconset/icon_128x128@2x.png;
|
||||
convert -background none -resize 512x512 suyu.svg suyu.iconset/icon_256x256@2x.png;
|
||||
convert -background none -resize 512x512 suyu.svg suyu.iconset/icon_512x512.png;
|
||||
convert -background none -resize 1024x1024 suyu.svg suyu.iconset/icon_512x512@2x.png;
|
||||
|
||||
# Change working dir to where this script is located.
|
||||
cd "${0%/*}"
|
||||
|
||||
if [ -z $1 ]; then
|
||||
echo "icns_generator.sh GNU GPLv3 License"
|
||||
echo "Run this script when a new logo is made and the svg file inside."
|
||||
echo ""
|
||||
echo "Syntax: ./icns_generator <input.svg>"
|
||||
echo ""
|
||||
echo "Don't forget to install imagemagick: "
|
||||
echo "$ brew install imagemagick"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Error Handling Stuff:
|
||||
## Check command availability
|
||||
check_command() {
|
||||
if ! command -v "$1" &> /dev/null; then
|
||||
read -s -n 1 -p "Error: '$1' command not found. Please install $2."
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
## Convert image with error handling
|
||||
convert_image() {
|
||||
convert -background none -resize "$2" "$1" "$3" || {
|
||||
read -s -n 1 -p "Error: Conversion failed for $1"
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
|
||||
# Check required commands
|
||||
check_command "convert" "ImageMagick"
|
||||
check_command "iconutil" "macOS"
|
||||
|
||||
# Create the iconset directory
|
||||
mkdir suyu.iconset || {
|
||||
read -s -n 1 -p "Error: Unable to create suyu.iconset directory."
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Convert images
|
||||
convert_image "$1" 16x16 suyu.iconset/icon_16x16.png
|
||||
convert_image "$1" 32x32 suyu.iconset/icon_16x16@2x.png
|
||||
convert_image "$1" 32x32 suyu.iconset/icon_32x32.png
|
||||
convert_image "$1" 64x64 suyu.iconset/icon_32x32@2x.png
|
||||
convert_image "$1" 128x128 suyu.iconset/icon_128x128.png
|
||||
convert_image "$1" 256x256 suyu.iconset/icon_256x256.png
|
||||
convert_image "$1" 256x256 suyu.iconset/icon_128x128@2x.png
|
||||
convert_image "$1" 512x512 suyu.iconset/icon_256x256@2x.png
|
||||
convert_image "$1" 512x512 suyu.iconset/icon_512x512.png
|
||||
convert_image "$1" 1024x1024 suyu.iconset/icon_512x512@2x.png
|
||||
|
||||
# Create the ICNS file
|
||||
iconutil -c icns suyu.iconset || {
|
||||
read -s -n 1 -p "Error: Failed to create ICNS file."
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Remove the temporary iconset directory
|
||||
rm -rf suyu.iconset || {
|
||||
read -s -n 1 -p "Error: Unable to remove suyu.iconset directory."
|
||||
exit 1
|
||||
}
|
||||
|
||||
echo -s -n 1 -p "Icon generation completed successfully."
|
||||
echo ""
|
||||
iconutil -c icns suyu.iconset
|
||||
rm -rf suyu.iconset
|
||||
|
|
BIN
dist/suyu.icns
vendored
BIN
dist/suyu.icns
vendored
Binary file not shown.
2
externals/dynarmic
vendored
2
externals/dynarmic
vendored
|
@ -1 +1 @@
|
|||
Subproject commit ba8192d89078af51ae6f97c9352e3683612cdff1
|
||||
Subproject commit f35a122a4654d9f7edc32e0b44ad09f17819ae6d
|
2
externals/xbyak
vendored
2
externals/xbyak
vendored
|
@ -1 +1 @@
|
|||
Subproject commit 9c0f5d3ecb06d2c93c2b59becb9b3b763213e74e
|
||||
Subproject commit a1ac3750f9a639b5a6c6d6c7da4259b8d6790989
|
|
@ -72,12 +72,6 @@ class AppletLauncherFragment : Fragment() {
|
|||
R.string.mii_edit_applet_description,
|
||||
R.drawable.ic_mii,
|
||||
AppletInfo.MiiEdit
|
||||
),
|
||||
Applet(
|
||||
R.string.qlaunch_applet,
|
||||
R.string.qlaunch_description,
|
||||
R.drawable.ic_home,
|
||||
AppletInfo.QLaunch
|
||||
)
|
||||
)
|
||||
|
||||
|
|
|
@ -20,7 +20,7 @@ enum class AppletInfo(val appletId: Int, val entryId: Long = 0) {
|
|||
None(0x00),
|
||||
Application(0x01),
|
||||
OverlayDisplay(0x02),
|
||||
QLaunch(0x03, 0x0100000000001000),
|
||||
QLaunch(0x03),
|
||||
Starter(0x04),
|
||||
Auth(0x0A),
|
||||
Cabinet(0x0B, 0x0100000000001002),
|
||||
|
|
|
@ -1,10 +0,0 @@
|
|||
<vector android:alpha="1"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="?attr/colorControlNormal"
|
||||
android:pathData="M21.59,11.31 L12.41,2.9a0.55,0.55 0,0 0,-0.75 0L2.47,11.31a0.54,0.54 0,0 0,0.38 0.93H4.41a0.35,0.35 0,0 1,0.35 0.35V20.32a0.54,0.54 0,0 0,0.54 0.54H18.77a0.54,0.54 0,0 0,0.54 -0.54V12.58a0.35,0.35 0,0 1,0.35 -0.35H21.21A0.54,0.54 0,0 0,21.59 11.31ZM15,16.65a0.43,0.43 0,0 1,-0.43 0.43H9.5a0.43,0.43 0,0 1,-0.43 -0.43V12.66a0.43,0.43 0,0 1,0.43 -0.43H14.56a0.43,0.43 0,0 1,0.43 0.43Z"
|
||||
/>
|
||||
</vector>
|
|
@ -145,8 +145,6 @@
|
|||
<string name="keys_missing_help">https://suyu-emu.org/help/quickstart/#dumping-decryption-keys</string>
|
||||
|
||||
<!-- Applet launcher strings -->
|
||||
<string name="qlaunch_applet">Qlaunch</string>
|
||||
<string name="qlaunch_description">Launch applications from the system home screen</string>
|
||||
<string name="applets">Applet launcher</string>
|
||||
<string name="applets_description">Launch system applets using installed firmware</string>
|
||||
<string name="applets_error_firmware">Firmware not installed</string>
|
||||
|
|
|
@ -136,6 +136,8 @@ add_library(common STATIC
|
|||
string_util.cpp
|
||||
string_util.h
|
||||
swap.h
|
||||
telemetry.cpp
|
||||
telemetry.h
|
||||
thread.cpp
|
||||
thread.h
|
||||
thread_queue_list.h
|
||||
|
@ -252,8 +254,14 @@ if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
|
|||
)
|
||||
endif()
|
||||
|
||||
function (target_link_static_library TARGET MODE LIBRARY)
|
||||
set(CMAKE_FIND_LIBRARY_SUFFIXES ${CMAKE_STATIC_LIBRARY_SUFFIX})
|
||||
target_link_libraries(${TARGET} ${MODE} ${LIBRARY})
|
||||
endfunction()
|
||||
|
||||
target_link_libraries(common PUBLIC Boost::context Boost::headers fmt::fmt microprofile stb::headers Threads::Threads)
|
||||
target_link_libraries(common PRIVATE lz4::lz4 zstd::zstd LLVM::Demangle)
|
||||
target_link_libraries(common PRIVATE lz4::lz4 zstd::zstd)
|
||||
target_link_static_library(common PRIVATE LLVM::Demangle)
|
||||
|
||||
if (ANDROID)
|
||||
# For ASharedMemory_create
|
||||
|
|
|
@ -530,9 +530,9 @@ struct Values {
|
|||
Setting<bool> mouse_enabled{linkage, false, "mouse_enabled", Category::Controls};
|
||||
|
||||
Setting<u8, true> mouse_panning_x_sensitivity{
|
||||
linkage, 50, 1, 200, "mouse_panning_x_sensitivity", Category::Controls};
|
||||
linkage, 50, 1, 100, "mouse_panning_x_sensitivity", Category::Controls};
|
||||
Setting<u8, true> mouse_panning_y_sensitivity{
|
||||
linkage, 50, 1, 200, "mouse_panning_y_sensitivity", Category::Controls};
|
||||
linkage, 50, 1, 100, "mouse_panning_y_sensitivity", Category::Controls};
|
||||
Setting<u8, true> mouse_panning_deadzone_counterweight{
|
||||
linkage, 20, 0, 100, "mouse_panning_deadzone_counterweight", Category::Controls};
|
||||
Setting<u8, true> mouse_panning_decay_strength{
|
||||
|
@ -611,7 +611,8 @@ struct Values {
|
|||
Category::Network};
|
||||
|
||||
// WebService
|
||||
Setting<std::string> web_api_url{linkage, "https://suyu.dev", "web_api_url",
|
||||
Setting<bool> enable_telemetry{linkage, false, "enable_telemetry", Category::WebService};
|
||||
Setting<std::string> web_api_url{linkage, "http://74.113.97.71:3000", "web_api_url",
|
||||
Category::WebService};
|
||||
Setting<std::string> suyu_username{linkage, std::string(), "suyu_username",
|
||||
Category::WebService};
|
||||
|
|
119
src/common/telemetry.cpp
Normal file
119
src/common/telemetry.cpp
Normal file
|
@ -0,0 +1,119 @@
|
|||
// SPDX-FileCopyrightText: 2017 Citra Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include "common/scm_rev.h"
|
||||
#include "common/telemetry.h"
|
||||
|
||||
#ifdef ARCHITECTURE_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) {
|
||||
#ifdef ARCHITECTURE_x86_64
|
||||
|
||||
const auto& caps = Common::GetCPUCaps();
|
||||
const auto add_field = [&fc](std::string_view field_name, const auto& field_value) {
|
||||
fc.AddField(FieldType::UserSystem, field_name, field_value);
|
||||
};
|
||||
add_field("CPU_Model", caps.cpu_string);
|
||||
add_field("CPU_BrandString", caps.brand_string);
|
||||
|
||||
add_field("CPU_Extension_x64_SSE", caps.sse);
|
||||
add_field("CPU_Extension_x64_SSE2", caps.sse2);
|
||||
add_field("CPU_Extension_x64_SSE3", caps.sse3);
|
||||
add_field("CPU_Extension_x64_SSSE3", caps.ssse3);
|
||||
add_field("CPU_Extension_x64_SSE41", caps.sse4_1);
|
||||
add_field("CPU_Extension_x64_SSE42", caps.sse4_2);
|
||||
|
||||
add_field("CPU_Extension_x64_AVX", caps.avx);
|
||||
add_field("CPU_Extension_x64_AVX_VNNI", caps.avx_vnni);
|
||||
add_field("CPU_Extension_x64_AVX2", caps.avx2);
|
||||
|
||||
// Skylake-X/SP level AVX512, for compatibility with the previous telemetry field
|
||||
add_field("CPU_Extension_x64_AVX512",
|
||||
caps.avx512f && caps.avx512cd && caps.avx512vl && caps.avx512dq && caps.avx512bw);
|
||||
|
||||
add_field("CPU_Extension_x64_AVX512F", caps.avx512f);
|
||||
add_field("CPU_Extension_x64_AVX512CD", caps.avx512cd);
|
||||
add_field("CPU_Extension_x64_AVX512VL", caps.avx512vl);
|
||||
add_field("CPU_Extension_x64_AVX512DQ", caps.avx512dq);
|
||||
add_field("CPU_Extension_x64_AVX512BW", caps.avx512bw);
|
||||
add_field("CPU_Extension_x64_AVX512BITALG", caps.avx512bitalg);
|
||||
add_field("CPU_Extension_x64_AVX512VBMI", caps.avx512vbmi);
|
||||
|
||||
add_field("CPU_Extension_x64_AES", caps.aes);
|
||||
add_field("CPU_Extension_x64_BMI1", caps.bmi1);
|
||||
add_field("CPU_Extension_x64_BMI2", caps.bmi2);
|
||||
add_field("CPU_Extension_x64_F16C", caps.f16c);
|
||||
add_field("CPU_Extension_x64_FMA", caps.fma);
|
||||
add_field("CPU_Extension_x64_FMA4", caps.fma4);
|
||||
add_field("CPU_Extension_x64_GFNI", caps.gfni);
|
||||
add_field("CPU_Extension_x64_INVARIANT_TSC", caps.invariant_tsc);
|
||||
add_field("CPU_Extension_x64_LZCNT", caps.lzcnt);
|
||||
add_field("CPU_Extension_x64_MONITORX", caps.monitorx);
|
||||
add_field("CPU_Extension_x64_MOVBE", caps.movbe);
|
||||
add_field("CPU_Extension_x64_PCLMULQDQ", caps.pclmulqdq);
|
||||
add_field("CPU_Extension_x64_POPCNT", caps.popcnt);
|
||||
add_field("CPU_Extension_x64_SHA", caps.sha);
|
||||
add_field("CPU_Extension_x64_WAITPKG", caps.waitpkg);
|
||||
#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
|
209
src/common/telemetry.h
Normal file
209
src/common/telemetry.h
Normal file
|
@ -0,0 +1,209 @@
|
|||
// SPDX-FileCopyrightText: 2017 Citra Emulator Project & 2024 suyu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <chrono>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include "common/common_funcs.h"
|
||||
#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, ///< suyu 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 {
|
||||
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:
|
||||
SUYU_NON_COPYABLE(Field);
|
||||
|
||||
Field(FieldType type_, std::string_view name_, T value_)
|
||||
: name(name_), type(type_), value(std::move(value_)) {}
|
||||
|
||||
~Field() override = default;
|
||||
|
||||
Field(Field&&) noexcept = default;
|
||||
Field& operator=(Field&& other) noexcept = 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 {
|
||||
public:
|
||||
SUYU_NON_COPYABLE(FieldCollection);
|
||||
|
||||
FieldCollection() = default;
|
||||
~FieldCollection() = default;
|
||||
|
||||
FieldCollection(FieldCollection&&) noexcept = default;
|
||||
FieldCollection& operator=(FieldCollection&&) noexcept = 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, std::string_view 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 {
|
||||
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 final : public VisitorInterface {
|
||||
SUYU_NON_COPYABLE(NullVisitor);
|
||||
|
||||
NullVisitor() = default;
|
||||
~NullVisitor() override = 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
|
|
@ -1136,6 +1136,8 @@ add_library(core STATIC
|
|||
precompiled_headers.h
|
||||
reporter.cpp
|
||||
reporter.h
|
||||
telemetry_session.cpp
|
||||
telemetry_session.h
|
||||
tools/freezer.cpp
|
||||
tools/freezer.h
|
||||
tools/renderdoc.cpp
|
||||
|
|
|
@ -11,8 +11,6 @@
|
|||
#include <dynarmic/frontend/A64/decoder/a64.h>
|
||||
#include <dynarmic/frontend/imm.h>
|
||||
|
||||
#include "common/common_types.h"
|
||||
|
||||
#pragma GCC diagnostic pop
|
||||
|
||||
namespace Core {
|
||||
|
|
|
@ -55,6 +55,7 @@
|
|||
#include "core/memory/cheat_engine.h"
|
||||
#include "core/perf_stats.h"
|
||||
#include "core/reporter.h"
|
||||
#include "core/telemetry_session.h"
|
||||
#include "core/tools/freezer.h"
|
||||
#include "core/tools/renderdoc.h"
|
||||
#include "hid_core/hid_core.h"
|
||||
|
@ -271,6 +272,8 @@ struct System::Impl {
|
|||
}
|
||||
|
||||
SystemResultStatus SetupForApplicationProcess(System& system, Frontend::EmuWindow& emu_window) {
|
||||
telemetry_session = std::make_unique<Core::TelemetrySession>();
|
||||
|
||||
host1x_core = std::make_unique<Tegra::Host1x::Host1x>(system);
|
||||
gpu_core = VideoCore::CreateGPU(emu_window, system);
|
||||
if (!gpu_core) {
|
||||
|
@ -351,6 +354,8 @@ struct System::Impl {
|
|||
return init_result;
|
||||
}
|
||||
|
||||
telemetry_session->AddInitialInfo(*app_loader, fs_controller, *content_provider);
|
||||
|
||||
// Initialize cheat engine
|
||||
if (cheat_engine) {
|
||||
cheat_engine->Initialize();
|
||||
|
@ -396,6 +401,21 @@ struct System::Impl {
|
|||
void ShutdownMainProcess() {
|
||||
SetShuttingDown(true);
|
||||
|
||||
// Log last frame performance stats if game was loaded
|
||||
if (perf_stats) {
|
||||
const auto perf_results = GetAndResetPerfStats();
|
||||
constexpr auto performance = Common::Telemetry::FieldType::Performance;
|
||||
|
||||
telemetry_session->AddField(performance, "Shutdown_EmulationSpeed",
|
||||
perf_results.emulation_speed * 100.0);
|
||||
telemetry_session->AddField(performance, "Shutdown_Framerate",
|
||||
perf_results.average_game_fps);
|
||||
telemetry_session->AddField(performance, "Shutdown_Frametime",
|
||||
perf_results.frametime * 1000.0);
|
||||
telemetry_session->AddField(performance, "Mean_Frametime_MS",
|
||||
perf_stats->GetMeanFrametime());
|
||||
}
|
||||
|
||||
is_powered_on = false;
|
||||
exit_locked = false;
|
||||
exit_requested = false;
|
||||
|
@ -414,6 +434,7 @@ struct System::Impl {
|
|||
service_manager.reset();
|
||||
fs_controller.Reset();
|
||||
cheat_engine.reset();
|
||||
telemetry_session.reset();
|
||||
core_timing.ClearPendingEvents();
|
||||
app_loader.reset();
|
||||
audio_core.reset();
|
||||
|
@ -513,6 +534,9 @@ struct System::Impl {
|
|||
/// Services
|
||||
std::unique_ptr<Service::Services> services;
|
||||
|
||||
/// Telemetry session for this emulation session
|
||||
std::unique_ptr<Core::TelemetrySession> telemetry_session;
|
||||
|
||||
/// Network instance
|
||||
Network::NetworkInstance network_instance;
|
||||
|
||||
|
@ -639,6 +663,14 @@ PerfStatsResults System::GetAndResetPerfStats() {
|
|||
return impl->GetAndResetPerfStats();
|
||||
}
|
||||
|
||||
TelemetrySession& System::TelemetrySession() {
|
||||
return *impl->telemetry_session;
|
||||
}
|
||||
|
||||
const TelemetrySession& System::TelemetrySession() const {
|
||||
return *impl->telemetry_session;
|
||||
}
|
||||
|
||||
Kernel::PhysicalCore& System::CurrentPhysicalCore() {
|
||||
return impl->kernel.CurrentPhysicalCore();
|
||||
}
|
||||
|
|
|
@ -122,6 +122,7 @@ class GPUDirtyMemoryManager;
|
|||
class PerfStats;
|
||||
class Reporter;
|
||||
class SpeedLimiter;
|
||||
class TelemetrySession;
|
||||
|
||||
struct PerfStatsResults;
|
||||
|
||||
|
@ -217,6 +218,12 @@ public:
|
|||
*/
|
||||
[[nodiscard]] bool IsPoweredOn() const;
|
||||
|
||||
/// Gets a reference to the telemetry session for this emulation session.
|
||||
[[nodiscard]] Core::TelemetrySession& TelemetrySession();
|
||||
|
||||
/// Gets a reference to the telemetry session for this emulation session.
|
||||
[[nodiscard]] const Core::TelemetrySession& TelemetrySession() const;
|
||||
|
||||
/// Prepare the core emulation for a reschedule
|
||||
void PrepareReschedule(u32 core_index);
|
||||
|
||||
|
|
|
@ -643,7 +643,7 @@ void KeyManager::ReloadKeys() {
|
|||
const auto suyu_keys_dir = Common::FS::GetSuyuPath(Common::FS::SuyuPath::KeysDir);
|
||||
|
||||
if (!Common::FS::CreateDir(suyu_keys_dir)) {
|
||||
LOG_ERROR(Crypto, "Failed to create the keys directory.");
|
||||
LOG_ERROR(Core, "Failed to create the keys directory.");
|
||||
}
|
||||
|
||||
if (Settings::values.use_dev_keys) {
|
||||
|
@ -668,8 +668,6 @@ static bool ValidCryptoRevisionString(std::string_view base, size_t begin, size_
|
|||
|
||||
void KeyManager::LoadFromFile(const std::filesystem::path& file_path, bool is_title_keys) {
|
||||
if (!Common::FS::Exists(file_path)) {
|
||||
LOG_ERROR(Crypto, "Cannot handle key file '{}': File not found",
|
||||
file_path.generic_string());
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -677,12 +675,9 @@ void KeyManager::LoadFromFile(const std::filesystem::path& file_path, bool is_ti
|
|||
Common::FS::OpenFileStream(file, file_path, std::ios_base::in);
|
||||
|
||||
if (!file.is_open()) {
|
||||
LOG_ERROR(Crypto, "Failed to load key file at '{}': Can't open file",
|
||||
file_path.generic_string());
|
||||
return;
|
||||
}
|
||||
|
||||
LOG_INFO(Crypto, "Loading key file at '{}'", file_path.generic_string());
|
||||
std::string line;
|
||||
while (std::getline(file, line)) {
|
||||
std::vector<std::string> out;
|
||||
|
@ -708,8 +703,6 @@ void KeyManager::LoadFromFile(const std::filesystem::path& file_path, bool is_ti
|
|||
u128 rights_id{};
|
||||
std::memcpy(rights_id.data(), rights_id_raw.data(), rights_id_raw.size());
|
||||
Key128 key = Common::HexStringToArray<16>(out[1]);
|
||||
|
||||
LOG_INFO(Crypto, "Successfully loaded title key");
|
||||
s128_keys[{S128KeyType::Titlekey, rights_id[1], rights_id[0]}] = key;
|
||||
} else {
|
||||
out[0] = Common::ToLower(out[0]);
|
||||
|
@ -792,20 +785,6 @@ bool KeyManager::BaseDeriveNecessary() const {
|
|||
return !HasKey(key_type, index1, index2);
|
||||
};
|
||||
|
||||
// Ensure the files exists
|
||||
const auto suyu_keys_dir = Common::FS::GetSuyuPath(Common::FS::SuyuPath::KeysDir);
|
||||
|
||||
if (!Common::FS::Exists(suyu_keys_dir /
|
||||
(Settings::values.use_dev_keys ? "dev.keys" : "prod.keys"))) {
|
||||
LOG_ERROR(Crypto, "No {} found",
|
||||
(Settings::values.use_dev_keys ? "dev.keys" : "prod.keys"));
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!Common::FS::Exists(suyu_keys_dir / "title.keys")) {
|
||||
LOG_WARNING(Crypto, "Could not locate a title.keys file");
|
||||
}
|
||||
|
||||
if (check_key_existence(S256KeyType::Header)) {
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -38,7 +38,7 @@ ICommonStateGetter::ICommonStateGetter(Core::System& system_, std::shared_ptr<Ap
|
|||
{30, nullptr, "GetHomeButtonReaderLockAccessor"},
|
||||
{31, D<&ICommonStateGetter::GetReaderLockAccessorEx>, "GetReaderLockAccessorEx"},
|
||||
{32, D<&ICommonStateGetter::GetWriterLockAccessorEx>, "GetWriterLockAccessorEx"},
|
||||
{40, D<&ICommonStateGetter::GetCradleFwVersion>, "GetCradleFwVersion"},
|
||||
{40, nullptr, "GetCradleFwVersion"},
|
||||
{50, D<&ICommonStateGetter::IsVrModeEnabled>, "IsVrModeEnabled"},
|
||||
{51, D<&ICommonStateGetter::SetVrModeEnabled>, "SetVrModeEnabled"},
|
||||
{52, D<&ICommonStateGetter::SetLcdBacklighOffEnabled>, "SetLcdBacklighOffEnabled"},
|
||||
|
@ -159,17 +159,6 @@ Result ICommonStateGetter::GetBootMode(Out<PM::SystemBootMode> out_boot_mode) {
|
|||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result ICommonStateGetter::GetCradleFwVersion(OutArray<uint32_t, 4> out_version) {
|
||||
LOG_DEBUG(Service_AM, "(STUBBED) called");
|
||||
|
||||
out_version[0] = 0;
|
||||
out_version[1] = 0;
|
||||
out_version[2] = 0;
|
||||
out_version[3] = 0;
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result ICommonStateGetter::IsVrModeEnabled(Out<bool> out_is_vr_mode_enabled) {
|
||||
LOG_DEBUG(Service_AM, "called");
|
||||
|
||||
|
|
|
@ -38,7 +38,6 @@ private:
|
|||
Result GetOperationMode(Out<OperationMode> out_operation_mode);
|
||||
Result GetPerformanceMode(Out<APM::PerformanceMode> out_performance_mode);
|
||||
Result GetBootMode(Out<PM::SystemBootMode> out_boot_mode);
|
||||
Result GetCradleFwVersion(OutArray<uint32_t, 4> out_version);
|
||||
Result IsVrModeEnabled(Out<bool> out_is_vr_mode_enabled);
|
||||
Result SetVrModeEnabled(bool is_vr_mode_enabled);
|
||||
Result SetLcdBacklighOffEnabled(bool is_lcd_backlight_off_enabled);
|
||||
|
|
|
@ -14,7 +14,7 @@ IGlobalStateController::IGlobalStateController(Core::System& system_)
|
|||
static const FunctionInfo functions[] = {
|
||||
{0, nullptr, "RequestToEnterSleep"},
|
||||
{1, nullptr, "EnterSleep"},
|
||||
{2, D<&IGlobalStateController::StartSleepSequence>, "StartSleepSequence"},
|
||||
{2, nullptr, "StartSleepSequence"},
|
||||
{3, D<&IGlobalStateController::StartShutdownSequence>, "StartShutdownSequence"},
|
||||
{4, D<&IGlobalStateController::StartRebootSequence>, "StartRebootSequence"},
|
||||
{9, nullptr, "IsAutoPowerDownRequested"},
|
||||
|
@ -31,13 +31,6 @@ IGlobalStateController::IGlobalStateController(Core::System& system_)
|
|||
RegisterHandlers(functions);
|
||||
}
|
||||
|
||||
IGlobalStateController::~IGlobalStateController() = default;
|
||||
|
||||
Result IGlobalStateController::StartSleepSequence(u8 a) {
|
||||
LOG_WARNING(Service_AM, "(STUBBED) called, a={}", a);
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result IGlobalStateController::StartShutdownSequence() {
|
||||
LOG_INFO(Service_AM, "called");
|
||||
system.Exit();
|
||||
|
@ -50,6 +43,8 @@ Result IGlobalStateController::StartRebootSequence() {
|
|||
R_SUCCEED();
|
||||
}
|
||||
|
||||
IGlobalStateController::~IGlobalStateController() = default;
|
||||
|
||||
Result IGlobalStateController::LoadAndApplyIdlePolicySettings() {
|
||||
LOG_WARNING(Service_AM, "(STUBBED) called");
|
||||
R_SUCCEED();
|
||||
|
|
|
@ -18,7 +18,6 @@ public:
|
|||
~IGlobalStateController() override;
|
||||
|
||||
private:
|
||||
Result StartSleepSequence(u8 a);
|
||||
Result StartShutdownSequence();
|
||||
Result StartRebootSequence();
|
||||
Result LoadAndApplyIdlePolicySettings();
|
||||
|
|
|
@ -23,7 +23,7 @@ IHomeMenuFunctions::IHomeMenuFunctions(Core::System& system_, std::shared_ptr<Ap
|
|||
{21, D<&IHomeMenuFunctions::GetPopFromGeneralChannelEvent>, "GetPopFromGeneralChannelEvent"},
|
||||
{30, nullptr, "GetHomeButtonWriterLockAccessor"},
|
||||
{31, nullptr, "GetWriterLockAccessorEx"},
|
||||
{40, D<&IHomeMenuFunctions::IsSleepEnabled>, "IsSleepEnabled"},
|
||||
{40, nullptr, "IsSleepEnabled"},
|
||||
{41, D<&IHomeMenuFunctions::IsRebootEnabled>, "IsRebootEnabled"},
|
||||
{50, nullptr, "LaunchSystemApplet"},
|
||||
{51, nullptr, "LaunchStarter"},
|
||||
|
@ -64,15 +64,9 @@ Result IHomeMenuFunctions::GetPopFromGeneralChannelEvent(
|
|||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result IHomeMenuFunctions::IsSleepEnabled(Out<bool> out_is_sleep_enabled) {
|
||||
Result IHomeMenuFunctions::IsRebootEnabled(Out<bool> out_is_reboot_enbaled) {
|
||||
LOG_INFO(Service_AM, "called");
|
||||
*out_is_sleep_enabled = true;
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result IHomeMenuFunctions::IsRebootEnabled(Out<bool> out_is_reboot_enabled) {
|
||||
LOG_INFO(Service_AM, "called");
|
||||
*out_is_reboot_enabled = true;
|
||||
*out_is_reboot_enbaled = true;
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
|
|
|
@ -24,8 +24,7 @@ private:
|
|||
Result LockForeground();
|
||||
Result UnlockForeground();
|
||||
Result GetPopFromGeneralChannelEvent(OutCopyHandle<Kernel::KReadableEvent> out_event);
|
||||
Result IsSleepEnabled(Out<bool> out_is_sleep_enabled);
|
||||
Result IsRebootEnabled(Out<bool> out_is_reboot_enabled);
|
||||
Result IsRebootEnabled(Out<bool> out_is_reboot_enbaled);
|
||||
Result IsForceTerminateApplicationDisabledForDebug(
|
||||
Out<bool> out_is_force_terminate_application_disabled_for_debug);
|
||||
|
||||
|
|
|
@ -67,7 +67,7 @@ FSP_SRV::FSP_SRV(Core::System& system_)
|
|||
{24, nullptr, "RegisterSaveDataFileSystemAtomicDeletion"},
|
||||
{25, nullptr, "DeleteSaveDataFileSystemBySaveDataSpaceId"},
|
||||
{26, nullptr, "FormatSdCardDryRun"},
|
||||
{27, D<&FSP_SRV::IsExFatSupported>, "IsExFatSupported"},
|
||||
{27, nullptr, "IsExFatSupported"},
|
||||
{28, nullptr, "DeleteSaveDataFileSystemBySaveDataAttribute"},
|
||||
{30, nullptr, "OpenGameCardStorage"},
|
||||
{31, nullptr, "OpenGameCardFileSystem"},
|
||||
|
@ -235,14 +235,6 @@ Result FSP_SRV::CreateSaveDataFileSystem(FileSys::SaveDataCreationInfo save_crea
|
|||
save_struct));
|
||||
}
|
||||
|
||||
Result FSP_SRV::IsExFatSupported(Out<bool> out_is_supported) {
|
||||
LOG_WARNING(Service_FS, "(STUBBED) called");
|
||||
|
||||
*out_is_supported = true;
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result FSP_SRV::CreateSaveDataFileSystemBySystemSaveDataId(
|
||||
FileSys::SaveDataAttribute save_struct, FileSys::SaveDataCreationInfo save_create_struct) {
|
||||
LOG_DEBUG(Service_FS, "called save_struct = {}", save_struct.DebugInfo());
|
||||
|
|
|
@ -53,7 +53,6 @@ private:
|
|||
Result OpenSdCardFileSystem(OutInterface<IFileSystem> out_interface);
|
||||
Result CreateSaveDataFileSystem(FileSys::SaveDataCreationInfo save_create_struct,
|
||||
FileSys::SaveDataAttribute save_struct, u128 uid);
|
||||
Result IsExFatSupported(Out<bool> out_is_supported);
|
||||
Result CreateSaveDataFileSystemBySystemSaveDataId(
|
||||
FileSys::SaveDataAttribute save_struct, FileSys::SaveDataCreationInfo save_create_struct);
|
||||
Result OpenSaveDataFileSystem(OutInterface<IFileSystem> out_interface,
|
||||
|
|
|
@ -30,10 +30,10 @@ Result ISfMonitorService::Initialize(Out<u32> out_value) {
|
|||
}
|
||||
|
||||
Result ISfMonitorService::GetGroupInfo(
|
||||
GroupInfo in_group_info, OutLargeData<GroupInfo, BufferAttr_HipcAutoSelect> out_group_info) {
|
||||
OutLargeData<GroupInfo, BufferAttr_HipcAutoSelect> out_group_info) {
|
||||
LOG_WARNING(Service_LDN, "(STUBBED) called");
|
||||
|
||||
memcpy(out_group_info, &in_group_info, sizeof(GroupInfo));
|
||||
*out_group_info = GroupInfo{};
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
|
|
|
@ -20,8 +20,7 @@ public:
|
|||
|
||||
private:
|
||||
Result Initialize(Out<u32> out_value);
|
||||
Result GetGroupInfo(GroupInfo in_group_info,
|
||||
OutLargeData<GroupInfo, BufferAttr_HipcAutoSelect> out_group_info);
|
||||
Result GetGroupInfo(OutLargeData<GroupInfo, BufferAttr_HipcAutoSelect> out_group_info);
|
||||
};
|
||||
|
||||
} // namespace Service::LDN
|
||||
|
|
|
@ -40,10 +40,10 @@ Result ISfServiceMonitor::Initialize(Out<u32> out_value) {
|
|||
}
|
||||
|
||||
Result ISfServiceMonitor::GetGroupInfo(
|
||||
GroupInfo in_group_info, OutLargeData<GroupInfo, BufferAttr_HipcAutoSelect> out_group_info) {
|
||||
OutLargeData<GroupInfo, BufferAttr_HipcAutoSelect> out_group_info) {
|
||||
LOG_WARNING(Service_LDN, "(STUBBED) called");
|
||||
|
||||
memcpy(out_group_info, &in_group_info, sizeof(GroupInfo));
|
||||
*out_group_info = GroupInfo{};
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
|
|
|
@ -20,8 +20,7 @@ public:
|
|||
|
||||
private:
|
||||
Result Initialize(Out<u32> out_value);
|
||||
Result GetGroupInfo(GroupInfo in_group_info,
|
||||
OutLargeData<GroupInfo, BufferAttr_HipcAutoSelect> out_group_info);
|
||||
Result GetGroupInfo(OutLargeData<GroupInfo, BufferAttr_HipcAutoSelect> out_group_info);
|
||||
};
|
||||
|
||||
} // namespace Service::LDN
|
||||
|
|
|
@ -507,7 +507,7 @@ void IGeneralService::GetCurrentIpConfigInfo(HLERequestContext& ctx) {
|
|||
}
|
||||
|
||||
void IGeneralService::IsWirelessCommunicationEnabled(HLERequestContext& ctx) {
|
||||
LOG_WARNING(Service_NIFM, "called");
|
||||
LOG_WARNING(Service_NIFM, "(STUBBED) called");
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
rb.Push(ResultSuccess);
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include <optional>
|
||||
#include <boost/container/small_vector.hpp>
|
||||
|
||||
#include "common/microprofile.h"
|
||||
|
|
|
@ -509,13 +509,4 @@ struct TvSettings {
|
|||
};
|
||||
static_assert(sizeof(TvSettings) == 0x20, "TvSettings is an invalid size");
|
||||
|
||||
/// This is nn::settings::system::RebootlessSystemUpdateVersion
|
||||
struct RebootlessSystemUpdateVersion {
|
||||
u32 version;
|
||||
u8 reserved[0x1c];
|
||||
char display_version[0x20];
|
||||
};
|
||||
static_assert(sizeof(RebootlessSystemUpdateVersion) == 0x40,
|
||||
"RebootlessSystemUpdateVersion is an invalid size");
|
||||
|
||||
} // namespace Service::Set
|
||||
|
|
|
@ -238,7 +238,7 @@ ISystemSettingsServer::ISystemSettingsServer(Core::System& system_)
|
|||
{146, nullptr, "SetConsoleSixAxisSensorAngularVelocityTimeBias"},
|
||||
{147, nullptr, "GetConsoleSixAxisSensorAngularAcceleration"},
|
||||
{148, nullptr, "SetConsoleSixAxisSensorAngularAcceleration"},
|
||||
{149, C<&ISystemSettingsServer::GetRebootlessSystemUpdateVersion>, "GetRebootlessSystemUpdateVersion"},
|
||||
{149, nullptr, "GetRebootlessSystemUpdateVersion"},
|
||||
{150, C<&ISystemSettingsServer::GetDeviceTimeZoneLocationUpdatedTime>, "GetDeviceTimeZoneLocationUpdatedTime"},
|
||||
{151, C<&ISystemSettingsServer::SetDeviceTimeZoneLocationUpdatedTime>, "SetDeviceTimeZoneLocationUpdatedTime"},
|
||||
{152, C<&ISystemSettingsServer::GetUserSystemClockAutomaticCorrectionUpdatedTime>, "GetUserSystemClockAutomaticCorrectionUpdatedTime"},
|
||||
|
@ -1194,16 +1194,6 @@ Result ISystemSettingsServer::SetKeyboardLayout(KeyboardLayout keyboard_layout)
|
|||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result ISystemSettingsServer::GetRebootlessSystemUpdateVersion(
|
||||
Out<RebootlessSystemUpdateVersion> out_rebootless_system_update) {
|
||||
LOG_INFO(Service_SET, "(STUBBED) called");
|
||||
|
||||
out_rebootless_system_update->version = 0;
|
||||
strcpy(out_rebootless_system_update->display_version, "0.0.0");
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result ISystemSettingsServer::GetDeviceTimeZoneLocationUpdatedTime(
|
||||
Out<Service::PSC::Time::SteadyClockTimePoint> out_time_point) {
|
||||
LOG_INFO(Service_SET, "called");
|
||||
|
|
|
@ -136,8 +136,6 @@ public:
|
|||
Result SetAppletLaunchFlags(u32 applet_launch_flag);
|
||||
Result GetKeyboardLayout(Out<KeyboardLayout> out_keyboard_layout);
|
||||
Result SetKeyboardLayout(KeyboardLayout keyboard_layout);
|
||||
Result GetRebootlessSystemUpdateVersion(
|
||||
Out<RebootlessSystemUpdateVersion> out_rebootless_system_update);
|
||||
Result GetDeviceTimeZoneLocationUpdatedTime(
|
||||
Out<Service::PSC::Time::SteadyClockTimePoint> out_time_point);
|
||||
Result SetDeviceTimeZoneLocationUpdatedTime(
|
||||
|
|
294
src/core/telemetry_session.cpp
Normal file
294
src/core/telemetry_session.cpp
Normal file
|
@ -0,0 +1,294 @@
|
|||
// SPDX-FileCopyrightText: 2017 Citra Emulator Project & 2024 suyu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <array>
|
||||
|
||||
#include <mbedtls/ctr_drbg.h>
|
||||
#include <mbedtls/entropy.h>
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/fs/file.h"
|
||||
#include "common/fs/fs.h"
|
||||
#include "common/fs/path_util.h"
|
||||
#include "common/logging/log.h"
|
||||
|
||||
#include "common/settings.h"
|
||||
#include "common/settings_enums.h"
|
||||
#include "core/file_sys/control_metadata.h"
|
||||
#include "core/file_sys/patch_manager.h"
|
||||
#include "core/loader/loader.h"
|
||||
#include "core/telemetry_session.h"
|
||||
|
||||
#ifdef ENABLE_WEB_SERVICE
|
||||
#include "web_service/telemetry_json.h"
|
||||
#include "web_service/verify_login.h"
|
||||
#endif
|
||||
|
||||
namespace Core {
|
||||
|
||||
namespace Telemetry = Common::Telemetry;
|
||||
|
||||
static u64 GenerateTelemetryId() {
|
||||
u64 telemetry_id{};
|
||||
|
||||
mbedtls_entropy_context entropy;
|
||||
mbedtls_entropy_init(&entropy);
|
||||
mbedtls_ctr_drbg_context ctr_drbg;
|
||||
static constexpr std::array<char, 18> personalization{{"suyu Telemetry ID"}};
|
||||
|
||||
mbedtls_ctr_drbg_init(&ctr_drbg);
|
||||
ASSERT(mbedtls_ctr_drbg_seed(&ctr_drbg, mbedtls_entropy_func, &entropy,
|
||||
reinterpret_cast<const unsigned char*>(personalization.data()),
|
||||
personalization.size()) == 0);
|
||||
ASSERT(mbedtls_ctr_drbg_random(&ctr_drbg, reinterpret_cast<unsigned char*>(&telemetry_id),
|
||||
sizeof(u64)) == 0);
|
||||
|
||||
mbedtls_ctr_drbg_free(&ctr_drbg);
|
||||
mbedtls_entropy_free(&entropy);
|
||||
|
||||
return telemetry_id;
|
||||
}
|
||||
|
||||
static const char* TranslateRenderer(Settings::RendererBackend backend) {
|
||||
switch (backend) {
|
||||
case Settings::RendererBackend::OpenGL:
|
||||
return "OpenGL";
|
||||
case Settings::RendererBackend::Vulkan:
|
||||
return "Vulkan";
|
||||
case Settings::RendererBackend::Null:
|
||||
return "Null";
|
||||
}
|
||||
return "Unknown";
|
||||
}
|
||||
|
||||
static const char* TranslateGPUAccuracyLevel(Settings::GpuAccuracy backend) {
|
||||
switch (backend) {
|
||||
case Settings::GpuAccuracy::Normal:
|
||||
return "Normal";
|
||||
case Settings::GpuAccuracy::High:
|
||||
return "High";
|
||||
case Settings::GpuAccuracy::Extreme:
|
||||
return "Extreme";
|
||||
}
|
||||
return "Unknown";
|
||||
}
|
||||
|
||||
static const char* TranslateNvdecEmulation(Settings::NvdecEmulation backend) {
|
||||
switch (backend) {
|
||||
case Settings::NvdecEmulation::Off:
|
||||
return "Off";
|
||||
case Settings::NvdecEmulation::Cpu:
|
||||
return "CPU";
|
||||
case Settings::NvdecEmulation::Gpu:
|
||||
return "GPU";
|
||||
}
|
||||
return "Unknown";
|
||||
}
|
||||
|
||||
static constexpr const char* TranslateVSyncMode(Settings::VSyncMode mode) {
|
||||
switch (mode) {
|
||||
case Settings::VSyncMode::Immediate:
|
||||
return "Immediate";
|
||||
case Settings::VSyncMode::Mailbox:
|
||||
return "Mailbox";
|
||||
case Settings::VSyncMode::Fifo:
|
||||
return "FIFO";
|
||||
case Settings::VSyncMode::FifoRelaxed:
|
||||
return "FIFO Relaxed";
|
||||
}
|
||||
return "Unknown";
|
||||
}
|
||||
|
||||
static constexpr const char* TranslateASTCDecodeMode(Settings::AstcDecodeMode mode) {
|
||||
switch (mode) {
|
||||
case Settings::AstcDecodeMode::Cpu:
|
||||
return "CPU";
|
||||
case Settings::AstcDecodeMode::Gpu:
|
||||
return "GPU";
|
||||
case Settings::AstcDecodeMode::CpuAsynchronous:
|
||||
return "CPU Asynchronous";
|
||||
}
|
||||
return "Unknown";
|
||||
}
|
||||
|
||||
u64 GetTelemetryId() {
|
||||
u64 telemetry_id{};
|
||||
const auto filename = Common::FS::GetSuyuPath(Common::FS::SuyuPath::ConfigDir) / "telemetry_id";
|
||||
|
||||
bool generate_new_id = !Common::FS::Exists(filename);
|
||||
|
||||
if (!generate_new_id) {
|
||||
Common::FS::IOFile file{filename, Common::FS::FileAccessMode::Read,
|
||||
Common::FS::FileType::BinaryFile};
|
||||
|
||||
if (!file.IsOpen()) {
|
||||
LOG_ERROR(Core, "failed to open telemetry_id: {}",
|
||||
Common::FS::PathToUTF8String(filename));
|
||||
return {};
|
||||
}
|
||||
|
||||
if (!file.ReadObject(telemetry_id) || telemetry_id == 0) {
|
||||
LOG_ERROR(Frontend, "telemetry_id is 0. Generating a new one.", telemetry_id);
|
||||
generate_new_id = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (generate_new_id) {
|
||||
Common::FS::IOFile file{filename, Common::FS::FileAccessMode::Write,
|
||||
Common::FS::FileType::BinaryFile};
|
||||
|
||||
if (!file.IsOpen()) {
|
||||
LOG_ERROR(Core, "failed to open telemetry_id: {}",
|
||||
Common::FS::PathToUTF8String(filename));
|
||||
return {};
|
||||
}
|
||||
|
||||
telemetry_id = GenerateTelemetryId();
|
||||
|
||||
if (!file.WriteObject(telemetry_id)) {
|
||||
LOG_ERROR(Core, "Failed to write telemetry_id to file.");
|
||||
}
|
||||
}
|
||||
|
||||
return telemetry_id;
|
||||
}
|
||||
|
||||
u64 RegenerateTelemetryId() {
|
||||
const u64 new_telemetry_id{GenerateTelemetryId()};
|
||||
const auto filename = Common::FS::GetSuyuPath(Common::FS::SuyuPath::ConfigDir) / "telemetry_id";
|
||||
|
||||
Common::FS::IOFile file{filename, Common::FS::FileAccessMode::Write,
|
||||
Common::FS::FileType::BinaryFile};
|
||||
|
||||
if (!file.IsOpen()) {
|
||||
LOG_ERROR(Core, "failed to open telemetry_id: {}", Common::FS::PathToUTF8String(filename));
|
||||
return {};
|
||||
}
|
||||
|
||||
if (!file.WriteObject(new_telemetry_id)) {
|
||||
LOG_ERROR(Core, "Failed to write telemetry_id to file.");
|
||||
}
|
||||
|
||||
return new_telemetry_id;
|
||||
}
|
||||
|
||||
bool VerifyLogin(const std::string& username, const std::string& token) {
|
||||
#ifdef ENABLE_WEB_SERVICE
|
||||
return WebService::VerifyLogin(Settings::values.web_api_url.GetValue(), username, token);
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
TelemetrySession::TelemetrySession() = default;
|
||||
|
||||
TelemetrySession::~TelemetrySession() {
|
||||
// Log one-time session end information
|
||||
const s64 shutdown_time{std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
std::chrono::system_clock::now().time_since_epoch())
|
||||
.count()};
|
||||
AddField(Telemetry::FieldType::Session, "Shutdown_Time", shutdown_time);
|
||||
|
||||
#ifdef ENABLE_WEB_SERVICE
|
||||
auto backend = std::make_unique<WebService::TelemetryJson>(
|
||||
Settings::values.web_api_url.GetValue(), Settings::values.suyu_username.GetValue(),
|
||||
Settings::values.suyu_token.GetValue());
|
||||
#else
|
||||
auto backend = std::make_unique<Telemetry::NullVisitor>();
|
||||
#endif
|
||||
|
||||
// Complete the session, submitting to the web service backend if necessary
|
||||
field_collection.Accept(*backend);
|
||||
if (Settings::values.enable_telemetry) {
|
||||
backend->Complete();
|
||||
}
|
||||
}
|
||||
|
||||
void TelemetrySession::AddInitialInfo(Loader::AppLoader& app_loader,
|
||||
const Service::FileSystem::FileSystemController& fsc,
|
||||
const FileSys::ContentProvider& content_provider) {
|
||||
// Log one-time top-level information
|
||||
AddField(Telemetry::FieldType::None, "TelemetryId", GetTelemetryId());
|
||||
|
||||
// Log one-time session start information
|
||||
const s64 init_time{std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
std::chrono::system_clock::now().time_since_epoch())
|
||||
.count()};
|
||||
AddField(Telemetry::FieldType::Session, "Init_Time", init_time);
|
||||
|
||||
u64 program_id{};
|
||||
const Loader::ResultStatus res{app_loader.ReadProgramId(program_id)};
|
||||
if (res == Loader::ResultStatus::Success) {
|
||||
const std::string formatted_program_id{fmt::format("{:016X}", program_id)};
|
||||
AddField(Telemetry::FieldType::Session, "ProgramId", formatted_program_id);
|
||||
|
||||
std::string name;
|
||||
app_loader.ReadTitle(name);
|
||||
|
||||
if (name.empty()) {
|
||||
const auto metadata = [&content_provider, &fsc, program_id] {
|
||||
const FileSys::PatchManager pm{program_id, fsc, content_provider};
|
||||
return pm.GetControlMetadata();
|
||||
}();
|
||||
if (metadata.first != nullptr) {
|
||||
name = metadata.first->GetApplicationName();
|
||||
}
|
||||
}
|
||||
|
||||
if (!name.empty()) {
|
||||
AddField(Telemetry::FieldType::Session, "ProgramName", name);
|
||||
}
|
||||
}
|
||||
|
||||
AddField(Telemetry::FieldType::Session, "ProgramFormat",
|
||||
static_cast<u8>(app_loader.GetFileType()));
|
||||
|
||||
// Log application information
|
||||
Telemetry::AppendBuildInfo(field_collection);
|
||||
|
||||
// Log user system information
|
||||
Telemetry::AppendCPUInfo(field_collection);
|
||||
Telemetry::AppendOSInfo(field_collection);
|
||||
|
||||
// Log user configuration information
|
||||
constexpr auto field_type = Telemetry::FieldType::UserConfig;
|
||||
AddField(field_type, "Audio_SinkId",
|
||||
Settings::CanonicalizeEnum(Settings::values.sink_id.GetValue()));
|
||||
AddField(field_type, "Core_UseMultiCore", Settings::values.use_multi_core.GetValue());
|
||||
AddField(field_type, "Renderer_Backend",
|
||||
TranslateRenderer(Settings::values.renderer_backend.GetValue()));
|
||||
AddField(field_type, "Renderer_UseSpeedLimit", Settings::values.use_speed_limit.GetValue());
|
||||
AddField(field_type, "Renderer_SpeedLimit", Settings::values.speed_limit.GetValue());
|
||||
AddField(field_type, "Renderer_UseDiskShaderCache",
|
||||
Settings::values.use_disk_shader_cache.GetValue());
|
||||
AddField(field_type, "Renderer_GPUAccuracyLevel",
|
||||
TranslateGPUAccuracyLevel(Settings::values.gpu_accuracy.GetValue()));
|
||||
AddField(field_type, "Renderer_UseAsynchronousGpuEmulation",
|
||||
Settings::values.use_asynchronous_gpu_emulation.GetValue());
|
||||
AddField(field_type, "Renderer_NvdecEmulation",
|
||||
TranslateNvdecEmulation(Settings::values.nvdec_emulation.GetValue()));
|
||||
AddField(field_type, "Renderer_AccelerateASTC",
|
||||
TranslateASTCDecodeMode(Settings::values.accelerate_astc.GetValue()));
|
||||
AddField(field_type, "Renderer_UseVsync",
|
||||
TranslateVSyncMode(Settings::values.vsync_mode.GetValue()));
|
||||
AddField(field_type, "Renderer_ShaderBackend",
|
||||
static_cast<u32>(Settings::values.shader_backend.GetValue()));
|
||||
AddField(field_type, "Renderer_UseAsynchronousShaders",
|
||||
Settings::values.use_asynchronous_shaders.GetValue());
|
||||
AddField(field_type, "System_UseDockedMode", Settings::IsDockedMode());
|
||||
}
|
||||
|
||||
bool TelemetrySession::SubmitTestcase() {
|
||||
#ifdef ENABLE_WEB_SERVICE
|
||||
auto backend = std::make_unique<WebService::TelemetryJson>(
|
||||
Settings::values.web_api_url.GetValue(), Settings::values.suyu_username.GetValue(),
|
||||
Settings::values.suyu_token.GetValue());
|
||||
field_collection.Accept(*backend);
|
||||
return backend->SubmitTestcase();
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace Core
|
101
src/core/telemetry_session.h
Normal file
101
src/core/telemetry_session.h
Normal file
|
@ -0,0 +1,101 @@
|
|||
// SPDX-FileCopyrightText: 2017 Citra Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include "common/telemetry.h"
|
||||
|
||||
namespace FileSys {
|
||||
class ContentProvider;
|
||||
}
|
||||
|
||||
namespace Loader {
|
||||
class AppLoader;
|
||||
}
|
||||
|
||||
namespace Service::FileSystem {
|
||||
class FileSystemController;
|
||||
}
|
||||
|
||||
namespace Core {
|
||||
|
||||
/**
|
||||
* Instruments telemetry for this emulation session. Creates a new set of telemetry fields on each
|
||||
* session, logging any one-time fields. Interfaces with the telemetry backend used for submitting
|
||||
* data to the web service. Submits session data on close.
|
||||
*/
|
||||
class TelemetrySession {
|
||||
public:
|
||||
explicit TelemetrySession();
|
||||
~TelemetrySession();
|
||||
|
||||
TelemetrySession(const TelemetrySession&) = delete;
|
||||
TelemetrySession& operator=(const TelemetrySession&) = delete;
|
||||
|
||||
TelemetrySession(TelemetrySession&&) = delete;
|
||||
TelemetrySession& operator=(TelemetrySession&&) = delete;
|
||||
|
||||
/**
|
||||
* Adds the initial telemetry info necessary when starting up a title.
|
||||
*
|
||||
* This includes information such as:
|
||||
* - Telemetry ID
|
||||
* - Initialization time
|
||||
* - Title ID
|
||||
* - Title name
|
||||
* - Title file format
|
||||
* - Miscellaneous settings values.
|
||||
*
|
||||
* @param app_loader The application loader to use to retrieve
|
||||
* title-specific information.
|
||||
* @param fsc Filesystem controller to use to retrieve info.
|
||||
* @param content_provider Content provider to use to retrieve info.
|
||||
*/
|
||||
void AddInitialInfo(Loader::AppLoader& app_loader,
|
||||
const Service::FileSystem::FileSystemController& fsc,
|
||||
const FileSys::ContentProvider& content_provider);
|
||||
|
||||
/**
|
||||
* Wrapper around the Telemetry::FieldCollection::AddField method.
|
||||
* @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(Common::Telemetry::FieldType type, const char* name, T value) {
|
||||
field_collection.AddField(type, name, std::move(value));
|
||||
}
|
||||
|
||||
/**
|
||||
* Submits a Testcase.
|
||||
* @returns A bool indicating whether the submission succeeded
|
||||
*/
|
||||
bool SubmitTestcase();
|
||||
|
||||
private:
|
||||
/// Tracks all added fields for the session
|
||||
Common::Telemetry::FieldCollection field_collection;
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets TelemetryId, a unique identifier used for the user's telemetry sessions.
|
||||
* @returns The current TelemetryId for the session.
|
||||
*/
|
||||
u64 GetTelemetryId();
|
||||
|
||||
/**
|
||||
* Regenerates TelemetryId, a unique identifier used for the user's telemetry sessions.
|
||||
* @returns The new TelemetryId that was generated.
|
||||
*/
|
||||
u64 RegenerateTelemetryId();
|
||||
|
||||
/**
|
||||
* Verifies the username and token.
|
||||
* @param username suyu username to use for authentication.
|
||||
* @param token suyu token to use for authentication.
|
||||
* @returns Future with bool indicating whether the verification succeeded
|
||||
*/
|
||||
bool VerifyLogin(const std::string& username, const std::string& token);
|
||||
|
||||
} // namespace Core
|
|
@ -762,8 +762,6 @@ void EmulatedController::StartMotionCalibration() {
|
|||
|
||||
void EmulatedController::SetButton(const Common::Input::CallbackStatus& callback, std::size_t index,
|
||||
Common::UUID uuid) {
|
||||
const auto player_index = Service::HID::NpadIdTypeToIndex(npad_id_type);
|
||||
const auto& player = Settings::values.players.GetValue()[player_index];
|
||||
if (index >= controller.button_values.size()) {
|
||||
return;
|
||||
}
|
||||
|
@ -919,8 +917,13 @@ void EmulatedController::SetButton(const Common::Input::CallbackStatus& callback
|
|||
|
||||
lock.unlock();
|
||||
|
||||
if (player.connected) {
|
||||
Connect();
|
||||
if (!is_connected) {
|
||||
if (npad_id_type == NpadIdType::Player1 && npad_type != NpadStyleIndex::Handheld) {
|
||||
Connect();
|
||||
}
|
||||
if (npad_id_type == NpadIdType::Handheld && npad_type == NpadStyleIndex::Handheld) {
|
||||
Connect();
|
||||
}
|
||||
}
|
||||
TriggerOnChange(ControllerTriggerType::Button, true);
|
||||
}
|
||||
|
|
|
@ -357,7 +357,7 @@ if (APPLE)
|
|||
|
||||
if (NOT USE_SYSTEM_MOLTENVK)
|
||||
set(MOLTENVK_PLATFORM "macOS")
|
||||
set(MOLTENVK_VERSION "v1.2.8")
|
||||
set(MOLTENVK_VERSION "v1.2.7")
|
||||
download_moltenvk_external(${MOLTENVK_PLATFORM} ${MOLTENVK_VERSION})
|
||||
endif()
|
||||
find_library(MOLTENVK_LIBRARY MoltenVK REQUIRED)
|
||||
|
|
|
@ -6,12 +6,14 @@
|
|||
#include <QPushButton>
|
||||
#include <QtConcurrent/qtconcurrentrun.h>
|
||||
#include "common/logging/log.h"
|
||||
#include "common/telemetry.h"
|
||||
#include "core/telemetry_session.h"
|
||||
#include "suyu/compatdb.h"
|
||||
#include "ui_compatdb.h"
|
||||
|
||||
CompatDB::CompatDB(QWidget* parent)
|
||||
CompatDB::CompatDB(Core::TelemetrySession& telemetry_session_, QWidget* parent)
|
||||
: QWizard(parent, Qt::WindowTitleHint | Qt::WindowCloseButtonHint | Qt::WindowSystemMenuHint),
|
||||
ui{std::make_unique<Ui::CompatDB>()} {
|
||||
ui{std::make_unique<Ui::CompatDB>()}, telemetry_session{telemetry_session_} {
|
||||
ui->setupUi(this);
|
||||
|
||||
connect(ui->radioButton_GameBoot_Yes, &QRadioButton::clicked, this, &CompatDB::EnableNext);
|
||||
|
@ -112,10 +114,15 @@ void CompatDB::Submit() {
|
|||
case CompatDBPage::Final:
|
||||
back();
|
||||
LOG_INFO(Frontend, "Compatibility Rating: {}", compatibility);
|
||||
telemetry_session.AddField(Common::Telemetry::FieldType::UserFeedback, "Compatibility",
|
||||
compatibility);
|
||||
|
||||
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());
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
#include <memory>
|
||||
#include <QFutureWatcher>
|
||||
#include <QWizard>
|
||||
#include "core/telemetry_session.h"
|
||||
|
||||
namespace Ui {
|
||||
class CompatDB;
|
||||
|
@ -24,7 +25,7 @@ class CompatDB : public QWizard {
|
|||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit CompatDB(QWidget* parent = nullptr);
|
||||
explicit CompatDB(Core::TelemetrySession& telemetry_session_, QWidget* parent = nullptr);
|
||||
~CompatDB();
|
||||
int nextId() const override;
|
||||
|
||||
|
@ -37,4 +38,6 @@ private:
|
|||
CompatibilityStatus CalculateCompatibility() const;
|
||||
void OnTestcaseSubmitted();
|
||||
void EnableNext();
|
||||
|
||||
Core::TelemetrySession& telemetry_session;
|
||||
};
|
||||
|
|
|
@ -43,7 +43,7 @@
|
|||
<number>1</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>200</number>
|
||||
<number>100</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>50</number>
|
||||
|
@ -69,7 +69,7 @@
|
|||
<number>1</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>200</number>
|
||||
<number>100</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>50</number>
|
||||
|
|
|
@ -17,17 +17,14 @@
|
|||
#include <QTimer>
|
||||
|
||||
#include "common/fs/fs_util.h"
|
||||
#include "common/hex_util.h"
|
||||
#include "common/settings_enums.h"
|
||||
#include "common/settings_input.h"
|
||||
#include "configuration/shared_widget.h"
|
||||
#include "core/core.h"
|
||||
#include "core/file_sys/control_metadata.h"
|
||||
#include "core/file_sys/ips_layer.h"
|
||||
#include "core/file_sys/patch_manager.h"
|
||||
#include "core/file_sys/xts_archive.h"
|
||||
#include "core/loader/loader.h"
|
||||
#include "core/loader/nso.h"
|
||||
#include "frontend_common/config.h"
|
||||
#include "suyu/configuration/configuration_shared.h"
|
||||
#include "suyu/configuration/configure_audio.h"
|
||||
|
@ -48,12 +45,9 @@ ConfigurePerGame::ConfigurePerGame(QWidget* parent, u64 title_id_, const std::st
|
|||
std::vector<VkDeviceInfo::Record>& vk_device_records,
|
||||
Core::System& system_)
|
||||
: QDialog(parent),
|
||||
ui(std::make_unique<Ui::ConfigurePerGame>()), pm{title_id_, system_.GetFileSystemController(),
|
||||
system_.GetContentProvider()},
|
||||
title_id{title_id_}, system{system_}, builder{std::make_unique<ConfigurationShared::Builder>(
|
||||
this, !system_.IsPoweredOn())},
|
||||
ui(std::make_unique<Ui::ConfigurePerGame>()), title_id{title_id_}, system{system_},
|
||||
builder{std::make_unique<ConfigurationShared::Builder>(this, !system_.IsPoweredOn())},
|
||||
tab_group{std::make_shared<std::vector<ConfigurationShared::Tab*>>()} {
|
||||
|
||||
const auto file_path = std::filesystem::path(Common::FS::ToU8String(file_name));
|
||||
const auto config_file_name = title_id == 0 ? Common::FS::PathToUTF8String(file_path.filename())
|
||||
: fmt::format("{:016X}", title_id);
|
||||
|
@ -147,14 +141,6 @@ void ConfigurePerGame::LoadFromFile(FileSys::VirtualFile file_) {
|
|||
LoadConfiguration();
|
||||
}
|
||||
|
||||
std::string ConfigurePerGame::GetBuildID() {
|
||||
LOG_INFO(Core, "{}", file->GetExtension());
|
||||
|
||||
// https://github.com/Ryujinx/Ryujinx/blob/master/src/Ryujinx.UI.Common/App/ApplicationData.cs#L71
|
||||
|
||||
return "Invalid File";
|
||||
}
|
||||
|
||||
void ConfigurePerGame::LoadConfiguration() {
|
||||
if (file == nullptr) {
|
||||
return;
|
||||
|
@ -162,14 +148,13 @@ void ConfigurePerGame::LoadConfiguration() {
|
|||
|
||||
addons_tab->LoadFromFile(file);
|
||||
|
||||
const auto control = pm.GetControlMetadata();
|
||||
const auto loader = Loader::GetLoader(system, file);
|
||||
|
||||
ui->display_title_id->setText(
|
||||
QStringLiteral("%1").arg(title_id, 16, 16, QLatin1Char{'0'}).toUpper());
|
||||
|
||||
// TODO: Should get proper build id for UI
|
||||
// ui->display_build_id->setText(QString::fromStdString(GetBuildID()));
|
||||
const FileSys::PatchManager pm{title_id, system.GetFileSystemController(),
|
||||
system.GetContentProvider()};
|
||||
const auto control = pm.GetControlMetadata();
|
||||
const auto loader = Loader::GetLoader(system, file);
|
||||
|
||||
if (control.first != nullptr) {
|
||||
ui->display_version->setText(QString::fromStdString(control.first->GetVersionString()));
|
||||
|
|
|
@ -11,7 +11,6 @@
|
|||
#include <QList>
|
||||
|
||||
#include "configuration/shared_widget.h"
|
||||
#include "core/file_sys/patch_manager.h"
|
||||
#include "core/file_sys/vfs/vfs_types.h"
|
||||
#include "frontend_common/config.h"
|
||||
#include "suyu/configuration/configuration_shared.h"
|
||||
|
@ -69,11 +68,8 @@ private:
|
|||
|
||||
void LoadConfiguration();
|
||||
|
||||
std::string GetBuildID();
|
||||
|
||||
std::unique_ptr<Ui::ConfigurePerGame> ui;
|
||||
FileSys::VirtualFile file;
|
||||
FileSys::PatchManager pm;
|
||||
u64 title_id;
|
||||
|
||||
QGraphicsScene* scene;
|
||||
|
|
|
@ -67,18 +67,8 @@
|
|||
</item>
|
||||
<item>
|
||||
<layout class="QGridLayout" name="gridLayout_2">
|
||||
<item row="2" column="1">
|
||||
<widget class="QLineEdit" name="display_developer">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="readOnly">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="1">
|
||||
<widget class="QLineEdit" name="display_format">
|
||||
<widget class="QLineEdit" name="display_size">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
|
@ -87,58 +77,6 @@
|
|||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="0">
|
||||
<widget class="QLabel" name="label_6">
|
||||
<property name="text">
|
||||
<string>Size</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="8" column="0">
|
||||
<widget class="QLabel" name="label_7">
|
||||
<property name="text">
|
||||
<string>Filename</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="text">
|
||||
<string>Title ID</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Name</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<widget class="QLineEdit" name="display_title_id">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="readOnly">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Developer</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="0">
|
||||
<widget class="QLabel" name="label_5">
|
||||
<property name="text">
|
||||
<string>Format</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QLineEdit" name="display_version">
|
||||
<property name="enabled">
|
||||
|
@ -149,14 +87,31 @@
|
|||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="label_3">
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Version</string>
|
||||
<string>Name</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="8" column="1">
|
||||
<item row="4" column="0">
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="text">
|
||||
<string>Title ID</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<widget class="QLineEdit" name="display_title_id">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="readOnly">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="1">
|
||||
<widget class="QLineEdit" name="display_filename">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
|
@ -166,6 +121,23 @@
|
|||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="1">
|
||||
<widget class="QLineEdit" name="display_format">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="readOnly">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="0">
|
||||
<widget class="QLabel" name="label_7">
|
||||
<property name="text">
|
||||
<string>Filename</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLineEdit" name="display_name">
|
||||
<property name="enabled">
|
||||
|
@ -176,8 +148,8 @@
|
|||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="1">
|
||||
<widget class="QLineEdit" name="display_size">
|
||||
<item row="2" column="1">
|
||||
<widget class="QLineEdit" name="display_developer">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
|
@ -185,22 +157,35 @@
|
|||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<!-- TODO: Gotta implement proper working build id -->
|
||||
<!--item row="5" column="1">
|
||||
<widget class="QLineEdit" name="display_build_id">
|
||||
<property name="readOnly">
|
||||
<bool>true</bool>
|
||||
</item>
|
||||
<item row="5" column="0">
|
||||
<widget class="QLabel" name="label_5">
|
||||
<property name="text">
|
||||
<string>Format</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="0">
|
||||
<widget class="QLabel" name="label_9">
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>Build ID</string>
|
||||
<string>Version</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item-->
|
||||
</item>
|
||||
<item row="6" column="0">
|
||||
<widget class="QLabel" name="label_6">
|
||||
<property name="text">
|
||||
<string>Size</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Developer</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
// SPDX-FileCopyrightText: 2016 Citra Emulator Project
|
||||
// SPDX-FileCopyrightText: 2024 suyu Emulator Project
|
||||
// SPDX-FileCopyrightText: 2016 Citra Emulator Project & 2024 suyu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <algorithm>
|
||||
|
@ -25,7 +24,6 @@
|
|||
|
||||
#include "common/fs/fs.h"
|
||||
#include "common/fs/path_util.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/core.h"
|
||||
#include "core/file_sys/patch_manager.h"
|
||||
#include "core/loader/loader.h"
|
||||
|
|
|
@ -5,10 +5,10 @@
|
|||
#include <QMessageBox>
|
||||
#include <QtConcurrent/QtConcurrentRun>
|
||||
#include "common/settings.h"
|
||||
#include "core/telemetry_session.h"
|
||||
#include "suyu/configuration/configure_web.h"
|
||||
#include "suyu/uisettings.h"
|
||||
#include "ui_configure_web.h"
|
||||
#include "web_service/verify_login.h"
|
||||
|
||||
static constexpr char token_delimiter{':'};
|
||||
|
||||
|
@ -38,6 +38,8 @@ static std::string TokenFromDisplayToken(const std::string& display_token) {
|
|||
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);
|
||||
|
||||
|
@ -62,18 +64,26 @@ void ConfigureWeb::changeEvent(QEvent* event) {
|
|||
void ConfigureWeb::RetranslateUI() {
|
||||
ui->retranslateUi(this);
|
||||
|
||||
ui->telemetry_learn_more->setText(
|
||||
tr("<a href='https://suyu.dev/help/feature/telemetry/'><span style=\"text-decoration: "
|
||||
"underline; color:#039be5;\">Learn more</span></a>"));
|
||||
|
||||
ui->web_signup_link->setText(
|
||||
tr("<a href='https://suyu.dev/signup'><span style=\"text-decoration: underline; "
|
||||
tr("<a href='https://profile.suyu.dev/'><span style=\"text-decoration: underline; "
|
||||
"color:#039be5;\">Sign up</span></a>"));
|
||||
|
||||
ui->web_token_info_link->setText(
|
||||
tr("<a href='https://suyu.dev/account'><span style=\"text-decoration: "
|
||||
tr("<a href='https://suyu.dev/wiki/suyu-web-service/'><span style=\"text-decoration: "
|
||||
"underline; color:#039be5;\">What is my token?</span></a>"));
|
||||
|
||||
ui->label_telemetry_id->setText(
|
||||
tr("Telemetry ID: 0x%1").arg(QString::number(Core::GetTelemetryId(), 16).toUpper()));
|
||||
}
|
||||
|
||||
void ConfigureWeb::SetConfiguration() {
|
||||
ui->web_credentials_disclaimer->setWordWrap(true);
|
||||
|
||||
ui->telemetry_learn_more->setOpenExternalLinks(true);
|
||||
ui->web_signup_link->setOpenExternalLinks(true);
|
||||
ui->web_token_info_link->setOpenExternalLinks(true);
|
||||
|
||||
|
@ -83,6 +93,7 @@ void ConfigureWeb::SetConfiguration() {
|
|||
ui->username->setText(QString::fromStdString(Settings::values.suyu_username.GetValue()));
|
||||
}
|
||||
|
||||
ui->toggle_telemetry->setChecked(Settings::values.enable_telemetry.GetValue());
|
||||
ui->edit_token->setText(QString::fromStdString(GenerateDisplayToken(
|
||||
Settings::values.suyu_username.GetValue(), Settings::values.suyu_token.GetValue())));
|
||||
|
||||
|
@ -95,6 +106,7 @@ void ConfigureWeb::SetConfiguration() {
|
|||
}
|
||||
|
||||
void ConfigureWeb::ApplyConfiguration() {
|
||||
Settings::values.enable_telemetry = ui->toggle_telemetry->isChecked();
|
||||
UISettings::values.enable_discord_presence = ui->toggle_discordrpc->isChecked();
|
||||
if (user_verified) {
|
||||
Settings::values.suyu_username =
|
||||
|
@ -107,6 +119,12 @@ void ConfigureWeb::ApplyConfiguration() {
|
|||
}
|
||||
}
|
||||
|
||||
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;
|
||||
|
@ -132,12 +150,7 @@ void ConfigureWeb::VerifyLogin() {
|
|||
verify_watcher.setFuture(QtConcurrent::run(
|
||||
[username = UsernameFromDisplayToken(ui->edit_token->text().toStdString()),
|
||||
token = TokenFromDisplayToken(ui->edit_token->text().toStdString())] {
|
||||
#ifdef ENABLE_WEB_SERVICE
|
||||
return WebService::VerifyLogin(Settings::values.web_api_url.GetValue(), username,
|
||||
token);
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
return Core::VerifyLogin(username, token);
|
||||
}));
|
||||
}
|
||||
|
||||
|
|
|
@ -25,6 +25,7 @@ private:
|
|||
void changeEvent(QEvent* event) override;
|
||||
void RetranslateUI();
|
||||
|
||||
void RefreshTelemetryID();
|
||||
void OnLoginChanged();
|
||||
void VerifyLogin();
|
||||
void OnLoginVerified();
|
||||
|
|
|
@ -125,6 +125,56 @@
|
|||
</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 suyu 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>
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
// Modified by palfaiate on <2024/03/07>
|
||||
|
||||
#include <regex>
|
||||
#include <QApplication>
|
||||
#include <QDir>
|
||||
#include <QFileInfo>
|
||||
|
@ -383,17 +384,6 @@ GameList::~GameList() {
|
|||
UnloadController();
|
||||
}
|
||||
|
||||
void GameList::ClearList() {
|
||||
// Clear all items
|
||||
item_model->setRowCount(0);
|
||||
|
||||
// Notify a reload is pending
|
||||
UISettings::values.is_game_list_reload_pending.exchange(true);
|
||||
UISettings::values.is_game_list_reload_pending.notify_all();
|
||||
|
||||
// Load stuff back up
|
||||
}
|
||||
|
||||
void GameList::SetFilterFocus() {
|
||||
if (tree_view->model()->rowCount() > 0) {
|
||||
search_field->setFocus();
|
||||
|
@ -423,10 +413,6 @@ void GameList::AddEntry(const QList<QStandardItem*>& entry_items, GameListDir* p
|
|||
parent->appendRow(entry_items);
|
||||
}
|
||||
|
||||
void GameList::AddRootEntry(const QList<QStandardItem*>& entry_items) {
|
||||
item_model->invisibleRootItem()->appendRow(entry_items);
|
||||
}
|
||||
|
||||
void GameList::ValidateEntry(const QModelIndex& item) {
|
||||
const auto selected = item.sibling(item.row(), 0);
|
||||
|
||||
|
@ -482,9 +468,7 @@ bool GameList::IsEmpty() const {
|
|||
void GameList::DonePopulating(const QStringList& watch_list) {
|
||||
emit ShowList(!IsEmpty());
|
||||
|
||||
if (UISettings::values.show_folders_in_list) {
|
||||
item_model->invisibleRootItem()->appendRow(new GameListAddDir());
|
||||
}
|
||||
item_model->invisibleRootItem()->appendRow(new GameListAddDir());
|
||||
|
||||
// Add favorites row
|
||||
item_model->invisibleRootItem()->insertRow(0, new GameListFavorites());
|
||||
|
@ -501,7 +485,6 @@ void GameList::DonePopulating(const QStringList& watch_list) {
|
|||
if (!watch_dirs.isEmpty()) {
|
||||
watcher->removePaths(watch_dirs);
|
||||
}
|
||||
|
||||
// Workaround: Add the watch paths in chunks to allow the gui to refresh
|
||||
// This prevents the UI from stalling when a large number of watch paths are added
|
||||
// Also artificially caps the watcher to a certain number of directories
|
||||
|
@ -903,43 +886,22 @@ void GameList::ToggleFavorite(u64 program_id) {
|
|||
void GameList::AddFavorite(u64 program_id) {
|
||||
auto* favorites_row = item_model->item(0);
|
||||
|
||||
if (UISettings::values.show_folders_in_list) {
|
||||
for (int i = 0; i < item_model->rowCount(); i++) {
|
||||
const auto* folder = item_model->item(i);
|
||||
for (int j = 0; j < folder->rowCount(); j++) {
|
||||
if (folder->child(j)->data(GameListItemPath::ProgramIdRole).toULongLong() ==
|
||||
program_id) {
|
||||
QList<QStandardItem*> list;
|
||||
for (int k = 0; k < COLUMN_COUNT; k++) {
|
||||
list.append(folder->child(j, k)->clone());
|
||||
}
|
||||
list[0]->setData(folder->child(j)->data(GameListItem::SortRole),
|
||||
GameListItem::SortRole);
|
||||
list[0]->setText(folder->child(j)->data(Qt::DisplayRole).toString());
|
||||
|
||||
favorites_row->appendRow(list);
|
||||
return;
|
||||
for (int i = 1; i < item_model->rowCount() - 1; i++) {
|
||||
const auto* folder = item_model->item(i);
|
||||
for (int j = 0; j < folder->rowCount(); j++) {
|
||||
if (folder->child(j)->data(GameListItemPath::ProgramIdRole).toULongLong() ==
|
||||
program_id) {
|
||||
QList<QStandardItem*> list;
|
||||
for (int k = 0; k < COLUMN_COUNT; k++) {
|
||||
list.append(folder->child(j, k)->clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
} else {
|
||||
for (int i = 0; i < item_model->rowCount(); i++) {
|
||||
const auto* game = item_model->item(i);
|
||||
if (game->data(GameListItemPath::ProgramIdRole).toULongLong() != program_id) {
|
||||
continue;
|
||||
}
|
||||
list[0]->setData(folder->child(j)->data(GameListItem::SortRole),
|
||||
GameListItem::SortRole);
|
||||
list[0]->setText(folder->child(j)->data(Qt::DisplayRole).toString());
|
||||
|
||||
QList<QStandardItem*> list;
|
||||
for (int j = 0; j < COLUMN_COUNT; j++) {
|
||||
list.append(item_model->item(i, j)->clone());
|
||||
favorites_row->appendRow(list);
|
||||
return;
|
||||
}
|
||||
|
||||
list[0]->setData(game->data(GameListItem::SortRole), GameListItem::SortRole);
|
||||
list[0]->setText(game->data(Qt::DisplayRole).toString());
|
||||
|
||||
favorites_row->appendRow(list);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -84,7 +84,6 @@ public:
|
|||
~GameList() override;
|
||||
|
||||
QString GetLastFilterResultItem() const;
|
||||
void ClearList();
|
||||
void ClearFilter();
|
||||
void SetFilterFocus();
|
||||
void SetFilterVisible(bool visibility);
|
||||
|
@ -138,7 +137,6 @@ private:
|
|||
|
||||
void AddDirEntry(GameListDir* entry_items);
|
||||
void AddEntry(const QList<QStandardItem*>& entry_items, GameListDir* parent);
|
||||
void AddRootEntry(const QList<QStandardItem*>& entry_items);
|
||||
void DonePopulating(const QStringList& watch_list);
|
||||
|
||||
private:
|
||||
|
|
|
@ -330,13 +330,7 @@ void GameListWorker::AddTitlesToGameList(GameListDir* parent_dir) {
|
|||
|
||||
auto entry = MakeGameListEntry(file->GetFullPath(), name, file->GetSize(), icon, *loader,
|
||||
program_id, compatibility_list, play_time_manager, patch);
|
||||
RecordEvent([=](GameList* game_list) {
|
||||
if (UISettings::values.show_folders_in_list) {
|
||||
game_list->AddEntry(entry, parent_dir);
|
||||
} else {
|
||||
game_list->AddRootEntry(entry);
|
||||
}
|
||||
});
|
||||
RecordEvent([=](GameList* game_list) { game_list->AddEntry(entry, parent_dir); });
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -414,7 +408,8 @@ void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_pa
|
|||
physical_name, name, Common::FS::GetSize(physical_name), icon, *loader,
|
||||
id, compatibility_list, play_time_manager, patch);
|
||||
|
||||
RecordEvent([=](GameList* game_list) { game_list->AddRootEntry(entry); });
|
||||
RecordEvent(
|
||||
[=](GameList* game_list) { game_list->AddEntry(entry, parent_dir); });
|
||||
}
|
||||
} else {
|
||||
std::vector<u8> icon;
|
||||
|
@ -430,13 +425,8 @@ void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_pa
|
|||
physical_name, name, Common::FS::GetSize(physical_name), icon, *loader,
|
||||
program_id, compatibility_list, play_time_manager, patch);
|
||||
|
||||
RecordEvent([=](GameList* game_list) {
|
||||
if (UISettings::values.show_folders_in_list) {
|
||||
game_list->AddEntry(entry, parent_dir);
|
||||
} else {
|
||||
game_list->AddRootEntry(entry);
|
||||
}
|
||||
});
|
||||
RecordEvent(
|
||||
[=](GameList* game_list) { game_list->AddEntry(entry, parent_dir); });
|
||||
}
|
||||
}
|
||||
} else if (is_dir) {
|
||||
|
@ -469,32 +459,20 @@ void GameListWorker::run() {
|
|||
|
||||
if (game_dir.path == std::string("SDMC")) {
|
||||
auto* const game_list_dir = new GameListDir(game_dir, GameListItemType::SdmcDir);
|
||||
|
||||
if (UISettings::values.show_folders_in_list)
|
||||
DirEntryReady(game_list_dir);
|
||||
|
||||
DirEntryReady(game_list_dir);
|
||||
AddTitlesToGameList(game_list_dir);
|
||||
} else if (game_dir.path == std::string("UserNAND")) {
|
||||
auto* const game_list_dir = new GameListDir(game_dir, GameListItemType::UserNandDir);
|
||||
|
||||
if (UISettings::values.show_folders_in_list)
|
||||
DirEntryReady(game_list_dir);
|
||||
|
||||
DirEntryReady(game_list_dir);
|
||||
AddTitlesToGameList(game_list_dir);
|
||||
} else if (game_dir.path == std::string("SysNAND")) {
|
||||
auto* const game_list_dir = new GameListDir(game_dir, GameListItemType::SysNandDir);
|
||||
|
||||
if (UISettings::values.show_folders_in_list)
|
||||
DirEntryReady(game_list_dir);
|
||||
|
||||
DirEntryReady(game_list_dir);
|
||||
AddTitlesToGameList(game_list_dir);
|
||||
} else {
|
||||
watch_list.append(QString::fromStdString(game_dir.path));
|
||||
auto* const game_list_dir = new GameListDir(game_dir);
|
||||
|
||||
if (UISettings::values.show_folders_in_list)
|
||||
DirEntryReady(game_list_dir);
|
||||
|
||||
DirEntryReady(game_list_dir);
|
||||
ScanFileSystem(ScanTarget::FillManualContentProvider, game_dir.path, game_dir.deep_scan,
|
||||
game_list_dir);
|
||||
ScanFileSystem(ScanTarget::PopulateGameList, game_dir.path, game_dir.deep_scan,
|
||||
|
|
|
@ -100,6 +100,7 @@
|
|||
#include "common/x64/cpu_detect.h"
|
||||
#endif
|
||||
#include "common/settings.h"
|
||||
#include "common/telemetry.h"
|
||||
#include "core/core.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "core/crypto/key_manager.h"
|
||||
|
@ -118,6 +119,7 @@
|
|||
#include "core/hle/service/sm/sm.h"
|
||||
#include "core/loader/loader.h"
|
||||
#include "core/perf_stats.h"
|
||||
#include "core/telemetry_session.h"
|
||||
#include "frontend_common/config.h"
|
||||
#include "input_common/drivers/tas_input.h"
|
||||
#include "input_common/drivers/virtual_amiibo.h"
|
||||
|
@ -186,9 +188,28 @@ constexpr size_t CopyBufferSize = 1_MiB;
|
|||
* 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,
|
||||
DRDDeprecation = 0x2,
|
||||
};
|
||||
|
||||
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://suyu.dev/help/feature/telemetry/'>Anonymous "
|
||||
"data is collected</a> to help improve suyu. "
|
||||
"<br/><br/>Would you like to share your usage data with us?");
|
||||
if (!question(this, tr("Telemetry"), telemetry_message)) {
|
||||
Settings::values.enable_telemetry = false;
|
||||
system->ApplySettings();
|
||||
}
|
||||
}
|
||||
|
||||
const int GMainWindow::max_recent_files_item;
|
||||
|
||||
static void RemoveCachedContents() {
|
||||
|
@ -396,6 +417,9 @@ GMainWindow::GMainWindow(std::unique_ptr<QtConfig> config_, bool has_broken_vulk
|
|||
game_list->LoadCompatibilityList();
|
||||
game_list->PopulateAsync(UISettings::values.game_dirs);
|
||||
|
||||
// Show one-time "callout" messages to the user
|
||||
ShowTelemetryCallout();
|
||||
|
||||
// make sure menubar has the arrow cursor instead of inheriting from this
|
||||
ui->menubar->setCursor(QCursor());
|
||||
statusBar()->setCursor(QCursor());
|
||||
|
@ -1416,7 +1440,6 @@ void GMainWindow::RestoreUIState() {
|
|||
game_list->SetFilterVisible(ui->action_Show_Filter_Bar->isChecked());
|
||||
|
||||
ui->action_Show_Status_Bar->setChecked(UISettings::values.show_status_bar.GetValue());
|
||||
ui->action_Show_Folders_In_List->setChecked(UISettings::values.show_folders_in_list.GetValue());
|
||||
statusBar()->setVisible(ui->action_Show_Status_Bar->isChecked());
|
||||
Debugger::ToggleConsole();
|
||||
}
|
||||
|
@ -1532,7 +1555,6 @@ void GMainWindow::ConnectMenuEvents() {
|
|||
connect_menu(ui->action_Display_Dock_Widget_Headers, &GMainWindow::OnDisplayTitleBars);
|
||||
connect_menu(ui->action_Show_Filter_Bar, &GMainWindow::OnToggleFilterBar);
|
||||
connect_menu(ui->action_Show_Status_Bar, &GMainWindow::OnToggleStatusBar);
|
||||
connect_menu(ui->action_Show_Folders_In_List, &GMainWindow::OnToggleFoldersInList);
|
||||
|
||||
connect_menu(ui->action_Reset_Window_Size_720, &GMainWindow::ResetWindowSize720);
|
||||
connect_menu(ui->action_Reset_Window_Size_900, &GMainWindow::ResetWindowSize900);
|
||||
|
@ -1752,15 +1774,6 @@ bool GMainWindow::LoadROM(const QString& filename, Service::AM::FrontendAppletPa
|
|||
return false;
|
||||
}
|
||||
|
||||
if (!ContentManager::AreKeysPresent()) {
|
||||
QMessageBox::warning(this, tr("Derivation Components Missing"),
|
||||
tr("Encryption keys are missing. "
|
||||
"In order to use this emulator"
|
||||
"you need to provide your own encryption keys"
|
||||
"in order to play them."));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Shutdown previous session if the emu thread is still active...
|
||||
if (emu_thread != nullptr) {
|
||||
ShutdownGame();
|
||||
|
@ -1857,6 +1870,8 @@ bool GMainWindow::LoadROM(const QString& filename, Service::AM::FrontendAppletPa
|
|||
return false;
|
||||
}
|
||||
current_game_path = filename;
|
||||
|
||||
system->TelemetrySession().AddField(Common::Telemetry::FieldType::App, "Frontend", "Qt");
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -3365,7 +3380,7 @@ void GMainWindow::OnMenuReportCompatibility() {
|
|||
|
||||
if (!Settings::values.suyu_token.GetValue().empty() &&
|
||||
!Settings::values.suyu_username.GetValue().empty()) {
|
||||
CompatDB compatdb{this};
|
||||
CompatDB compatdb{system->TelemetrySession(), this};
|
||||
compatdb.exec();
|
||||
} else {
|
||||
QMessageBox::critical(
|
||||
|
@ -3595,6 +3610,8 @@ void GMainWindow::OnConfigure() {
|
|||
|
||||
SetDefaultUIGeometry();
|
||||
RestoreUIState();
|
||||
|
||||
ShowTelemetryCallout();
|
||||
}
|
||||
InitializeHotkeys();
|
||||
|
||||
|
@ -4196,14 +4213,6 @@ void GMainWindow::OnToggleStatusBar() {
|
|||
statusBar()->setVisible(ui->action_Show_Status_Bar->isChecked());
|
||||
}
|
||||
|
||||
void GMainWindow::OnToggleFoldersInList() {
|
||||
UISettings::values.show_folders_in_list = ui->action_Show_Folders_In_List->isChecked();
|
||||
|
||||
game_list->ClearList();
|
||||
game_list->LoadCompatibilityList();
|
||||
game_list->PopulateAsync(UISettings::values.game_dirs);
|
||||
}
|
||||
|
||||
void GMainWindow::OnAlbum() {
|
||||
constexpr u64 AlbumId = static_cast<u64>(Service::AM::AppletProgramId::PhotoViewer);
|
||||
auto bis_system = system->GetFileSystemController().GetSystemNANDContents();
|
||||
|
@ -4592,7 +4601,6 @@ void GMainWindow::UpdateUISettings() {
|
|||
UISettings::values.display_titlebar = ui->action_Display_Dock_Widget_Headers->isChecked();
|
||||
UISettings::values.show_filter_bar = ui->action_Show_Filter_Bar->isChecked();
|
||||
UISettings::values.show_status_bar = ui->action_Show_Status_Bar->isChecked();
|
||||
UISettings::values.show_folders_in_list = ui->action_Show_Folders_In_List->isChecked();
|
||||
UISettings::values.first_start = false;
|
||||
}
|
||||
|
||||
|
@ -4628,13 +4636,13 @@ void GMainWindow::OnMouseActivity() {
|
|||
void GMainWindow::OnCheckFirmwareDecryption() {
|
||||
system->GetFileSystemController().CreateFactories(*vfs);
|
||||
if (!ContentManager::AreKeysPresent()) {
|
||||
QMessageBox::warning(this, tr("Derivation Components Missing"),
|
||||
tr("Encryption keys are missing. "
|
||||
"In order to use this emulator"
|
||||
"you need to provide your own encryption keys"
|
||||
"in order to play them."));
|
||||
QMessageBox::warning(
|
||||
this, tr("Derivation Components Missing"),
|
||||
tr("Encryption keys are missing. "
|
||||
"<br>Please follow <a href='https://suyu.dev/help/quickstart/'>the suyu "
|
||||
"quickstart guide</a> to get all your keys, firmware and "
|
||||
"games."));
|
||||
}
|
||||
|
||||
SetFirmwareVersion();
|
||||
UpdateMenuState();
|
||||
}
|
||||
|
|
|
@ -275,6 +275,7 @@ private:
|
|||
void BootGameFromList(const QString& filename, StartGameType with_config);
|
||||
void ShutdownGame();
|
||||
|
||||
void ShowTelemetryCallout();
|
||||
void SetDiscordEnabled(bool state);
|
||||
void LoadAmiibo(const QString& filename);
|
||||
|
||||
|
@ -383,7 +384,6 @@ private slots:
|
|||
void OnAbout();
|
||||
void OnToggleFilterBar();
|
||||
void OnToggleStatusBar();
|
||||
void OnToggleFoldersInList();
|
||||
void OnDisplayTitleBars(bool);
|
||||
void InitializeHotkeys();
|
||||
void ToggleFullscreen();
|
||||
|
|
|
@ -45,7 +45,7 @@
|
|||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>1280</width>
|
||||
<height>21</height>
|
||||
<height>22</height>
|
||||
</rect>
|
||||
</property>
|
||||
<widget class="QMenu" name="menu_File">
|
||||
|
@ -124,7 +124,6 @@
|
|||
<addaction name="action_Display_Dock_Widget_Headers"/>
|
||||
<addaction name="action_Show_Filter_Bar"/>
|
||||
<addaction name="action_Show_Status_Bar"/>
|
||||
<addaction name="action_Show_Folders_In_List" />
|
||||
<addaction name="separator"/>
|
||||
<addaction name="menu_Reset_Window_Size"/>
|
||||
<addaction name="menu_View_Debugging"/>
|
||||
|
@ -289,17 +288,6 @@
|
|||
<string>Show Status Bar</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_Show_Folders_In_List">
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Show Folders in List</string>
|
||||
</property>
|
||||
<property name="iconText">
|
||||
<string>Show Status Bar</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_View_Lobby">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
|
|
|
@ -200,7 +200,6 @@ struct Values {
|
|||
std::atomic_bool is_game_list_reload_pending{false};
|
||||
Setting<bool> cache_game_list{linkage, true, "cache_game_list", Category::UiGameList};
|
||||
Setting<bool> favorites_expanded{linkage, true, "favorites_expanded", Category::UiGameList};
|
||||
Setting<bool> show_folders_in_list{linkage, true, "show_folders_in_list", Category::UiGameList};
|
||||
QVector<u64> favorited_ids;
|
||||
|
||||
// Compatibility List
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
#include "common/scope_exit.h"
|
||||
#include "common/settings.h"
|
||||
#include "common/string_util.h"
|
||||
#include "common/telemetry.h"
|
||||
#include "core/core.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "core/cpu_manager.h"
|
||||
|
@ -28,6 +29,7 @@
|
|||
#include "core/hle/service/am/applet_manager.h"
|
||||
#include "core/hle/service/filesystem/filesystem.h"
|
||||
#include "core/loader/loader.h"
|
||||
#include "core/telemetry_session.h"
|
||||
#include "frontend_common/config.h"
|
||||
#include "input_common/main.h"
|
||||
#include "network/network.h"
|
||||
|
@ -401,6 +403,8 @@ int main(int argc, char** argv) {
|
|||
break;
|
||||
}
|
||||
|
||||
system.TelemetrySession().AddField(Common::Telemetry::FieldType::App, "Frontend", "SDL");
|
||||
|
||||
if (use_multiplayer) {
|
||||
if (auto member = system.GetRoomNetwork().GetRoomMember().lock()) {
|
||||
member->BindOnChatMessageReceived(OnMessageReceived);
|
||||
|
|
|
@ -9,7 +9,7 @@ if(LIBVA_FOUND)
|
|||
list(APPEND FFmpeg_LIBRARIES ${LIBVA_LIBRARIES})
|
||||
endif()
|
||||
|
||||
set(sources
|
||||
add_library(video_core STATIC
|
||||
buffer_cache/buffer_base.h
|
||||
buffer_cache/buffer_cache_base.h
|
||||
buffer_cache/buffer_cache.cpp
|
||||
|
@ -315,67 +315,6 @@ set(sources
|
|||
vulkan_common/vulkan.h
|
||||
)
|
||||
|
||||
if (APPLE)
|
||||
list(REMOVE_ITEM sources
|
||||
renderer_opengl/present/filters.cpp
|
||||
renderer_opengl/present/filters.h
|
||||
renderer_opengl/present/fsr.cpp
|
||||
renderer_opengl/present/fsr.h
|
||||
renderer_opengl/present/fxaa.cpp
|
||||
renderer_opengl/present/fxaa.h
|
||||
renderer_opengl/present/layer.cpp
|
||||
renderer_opengl/present/layer.h
|
||||
renderer_opengl/present/present_uniforms.h
|
||||
renderer_opengl/present/smaa.cpp
|
||||
renderer_opengl/present/smaa.h
|
||||
renderer_opengl/present/util.h
|
||||
renderer_opengl/present/window_adapt_pass.cpp
|
||||
renderer_opengl/present/window_adapt_pass.h
|
||||
renderer_opengl/blit_image.cpp
|
||||
renderer_opengl/blit_image.h
|
||||
renderer_opengl/gl_blit_screen.cpp
|
||||
renderer_opengl/gl_blit_screen.h
|
||||
renderer_opengl/gl_buffer_cache_base.cpp
|
||||
renderer_opengl/gl_buffer_cache.cpp
|
||||
renderer_opengl/gl_buffer_cache.h
|
||||
renderer_opengl/gl_compute_pipeline.cpp
|
||||
renderer_opengl/gl_compute_pipeline.h
|
||||
renderer_opengl/gl_device.cpp
|
||||
renderer_opengl/gl_device.h
|
||||
renderer_opengl/gl_fence_manager.cpp
|
||||
renderer_opengl/gl_fence_manager.h
|
||||
renderer_opengl/gl_graphics_pipeline.cpp
|
||||
renderer_opengl/gl_graphics_pipeline.h
|
||||
renderer_opengl/gl_rasterizer.cpp
|
||||
renderer_opengl/gl_rasterizer.h
|
||||
renderer_opengl/gl_resource_manager.cpp
|
||||
renderer_opengl/gl_resource_manager.h
|
||||
renderer_opengl/gl_shader_cache.cpp
|
||||
renderer_opengl/gl_shader_cache.h
|
||||
renderer_opengl/gl_shader_manager.cpp
|
||||
renderer_opengl/gl_shader_manager.h
|
||||
renderer_opengl/gl_shader_context.h
|
||||
renderer_opengl/gl_shader_util.cpp
|
||||
renderer_opengl/gl_shader_util.h
|
||||
renderer_opengl/gl_state_tracker.cpp
|
||||
renderer_opengl/gl_state_tracker.h
|
||||
renderer_opengl/gl_staging_buffer_pool.cpp
|
||||
renderer_opengl/gl_staging_buffer_pool.h
|
||||
renderer_opengl/gl_texture_cache.cpp
|
||||
renderer_opengl/gl_texture_cache.h
|
||||
renderer_opengl/gl_texture_cache_base.cpp
|
||||
renderer_opengl/gl_query_cache.cpp
|
||||
renderer_opengl/gl_query_cache.h
|
||||
renderer_opengl/maxwell_to_gl.h
|
||||
renderer_opengl/renderer_opengl.cpp
|
||||
renderer_opengl/renderer_opengl.h
|
||||
renderer_opengl/util_shaders.cpp
|
||||
renderer_opengl/util_shaders.h
|
||||
)
|
||||
endif()
|
||||
|
||||
add_library(video_core STATIC ${sources})
|
||||
|
||||
target_link_libraries(video_core PUBLIC common core)
|
||||
target_link_libraries(video_core PUBLIC glad shader_recompiler stb bc_decoder)
|
||||
|
||||
|
@ -408,7 +347,7 @@ if (MSVC)
|
|||
/we4244 # 'conversion': conversion from 'type1' to 'type2', possible loss of data
|
||||
)
|
||||
else()
|
||||
if (APPLE OR ARCHITECTURE_arm64)
|
||||
if (APPLE)
|
||||
# error: declaration shadows a typedef in 'interval_base_set<SubType, DomainT, Compare, Interval, Alloc>'
|
||||
# error: implicit conversion loses integer precision: 'int' to 'boost::icl::bound_type' (aka 'unsigned char')
|
||||
target_compile_options(video_core PRIVATE -Wno-shadow -Wno-unused-local-typedef)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project & 2024 suyu Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "common/assert.h"
|
||||
|
@ -217,166 +217,7 @@ bool DecoderContext::OpenContext(const Decoder& decoder) {
|
|||
|
||||
return true;
|
||||
}
|
||||
#ifndef ANDROID
|
||||
// Nasty but allows linux builds to pass.
|
||||
// Requires double checks when FFMPEG gets updated.
|
||||
// Hopefully a future FFMPEG update will all and expose a solution in the public API.
|
||||
namespace {
|
||||
|
||||
typedef struct FFCodecDefault {
|
||||
const char* key;
|
||||
const char* value;
|
||||
} FFCodecDefault;
|
||||
|
||||
typedef struct FFCodec {
|
||||
/**
|
||||
* The public AVCodec. See codec.h for it.
|
||||
*/
|
||||
AVCodec p;
|
||||
|
||||
/**
|
||||
* Internal codec capabilities FF_CODEC_CAP_*.
|
||||
*/
|
||||
unsigned caps_internal : 29;
|
||||
|
||||
/**
|
||||
* This field determines the type of the codec (decoder/encoder)
|
||||
* and also the exact callback cb implemented by the codec.
|
||||
* cb_type uses enum FFCodecType values.
|
||||
*/
|
||||
unsigned cb_type : 3;
|
||||
|
||||
int priv_data_size;
|
||||
/**
|
||||
* @name Frame-level threading support functions
|
||||
* @{
|
||||
*/
|
||||
/**
|
||||
* Copy necessary context variables from a previous thread context to the current one.
|
||||
* If not defined, the next thread will start automatically; otherwise, the codec
|
||||
* must call ff_thread_finish_setup().
|
||||
*
|
||||
* dst and src will (rarely) point to the same context, in which case memcpy should be skipped.
|
||||
*/
|
||||
int (*update_thread_context)(struct AVCodecContext* dst, const struct AVCodecContext* src);
|
||||
|
||||
/**
|
||||
* Copy variables back to the user-facing context
|
||||
*/
|
||||
int (*update_thread_context_for_user)(struct AVCodecContext* dst,
|
||||
const struct AVCodecContext* src);
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
* Private codec-specific defaults.
|
||||
*/
|
||||
const FFCodecDefault* defaults;
|
||||
|
||||
/**
|
||||
* Initialize codec static data, called from av_codec_iterate().
|
||||
*
|
||||
* This is not intended for time consuming operations as it is
|
||||
* run for every codec regardless of that codec being used.
|
||||
*/
|
||||
void (*init_static_data)(struct FFCodec* codec);
|
||||
|
||||
int (*init)(struct AVCodecContext*);
|
||||
|
||||
union {
|
||||
/**
|
||||
* Decode to an AVFrame.
|
||||
* cb is in this state if cb_type is FF_CODEC_CB_TYPE_DECODE.
|
||||
*
|
||||
* @param avctx codec context
|
||||
* @param[out] frame AVFrame for output
|
||||
* @param[out] got_frame_ptr decoder sets to 0 or 1 to indicate that
|
||||
* a non-empty frame was returned in frame.
|
||||
* @param[in] avpkt AVPacket containing the data to be decoded
|
||||
* @return amount of bytes read from the packet on success,
|
||||
* negative error code on failure
|
||||
*/
|
||||
int (*decode)(struct AVCodecContext* avctx, struct AVFrame* frame, int* got_frame_ptr,
|
||||
struct AVPacket* avpkt);
|
||||
/**
|
||||
* Decode subtitle data to an AVSubtitle.
|
||||
* cb is in this state if cb_type is FF_CODEC_CB_TYPE_DECODE_SUB.
|
||||
*
|
||||
* Apart from that this is like the decode callback.
|
||||
*/
|
||||
int (*decode_sub)(struct AVCodecContext* avctx, struct AVSubtitle* sub, int* got_frame_ptr,
|
||||
const struct AVPacket* avpkt);
|
||||
/**
|
||||
* Decode API with decoupled packet/frame dataflow.
|
||||
* cb is in this state if cb_type is FF_CODEC_CB_TYPE_RECEIVE_FRAME.
|
||||
*
|
||||
* This function is called to get one output frame. It should call
|
||||
* ff_decode_get_packet() to obtain input data.
|
||||
*/
|
||||
int (*receive_frame)(struct AVCodecContext* avctx, struct AVFrame* frame);
|
||||
/**
|
||||
* Encode data to an AVPacket.
|
||||
* cb is in this state if cb_type is FF_CODEC_CB_TYPE_ENCODE
|
||||
*
|
||||
* @param avctx codec context
|
||||
* @param[out] avpkt output AVPacket
|
||||
* @param[in] frame AVFrame containing the input to be encoded
|
||||
* @param[out] got_packet_ptr encoder sets to 0 or 1 to indicate that a
|
||||
* non-empty packet was returned in avpkt.
|
||||
* @return 0 on success, negative error code on failure
|
||||
*/
|
||||
int (*encode)(struct AVCodecContext* avctx, struct AVPacket* avpkt,
|
||||
const struct AVFrame* frame, int* got_packet_ptr);
|
||||
/**
|
||||
* Encode subtitles to a raw buffer.
|
||||
* cb is in this state if cb_type is FF_CODEC_CB_TYPE_ENCODE_SUB.
|
||||
*/
|
||||
int (*encode_sub)(struct AVCodecContext* avctx, uint8_t* buf, int buf_size,
|
||||
const struct AVSubtitle* sub);
|
||||
/**
|
||||
* Encode API with decoupled frame/packet dataflow.
|
||||
* cb is in this state if cb_type is FF_CODEC_CB_TYPE_RECEIVE_PACKET.
|
||||
*
|
||||
* This function is called to get one output packet.
|
||||
* It should call ff_encode_get_frame() to obtain input data.
|
||||
*/
|
||||
int (*receive_packet)(struct AVCodecContext* avctx, struct AVPacket* avpkt);
|
||||
} cb;
|
||||
|
||||
int (*close)(struct AVCodecContext*);
|
||||
|
||||
/**
|
||||
* Flush buffers.
|
||||
* Will be called when seeking
|
||||
*/
|
||||
void (*flush)(struct AVCodecContext*);
|
||||
|
||||
/**
|
||||
* Decoding only, a comma-separated list of bitstream filters to apply to
|
||||
* packets before decoding.
|
||||
*/
|
||||
const char* bsfs;
|
||||
|
||||
/**
|
||||
* Array of pointers to hardware configurations supported by the codec,
|
||||
* or NULL if no hardware supported. The array is terminated by a NULL
|
||||
* pointer.
|
||||
*
|
||||
* The user can only access this field via avcodec_get_hw_config().
|
||||
*/
|
||||
const struct AVCodecHWConfigInternal* const* hw_configs;
|
||||
|
||||
/**
|
||||
* List of supported codec_tags, terminated by FF_CODEC_TAGS_END.
|
||||
*/
|
||||
const uint32_t* codec_tags;
|
||||
} FFCodec;
|
||||
|
||||
static av_always_inline const FFCodec* ffcodec(const AVCodec* codec) {
|
||||
return (const FFCodec*)codec;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
#endif
|
||||
bool DecoderContext::SendPacket(const Packet& packet) {
|
||||
m_temp_frame = std::make_shared<Frame>();
|
||||
m_got_frame = 0;
|
||||
|
@ -386,10 +227,8 @@ bool DecoderContext::SendPacket(const Packet& packet) {
|
|||
#ifndef ANDROID
|
||||
if (!m_codec_context->hw_device_ctx && m_codec_context->codec_id == AV_CODEC_ID_H264) {
|
||||
m_decode_order = true;
|
||||
auto* codec{ffcodec(m_decoder.GetCodec())};
|
||||
if (const int ret = codec->cb.decode(m_codec_context, m_temp_frame->GetFrame(),
|
||||
&m_got_frame, packet.GetPacket());
|
||||
ret < 0) {
|
||||
const int ret = avcodec_send_frame(m_codec_context, m_temp_frame->GetFrame());
|
||||
if (ret < 0) {
|
||||
LOG_DEBUG(Service_NVDRV, "avcodec_send_packet error {}", AVError(ret));
|
||||
return false;
|
||||
}
|
||||
|
@ -411,7 +250,6 @@ std::shared_ptr<Frame> DecoderContext::ReceiveFrame() {
|
|||
#ifndef ANDROID
|
||||
if (!m_codec_context->hw_device_ctx && m_codec_context->codec_id == AV_CODEC_ID_H264) {
|
||||
m_decode_order = true;
|
||||
auto* codec{ffcodec(m_decoder.GetCodec())};
|
||||
int ret{0};
|
||||
|
||||
if (m_got_frame == 0) {
|
||||
|
@ -419,7 +257,7 @@ std::shared_ptr<Frame> DecoderContext::ReceiveFrame() {
|
|||
auto* pkt = packet.GetPacket();
|
||||
pkt->data = nullptr;
|
||||
pkt->size = 0;
|
||||
ret = codec->cb.decode(m_codec_context, m_temp_frame->GetFrame(), &m_got_frame, pkt);
|
||||
ret = avcodec_receive_packet(m_codec_context, pkt);
|
||||
m_codec_context->has_b_frames = 0;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,19 +1,27 @@
|
|||
// SPDX-FileCopyrightText: 2014 Citra Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
#include <cstdlib>
|
||||
#include <memory>
|
||||
|
||||
#include <glad/glad.h>
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/microprofile.h"
|
||||
#include "common/settings.h"
|
||||
#include "common/telemetry.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "core/frontend/emu_window.h"
|
||||
#include "core/telemetry_session.h"
|
||||
#include "video_core/capture.h"
|
||||
#include "video_core/present.h"
|
||||
#include "video_core/renderer_opengl/gl_blit_screen.h"
|
||||
#include "video_core/renderer_opengl/gl_rasterizer.h"
|
||||
#include "video_core/renderer_opengl/gl_shader_manager.h"
|
||||
#include "video_core/renderer_opengl/gl_shader_util.h"
|
||||
#include "video_core/renderer_opengl/renderer_opengl.h"
|
||||
#include "video_core/textures/decoders.h"
|
||||
|
||||
|
@ -82,18 +90,20 @@ void APIENTRY DebugHandler(GLenum source, GLenum type, GLuint id, GLenum severit
|
|||
}
|
||||
} // Anonymous namespace
|
||||
|
||||
RendererOpenGL::RendererOpenGL(Core::Frontend::EmuWindow& emu_window_,
|
||||
RendererOpenGL::RendererOpenGL(Core::TelemetrySession& telemetry_session_,
|
||||
Core::Frontend::EmuWindow& emu_window_,
|
||||
Tegra::MaxwellDeviceMemoryManager& device_memory_, Tegra::GPU& gpu_,
|
||||
std::unique_ptr<Core::Frontend::GraphicsContext> context_)
|
||||
: RendererBase{emu_window_, std::move(context_)}, emu_window{emu_window_},
|
||||
device_memory{device_memory_}, gpu{gpu_}, device{emu_window_}, state_tracker{},
|
||||
program_manager{device},
|
||||
: RendererBase{emu_window_, std::move(context_)}, telemetry_session{telemetry_session_},
|
||||
emu_window{emu_window_}, device_memory{device_memory_}, gpu{gpu_}, device{emu_window_},
|
||||
state_tracker{}, program_manager{device},
|
||||
rasterizer(emu_window, gpu, device_memory, device, program_manager, state_tracker) {
|
||||
if (Settings::values.renderer_debug && GLAD_GL_KHR_debug) {
|
||||
glEnable(GL_DEBUG_OUTPUT);
|
||||
glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS);
|
||||
glDebugMessageCallback(DebugHandler, nullptr);
|
||||
}
|
||||
AddTelemetryFields();
|
||||
|
||||
// Initialize default attributes to match hardware's disabled attributes
|
||||
GLint max_attribs{};
|
||||
|
@ -145,6 +155,21 @@ void RendererOpenGL::Composite(std::span<const Tegra::FramebufferConfig> framebu
|
|||
render_window.OnFrameDisplayed();
|
||||
}
|
||||
|
||||
void RendererOpenGL::AddTelemetryFields() {
|
||||
const char* const gl_version{reinterpret_cast<char const*>(glGetString(GL_VERSION))};
|
||||
const char* const gpu_vendor{reinterpret_cast<char const*>(glGetString(GL_VENDOR))};
|
||||
const char* const gpu_model{reinterpret_cast<char const*>(glGetString(GL_RENDERER))};
|
||||
|
||||
LOG_INFO(Render_OpenGL, "GL_VERSION: {}", gl_version);
|
||||
LOG_INFO(Render_OpenGL, "GL_VENDOR: {}", gpu_vendor);
|
||||
LOG_INFO(Render_OpenGL, "GL_RENDERER: {}", gpu_model);
|
||||
|
||||
constexpr auto user_system = Common::Telemetry::FieldType::UserSystem;
|
||||
telemetry_session.AddField(user_system, "GPU_Vendor", std::string(gpu_vendor));
|
||||
telemetry_session.AddField(user_system, "GPU_Model", std::string(gpu_model));
|
||||
telemetry_session.AddField(user_system, "GPU_OpenGL_Version", std::string(gl_version));
|
||||
}
|
||||
|
||||
void RendererOpenGL::RenderToBuffer(std::span<const Tegra::FramebufferConfig> framebuffers,
|
||||
const Layout::FramebufferLayout& layout, void* dst) {
|
||||
GLint old_read_fb;
|
||||
|
|
|
@ -34,7 +34,8 @@ class BlitScreen;
|
|||
|
||||
class RendererOpenGL final : public VideoCore::RendererBase {
|
||||
public:
|
||||
explicit RendererOpenGL(Core::Frontend::EmuWindow& emu_window_,
|
||||
explicit RendererOpenGL(Core::TelemetrySession& telemetry_session_,
|
||||
Core::Frontend::EmuWindow& emu_window_,
|
||||
Tegra::MaxwellDeviceMemoryManager& device_memory_, Tegra::GPU& gpu_,
|
||||
std::unique_ptr<Core::Frontend::GraphicsContext> context_);
|
||||
~RendererOpenGL() override;
|
||||
|
@ -52,11 +53,14 @@ public:
|
|||
}
|
||||
|
||||
private:
|
||||
void AddTelemetryFields();
|
||||
|
||||
void RenderToBuffer(std::span<const Tegra::FramebufferConfig> framebuffers,
|
||||
const Layout::FramebufferLayout& layout, void* dst);
|
||||
void RenderScreenshot(std::span<const Tegra::FramebufferConfig> framebuffers);
|
||||
void RenderAppletCaptureLayer(std::span<const Tegra::FramebufferConfig> framebuffers);
|
||||
|
||||
Core::TelemetrySession& telemetry_session;
|
||||
Core::Frontend::EmuWindow& emu_window;
|
||||
Tegra::MaxwellDeviceMemoryManager& device_memory;
|
||||
Tegra::GPU& gpu;
|
||||
|
|
|
@ -1,17 +1,24 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include "common/logging/log.h"
|
||||
#include "common/polyfill_ranges.h"
|
||||
#include "common/scope_exit.h"
|
||||
#include "common/settings.h"
|
||||
#include "common/telemetry.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "core/frontend/graphics_context.h"
|
||||
#include "core/telemetry_session.h"
|
||||
#include "video_core/capture.h"
|
||||
#include "video_core/gpu.h"
|
||||
#include "video_core/present.h"
|
||||
|
@ -46,6 +53,37 @@ constexpr VkExtent3D CaptureImageExtent{
|
|||
};
|
||||
|
||||
constexpr VkFormat CaptureFormat = VK_FORMAT_A8B8G8R8_UNORM_PACK32;
|
||||
|
||||
std::string GetReadableVersion(u32 version) {
|
||||
return fmt::format("{}.{}.{}", VK_VERSION_MAJOR(version), VK_VERSION_MINOR(version),
|
||||
VK_VERSION_PATCH(version));
|
||||
}
|
||||
|
||||
std::string GetDriverVersion(const Device& device) {
|
||||
// Extracted from
|
||||
// https://github.com/SaschaWillems/vulkan.gpuinfo.org/blob/5dddea46ea1120b0df14eef8f15ff8e318e35462/functions.php#L308-L314
|
||||
const u32 version = device.GetDriverVersion();
|
||||
|
||||
if (device.GetDriverID() == VK_DRIVER_ID_NVIDIA_PROPRIETARY) {
|
||||
const u32 major = (version >> 22) & 0x3ff;
|
||||
const u32 minor = (version >> 14) & 0x0ff;
|
||||
const u32 secondary = (version >> 6) & 0x0ff;
|
||||
const u32 tertiary = version & 0x003f;
|
||||
return fmt::format("{}.{}.{}.{}", major, minor, secondary, tertiary);
|
||||
}
|
||||
if (device.GetDriverID() == VK_DRIVER_ID_INTEL_PROPRIETARY_WINDOWS) {
|
||||
const u32 major = version >> 14;
|
||||
const u32 minor = version & 0x3fff;
|
||||
return fmt::format("{}.{}", major, minor);
|
||||
}
|
||||
return GetReadableVersion(version);
|
||||
}
|
||||
|
||||
std::string BuildCommaSeparatedExtensions(
|
||||
const std::set<std::string, std::less<>>& available_extensions) {
|
||||
return fmt::format("{}", fmt::join(available_extensions, ","));
|
||||
}
|
||||
|
||||
} // Anonymous namespace
|
||||
|
||||
Device CreateDevice(const vk::Instance& instance, const vk::InstanceDispatch& dld,
|
||||
|
@ -60,11 +98,12 @@ Device CreateDevice(const vk::Instance& instance, const vk::InstanceDispatch& dl
|
|||
return Device(*instance, physical_device, surface, dld);
|
||||
}
|
||||
|
||||
RendererVulkan::RendererVulkan(Core::Frontend::EmuWindow& emu_window,
|
||||
RendererVulkan::RendererVulkan(Core::TelemetrySession& telemetry_session_,
|
||||
Core::Frontend::EmuWindow& emu_window,
|
||||
Tegra::MaxwellDeviceMemoryManager& device_memory_, Tegra::GPU& gpu_,
|
||||
std::unique_ptr<Core::Frontend::GraphicsContext> context_) try
|
||||
: RendererBase(emu_window, std::move(context_)), device_memory(device_memory_), gpu(gpu_),
|
||||
library(OpenLibrary(context.get())),
|
||||
: RendererBase(emu_window, std::move(context_)), telemetry_session(telemetry_session_),
|
||||
device_memory(device_memory_), gpu(gpu_), library(OpenLibrary(context.get())),
|
||||
instance(CreateInstance(*library, dld, VK_API_VERSION_1_1, render_window.GetWindowInfo().type,
|
||||
Settings::values.renderer_debug.GetValue())),
|
||||
debug_messenger(Settings::values.renderer_debug ? CreateDebugUtilsCallback(instance)
|
||||
|
@ -89,6 +128,7 @@ RendererVulkan::RendererVulkan(Core::Frontend::EmuWindow& emu_window,
|
|||
turbo_mode.emplace(instance, dld);
|
||||
scheduler.RegisterOnSubmit([this] { turbo_mode->QueueSubmitted(); });
|
||||
}
|
||||
Report();
|
||||
} catch (const vk::Exception& exception) {
|
||||
LOG_ERROR(Render_Vulkan, "Vulkan initialization failed with error: {}", exception.what());
|
||||
throw std::runtime_error{fmt::format("Vulkan initialization error {}", exception.what())};
|
||||
|
@ -126,6 +166,32 @@ void RendererVulkan::Composite(std::span<const Tegra::FramebufferConfig> framebu
|
|||
rasterizer.TickFrame();
|
||||
}
|
||||
|
||||
void RendererVulkan::Report() const {
|
||||
using namespace Common::Literals;
|
||||
const std::string vendor_name{device.GetVendorName()};
|
||||
const std::string model_name{device.GetModelName()};
|
||||
const std::string driver_version = GetDriverVersion(device);
|
||||
const std::string driver_name = fmt::format("{} {}", vendor_name, driver_version);
|
||||
|
||||
const std::string api_version = GetReadableVersion(device.ApiVersion());
|
||||
|
||||
const std::string extensions = BuildCommaSeparatedExtensions(device.GetAvailableExtensions());
|
||||
|
||||
const auto available_vram = static_cast<f64>(device.GetDeviceLocalMemory()) / f64{1_GiB};
|
||||
|
||||
LOG_INFO(Render_Vulkan, "Driver: {}", driver_name);
|
||||
LOG_INFO(Render_Vulkan, "Device: {}", model_name);
|
||||
LOG_INFO(Render_Vulkan, "Vulkan: {}", api_version);
|
||||
LOG_INFO(Render_Vulkan, "Available VRAM: {:.2f} GiB", available_vram);
|
||||
|
||||
static constexpr auto field = Common::Telemetry::FieldType::UserSystem;
|
||||
telemetry_session.AddField(field, "GPU_Vendor", vendor_name);
|
||||
telemetry_session.AddField(field, "GPU_Model", model_name);
|
||||
telemetry_session.AddField(field, "GPU_Vulkan_Driver", driver_name);
|
||||
telemetry_session.AddField(field, "GPU_Vulkan_Version", api_version);
|
||||
telemetry_session.AddField(field, "GPU_Vulkan_Extensions", extensions);
|
||||
}
|
||||
|
||||
vk::Buffer RendererVulkan::RenderToBuffer(std::span<const Tegra::FramebufferConfig> framebuffers,
|
||||
const Layout::FramebufferLayout& layout, VkFormat format,
|
||||
VkDeviceSize buffer_size) {
|
||||
|
|
|
@ -40,7 +40,8 @@ Device CreateDevice(const vk::Instance& instance, const vk::InstanceDispatch& dl
|
|||
|
||||
class RendererVulkan final : public VideoCore::RendererBase {
|
||||
public:
|
||||
explicit RendererVulkan(Core::Frontend::EmuWindow& emu_window,
|
||||
explicit RendererVulkan(Core::TelemetrySession& telemtry_session,
|
||||
Core::Frontend::EmuWindow& emu_window,
|
||||
Tegra::MaxwellDeviceMemoryManager& device_memory_, Tegra::GPU& gpu_,
|
||||
std::unique_ptr<Core::Frontend::GraphicsContext> context_);
|
||||
~RendererVulkan() override;
|
||||
|
@ -58,12 +59,15 @@ public:
|
|||
}
|
||||
|
||||
private:
|
||||
void Report() const;
|
||||
|
||||
vk::Buffer RenderToBuffer(std::span<const Tegra::FramebufferConfig> framebuffers,
|
||||
const Layout::FramebufferLayout& layout, VkFormat format,
|
||||
VkDeviceSize buffer_size);
|
||||
void RenderScreenshot(std::span<const Tegra::FramebufferConfig> framebuffers);
|
||||
void RenderAppletCaptureLayer(std::span<const Tegra::FramebufferConfig> framebuffers);
|
||||
|
||||
Core::TelemetrySession& telemetry_session;
|
||||
Tegra::MaxwellDeviceMemoryManager& device_memory;
|
||||
Tegra::GPU& gpu;
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project & 2024 suyu Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include <algorithm>
|
||||
|
@ -136,10 +136,7 @@ constexpr VkBorderColor ConvertBorderColor(const std::array<float, 4>& color) {
|
|||
auto usage = ImageUsageFlags(format_info, info.format);
|
||||
if (is_3d) {
|
||||
flags |= VK_IMAGE_CREATE_2D_ARRAY_COMPATIBLE_BIT;
|
||||
// Force usage to be VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT only on MoltenVK
|
||||
if (device.IsMoltenVK()) {
|
||||
usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
|
||||
}
|
||||
usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
|
||||
}
|
||||
const auto [samples_x, samples_y] = VideoCommon::SamplesLog2(info.num_samples);
|
||||
return VkImageCreateInfo{
|
||||
|
@ -167,7 +164,8 @@ constexpr VkBorderColor ConvertBorderColor(const std::array<float, 4>& color) {
|
|||
|
||||
[[nodiscard]] vk::Image MakeImage(const Device& device, const MemoryAllocator& allocator,
|
||||
const ImageInfo& info, std::span<const VkFormat> view_formats) {
|
||||
if (info.type == ImageType::Buffer) {
|
||||
const bool is_buffer = (info.type == ImageType::Buffer);
|
||||
if (is_buffer) {
|
||||
return vk::Image{};
|
||||
}
|
||||
VkImageCreateInfo image_ci = MakeImageCreateInfo(device, info);
|
||||
|
|
|
@ -47,7 +47,7 @@ bool SurfaceTargetIsLayered(SurfaceTarget target) {
|
|||
case SurfaceTarget::TextureCubeArray:
|
||||
return true;
|
||||
default:
|
||||
LOG_CRITICAL(HW_GPU, "Unimplemented layered surface_target={}", target);
|
||||
LOG_CRITICAL(HW_GPU, "Unimplemented surface_target={}", target);
|
||||
ASSERT(false);
|
||||
return false;
|
||||
}
|
||||
|
@ -66,7 +66,7 @@ bool SurfaceTargetIsArray(SurfaceTarget target) {
|
|||
case SurfaceTarget::TextureCubeArray:
|
||||
return true;
|
||||
default:
|
||||
LOG_CRITICAL(HW_GPU, "Unimplemented array surface_target={}", target);
|
||||
LOG_CRITICAL(HW_GPU, "Unimplemented surface_target={}", target);
|
||||
ASSERT(false);
|
||||
return false;
|
||||
}
|
||||
|
@ -89,7 +89,7 @@ PixelFormat PixelFormatFromDepthFormat(Tegra::DepthFormat format) {
|
|||
case Tegra::DepthFormat::X8Z24_UNORM:
|
||||
return PixelFormat::X8_D24_UNORM;
|
||||
default:
|
||||
UNIMPLEMENTED_MSG("Unimplemented depth format={}", format);
|
||||
UNIMPLEMENTED_MSG("Unimplemented format={}", format);
|
||||
return PixelFormat::S8_UINT_D24_UNORM;
|
||||
}
|
||||
}
|
||||
|
@ -197,7 +197,7 @@ PixelFormat PixelFormatFromRenderTargetFormat(Tegra::RenderTargetFormat format)
|
|||
case Tegra::RenderTargetFormat::R8_UINT:
|
||||
return PixelFormat::R8_UINT;
|
||||
default:
|
||||
UNIMPLEMENTED_MSG("Unimplemented render target format={}", format);
|
||||
UNIMPLEMENTED_MSG("Unimplemented format={}", format);
|
||||
return PixelFormat::A8B8G8R8_UNORM;
|
||||
}
|
||||
}
|
||||
|
@ -212,7 +212,7 @@ PixelFormat PixelFormatFromGPUPixelFormat(Service::android::PixelFormat format)
|
|||
case Service::android::PixelFormat::Bgra8888:
|
||||
return PixelFormat::B8G8R8A8_UNORM;
|
||||
default:
|
||||
UNIMPLEMENTED_MSG("Unimplemented android pixel format={}", format);
|
||||
UNIMPLEMENTED_MSG("Unimplemented format={}", format);
|
||||
return PixelFormat::A8B8G8R8_UNORM;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
#include "common/logging/log.h"
|
||||
#include "common/settings.h"
|
||||
#include "core/core.h"
|
||||
#include "video_core/host1x/gpu_device_memory_manager.h"
|
||||
#include "video_core/host1x/host1x.h"
|
||||
#include "video_core/renderer_base.h"
|
||||
#include "video_core/renderer_null/renderer_null.h"
|
||||
|
@ -18,21 +19,16 @@ namespace {
|
|||
std::unique_ptr<VideoCore::RendererBase> CreateRenderer(
|
||||
Core::System& system, Core::Frontend::EmuWindow& emu_window, Tegra::GPU& gpu,
|
||||
std::unique_ptr<Core::Frontend::GraphicsContext> context) {
|
||||
auto& telemetry_session = system.TelemetrySession();
|
||||
auto& device_memory = system.Host1x().MemoryManager();
|
||||
|
||||
switch (Settings::values.renderer_backend.GetValue()) {
|
||||
#ifdef __APPLE__
|
||||
// do nothing for now, include metal in here at later date.
|
||||
#else
|
||||
// openGL, not supported on Apple so not bothering to include if macos
|
||||
case Settings::RendererBackend::OpenGL:
|
||||
return std::make_unique<OpenGL::RendererOpenGL>(emu_window, device_memory, gpu,
|
||||
std::move(context));
|
||||
#endif
|
||||
// common renderers
|
||||
return std::make_unique<OpenGL::RendererOpenGL>(telemetry_session, emu_window,
|
||||
device_memory, gpu, std::move(context));
|
||||
case Settings::RendererBackend::Vulkan:
|
||||
return std::make_unique<Vulkan::RendererVulkan>(emu_window, device_memory, gpu,
|
||||
std::move(context));
|
||||
return std::make_unique<Vulkan::RendererVulkan>(telemetry_session, emu_window,
|
||||
device_memory, gpu, std::move(context));
|
||||
case Settings::RendererBackend::Null:
|
||||
return std::make_unique<Null::RendererNull>(emu_window, gpu, std::move(context));
|
||||
default:
|
||||
|
|
|
@ -15,11 +15,7 @@
|
|||
#define VK_USE_PLATFORM_WAYLAND_KHR
|
||||
#endif
|
||||
|
||||
#ifdef __APPLE__
|
||||
#include <MoltenVK/mvk_vulkan.h>
|
||||
#else
|
||||
#include <vulkan/vulkan.h>
|
||||
#endif
|
||||
|
||||
// Sanitize macros
|
||||
#undef CreateEvent
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project & 2024 suyu Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <algorithm>
|
||||
|
@ -580,7 +580,6 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
|
|||
VK_EXT_EXTENDED_DYNAMIC_STATE_2_EXTENSION_NAME);
|
||||
}
|
||||
}
|
||||
// Mesa RadV drivers still have broken extendedDynamicState3ColorBlendEquation support.
|
||||
if (extensions.extended_dynamic_state3 && is_radv) {
|
||||
LOG_WARNING(Render_Vulkan, "RADV has broken extendedDynamicState3ColorBlendEquation");
|
||||
features.extended_dynamic_state3.extendedDynamicState3ColorBlendEnable = false;
|
||||
|
@ -595,8 +594,6 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
|
|||
dynamic_state3_enables = false;
|
||||
}
|
||||
}
|
||||
// AMD still has broken extendedDynamicState3ColorBlendEquation on RDNA3.
|
||||
// TODO: distinguis RDNA3 from other uArchs.
|
||||
if (extensions.extended_dynamic_state3 && is_amd_driver) {
|
||||
LOG_WARNING(Render_Vulkan,
|
||||
"AMD drivers have broken extendedDynamicState3ColorBlendEquation");
|
||||
|
@ -1364,7 +1361,6 @@ void Device::CollectToolingInfo() {
|
|||
LOG_INFO(Render_Vulkan, "Attached debugging tool: {}", name);
|
||||
has_renderdoc = has_renderdoc || name == "RenderDoc";
|
||||
has_nsight_graphics = has_nsight_graphics || name == "NVIDIA Nsight Graphics";
|
||||
has_radeon_gpu_profiler = has_radeon_gpu_profiler || name == "Radeon GPU Profiler";
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -592,7 +592,7 @@ public:
|
|||
|
||||
/// Returns true when a known debugging tool is attached.
|
||||
bool HasDebuggingToolAttached() const {
|
||||
return has_renderdoc || has_nsight_graphics || has_radeon_gpu_profiler;
|
||||
return has_renderdoc || has_nsight_graphics;
|
||||
}
|
||||
|
||||
/// @returns True if compute pipelines can cause crashing.
|
||||
|
@ -702,11 +702,6 @@ public:
|
|||
return properties.driver.driverID == VK_DRIVER_ID_NVIDIA_PROPRIETARY;
|
||||
}
|
||||
|
||||
/// Checks if we are runing MolvenVK.
|
||||
bool IsMoltenVK() const noexcept {
|
||||
return properties.driver.driverID == VK_DRIVER_ID_MOLTENVK;
|
||||
}
|
||||
|
||||
NvidiaArchitecture GetNvidiaArch() const noexcept {
|
||||
return nvidia_arch;
|
||||
}
|
||||
|
@ -821,7 +816,6 @@ private:
|
|||
bool has_broken_parallel_compiling{}; ///< Has broken parallel shader compiling.
|
||||
bool has_renderdoc{}; ///< Has RenderDoc attached
|
||||
bool has_nsight_graphics{}; ///< Has Nsight Graphics attached
|
||||
bool has_radeon_gpu_profiler{}; ///< Has Radeon GPU Profiler attached.
|
||||
bool supports_d24_depth{}; ///< Supports D24 depth buffers.
|
||||
bool cant_blit_msaa{}; ///< Does not support MSAA<->MSAA blitting.
|
||||
bool must_emulate_scaled_formats{}; ///< Requires scaled vertex format emulation
|
||||
|
|
|
@ -5,6 +5,8 @@ add_library(web_service STATIC
|
|||
announce_room_json.cpp
|
||||
announce_room_json.h
|
||||
precompiled_headers.h
|
||||
telemetry_json.cpp
|
||||
telemetry_json.h
|
||||
verify_login.cpp
|
||||
verify_login.h
|
||||
verify_user_jwt.cpp
|
||||
|
|
130
src/web_service/telemetry_json.cpp
Normal file
130
src/web_service/telemetry_json.cpp
Normal file
|
@ -0,0 +1,130 @@
|
|||
// SPDX-FileCopyrightText: 2017 Citra Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
#include "common/detached_tasks.h"
|
||||
#include "web_service/telemetry_json.h"
|
||||
#include "web_service/web_backend.h"
|
||||
#include "web_service/web_result.h"
|
||||
|
||||
namespace WebService {
|
||||
|
||||
namespace Telemetry = Common::Telemetry;
|
||||
|
||||
struct TelemetryJson::Impl {
|
||||
Impl(std::string host_, std::string username_, std::string token_)
|
||||
: host{std::move(host_)}, username{std::move(username_)}, token{std::move(token_)} {}
|
||||
|
||||
nlohmann::json& TopSection() {
|
||||
return sections[static_cast<u8>(Telemetry::FieldType::None)];
|
||||
}
|
||||
|
||||
const nlohmann::json& TopSection() const {
|
||||
return sections[static_cast<u8>(Telemetry::FieldType::None)];
|
||||
}
|
||||
|
||||
template <class T>
|
||||
void Serialize(Telemetry::FieldType type, const std::string& name, T value) {
|
||||
sections[static_cast<u8>(type)][name] = value;
|
||||
}
|
||||
|
||||
void SerializeSection(Telemetry::FieldType type, const std::string& name) {
|
||||
TopSection()[name] = sections[static_cast<unsigned>(type)];
|
||||
}
|
||||
|
||||
nlohmann::json output;
|
||||
std::array<nlohmann::json, 7> sections;
|
||||
std::string host;
|
||||
std::string username;
|
||||
std::string token;
|
||||
};
|
||||
|
||||
TelemetryJson::TelemetryJson(std::string host, std::string username, std::string token)
|
||||
: impl{std::make_unique<Impl>(std::move(host), std::move(username), std::move(token))} {}
|
||||
TelemetryJson::~TelemetryJson() = default;
|
||||
|
||||
void TelemetryJson::Visit(const Telemetry::Field<bool>& field) {
|
||||
impl->Serialize(field.GetType(), field.GetName(), field.GetValue());
|
||||
}
|
||||
|
||||
void TelemetryJson::Visit(const Telemetry::Field<double>& field) {
|
||||
impl->Serialize(field.GetType(), field.GetName(), field.GetValue());
|
||||
}
|
||||
|
||||
void TelemetryJson::Visit(const Telemetry::Field<float>& field) {
|
||||
impl->Serialize(field.GetType(), field.GetName(), field.GetValue());
|
||||
}
|
||||
|
||||
void TelemetryJson::Visit(const Telemetry::Field<u8>& field) {
|
||||
impl->Serialize(field.GetType(), field.GetName(), field.GetValue());
|
||||
}
|
||||
|
||||
void TelemetryJson::Visit(const Telemetry::Field<u16>& field) {
|
||||
impl->Serialize(field.GetType(), field.GetName(), field.GetValue());
|
||||
}
|
||||
|
||||
void TelemetryJson::Visit(const Telemetry::Field<u32>& field) {
|
||||
impl->Serialize(field.GetType(), field.GetName(), field.GetValue());
|
||||
}
|
||||
|
||||
void TelemetryJson::Visit(const Telemetry::Field<u64>& field) {
|
||||
impl->Serialize(field.GetType(), field.GetName(), field.GetValue());
|
||||
}
|
||||
|
||||
void TelemetryJson::Visit(const Telemetry::Field<s8>& field) {
|
||||
impl->Serialize(field.GetType(), field.GetName(), field.GetValue());
|
||||
}
|
||||
|
||||
void TelemetryJson::Visit(const Telemetry::Field<s16>& field) {
|
||||
impl->Serialize(field.GetType(), field.GetName(), field.GetValue());
|
||||
}
|
||||
|
||||
void TelemetryJson::Visit(const Telemetry::Field<s32>& field) {
|
||||
impl->Serialize(field.GetType(), field.GetName(), field.GetValue());
|
||||
}
|
||||
|
||||
void TelemetryJson::Visit(const Telemetry::Field<s64>& field) {
|
||||
impl->Serialize(field.GetType(), field.GetName(), field.GetValue());
|
||||
}
|
||||
|
||||
void TelemetryJson::Visit(const Telemetry::Field<std::string>& field) {
|
||||
impl->Serialize(field.GetType(), field.GetName(), field.GetValue());
|
||||
}
|
||||
|
||||
void TelemetryJson::Visit(const Telemetry::Field<const char*>& field) {
|
||||
impl->Serialize(field.GetType(), field.GetName(), std::string(field.GetValue()));
|
||||
}
|
||||
|
||||
void TelemetryJson::Visit(const Telemetry::Field<std::chrono::microseconds>& field) {
|
||||
impl->Serialize(field.GetType(), field.GetName(), field.GetValue().count());
|
||||
}
|
||||
|
||||
void TelemetryJson::Complete() {
|
||||
impl->SerializeSection(Telemetry::FieldType::App, "App");
|
||||
impl->SerializeSection(Telemetry::FieldType::Session, "Session");
|
||||
impl->SerializeSection(Telemetry::FieldType::Performance, "Performance");
|
||||
impl->SerializeSection(Telemetry::FieldType::UserConfig, "UserConfig");
|
||||
impl->SerializeSection(Telemetry::FieldType::UserSystem, "UserSystem");
|
||||
|
||||
auto content = impl->TopSection().dump();
|
||||
// Send the telemetry async but don't handle the errors since they were written to the log
|
||||
Common::DetachedTasks::AddTask([host{impl->host}, content]() {
|
||||
Client{host, "", ""}.PostJson("/telemetry", content, true);
|
||||
});
|
||||
}
|
||||
|
||||
bool TelemetryJson::SubmitTestcase() {
|
||||
impl->SerializeSection(Telemetry::FieldType::App, "App");
|
||||
impl->SerializeSection(Telemetry::FieldType::Session, "Session");
|
||||
impl->SerializeSection(Telemetry::FieldType::UserFeedback, "UserFeedback");
|
||||
impl->SerializeSection(Telemetry::FieldType::UserSystem, "UserSystem");
|
||||
impl->SerializeSection(Telemetry::FieldType::UserConfig, "UserConfig");
|
||||
|
||||
auto content = impl->TopSection().dump();
|
||||
Client client(impl->host, impl->username, impl->token);
|
||||
auto value = client.PostJson("/gamedb/testcase", content, false);
|
||||
|
||||
return value.result_code == WebResult::Code::Success;
|
||||
}
|
||||
|
||||
} // namespace WebService
|
44
src/web_service/telemetry_json.h
Normal file
44
src/web_service/telemetry_json.h
Normal file
|
@ -0,0 +1,44 @@
|
|||
// SPDX-FileCopyrightText: 2017 Citra Emulator Project & 2024 suyu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <chrono>
|
||||
#include <string>
|
||||
#include "common/telemetry.h"
|
||||
|
||||
namespace WebService {
|
||||
|
||||
/**
|
||||
* Implementation of VisitorInterface that serialized telemetry into JSON, and submits it to the
|
||||
* suyu web service
|
||||
*/
|
||||
class TelemetryJson : public Common::Telemetry::VisitorInterface {
|
||||
public:
|
||||
TelemetryJson(std::string host, std::string username, std::string token);
|
||||
~TelemetryJson() override;
|
||||
|
||||
void Visit(const Common::Telemetry::Field<bool>& field) override;
|
||||
void Visit(const Common::Telemetry::Field<double>& field) override;
|
||||
void Visit(const Common::Telemetry::Field<float>& field) override;
|
||||
void Visit(const Common::Telemetry::Field<u8>& field) override;
|
||||
void Visit(const Common::Telemetry::Field<u16>& field) override;
|
||||
void Visit(const Common::Telemetry::Field<u32>& field) override;
|
||||
void Visit(const Common::Telemetry::Field<u64>& field) override;
|
||||
void Visit(const Common::Telemetry::Field<s8>& field) override;
|
||||
void Visit(const Common::Telemetry::Field<s16>& field) override;
|
||||
void Visit(const Common::Telemetry::Field<s32>& field) override;
|
||||
void Visit(const Common::Telemetry::Field<s64>& field) override;
|
||||
void Visit(const Common::Telemetry::Field<std::string>& field) override;
|
||||
void Visit(const Common::Telemetry::Field<const char*>& field) override;
|
||||
void Visit(const Common::Telemetry::Field<std::chrono::microseconds>& field) override;
|
||||
|
||||
void Complete() override;
|
||||
bool SubmitTestcase() override;
|
||||
|
||||
private:
|
||||
struct Impl;
|
||||
std::unique_ptr<Impl> impl;
|
||||
};
|
||||
|
||||
} // namespace WebService
|
Loading…
Reference in a new issue