Merge pull request #1332 from FearlessTobi/port-web-backend
Port web_service from Citra
This commit is contained in:
		
							
								
								
									
										6
									
								
								.gitmodules
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								.gitmodules
									
									
									
									
										vendored
									
									
								
							| @@ -34,3 +34,9 @@ | |||||||
| [submodule "soundtouch"] | [submodule "soundtouch"] | ||||||
| 	path = externals/soundtouch | 	path = externals/soundtouch | ||||||
| 	url = https://github.com/citra-emu/ext-soundtouch.git | 	url = https://github.com/citra-emu/ext-soundtouch.git | ||||||
|  | [submodule "libressl"] | ||||||
|  |     path = externals/libressl | ||||||
|  |     url = https://github.com/citra-emu/ext-libressl-portable.git | ||||||
|  | [submodule "discord-rpc"] | ||||||
|  |     path = externals/discord-rpc | ||||||
|  |     url = https://github.com/discordapp/discord-rpc.git | ||||||
|   | |||||||
| @@ -10,3 +10,7 @@ TRAVIS_JOB_ID | |||||||
| TRAVIS_JOB_NUMBER | TRAVIS_JOB_NUMBER | ||||||
| TRAVIS_REPO_SLUG | TRAVIS_REPO_SLUG | ||||||
| TRAVIS_TAG | TRAVIS_TAG | ||||||
|  |  | ||||||
|  | # yuzu specific flags | ||||||
|  | ENABLE_COMPATIBILITY_REPORTING | ||||||
|  | USE_DISCORD_PRESENCE | ||||||
|   | |||||||
| @@ -1,4 +1,4 @@ | |||||||
| #!/bin/bash -ex | #!/bin/bash -ex | ||||||
|  |  | ||||||
| mkdir -p "$HOME/.ccache" | mkdir -p "$HOME/.ccache" | ||||||
| docker run --env-file .travis/common/travis-ci.env -v $(pwd):/yuzu -v "$HOME/.ccache":/root/.ccache ubuntu:18.04 /bin/bash /yuzu/.travis/linux/docker.sh | docker run -e ENABLE_COMPATIBILITY_REPORTING --env-file .travis/common/travis-ci.env -v $(pwd):/yuzu -v "$HOME/.ccache":/root/.ccache ubuntu:18.04 /bin/bash /yuzu/.travis/linux/docker.sh | ||||||
|   | |||||||
| @@ -6,7 +6,7 @@ apt-get install --no-install-recommends -y build-essential git libqt5opengl5-dev | |||||||
| cd /yuzu | cd /yuzu | ||||||
|  |  | ||||||
| mkdir build && cd build | mkdir build && cd build | ||||||
| cmake .. -DYUZU_USE_BUNDLED_UNICORN=ON -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=/usr/lib/ccache/gcc -DCMAKE_CXX_COMPILER=/usr/lib/ccache/g++ -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -G Ninja | cmake .. -DYUZU_USE_BUNDLED_UNICORN=ON -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=/usr/lib/ccache/gcc -DCMAKE_CXX_COMPILER=/usr/lib/ccache/g++ -DYUZU_ENABLE_COMPATIBILITY_REPORTING=${ENABLE_COMPATIBILITY_REPORTING:-"OFF"} -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DUSE_DISCORD_PRESENCE=ON -G Ninja | ||||||
| ninja | ninja | ||||||
|  |  | ||||||
| ccache -s | ccache -s | ||||||
|   | |||||||
| @@ -9,7 +9,7 @@ export PATH="/usr/local/opt/ccache/libexec:$PATH" | |||||||
|  |  | ||||||
| mkdir build && cd build | mkdir build && cd build | ||||||
| cmake --version | cmake --version | ||||||
| cmake .. -DYUZU_USE_BUNDLED_UNICORN=ON -DCMAKE_BUILD_TYPE=Release -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON | cmake .. -DYUZU_USE_BUNDLED_UNICORN=ON -DCMAKE_BUILD_TYPE=Release -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DYUZU_ENABLE_COMPATIBILITY_REPORTING=${ENABLE_COMPATIBILITY_REPORTING:-"OFF"} -DUSE_DISCORD_PRESENCE=ON | ||||||
| make -j4 | make -j4 | ||||||
|  |  | ||||||
| ccache -s | ccache -s | ||||||
|   | |||||||
| @@ -15,10 +15,14 @@ CMAKE_DEPENDENT_OPTION(YUZU_USE_BUNDLED_SDL2 "Download bundled SDL2 binaries" ON | |||||||
| option(ENABLE_QT "Enable the Qt frontend" ON) | option(ENABLE_QT "Enable the Qt frontend" ON) | ||||||
| CMAKE_DEPENDENT_OPTION(YUZU_USE_BUNDLED_QT "Download bundled Qt binaries" ON "ENABLE_QT;MSVC" OFF) | CMAKE_DEPENDENT_OPTION(YUZU_USE_BUNDLED_QT "Download bundled Qt binaries" ON "ENABLE_QT;MSVC" OFF) | ||||||
|  |  | ||||||
|  | option(ENABLE_WEB_SERVICE "Enable web services (telemetry, etc.)" ON) | ||||||
|  |  | ||||||
| option(YUZU_USE_BUNDLED_UNICORN "Build/Download bundled Unicorn" ON) | option(YUZU_USE_BUNDLED_UNICORN "Build/Download bundled Unicorn" ON) | ||||||
|  |  | ||||||
| option(ENABLE_CUBEB "Enables the cubeb audio backend" ON) | option(ENABLE_CUBEB "Enables the cubeb audio backend" ON) | ||||||
|  |  | ||||||
|  | option(USE_DISCORD_PRESENCE "Enables Discord Rich Presence" OFF) | ||||||
|  |  | ||||||
| if(NOT EXISTS ${CMAKE_SOURCE_DIR}/.git/hooks/pre-commit) | if(NOT EXISTS ${CMAKE_SOURCE_DIR}/.git/hooks/pre-commit) | ||||||
|     message(STATUS "Copying pre-commit hook") |     message(STATUS "Copying pre-commit hook") | ||||||
|     file(COPY hooks/pre-commit |     file(COPY hooks/pre-commit | ||||||
|   | |||||||
| @@ -39,11 +39,12 @@ before_build: | |||||||
|   - mkdir %BUILD_TYPE%_build |   - mkdir %BUILD_TYPE%_build | ||||||
|   - cd %BUILD_TYPE%_build |   - cd %BUILD_TYPE%_build | ||||||
|   - ps: | |   - ps: | | ||||||
|  |         $COMPAT = if ($env:ENABLE_COMPATIBILITY_REPORTING -eq $null) {0} else {$env:ENABLE_COMPATIBILITY_REPORTING} | ||||||
|         if ($env:BUILD_TYPE -eq 'msvc') { |         if ($env:BUILD_TYPE -eq 'msvc') { | ||||||
|           # redirect stderr and change the exit code to prevent powershell from cancelling the build if cmake prints a warning |           # redirect stderr and change the exit code to prevent powershell from cancelling the build if cmake prints a warning | ||||||
|           cmd /C 'cmake -G "Visual Studio 15 2017 Win64" -DYUZU_USE_BUNDLED_QT=1 -DYUZU_USE_BUNDLED_SDL2=1 -DYUZU_USE_BUNDLED_UNICORN=1 -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON .. 2>&1 && exit 0' |           cmd /C 'cmake -G "Visual Studio 15 2017 Win64" -DYUZU_USE_BUNDLED_QT=1 -DYUZU_USE_BUNDLED_SDL2=1 -DYUZU_USE_BUNDLED_UNICORN=1 -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DYUZU_ENABLE_COMPATIBILITY_REPORTING=${COMPAT} -DUSE_DISCORD_PRESENCE=ON .. 2>&1 && exit 0' | ||||||
|         } else { |         } else { | ||||||
|           C:\msys64\usr\bin\bash.exe -lc "cmake -G 'MSYS Makefiles' -DYUZU_BUILD_UNICORN=1 -DCMAKE_BUILD_TYPE=Release -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON .. 2>&1" |           C:\msys64\usr\bin\bash.exe -lc "cmake -G 'MSYS Makefiles' -DYUZU_BUILD_UNICORN=1 -DCMAKE_BUILD_TYPE=Release -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DYUZU_ENABLE_COMPATIBILITY_REPORTING=${COMPAT} -DUSE_DISCORD_PRESENCE=ON .. 2>&1" | ||||||
|         } |         } | ||||||
|   - cd .. |   - cd .. | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										25
									
								
								externals/CMakeLists.txt
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										25
									
								
								externals/CMakeLists.txt
									
									
									
									
										vendored
									
									
								
							| @@ -70,3 +70,28 @@ if(ENABLE_CUBEB) | |||||||
|     set(BUILD_TESTS OFF CACHE BOOL "") |     set(BUILD_TESTS OFF CACHE BOOL "") | ||||||
|     add_subdirectory(cubeb EXCLUDE_FROM_ALL) |     add_subdirectory(cubeb EXCLUDE_FROM_ALL) | ||||||
| endif() | endif() | ||||||
|  |  | ||||||
|  | # DiscordRPC | ||||||
|  | if (USE_DISCORD_PRESENCE) | ||||||
|  |     add_subdirectory(discord-rpc EXCLUDE_FROM_ALL) | ||||||
|  |     target_include_directories(discord-rpc INTERFACE ./discord-rpc/include) | ||||||
|  | endif() | ||||||
|  |  | ||||||
|  | if (ENABLE_WEB_SERVICE) | ||||||
|  |     # LibreSSL | ||||||
|  |     set(LIBRESSL_SKIP_INSTALL ON CACHE BOOL "") | ||||||
|  |     add_subdirectory(libressl EXCLUDE_FROM_ALL) | ||||||
|  |     target_include_directories(ssl INTERFACE ./libressl/include) | ||||||
|  | 	target_compile_definitions(ssl PRIVATE -DHAVE_INET_NTOP) | ||||||
|  |  | ||||||
|  |     # lurlparser | ||||||
|  |     add_subdirectory(lurlparser EXCLUDE_FROM_ALL) | ||||||
|  |  | ||||||
|  |     # httplib | ||||||
|  |     add_library(httplib INTERFACE) | ||||||
|  |     target_include_directories(httplib INTERFACE ./httplib) | ||||||
|  |  | ||||||
|  |     # JSON | ||||||
|  |     add_library(json-headers INTERFACE) | ||||||
|  |     target_include_directories(json-headers INTERFACE ./json) | ||||||
|  | endif() | ||||||
|   | |||||||
							
								
								
									
										1
									
								
								externals/discord-rpc
									
									
									
									
										vendored
									
									
										Submodule
									
								
							
							
								
								
								
								
								
							
						
						
									
										1
									
								
								externals/discord-rpc
									
									
									
									
										vendored
									
									
										Submodule
									
								
							 Submodule externals/discord-rpc added at e32d001809
									
								
							
							
								
								
									
										15
									
								
								externals/httplib/README.md
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								externals/httplib/README.md
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,15 @@ | |||||||
|  | From https://github.com/yhirose/cpp-httplib/commit/d9479bc0b12e8a1e8bce2d34da4feeef488581f3 | ||||||
|  |  | ||||||
|  | MIT License | ||||||
|  |  | ||||||
|  | === | ||||||
|  |  | ||||||
|  | cpp-httplib | ||||||
|  |  | ||||||
|  | A C++11 header-only HTTP library. | ||||||
|  |  | ||||||
|  | It's extremely easy to setup. Just include httplib.h file in your code! | ||||||
|  |  | ||||||
|  | Inspired by Sinatra and express. | ||||||
|  |  | ||||||
|  | © 2017 Yuji Hirose | ||||||
							
								
								
									
										2344
									
								
								externals/httplib/httplib.h
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										2344
									
								
								externals/httplib/httplib.h
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										9
									
								
								externals/json/README.md
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								externals/json/README.md
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,9 @@ | |||||||
|  | JSON for Modern C++ | ||||||
|  | =================== | ||||||
|  |  | ||||||
|  | v3.1.2 | ||||||
|  |  | ||||||
|  | This is a mirror providing the single required header file. | ||||||
|  |  | ||||||
|  | The original repository can be found at: | ||||||
|  | https://github.com/nlohmann/json/commit/d2dd27dc3b8472dbaa7d66f83619b3ebcd9185fe | ||||||
							
								
								
									
										17300
									
								
								externals/json/json.hpp
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										17300
									
								
								externals/json/json.hpp
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										1
									
								
								externals/libressl
									
									
									
									
										vendored
									
									
										Submodule
									
								
							
							
								
								
								
								
								
							
						
						
									
										1
									
								
								externals/libressl
									
									
									
									
										vendored
									
									
										Submodule
									
								
							 Submodule externals/libressl added at 7d01cb01cb
									
								
							
							
								
								
									
										8
									
								
								externals/lurlparser/CMakeLists.txt
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								externals/lurlparser/CMakeLists.txt
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,8 @@ | |||||||
|  | add_library(lurlparser | ||||||
|  |         LUrlParser.cpp | ||||||
|  |         LUrlParser.h | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | create_target_directory_groups(lurlparser) | ||||||
|  |  | ||||||
|  | target_include_directories(lurlparser INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}) | ||||||
							
								
								
									
										265
									
								
								externals/lurlparser/LUrlParser.cpp
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										265
									
								
								externals/lurlparser/LUrlParser.cpp
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,265 @@ | |||||||
|  | /* | ||||||
|  |  * Lightweight URL & URI parser (RFC 1738, RFC 3986) | ||||||
|  |  * https://github.com/corporateshark/LUrlParser | ||||||
|  |  * | ||||||
|  |  * The MIT License (MIT) | ||||||
|  |  * | ||||||
|  |  * Copyright (C) 2015 Sergey Kosarevsky (sk@linderdaum.com) | ||||||
|  |  * | ||||||
|  |  * Permission is hereby granted, free of charge, to any person obtaining a copy | ||||||
|  |  * of this software and associated documentation files (the "Software"), to deal | ||||||
|  |  * in the Software without restriction, including without limitation the rights | ||||||
|  |  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||||||
|  |  * copies of the Software, and to permit persons to whom the Software is | ||||||
|  |  * furnished to do so, subject to the following conditions: | ||||||
|  |  * | ||||||
|  |  * The above copyright notice and this permission notice shall be included in all | ||||||
|  |  * copies or substantial portions of the Software. | ||||||
|  |  * | ||||||
|  |  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||||||
|  |  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||||||
|  |  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||||||
|  |  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||||||
|  |  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||||||
|  |  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||||||
|  |  * SOFTWARE. | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | #include "LUrlParser.h" | ||||||
|  |  | ||||||
|  | #include <algorithm> | ||||||
|  | #include <cstring> | ||||||
|  | #include <stdlib.h> | ||||||
|  |  | ||||||
|  | // check if the scheme name is valid | ||||||
|  | static bool IsSchemeValid( const std::string& SchemeName ) | ||||||
|  | { | ||||||
|  |     for ( auto c : SchemeName  ) | ||||||
|  |     { | ||||||
|  |         if ( !isalpha( c ) && c != '+' && c != '-' && c != '.' ) return false; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return true; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bool LUrlParser::clParseURL::GetPort( int* OutPort ) const | ||||||
|  | { | ||||||
|  |     if ( !IsValid() ) { return false; } | ||||||
|  |  | ||||||
|  |     int Port = atoi( m_Port.c_str() ); | ||||||
|  |  | ||||||
|  |     if ( Port <= 0 || Port > 65535 ) { return false; } | ||||||
|  |  | ||||||
|  |     if ( OutPort ) { *OutPort = Port; } | ||||||
|  |  | ||||||
|  |     return true; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // based on RFC 1738 and RFC 3986 | ||||||
|  | LUrlParser::clParseURL LUrlParser::clParseURL::ParseURL( const std::string& URL ) | ||||||
|  | { | ||||||
|  |     LUrlParser::clParseURL Result; | ||||||
|  |  | ||||||
|  |     const char* CurrentString = URL.c_str(); | ||||||
|  |  | ||||||
|  |     /* | ||||||
|  |      *	<scheme>:<scheme-specific-part> | ||||||
|  |      *	<scheme> := [a-z\+\-\.]+ | ||||||
|  |      *	For resiliency, programs interpreting URLs should treat upper case letters as equivalent to lower case in scheme names | ||||||
|  |      */ | ||||||
|  |  | ||||||
|  |     // try to read scheme | ||||||
|  |     { | ||||||
|  |         const char* LocalString = strchr( CurrentString, ':' ); | ||||||
|  |  | ||||||
|  |         if ( !LocalString ) | ||||||
|  |         { | ||||||
|  |             return clParseURL( LUrlParserError_NoUrlCharacter ); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         // save the scheme name | ||||||
|  |         Result.m_Scheme = std::string( CurrentString, LocalString - CurrentString ); | ||||||
|  |  | ||||||
|  |         if ( !IsSchemeValid( Result.m_Scheme ) ) | ||||||
|  |         { | ||||||
|  |             return clParseURL( LUrlParserError_InvalidSchemeName ); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         // scheme should be lowercase | ||||||
|  |         std::transform( Result.m_Scheme.begin(), Result.m_Scheme.end(), Result.m_Scheme.begin(), ::tolower ); | ||||||
|  |  | ||||||
|  |         // skip ':' | ||||||
|  |         CurrentString = LocalString+1; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /* | ||||||
|  |      *	//<user>:<password>@<host>:<port>/<url-path> | ||||||
|  |      *	any ":", "@" and "/" must be normalized | ||||||
|  |      */ | ||||||
|  |  | ||||||
|  |     // skip "//" | ||||||
|  |     if ( *CurrentString++ != '/' ) return clParseURL( LUrlParserError_NoDoubleSlash ); | ||||||
|  |     if ( *CurrentString++ != '/' ) return clParseURL( LUrlParserError_NoDoubleSlash ); | ||||||
|  |  | ||||||
|  |     // check if the user name and password are specified | ||||||
|  |     bool bHasUserName = false; | ||||||
|  |  | ||||||
|  |     const char* LocalString = CurrentString; | ||||||
|  |  | ||||||
|  |     while ( *LocalString ) | ||||||
|  |     { | ||||||
|  |         if ( *LocalString == '@' ) | ||||||
|  |         { | ||||||
|  |             // user name and password are specified | ||||||
|  |             bHasUserName = true; | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  |         else if ( *LocalString == '/' ) | ||||||
|  |         { | ||||||
|  |             // end of <host>:<port> specification | ||||||
|  |             bHasUserName = false; | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         LocalString++; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // user name and password | ||||||
|  |     LocalString = CurrentString; | ||||||
|  |  | ||||||
|  |     if ( bHasUserName ) | ||||||
|  |     { | ||||||
|  |         // read user name | ||||||
|  |         while ( *LocalString && *LocalString != ':' && *LocalString != '@' ) LocalString++; | ||||||
|  |  | ||||||
|  |         Result.m_UserName = std::string( CurrentString, LocalString - CurrentString ); | ||||||
|  |  | ||||||
|  |         // proceed with the current pointer | ||||||
|  |         CurrentString = LocalString; | ||||||
|  |  | ||||||
|  |         if ( *CurrentString == ':' ) | ||||||
|  |         { | ||||||
|  |             // skip ':' | ||||||
|  |             CurrentString++; | ||||||
|  |  | ||||||
|  |             // read password | ||||||
|  |             LocalString = CurrentString; | ||||||
|  |  | ||||||
|  |             while ( *LocalString && *LocalString != '@' ) LocalString++; | ||||||
|  |  | ||||||
|  |             Result.m_Password = std::string( CurrentString, LocalString - CurrentString ); | ||||||
|  |  | ||||||
|  |             CurrentString = LocalString; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         // skip '@' | ||||||
|  |         if ( *CurrentString != '@' ) | ||||||
|  |         { | ||||||
|  |             return clParseURL( LUrlParserError_NoAtSign ); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         CurrentString++; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     bool bHasBracket = ( *CurrentString == '[' ); | ||||||
|  |  | ||||||
|  |     // go ahead, read the host name | ||||||
|  |     LocalString = CurrentString; | ||||||
|  |  | ||||||
|  |     while ( *LocalString ) | ||||||
|  |     { | ||||||
|  |         if ( bHasBracket && *LocalString == ']' ) | ||||||
|  |         { | ||||||
|  |             // end of IPv6 address | ||||||
|  |             LocalString++; | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  |         else if ( !bHasBracket && ( *LocalString == ':' || *LocalString == '/' ) ) | ||||||
|  |         { | ||||||
|  |             // port number is specified | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         LocalString++; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     Result.m_Host = std::string( CurrentString, LocalString - CurrentString ); | ||||||
|  |  | ||||||
|  |     CurrentString = LocalString; | ||||||
|  |  | ||||||
|  |     // is port number specified? | ||||||
|  |     if ( *CurrentString == ':' ) | ||||||
|  |     { | ||||||
|  |         CurrentString++; | ||||||
|  |  | ||||||
|  |         // read port number | ||||||
|  |         LocalString = CurrentString; | ||||||
|  |  | ||||||
|  |         while ( *LocalString && *LocalString != '/' ) LocalString++; | ||||||
|  |  | ||||||
|  |         Result.m_Port = std::string( CurrentString, LocalString - CurrentString ); | ||||||
|  |  | ||||||
|  |         CurrentString = LocalString; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // end of string | ||||||
|  |     if ( !*CurrentString ) | ||||||
|  |     { | ||||||
|  |         Result.m_ErrorCode = LUrlParserError_Ok; | ||||||
|  |  | ||||||
|  |         return Result; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // skip '/' | ||||||
|  |     if ( *CurrentString != '/' ) | ||||||
|  |     { | ||||||
|  |         return clParseURL( LUrlParserError_NoSlash ); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     CurrentString++; | ||||||
|  |  | ||||||
|  |     // parse the path | ||||||
|  |     LocalString = CurrentString; | ||||||
|  |  | ||||||
|  |     while ( *LocalString && *LocalString != '#' && *LocalString != '?' ) LocalString++; | ||||||
|  |  | ||||||
|  |     Result.m_Path = std::string( CurrentString, LocalString - CurrentString ); | ||||||
|  |  | ||||||
|  |     CurrentString = LocalString; | ||||||
|  |  | ||||||
|  |     // check for query | ||||||
|  |     if ( *CurrentString == '?' ) | ||||||
|  |     { | ||||||
|  |         // skip '?' | ||||||
|  |         CurrentString++; | ||||||
|  |  | ||||||
|  |         // read query | ||||||
|  |         LocalString = CurrentString; | ||||||
|  |  | ||||||
|  |         while ( *LocalString && *LocalString != '#' ) LocalString++; | ||||||
|  |  | ||||||
|  |         Result.m_Query = std::string( CurrentString, LocalString - CurrentString ); | ||||||
|  |  | ||||||
|  |         CurrentString = LocalString; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // check for fragment | ||||||
|  |     if ( *CurrentString == '#' ) | ||||||
|  |     { | ||||||
|  |         // skip '#' | ||||||
|  |         CurrentString++; | ||||||
|  |  | ||||||
|  |         // read fragment | ||||||
|  |         LocalString = CurrentString; | ||||||
|  |  | ||||||
|  |         while ( *LocalString ) LocalString++; | ||||||
|  |  | ||||||
|  |         Result.m_Fragment = std::string( CurrentString, LocalString - CurrentString ); | ||||||
|  |  | ||||||
|  |         CurrentString = LocalString; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     Result.m_ErrorCode = LUrlParserError_Ok; | ||||||
|  |  | ||||||
|  |     return Result; | ||||||
|  | } | ||||||
							
								
								
									
										78
									
								
								externals/lurlparser/LUrlParser.h
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										78
									
								
								externals/lurlparser/LUrlParser.h
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,78 @@ | |||||||
|  | /* | ||||||
|  |  * Lightweight URL & URI parser (RFC 1738, RFC 3986) | ||||||
|  |  * https://github.com/corporateshark/LUrlParser | ||||||
|  |  * | ||||||
|  |  * The MIT License (MIT) | ||||||
|  |  * | ||||||
|  |  * Copyright (C) 2015 Sergey Kosarevsky (sk@linderdaum.com) | ||||||
|  |  * | ||||||
|  |  * Permission is hereby granted, free of charge, to any person obtaining a copy | ||||||
|  |  * of this software and associated documentation files (the "Software"), to deal | ||||||
|  |  * in the Software without restriction, including without limitation the rights | ||||||
|  |  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||||||
|  |  * copies of the Software, and to permit persons to whom the Software is | ||||||
|  |  * furnished to do so, subject to the following conditions: | ||||||
|  |  * | ||||||
|  |  * The above copyright notice and this permission notice shall be included in all | ||||||
|  |  * copies or substantial portions of the Software. | ||||||
|  |  * | ||||||
|  |  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||||||
|  |  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||||||
|  |  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||||||
|  |  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||||||
|  |  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||||||
|  |  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||||||
|  |  * SOFTWARE. | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | #include <string> | ||||||
|  |  | ||||||
|  | namespace LUrlParser | ||||||
|  | { | ||||||
|  | enum LUrlParserError | ||||||
|  | { | ||||||
|  |     LUrlParserError_Ok = 0, | ||||||
|  |     LUrlParserError_Uninitialized = 1, | ||||||
|  |     LUrlParserError_NoUrlCharacter = 2, | ||||||
|  |     LUrlParserError_InvalidSchemeName = 3, | ||||||
|  |     LUrlParserError_NoDoubleSlash = 4, | ||||||
|  |     LUrlParserError_NoAtSign = 5, | ||||||
|  |     LUrlParserError_UnexpectedEndOfLine = 6, | ||||||
|  |     LUrlParserError_NoSlash = 7, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | class clParseURL | ||||||
|  | { | ||||||
|  | public: | ||||||
|  |     LUrlParserError m_ErrorCode; | ||||||
|  |     std::string m_Scheme; | ||||||
|  |     std::string m_Host; | ||||||
|  |     std::string m_Port; | ||||||
|  |     std::string m_Path; | ||||||
|  |     std::string m_Query; | ||||||
|  |     std::string m_Fragment; | ||||||
|  |     std::string m_UserName; | ||||||
|  |     std::string m_Password; | ||||||
|  |  | ||||||
|  |     clParseURL() | ||||||
|  |             : m_ErrorCode( LUrlParserError_Uninitialized ) | ||||||
|  |     {} | ||||||
|  |  | ||||||
|  |     /// return 'true' if the parsing was successful | ||||||
|  |     bool IsValid() const { return m_ErrorCode == LUrlParserError_Ok; } | ||||||
|  |  | ||||||
|  |     /// helper to convert the port number to int, return 'true' if the port is valid (within the 0..65535 range) | ||||||
|  |     bool GetPort( int* OutPort ) const; | ||||||
|  |  | ||||||
|  |     /// parse the URL | ||||||
|  |     static clParseURL ParseURL( const std::string& URL ); | ||||||
|  |  | ||||||
|  | private: | ||||||
|  |     explicit clParseURL( LUrlParserError ErrorCode ) | ||||||
|  |             : m_ErrorCode( ErrorCode ) | ||||||
|  |     {} | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | } // namespace LUrlParser | ||||||
							
								
								
									
										19
									
								
								externals/lurlparser/README.md
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								externals/lurlparser/README.md
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,19 @@ | |||||||
|  | From https://github.com/corporateshark/LUrlParser/commit/455d5e2d27e3946f11ad0328fee9ee2628e6a8e2 | ||||||
|  |  | ||||||
|  | MIT License | ||||||
|  |  | ||||||
|  | === | ||||||
|  |  | ||||||
|  | Lightweight URL & URI parser (RFC 1738, RFC 3986) | ||||||
|  |  | ||||||
|  | (C) Sergey Kosarevsky, 2015 | ||||||
|  |  | ||||||
|  | @corporateshark sk@linderdaum.com | ||||||
|  |  | ||||||
|  | http://www.linderdaum.com | ||||||
|  |  | ||||||
|  | http://blog.linderdaum.com | ||||||
|  |  | ||||||
|  | ============================= | ||||||
|  |  | ||||||
|  | A tiny and lightweight URL & URI parser (RFC 1738, RFC 3986) written in C++. | ||||||
| @@ -13,3 +13,6 @@ endif() | |||||||
| if (ENABLE_QT) | if (ENABLE_QT) | ||||||
|     add_subdirectory(yuzu) |     add_subdirectory(yuzu) | ||||||
| endif() | endif() | ||||||
|  | if (ENABLE_WEB_SERVICE) | ||||||
|  |     add_subdirectory(web_service) | ||||||
|  | endif() | ||||||
|   | |||||||
| @@ -41,6 +41,8 @@ configure_file("${CMAKE_CURRENT_SOURCE_DIR}/scm_rev.cpp.in" "${CMAKE_CURRENT_SOU | |||||||
| add_library(common STATIC | add_library(common STATIC | ||||||
|     alignment.h |     alignment.h | ||||||
|     assert.h |     assert.h | ||||||
|  |     detached_tasks.cpp | ||||||
|  |     detached_tasks.h | ||||||
|     bit_field.h |     bit_field.h | ||||||
|     bit_set.h |     bit_set.h | ||||||
|     cityhash.cpp |     cityhash.cpp | ||||||
| @@ -87,6 +89,7 @@ add_library(common STATIC | |||||||
|     timer.cpp |     timer.cpp | ||||||
|     timer.h |     timer.h | ||||||
|     vector_math.h |     vector_math.h | ||||||
|  |     web_result.h | ||||||
| ) | ) | ||||||
|  |  | ||||||
| if(ARCHITECTURE_x86_64) | if(ARCHITECTURE_x86_64) | ||||||
|   | |||||||
							
								
								
									
										41
									
								
								src/common/detached_tasks.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								src/common/detached_tasks.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,41 @@ | |||||||
|  | // Copyright 2018 Citra Emulator Project | ||||||
|  | // Licensed under GPLv2 or any later version | ||||||
|  | // Refer to the license.txt file included. | ||||||
|  |  | ||||||
|  | #include <thread> | ||||||
|  | #include "common/assert.h" | ||||||
|  | #include "common/detached_tasks.h" | ||||||
|  |  | ||||||
|  | namespace Common { | ||||||
|  |  | ||||||
|  | DetachedTasks* DetachedTasks::instance = nullptr; | ||||||
|  |  | ||||||
|  | DetachedTasks::DetachedTasks() { | ||||||
|  |     ASSERT(instance == nullptr); | ||||||
|  |     instance = this; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void DetachedTasks::WaitForAllTasks() { | ||||||
|  |     std::unique_lock<std::mutex> lock(mutex); | ||||||
|  |     cv.wait(lock, [this]() { return count == 0; }); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | DetachedTasks::~DetachedTasks() { | ||||||
|  |     std::unique_lock<std::mutex> lock(mutex); | ||||||
|  |     ASSERT(count == 0); | ||||||
|  |     instance = nullptr; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void DetachedTasks::AddTask(std::function<void()> task) { | ||||||
|  |     std::unique_lock<std::mutex> lock(instance->mutex); | ||||||
|  |     ++instance->count; | ||||||
|  |     std::thread([task{std::move(task)}]() { | ||||||
|  |         task(); | ||||||
|  |         std::unique_lock<std::mutex> lock(instance->mutex); | ||||||
|  |         --instance->count; | ||||||
|  |         std::notify_all_at_thread_exit(instance->cv, std::move(lock)); | ||||||
|  |     }) | ||||||
|  |         .detach(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | } // namespace Common | ||||||
							
								
								
									
										40
									
								
								src/common/detached_tasks.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								src/common/detached_tasks.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,40 @@ | |||||||
|  | // Copyright 2018 Citra Emulator Project | ||||||
|  | // Licensed under GPLv2 or any later version | ||||||
|  | // Refer to the license.txt file included. | ||||||
|  |  | ||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | #include <condition_variable> | ||||||
|  | #include <functional> | ||||||
|  |  | ||||||
|  | namespace Common { | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * A background manager which ensures that all detached task is finished before program exits. | ||||||
|  |  * | ||||||
|  |  * Some tasks, telemetry submission for example, prefer executing asynchronously and don't care | ||||||
|  |  * about the result. These tasks are suitable for std::thread::detach(). However, this is unsafe if | ||||||
|  |  * the task is launched just before the program exits (which is a common case for telemetry), so we | ||||||
|  |  * need to block on these tasks on program exit. | ||||||
|  |  * | ||||||
|  |  * To make detached task safe, a single DetachedTasks object should be placed in the main(), and | ||||||
|  |  * call WaitForAllTasks() after all program execution but before global/static variable destruction. | ||||||
|  |  * Any potentially unsafe detached task should be executed via DetachedTasks::AddTask. | ||||||
|  |  */ | ||||||
|  | class DetachedTasks { | ||||||
|  | public: | ||||||
|  |     DetachedTasks(); | ||||||
|  |     ~DetachedTasks(); | ||||||
|  |     void WaitForAllTasks(); | ||||||
|  |  | ||||||
|  |     static void AddTask(std::function<void()> task); | ||||||
|  |  | ||||||
|  | private: | ||||||
|  |     static DetachedTasks* instance; | ||||||
|  |  | ||||||
|  |     std::condition_variable cv; | ||||||
|  |     std::mutex mutex; | ||||||
|  |     int count = 0; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | } // namespace Common | ||||||
							
								
								
									
										24
									
								
								src/common/web_result.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								src/common/web_result.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,24 @@ | |||||||
|  | // Copyright 2018 yuzu Emulator Project | ||||||
|  | // Licensed under GPLv2 or any later version | ||||||
|  | // Refer to the license.txt file included. | ||||||
|  |  | ||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | #include <string> | ||||||
|  |  | ||||||
|  | namespace Common { | ||||||
|  | struct WebResult { | ||||||
|  |     enum class Code : u32 { | ||||||
|  |         Success, | ||||||
|  |         InvalidURL, | ||||||
|  |         CredentialsMissing, | ||||||
|  |         LibError, | ||||||
|  |         HttpError, | ||||||
|  |         WrongContent, | ||||||
|  |         NoWebservice, | ||||||
|  |     }; | ||||||
|  |     Code result_code; | ||||||
|  |     std::string result_string; | ||||||
|  |     std::string returned_data; | ||||||
|  | }; | ||||||
|  | } // namespace Common | ||||||
| @@ -396,6 +396,10 @@ create_target_directory_groups(core) | |||||||
|  |  | ||||||
| target_link_libraries(core PUBLIC common PRIVATE audio_core video_core) | target_link_libraries(core PUBLIC common PRIVATE audio_core video_core) | ||||||
| target_link_libraries(core PUBLIC Boost::boost PRIVATE fmt lz4_static mbedtls opus unicorn open_source_archives) | target_link_libraries(core PUBLIC Boost::boost PRIVATE fmt lz4_static mbedtls opus unicorn open_source_archives) | ||||||
|  | if (ENABLE_WEB_SERVICE) | ||||||
|  |     add_definitions(-DENABLE_WEB_SERVICE) | ||||||
|  |     target_link_libraries(core PUBLIC json-headers web_service) | ||||||
|  | endif() | ||||||
|  |  | ||||||
| if (ARCHITECTURE_x86_64) | if (ARCHITECTURE_x86_64) | ||||||
|     target_sources(core PRIVATE |     target_sources(core PRIVATE | ||||||
|   | |||||||
| @@ -155,6 +155,12 @@ struct Values { | |||||||
|     // Debugging |     // Debugging | ||||||
|     bool use_gdbstub; |     bool use_gdbstub; | ||||||
|     u16 gdbstub_port; |     u16 gdbstub_port; | ||||||
|  |  | ||||||
|  |     // WebService | ||||||
|  |     bool enable_telemetry; | ||||||
|  |     std::string web_api_url; | ||||||
|  |     std::string yuzu_username; | ||||||
|  |     std::string yuzu_token; | ||||||
| } extern values; | } extern values; | ||||||
|  |  | ||||||
| void Apply(); | void Apply(); | ||||||
|   | |||||||
| @@ -6,6 +6,8 @@ | |||||||
| #include "common/common_types.h" | #include "common/common_types.h" | ||||||
| #include "common/file_util.h" | #include "common/file_util.h" | ||||||
|  |  | ||||||
|  | #include <mbedtls/ctr_drbg.h> | ||||||
|  | #include <mbedtls/entropy.h> | ||||||
| #include "core/core.h" | #include "core/core.h" | ||||||
| #include "core/file_sys/control_metadata.h" | #include "core/file_sys/control_metadata.h" | ||||||
| #include "core/file_sys/patch_manager.h" | #include "core/file_sys/patch_manager.h" | ||||||
| @@ -13,10 +15,31 @@ | |||||||
| #include "core/settings.h" | #include "core/settings.h" | ||||||
| #include "core/telemetry_session.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 Core { | ||||||
|  |  | ||||||
| static u64 GenerateTelemetryId() { | static u64 GenerateTelemetryId() { | ||||||
|     u64 telemetry_id{}; |     u64 telemetry_id{}; | ||||||
|  |  | ||||||
|  |     mbedtls_entropy_context entropy; | ||||||
|  |     mbedtls_entropy_init(&entropy); | ||||||
|  |     mbedtls_ctr_drbg_context ctr_drbg; | ||||||
|  |     std::string personalization = "yuzu 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.c_str()), | ||||||
|  |                                  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; |     return telemetry_id; | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -25,14 +48,21 @@ u64 GetTelemetryId() { | |||||||
|     const std::string filename{FileUtil::GetUserPath(FileUtil::UserPath::ConfigDir) + |     const std::string filename{FileUtil::GetUserPath(FileUtil::UserPath::ConfigDir) + | ||||||
|                                "telemetry_id"}; |                                "telemetry_id"}; | ||||||
|  |  | ||||||
|     if (FileUtil::Exists(filename)) { |     bool generate_new_id = !FileUtil::Exists(filename); | ||||||
|  |     if (!generate_new_id) { | ||||||
|         FileUtil::IOFile file(filename, "rb"); |         FileUtil::IOFile file(filename, "rb"); | ||||||
|         if (!file.IsOpen()) { |         if (!file.IsOpen()) { | ||||||
|             LOG_ERROR(Core, "failed to open telemetry_id: {}", filename); |             LOG_ERROR(Core, "failed to open telemetry_id: {}", filename); | ||||||
|             return {}; |             return {}; | ||||||
|         } |         } | ||||||
|         file.ReadBytes(&telemetry_id, sizeof(u64)); |         file.ReadBytes(&telemetry_id, sizeof(u64)); | ||||||
|     } else { |         if (telemetry_id == 0) { | ||||||
|  |             LOG_ERROR(Frontend, "telemetry_id is 0. Generating a new one.", telemetry_id); | ||||||
|  |             generate_new_id = true; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     if (generate_new_id) { | ||||||
|         FileUtil::IOFile file(filename, "wb"); |         FileUtil::IOFile file(filename, "wb"); | ||||||
|         if (!file.IsOpen()) { |         if (!file.IsOpen()) { | ||||||
|             LOG_ERROR(Core, "failed to open telemetry_id: {}", filename); |             LOG_ERROR(Core, "failed to open telemetry_id: {}", filename); | ||||||
| @@ -59,23 +89,20 @@ u64 RegenerateTelemetryId() { | |||||||
|     return new_telemetry_id; |     return new_telemetry_id; | ||||||
| } | } | ||||||
|  |  | ||||||
| std::future<bool> VerifyLogin(std::string username, std::string token, std::function<void()> func) { | bool VerifyLogin(const std::string& username, const std::string& token) { | ||||||
| #ifdef ENABLE_WEB_SERVICE | #ifdef ENABLE_WEB_SERVICE | ||||||
|     return WebService::VerifyLogin(username, token, Settings::values.verify_endpoint_url, func); |     return WebService::VerifyLogin(Settings::values.web_api_url, username, token); | ||||||
| #else | #else | ||||||
|     return std::async(std::launch::async, [func{std::move(func)}]() { |     return false; | ||||||
|         func(); |  | ||||||
|         return false; |  | ||||||
|     }); |  | ||||||
| #endif | #endif | ||||||
| } | } | ||||||
|  |  | ||||||
| TelemetrySession::TelemetrySession() { | TelemetrySession::TelemetrySession() { | ||||||
| #ifdef ENABLE_WEB_SERVICE | #ifdef ENABLE_WEB_SERVICE | ||||||
|     if (Settings::values.enable_telemetry) { |     if (Settings::values.enable_telemetry) { | ||||||
|         backend = std::make_unique<WebService::TelemetryJson>( |         backend = std::make_unique<WebService::TelemetryJson>(Settings::values.web_api_url, | ||||||
|             Settings::values.telemetry_endpoint_url, Settings::values.yuzu_username, |                                                               Settings::values.yuzu_username, | ||||||
|             Settings::values.yuzu_token); |                                                               Settings::values.yuzu_token); | ||||||
|     } else { |     } else { | ||||||
|         backend = std::make_unique<Telemetry::NullVisitor>(); |         backend = std::make_unique<Telemetry::NullVisitor>(); | ||||||
|     } |     } | ||||||
| @@ -94,7 +121,8 @@ TelemetrySession::TelemetrySession() { | |||||||
|     u64 program_id{}; |     u64 program_id{}; | ||||||
|     const Loader::ResultStatus res{System::GetInstance().GetAppLoader().ReadProgramId(program_id)}; |     const Loader::ResultStatus res{System::GetInstance().GetAppLoader().ReadProgramId(program_id)}; | ||||||
|     if (res == Loader::ResultStatus::Success) { |     if (res == Loader::ResultStatus::Success) { | ||||||
|         AddField(Telemetry::FieldType::Session, "ProgramId", program_id); |         const std::string formatted_program_id{fmt::format("{:016X}", program_id)}; | ||||||
|  |         AddField(Telemetry::FieldType::Session, "ProgramId", formatted_program_id); | ||||||
|  |  | ||||||
|         std::string name; |         std::string name; | ||||||
|         System::GetInstance().GetAppLoader().ReadTitle(name); |         System::GetInstance().GetAppLoader().ReadTitle(name); | ||||||
|   | |||||||
| @@ -4,7 +4,6 @@ | |||||||
|  |  | ||||||
| #pragma once | #pragma once | ||||||
|  |  | ||||||
| #include <future> |  | ||||||
| #include <memory> | #include <memory> | ||||||
| #include "common/telemetry.h" | #include "common/telemetry.h" | ||||||
|  |  | ||||||
| @@ -31,6 +30,8 @@ public: | |||||||
|         field_collection.AddField(type, name, std::move(value)); |         field_collection.AddField(type, name, std::move(value)); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     static void FinalizeAsyncJob(); | ||||||
|  |  | ||||||
| private: | private: | ||||||
|     Telemetry::FieldCollection field_collection; ///< Tracks all added fields for the session |     Telemetry::FieldCollection field_collection; ///< Tracks all added fields for the session | ||||||
|     std::unique_ptr<Telemetry::VisitorInterface> backend; ///< Backend interface that logs fields |     std::unique_ptr<Telemetry::VisitorInterface> backend; ///< Backend interface that logs fields | ||||||
| @@ -55,6 +56,6 @@ u64 RegenerateTelemetryId(); | |||||||
|  * @param func A function that gets exectued when the verification is finished |  * @param func A function that gets exectued when the verification is finished | ||||||
|  * @returns Future with bool indicating whether the verification succeeded |  * @returns Future with bool indicating whether the verification succeeded | ||||||
|  */ |  */ | ||||||
| std::future<bool> VerifyLogin(std::string username, std::string token, std::function<void()> func); | bool VerifyLogin(const std::string& username, const std::string& token); | ||||||
|  |  | ||||||
| } // namespace Core | } // namespace Core | ||||||
|   | |||||||
							
								
								
									
										16
									
								
								src/web_service/CMakeLists.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								src/web_service/CMakeLists.txt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,16 @@ | |||||||
|  | add_library(web_service STATIC | ||||||
|  |     telemetry_json.cpp | ||||||
|  |     telemetry_json.h | ||||||
|  |     verify_login.cpp | ||||||
|  |     verify_login.h | ||||||
|  |     web_backend.cpp | ||||||
|  |     web_backend.h | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | create_target_directory_groups(web_service) | ||||||
|  |  | ||||||
|  | get_directory_property(OPENSSL_LIBS | ||||||
|  |         DIRECTORY ${CMAKE_SOURCE_DIR}/externals/libressl | ||||||
|  |         DEFINITION OPENSSL_LIBS) | ||||||
|  | target_compile_definitions(web_service PUBLIC -DCPPHTTPLIB_OPENSSL_SUPPORT) | ||||||
|  | target_link_libraries(web_service PRIVATE common json-headers ${OPENSSL_LIBS} httplib lurlparser) | ||||||
							
								
								
									
										99
									
								
								src/web_service/telemetry_json.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										99
									
								
								src/web_service/telemetry_json.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,99 @@ | |||||||
|  | // Copyright 2017 Citra Emulator Project | ||||||
|  | // Licensed under GPLv2 or any later version | ||||||
|  | // Refer to the license.txt file included. | ||||||
|  |  | ||||||
|  | #include <thread> | ||||||
|  | #include "common/assert.h" | ||||||
|  | #include "common/detached_tasks.h" | ||||||
|  | #include "web_service/telemetry_json.h" | ||||||
|  | #include "web_service/web_backend.h" | ||||||
|  |  | ||||||
|  | namespace WebService { | ||||||
|  |  | ||||||
|  | TelemetryJson::TelemetryJson(const std::string& host, const std::string& username, | ||||||
|  |                              const std::string& token) | ||||||
|  |     : host(std::move(host)), username(std::move(username)), token(std::move(token)) {} | ||||||
|  | TelemetryJson::~TelemetryJson() = default; | ||||||
|  |  | ||||||
|  | template <class T> | ||||||
|  | void TelemetryJson::Serialize(Telemetry::FieldType type, const std::string& name, T value) { | ||||||
|  |     sections[static_cast<u8>(type)][name] = value; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void TelemetryJson::SerializeSection(Telemetry::FieldType type, const std::string& name) { | ||||||
|  |     TopSection()[name] = sections[static_cast<unsigned>(type)]; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void TelemetryJson::Visit(const Telemetry::Field<bool>& field) { | ||||||
|  |     Serialize(field.GetType(), field.GetName(), field.GetValue()); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void TelemetryJson::Visit(const Telemetry::Field<double>& field) { | ||||||
|  |     Serialize(field.GetType(), field.GetName(), field.GetValue()); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void TelemetryJson::Visit(const Telemetry::Field<float>& field) { | ||||||
|  |     Serialize(field.GetType(), field.GetName(), field.GetValue()); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void TelemetryJson::Visit(const Telemetry::Field<u8>& field) { | ||||||
|  |     Serialize(field.GetType(), field.GetName(), field.GetValue()); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void TelemetryJson::Visit(const Telemetry::Field<u16>& field) { | ||||||
|  |     Serialize(field.GetType(), field.GetName(), field.GetValue()); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void TelemetryJson::Visit(const Telemetry::Field<u32>& field) { | ||||||
|  |     Serialize(field.GetType(), field.GetName(), field.GetValue()); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void TelemetryJson::Visit(const Telemetry::Field<u64>& field) { | ||||||
|  |     Serialize(field.GetType(), field.GetName(), field.GetValue()); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void TelemetryJson::Visit(const Telemetry::Field<s8>& field) { | ||||||
|  |     Serialize(field.GetType(), field.GetName(), field.GetValue()); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void TelemetryJson::Visit(const Telemetry::Field<s16>& field) { | ||||||
|  |     Serialize(field.GetType(), field.GetName(), field.GetValue()); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void TelemetryJson::Visit(const Telemetry::Field<s32>& field) { | ||||||
|  |     Serialize(field.GetType(), field.GetName(), field.GetValue()); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void TelemetryJson::Visit(const Telemetry::Field<s64>& field) { | ||||||
|  |     Serialize(field.GetType(), field.GetName(), field.GetValue()); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void TelemetryJson::Visit(const Telemetry::Field<std::string>& field) { | ||||||
|  |     Serialize(field.GetType(), field.GetName(), field.GetValue()); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void TelemetryJson::Visit(const Telemetry::Field<const char*>& field) { | ||||||
|  |     Serialize(field.GetType(), field.GetName(), std::string(field.GetValue())); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void TelemetryJson::Visit(const Telemetry::Field<std::chrono::microseconds>& field) { | ||||||
|  |     Serialize(field.GetType(), field.GetName(), field.GetValue().count()); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void TelemetryJson::Complete() { | ||||||
|  |     SerializeSection(Telemetry::FieldType::App, "App"); | ||||||
|  |     SerializeSection(Telemetry::FieldType::Session, "Session"); | ||||||
|  |     SerializeSection(Telemetry::FieldType::Performance, "Performance"); | ||||||
|  |     SerializeSection(Telemetry::FieldType::UserFeedback, "UserFeedback"); | ||||||
|  |     SerializeSection(Telemetry::FieldType::UserConfig, "UserConfig"); | ||||||
|  |     SerializeSection(Telemetry::FieldType::UserSystem, "UserSystem"); | ||||||
|  |  | ||||||
|  |     auto content = TopSection().dump(); | ||||||
|  |     // Send the telemetry async but don't handle the errors since they were written to the log | ||||||
|  |     Common::DetachedTasks::AddTask( | ||||||
|  |         [host{this->host}, username{this->username}, token{this->token}, content]() { | ||||||
|  |             Client{host, username, token}.PostJson("/telemetry", content, true); | ||||||
|  |         }); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | } // namespace WebService | ||||||
							
								
								
									
										58
									
								
								src/web_service/telemetry_json.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								src/web_service/telemetry_json.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,58 @@ | |||||||
|  | // Copyright 2017 Citra Emulator Project | ||||||
|  | // Licensed under GPLv2 or any later version | ||||||
|  | // Refer to the license.txt file included. | ||||||
|  |  | ||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | #include <array> | ||||||
|  | #include <string> | ||||||
|  | #include <json.hpp> | ||||||
|  | #include "common/telemetry.h" | ||||||
|  | #include "common/web_result.h" | ||||||
|  |  | ||||||
|  | namespace WebService { | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Implementation of VisitorInterface that serialized telemetry into JSON, and submits it to the | ||||||
|  |  * yuzu web service | ||||||
|  |  */ | ||||||
|  | class TelemetryJson : public Telemetry::VisitorInterface { | ||||||
|  | public: | ||||||
|  |     TelemetryJson(const std::string& host, const std::string& username, const std::string& token); | ||||||
|  |     ~TelemetryJson(); | ||||||
|  |  | ||||||
|  |     void Visit(const Telemetry::Field<bool>& field) override; | ||||||
|  |     void Visit(const Telemetry::Field<double>& field) override; | ||||||
|  |     void Visit(const Telemetry::Field<float>& field) override; | ||||||
|  |     void Visit(const Telemetry::Field<u8>& field) override; | ||||||
|  |     void Visit(const Telemetry::Field<u16>& field) override; | ||||||
|  |     void Visit(const Telemetry::Field<u32>& field) override; | ||||||
|  |     void Visit(const Telemetry::Field<u64>& field) override; | ||||||
|  |     void Visit(const Telemetry::Field<s8>& field) override; | ||||||
|  |     void Visit(const Telemetry::Field<s16>& field) override; | ||||||
|  |     void Visit(const Telemetry::Field<s32>& field) override; | ||||||
|  |     void Visit(const Telemetry::Field<s64>& field) override; | ||||||
|  |     void Visit(const Telemetry::Field<std::string>& field) override; | ||||||
|  |     void Visit(const Telemetry::Field<const char*>& field) override; | ||||||
|  |     void Visit(const Telemetry::Field<std::chrono::microseconds>& field) override; | ||||||
|  |  | ||||||
|  |     void Complete() override; | ||||||
|  |  | ||||||
|  | private: | ||||||
|  |     nlohmann::json& TopSection() { | ||||||
|  |         return sections[static_cast<u8>(Telemetry::FieldType::None)]; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     template <class T> | ||||||
|  |     void Serialize(Telemetry::FieldType type, const std::string& name, T value); | ||||||
|  |  | ||||||
|  |     void SerializeSection(Telemetry::FieldType type, const std::string& name); | ||||||
|  |  | ||||||
|  |     nlohmann::json output; | ||||||
|  |     std::array<nlohmann::json, 7> sections; | ||||||
|  |     std::string host; | ||||||
|  |     std::string username; | ||||||
|  |     std::string token; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | } // namespace WebService | ||||||
							
								
								
									
										27
									
								
								src/web_service/verify_login.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								src/web_service/verify_login.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,27 @@ | |||||||
|  | // Copyright 2017 Citra Emulator Project | ||||||
|  | // Licensed under GPLv2 or any later version | ||||||
|  | // Refer to the license.txt file included. | ||||||
|  |  | ||||||
|  | #include <json.hpp> | ||||||
|  | #include "web_service/verify_login.h" | ||||||
|  | #include "web_service/web_backend.h" | ||||||
|  |  | ||||||
|  | namespace WebService { | ||||||
|  |  | ||||||
|  | bool VerifyLogin(const std::string& host, const std::string& username, const std::string& token) { | ||||||
|  |     Client client(host, username, token); | ||||||
|  |     auto reply = client.GetJson("/profile", false).returned_data; | ||||||
|  |     if (reply.empty()) { | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
|  |     nlohmann::json json = nlohmann::json::parse(reply); | ||||||
|  |     const auto iter = json.find("username"); | ||||||
|  |  | ||||||
|  |     if (iter == json.end()) { | ||||||
|  |         return username.empty(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return username == *iter; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | } // namespace WebService | ||||||
							
								
								
									
										22
									
								
								src/web_service/verify_login.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								src/web_service/verify_login.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,22 @@ | |||||||
|  | // Copyright 2017 Citra Emulator Project | ||||||
|  | // Licensed under GPLv2 or any later version | ||||||
|  | // Refer to the license.txt file included. | ||||||
|  |  | ||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | #include <functional> | ||||||
|  | #include <future> | ||||||
|  | #include <string> | ||||||
|  |  | ||||||
|  | namespace WebService { | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Checks if username and token is valid | ||||||
|  |  * @param host the web API URL | ||||||
|  |  * @param username yuzu username to use for authentication. | ||||||
|  |  * @param token yuzu token to use for authentication. | ||||||
|  |  * @returns a bool indicating whether the verification succeeded | ||||||
|  |  */ | ||||||
|  | bool VerifyLogin(const std::string& host, const std::string& username, const std::string& token); | ||||||
|  |  | ||||||
|  | } // namespace WebService | ||||||
							
								
								
									
										149
									
								
								src/web_service/web_backend.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										149
									
								
								src/web_service/web_backend.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,149 @@ | |||||||
|  | // Copyright 2017 Citra Emulator Project | ||||||
|  | // Licensed under GPLv2 or any later version | ||||||
|  | // Refer to the license.txt file included. | ||||||
|  |  | ||||||
|  | #include <cstdlib> | ||||||
|  | #include <string> | ||||||
|  | #include <thread> | ||||||
|  | #include <LUrlParser.h> | ||||||
|  | #include "common/logging/log.h" | ||||||
|  | #include "common/web_result.h" | ||||||
|  | #include "core/settings.h" | ||||||
|  | #include "web_service/web_backend.h" | ||||||
|  |  | ||||||
|  | namespace WebService { | ||||||
|  |  | ||||||
|  | constexpr std::array<const char, 1> API_VERSION{'1'}; | ||||||
|  |  | ||||||
|  | constexpr u32 HTTP_PORT = 80; | ||||||
|  | constexpr u32 HTTPS_PORT = 443; | ||||||
|  |  | ||||||
|  | constexpr u32 TIMEOUT_SECONDS = 30; | ||||||
|  |  | ||||||
|  | Client::JWTCache Client::jwt_cache{}; | ||||||
|  |  | ||||||
|  | Client::Client(const std::string& host, const std::string& username, const std::string& token) | ||||||
|  |     : host(host), username(username), token(token) { | ||||||
|  |     std::lock_guard<std::mutex> lock(jwt_cache.mutex); | ||||||
|  |     if (username == jwt_cache.username && token == jwt_cache.token) { | ||||||
|  |         jwt = jwt_cache.jwt; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | Common::WebResult Client::GenericJson(const std::string& method, const std::string& path, | ||||||
|  |                                       const std::string& data, const std::string& jwt, | ||||||
|  |                                       const std::string& username, const std::string& token) { | ||||||
|  |     if (cli == nullptr) { | ||||||
|  |         auto parsedUrl = LUrlParser::clParseURL::ParseURL(host); | ||||||
|  |         int port; | ||||||
|  |         if (parsedUrl.m_Scheme == "http") { | ||||||
|  |             if (!parsedUrl.GetPort(&port)) { | ||||||
|  |                 port = HTTP_PORT; | ||||||
|  |             } | ||||||
|  |             cli = | ||||||
|  |                 std::make_unique<httplib::Client>(parsedUrl.m_Host.c_str(), port, TIMEOUT_SECONDS); | ||||||
|  |         } else if (parsedUrl.m_Scheme == "https") { | ||||||
|  |             if (!parsedUrl.GetPort(&port)) { | ||||||
|  |                 port = HTTPS_PORT; | ||||||
|  |             } | ||||||
|  |             cli = std::make_unique<httplib::SSLClient>(parsedUrl.m_Host.c_str(), port, | ||||||
|  |                                                        TIMEOUT_SECONDS); | ||||||
|  |         } else { | ||||||
|  |             LOG_ERROR(WebService, "Bad URL scheme {}", parsedUrl.m_Scheme); | ||||||
|  |             return Common::WebResult{Common::WebResult::Code::InvalidURL, "Bad URL scheme"}; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     if (cli == nullptr) { | ||||||
|  |         LOG_ERROR(WebService, "Invalid URL {}", host + path); | ||||||
|  |         return Common::WebResult{Common::WebResult::Code::InvalidURL, "Invalid URL"}; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     httplib::Headers params; | ||||||
|  |     if (!jwt.empty()) { | ||||||
|  |         params = { | ||||||
|  |             {std::string("Authorization"), fmt::format("Bearer {}", jwt)}, | ||||||
|  |         }; | ||||||
|  |     } else if (!username.empty()) { | ||||||
|  |         params = { | ||||||
|  |             {std::string("x-username"), username}, | ||||||
|  |             {std::string("x-token"), token}, | ||||||
|  |         }; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     params.emplace(std::string("api-version"), std::string(API_VERSION.begin(), API_VERSION.end())); | ||||||
|  |     if (method != "GET") { | ||||||
|  |         params.emplace(std::string("Content-Type"), std::string("application/json")); | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     httplib::Request request; | ||||||
|  |     request.method = method; | ||||||
|  |     request.path = path; | ||||||
|  |     request.headers = params; | ||||||
|  |     request.body = data; | ||||||
|  |  | ||||||
|  |     httplib::Response response; | ||||||
|  |  | ||||||
|  |     if (!cli->send(request, response)) { | ||||||
|  |         LOG_ERROR(WebService, "{} to {} returned null", method, host + path); | ||||||
|  |         return Common::WebResult{Common::WebResult::Code::LibError, "Null response"}; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     if (response.status >= 400) { | ||||||
|  |         LOG_ERROR(WebService, "{} to {} returned error status code: {}", method, host + path, | ||||||
|  |                   response.status); | ||||||
|  |         return Common::WebResult{Common::WebResult::Code::HttpError, | ||||||
|  |                                  std::to_string(response.status)}; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     auto content_type = response.headers.find("content-type"); | ||||||
|  |  | ||||||
|  |     if (content_type == response.headers.end()) { | ||||||
|  |         LOG_ERROR(WebService, "{} to {} returned no content", method, host + path); | ||||||
|  |         return Common::WebResult{Common::WebResult::Code::WrongContent, ""}; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     if (content_type->second.find("application/json") == std::string::npos && | ||||||
|  |         content_type->second.find("text/html; charset=utf-8") == std::string::npos) { | ||||||
|  |         LOG_ERROR(WebService, "{} to {} returned wrong content: {}", method, host + path, | ||||||
|  |                   content_type->second); | ||||||
|  |         return Common::WebResult{Common::WebResult::Code::WrongContent, "Wrong content"}; | ||||||
|  |     } | ||||||
|  |     return Common::WebResult{Common::WebResult::Code::Success, "", response.body}; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void Client::UpdateJWT() { | ||||||
|  |     if (!username.empty() && !token.empty()) { | ||||||
|  |         auto result = GenericJson("POST", "/jwt/internal", "", "", username, token); | ||||||
|  |         if (result.result_code != Common::WebResult::Code::Success) { | ||||||
|  |             LOG_ERROR(WebService, "UpdateJWT failed"); | ||||||
|  |         } else { | ||||||
|  |             std::lock_guard<std::mutex> lock(jwt_cache.mutex); | ||||||
|  |             jwt_cache.username = username; | ||||||
|  |             jwt_cache.token = token; | ||||||
|  |             jwt_cache.jwt = jwt = result.returned_data; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | Common::WebResult Client::GenericJson(const std::string& method, const std::string& path, | ||||||
|  |                                       const std::string& data, bool allow_anonymous) { | ||||||
|  |     if (jwt.empty()) { | ||||||
|  |         UpdateJWT(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     if (jwt.empty() && !allow_anonymous) { | ||||||
|  |         LOG_ERROR(WebService, "Credentials must be provided for authenticated requests"); | ||||||
|  |         return Common::WebResult{Common::WebResult::Code::CredentialsMissing, "Credentials needed"}; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     auto result = GenericJson(method, path, data, jwt); | ||||||
|  |     if (result.result_string == "401") { | ||||||
|  |         // Try again with new JWT | ||||||
|  |         UpdateJWT(); | ||||||
|  |         result = GenericJson(method, path, data, jwt); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return result; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | } // namespace WebService | ||||||
							
								
								
									
										92
									
								
								src/web_service/web_backend.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										92
									
								
								src/web_service/web_backend.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,92 @@ | |||||||
|  | // Copyright 2017 Citra Emulator Project | ||||||
|  | // Licensed under GPLv2 or any later version | ||||||
|  | // Refer to the license.txt file included. | ||||||
|  |  | ||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | #include <functional> | ||||||
|  | #include <mutex> | ||||||
|  | #include <string> | ||||||
|  | #include <tuple> | ||||||
|  | #include <httplib.h> | ||||||
|  | #include "common/common_types.h" | ||||||
|  | #include "common/web_result.h" | ||||||
|  |  | ||||||
|  | namespace httplib { | ||||||
|  | class Client; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | namespace WebService { | ||||||
|  |  | ||||||
|  | class Client { | ||||||
|  | public: | ||||||
|  |     Client(const std::string& host, const std::string& username, const std::string& token); | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Posts JSON to the specified path. | ||||||
|  |      * @param path the URL segment after the host address. | ||||||
|  |      * @param data String of JSON data to use for the body of the POST request. | ||||||
|  |      * @param allow_anonymous If true, allow anonymous unauthenticated requests. | ||||||
|  |      * @return the result of the request. | ||||||
|  |      */ | ||||||
|  |     Common::WebResult PostJson(const std::string& path, const std::string& data, | ||||||
|  |                                bool allow_anonymous) { | ||||||
|  |         return GenericJson("POST", path, data, allow_anonymous); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Gets JSON from the specified path. | ||||||
|  |      * @param path the URL segment after the host address. | ||||||
|  |      * @param allow_anonymous If true, allow anonymous unauthenticated requests. | ||||||
|  |      * @return the result of the request. | ||||||
|  |      */ | ||||||
|  |     Common::WebResult GetJson(const std::string& path, bool allow_anonymous) { | ||||||
|  |         return GenericJson("GET", path, "", allow_anonymous); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Deletes JSON to the specified path. | ||||||
|  |      * @param path the URL segment after the host address. | ||||||
|  |      * @param data String of JSON data to use for the body of the DELETE request. | ||||||
|  |      * @param allow_anonymous If true, allow anonymous unauthenticated requests. | ||||||
|  |      * @return the result of the request. | ||||||
|  |      */ | ||||||
|  |     Common::WebResult DeleteJson(const std::string& path, const std::string& data, | ||||||
|  |                                  bool allow_anonymous) { | ||||||
|  |         return GenericJson("DELETE", path, data, allow_anonymous); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | private: | ||||||
|  |     /// A generic function handles POST, GET and DELETE request together | ||||||
|  |     Common::WebResult GenericJson(const std::string& method, const std::string& path, | ||||||
|  |                                   const std::string& data, bool allow_anonymous); | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * A generic function with explicit authentication method specified | ||||||
|  |      * JWT is used if the jwt parameter is not empty | ||||||
|  |      * username + token is used if jwt is empty but username and token are not empty | ||||||
|  |      * anonymous if all of jwt, username and token are empty | ||||||
|  |      */ | ||||||
|  |     Common::WebResult GenericJson(const std::string& method, const std::string& path, | ||||||
|  |                                   const std::string& data, const std::string& jwt = "", | ||||||
|  |                                   const std::string& username = "", const std::string& token = ""); | ||||||
|  |  | ||||||
|  |     // Retrieve a new JWT from given username and token | ||||||
|  |     void UpdateJWT(); | ||||||
|  |  | ||||||
|  |     std::string host; | ||||||
|  |     std::string username; | ||||||
|  |     std::string token; | ||||||
|  |     std::string jwt; | ||||||
|  |     std::unique_ptr<httplib::Client> cli; | ||||||
|  |  | ||||||
|  |     struct JWTCache { | ||||||
|  |         std::mutex mutex; | ||||||
|  |         std::string username; | ||||||
|  |         std::string token; | ||||||
|  |         std::string jwt; | ||||||
|  |     }; | ||||||
|  |     static JWTCache jwt_cache; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | } // namespace WebService | ||||||
| @@ -29,6 +29,8 @@ add_executable(yuzu | |||||||
|     configuration/configure_input.h |     configuration/configure_input.h | ||||||
|     configuration/configure_system.cpp |     configuration/configure_system.cpp | ||||||
|     configuration/configure_system.h |     configuration/configure_system.h | ||||||
|  |     configuration/configure_web.cpp | ||||||
|  |     configuration/configure_web.h | ||||||
|     debugger/graphics/graphics_breakpoint_observer.cpp |     debugger/graphics/graphics_breakpoint_observer.cpp | ||||||
|     debugger/graphics/graphics_breakpoint_observer.h |     debugger/graphics/graphics_breakpoint_observer.h | ||||||
|     debugger/graphics/graphics_breakpoints.cpp |     debugger/graphics/graphics_breakpoints.cpp | ||||||
| @@ -42,6 +44,7 @@ add_executable(yuzu | |||||||
|     debugger/profiler.h |     debugger/profiler.h | ||||||
|     debugger/wait_tree.cpp |     debugger/wait_tree.cpp | ||||||
|     debugger/wait_tree.h |     debugger/wait_tree.h | ||||||
|  |     discord.h | ||||||
|     game_list.cpp |     game_list.cpp | ||||||
|     game_list.h |     game_list.h | ||||||
|     game_list_p.h |     game_list_p.h | ||||||
| @@ -57,6 +60,8 @@ add_executable(yuzu | |||||||
|     util/spinbox.h |     util/spinbox.h | ||||||
|     util/util.cpp |     util/util.cpp | ||||||
|     util/util.h |     util/util.h | ||||||
|  |     compatdb.cpp | ||||||
|  |     compatdb.h | ||||||
|     yuzu.rc |     yuzu.rc | ||||||
| ) | ) | ||||||
|  |  | ||||||
| @@ -70,8 +75,10 @@ set(UIS | |||||||
|     configuration/configure_graphics.ui |     configuration/configure_graphics.ui | ||||||
|     configuration/configure_input.ui |     configuration/configure_input.ui | ||||||
|     configuration/configure_system.ui |     configuration/configure_system.ui | ||||||
|  |     configuration/configure_web.ui | ||||||
|     hotkeys.ui |     hotkeys.ui | ||||||
|     main.ui |     main.ui | ||||||
|  |     compatdb.ui | ||||||
| ) | ) | ||||||
|  |  | ||||||
| file(GLOB COMPAT_LIST | file(GLOB COMPAT_LIST | ||||||
| @@ -113,6 +120,19 @@ target_link_libraries(yuzu PRIVATE common core input_common video_core) | |||||||
| target_link_libraries(yuzu PRIVATE Boost::boost glad Qt5::OpenGL Qt5::Widgets) | target_link_libraries(yuzu PRIVATE Boost::boost glad Qt5::OpenGL Qt5::Widgets) | ||||||
| target_link_libraries(yuzu PRIVATE ${PLATFORM_LIBRARIES} Threads::Threads) | target_link_libraries(yuzu PRIVATE ${PLATFORM_LIBRARIES} Threads::Threads) | ||||||
|  |  | ||||||
|  | if (YUZU_ENABLE_COMPATIBILITY_REPORTING) | ||||||
|  |     add_definitions(-DYUZU_ENABLE_COMPATIBILITY_REPORTING) | ||||||
|  | endif() | ||||||
|  |  | ||||||
|  | if (USE_DISCORD_PRESENCE) | ||||||
|  |     target_sources(yuzu PUBLIC | ||||||
|  |         discord_impl.cpp | ||||||
|  |         discord_impl.h | ||||||
|  |     ) | ||||||
|  |     target_link_libraries(yuzu PRIVATE discord-rpc) | ||||||
|  |     target_compile_definitions(yuzu PRIVATE -DUSE_DISCORD_PRESENCE) | ||||||
|  | endif() | ||||||
|  |  | ||||||
| if(UNIX AND NOT APPLE) | if(UNIX AND NOT APPLE) | ||||||
|     install(TARGETS yuzu RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}/bin") |     install(TARGETS yuzu RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}/bin") | ||||||
| endif() | endif() | ||||||
|   | |||||||
							
								
								
									
										65
									
								
								src/yuzu/compatdb.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										65
									
								
								src/yuzu/compatdb.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,65 @@ | |||||||
|  | // Copyright 2017 Citra Emulator Project | ||||||
|  | // Licensed under GPLv2 or any later version | ||||||
|  | // Refer to the license.txt file included. | ||||||
|  |  | ||||||
|  | #include <QButtonGroup> | ||||||
|  | #include <QMessageBox> | ||||||
|  | #include <QPushButton> | ||||||
|  | #include "common/logging/log.h" | ||||||
|  | #include "common/telemetry.h" | ||||||
|  | #include "core/core.h" | ||||||
|  | #include "core/telemetry_session.h" | ||||||
|  | #include "ui_compatdb.h" | ||||||
|  | #include "yuzu/compatdb.h" | ||||||
|  |  | ||||||
|  | CompatDB::CompatDB(QWidget* parent) | ||||||
|  |     : QWizard(parent, Qt::WindowTitleHint | Qt::WindowCloseButtonHint | Qt::WindowSystemMenuHint), | ||||||
|  |       ui{std::make_unique<Ui::CompatDB>()} { | ||||||
|  |     ui->setupUi(this); | ||||||
|  |     connect(ui->radioButton_Perfect, &QRadioButton::clicked, this, &CompatDB::EnableNext); | ||||||
|  |     connect(ui->radioButton_Great, &QRadioButton::clicked, this, &CompatDB::EnableNext); | ||||||
|  |     connect(ui->radioButton_Okay, &QRadioButton::clicked, this, &CompatDB::EnableNext); | ||||||
|  |     connect(ui->radioButton_Bad, &QRadioButton::clicked, this, &CompatDB::EnableNext); | ||||||
|  |     connect(ui->radioButton_IntroMenu, &QRadioButton::clicked, this, &CompatDB::EnableNext); | ||||||
|  |     connect(ui->radioButton_WontBoot, &QRadioButton::clicked, this, &CompatDB::EnableNext); | ||||||
|  |     connect(button(NextButton), &QPushButton::clicked, this, &CompatDB::Submit); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | CompatDB::~CompatDB() = default; | ||||||
|  |  | ||||||
|  | enum class CompatDBPage { | ||||||
|  |     Intro = 0, | ||||||
|  |     Selection = 1, | ||||||
|  |     Final = 2, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | void CompatDB::Submit() { | ||||||
|  |     QButtonGroup* compatibility = new QButtonGroup(this); | ||||||
|  |     compatibility->addButton(ui->radioButton_Perfect, 0); | ||||||
|  |     compatibility->addButton(ui->radioButton_Great, 1); | ||||||
|  |     compatibility->addButton(ui->radioButton_Okay, 2); | ||||||
|  |     compatibility->addButton(ui->radioButton_Bad, 3); | ||||||
|  |     compatibility->addButton(ui->radioButton_IntroMenu, 4); | ||||||
|  |     compatibility->addButton(ui->radioButton_WontBoot, 5); | ||||||
|  |     switch ((static_cast<CompatDBPage>(currentId()))) { | ||||||
|  |     case CompatDBPage::Selection: | ||||||
|  |         if (compatibility->checkedId() == -1) { | ||||||
|  |             button(NextButton)->setEnabled(false); | ||||||
|  |         } | ||||||
|  |         break; | ||||||
|  |     case CompatDBPage::Final: | ||||||
|  |         LOG_DEBUG(Frontend, "Compatibility Rating: {}", compatibility->checkedId()); | ||||||
|  |         Core::Telemetry().AddField(Telemetry::FieldType::UserFeedback, "Compatibility", | ||||||
|  |                                    compatibility->checkedId()); | ||||||
|  |         // older versions of QT don't support the "NoCancelButtonOnLastPage" option, this is a | ||||||
|  |         // workaround | ||||||
|  |         button(QWizard::CancelButton)->setVisible(false); | ||||||
|  |         break; | ||||||
|  |     default: | ||||||
|  |         LOG_ERROR(Frontend, "Unexpected page: {}", currentId()); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void CompatDB::EnableNext() { | ||||||
|  |     button(NextButton)->setEnabled(true); | ||||||
|  | } | ||||||
							
								
								
									
										26
									
								
								src/yuzu/compatdb.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								src/yuzu/compatdb.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,26 @@ | |||||||
|  | // Copyright 2017 Citra Emulator Project | ||||||
|  | // Licensed under GPLv2 or any later version | ||||||
|  | // Refer to the license.txt file included. | ||||||
|  |  | ||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | #include <memory> | ||||||
|  | #include <QWizard> | ||||||
|  |  | ||||||
|  | namespace Ui { | ||||||
|  | class CompatDB; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class CompatDB : public QWizard { | ||||||
|  |     Q_OBJECT | ||||||
|  |  | ||||||
|  | public: | ||||||
|  |     explicit CompatDB(QWidget* parent = nullptr); | ||||||
|  |     ~CompatDB(); | ||||||
|  |  | ||||||
|  | private: | ||||||
|  |     std::unique_ptr<Ui::CompatDB> ui; | ||||||
|  |  | ||||||
|  |     void Submit(); | ||||||
|  |     void EnableNext(); | ||||||
|  | }; | ||||||
							
								
								
									
										215
									
								
								src/yuzu/compatdb.ui
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										215
									
								
								src/yuzu/compatdb.ui
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,215 @@ | |||||||
|  | <?xml version="1.0" encoding="UTF-8"?> | ||||||
|  | <ui version="4.0"> | ||||||
|  |  <class>CompatDB</class> | ||||||
|  |  <widget class="QWizard" name="CompatDB"> | ||||||
|  |   <property name="geometry"> | ||||||
|  |    <rect> | ||||||
|  |     <x>0</x> | ||||||
|  |     <y>0</y> | ||||||
|  |     <width>600</width> | ||||||
|  |     <height>482</height> | ||||||
|  |    </rect> | ||||||
|  |   </property> | ||||||
|  |   <property name="minimumSize"> | ||||||
|  |    <size> | ||||||
|  |     <width>500</width> | ||||||
|  |     <height>410</height> | ||||||
|  |    </size> | ||||||
|  |   </property> | ||||||
|  |   <property name="windowTitle"> | ||||||
|  |    <string>Report Compatibility</string> | ||||||
|  |   </property> | ||||||
|  |   <property name="options"> | ||||||
|  |    <set>QWizard::DisabledBackButtonOnLastPage|QWizard::HelpButtonOnRight|QWizard::NoBackButtonOnStartPage</set> | ||||||
|  |   </property> | ||||||
|  |   <widget class="QWizardPage" name="wizard_Info"> | ||||||
|  |    <property name="title"> | ||||||
|  |     <string>Report Game Compatibility</string> | ||||||
|  |    </property> | ||||||
|  |    <attribute name="pageId"> | ||||||
|  |     <string notr="true">0</string> | ||||||
|  |    </attribute> | ||||||
|  |    <layout class="QVBoxLayout" name="verticalLayout"> | ||||||
|  |     <item> | ||||||
|  |      <widget class="QLabel" name="lbl_Spiel"> | ||||||
|  |       <property name="text"> | ||||||
|  |        <string><html><head/><body><p><span style=" font-size:10pt;">Should you choose to submit a test case to the </span><a href="https://yuzu-emu.org/game/"><span style=" font-size:10pt; text-decoration: underline; color:#0000ff;">yuzu Compatibility List</span></a><span style=" font-size:10pt;">, The following information will be collected and displayed on the site:</span></p><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Hardware Information (CPU / GPU / Operating System)</li><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Which version of yuzu you are running</li><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">The connected yuzu account</li></ul></body></html></string> | ||||||
|  |       </property> | ||||||
|  |       <property name="wordWrap"> | ||||||
|  |        <bool>true</bool> | ||||||
|  |       </property> | ||||||
|  |       <property name="openExternalLinks"> | ||||||
|  |        <bool>true</bool> | ||||||
|  |       </property> | ||||||
|  |      </widget> | ||||||
|  |     </item> | ||||||
|  |     <item> | ||||||
|  |      <spacer name="verticalSpacer_2"> | ||||||
|  |       <property name="orientation"> | ||||||
|  |        <enum>Qt::Vertical</enum> | ||||||
|  |       </property> | ||||||
|  |       <property name="sizeHint" stdset="0"> | ||||||
|  |        <size> | ||||||
|  |         <width>20</width> | ||||||
|  |         <height>0</height> | ||||||
|  |        </size> | ||||||
|  |       </property> | ||||||
|  |      </spacer> | ||||||
|  |     </item> | ||||||
|  |    </layout> | ||||||
|  |   </widget> | ||||||
|  |   <widget class="QWizardPage" name="wizard_Report"> | ||||||
|  |    <property name="title"> | ||||||
|  |     <string>Report Game Compatibility</string> | ||||||
|  |    </property> | ||||||
|  |    <attribute name="pageId"> | ||||||
|  |     <string notr="true">1</string> | ||||||
|  |    </attribute> | ||||||
|  |    <layout class="QFormLayout" name="formLayout"> | ||||||
|  |     <item row="2" column="0"> | ||||||
|  |      <widget class="QRadioButton" name="radioButton_Perfect"> | ||||||
|  |       <property name="text"> | ||||||
|  |        <string>Perfect</string> | ||||||
|  |       </property> | ||||||
|  |      </widget> | ||||||
|  |     </item> | ||||||
|  |     <item row="2" column="1"> | ||||||
|  |      <widget class="QLabel" name="lbl_Perfect"> | ||||||
|  |       <property name="text"> | ||||||
|  |        <string><html><head/><body><p>Game functions flawlessly with no audio or graphical glitches.</p></body></html></string> | ||||||
|  |       </property> | ||||||
|  |       <property name="wordWrap"> | ||||||
|  |        <bool>true</bool> | ||||||
|  |       </property> | ||||||
|  |      </widget> | ||||||
|  |     </item> | ||||||
|  |     <item row="4" column="0"> | ||||||
|  |      <widget class="QRadioButton" name="radioButton_Great"> | ||||||
|  |       <property name="text"> | ||||||
|  |        <string>Great </string> | ||||||
|  |       </property> | ||||||
|  |      </widget> | ||||||
|  |     </item> | ||||||
|  |     <item row="4" column="1"> | ||||||
|  |      <widget class="QLabel" name="lbl_Great"> | ||||||
|  |       <property name="text"> | ||||||
|  |        <string><html><head/><body><p>Game functions with minor graphical or audio glitches and is playable from start to finish. May require some workarounds.</p></body></html></string> | ||||||
|  |       </property> | ||||||
|  |       <property name="wordWrap"> | ||||||
|  |        <bool>true</bool> | ||||||
|  |       </property> | ||||||
|  |      </widget> | ||||||
|  |     </item> | ||||||
|  |     <item row="5" column="0"> | ||||||
|  |      <widget class="QRadioButton" name="radioButton_Okay"> | ||||||
|  |       <property name="text"> | ||||||
|  |        <string>Okay</string> | ||||||
|  |       </property> | ||||||
|  |      </widget> | ||||||
|  |     </item> | ||||||
|  |     <item row="5" column="1"> | ||||||
|  |      <widget class="QLabel" name="lbl_Okay"> | ||||||
|  |       <property name="text"> | ||||||
|  |        <string><html><head/><body><p>Game functions with major graphical or audio glitches, but game is playable from start to finish with workarounds.</p></body></html></string> | ||||||
|  |       </property> | ||||||
|  |       <property name="wordWrap"> | ||||||
|  |        <bool>true</bool> | ||||||
|  |       </property> | ||||||
|  |      </widget> | ||||||
|  |     </item> | ||||||
|  |     <item row="6" column="0"> | ||||||
|  |      <widget class="QRadioButton" name="radioButton_Bad"> | ||||||
|  |       <property name="text"> | ||||||
|  |        <string>Bad</string> | ||||||
|  |       </property> | ||||||
|  |      </widget> | ||||||
|  |     </item> | ||||||
|  |     <item row="6" column="1"> | ||||||
|  |      <widget class="QLabel" name="lbl_Bad"> | ||||||
|  |       <property name="text"> | ||||||
|  |        <string><html><head/><body><p>Game functions, but with major graphical or audio glitches. Unable to progress in specific areas due to glitches even with workarounds.</p></body></html></string> | ||||||
|  |       </property> | ||||||
|  |       <property name="wordWrap"> | ||||||
|  |        <bool>true</bool> | ||||||
|  |       </property> | ||||||
|  |      </widget> | ||||||
|  |     </item> | ||||||
|  |     <item row="7" column="0"> | ||||||
|  |      <widget class="QRadioButton" name="radioButton_IntroMenu"> | ||||||
|  |       <property name="text"> | ||||||
|  |        <string>Intro/Menu</string> | ||||||
|  |       </property> | ||||||
|  |      </widget> | ||||||
|  |     </item> | ||||||
|  |     <item row="7" column="1"> | ||||||
|  |      <widget class="QLabel" name="lbl_IntroMenu"> | ||||||
|  |       <property name="text"> | ||||||
|  |        <string><html><head/><body><p>Game is completely unplayable due to major graphical or audio glitches. Unable to progress past the Start Screen.</p></body></html></string> | ||||||
|  |       </property> | ||||||
|  |       <property name="wordWrap"> | ||||||
|  |        <bool>true</bool> | ||||||
|  |       </property> | ||||||
|  |      </widget> | ||||||
|  |     </item> | ||||||
|  |     <item row="8" column="0"> | ||||||
|  |      <widget class="QRadioButton" name="radioButton_WontBoot"> | ||||||
|  |       <property name="text"> | ||||||
|  |        <string>Won't Boot</string> | ||||||
|  |       </property> | ||||||
|  |       <property name="checkable"> | ||||||
|  |        <bool>true</bool> | ||||||
|  |       </property> | ||||||
|  |       <property name="checked"> | ||||||
|  |        <bool>false</bool> | ||||||
|  |       </property> | ||||||
|  |      </widget> | ||||||
|  |     </item> | ||||||
|  |     <item row="8" column="1"> | ||||||
|  |      <widget class="QLabel" name="lbl_WontBoot"> | ||||||
|  |       <property name="text"> | ||||||
|  |        <string><html><head/><body><p>The game crashes when attempting to startup.</p></body></html></string> | ||||||
|  |       </property> | ||||||
|  |      </widget> | ||||||
|  |     </item> | ||||||
|  |     <item row="0" column="0" colspan="2"> | ||||||
|  |      <widget class="QLabel" name="lbl_Independent"> | ||||||
|  |       <property name="font"> | ||||||
|  |        <font> | ||||||
|  |         <pointsize>10</pointsize> | ||||||
|  |        </font> | ||||||
|  |       </property> | ||||||
|  |       <property name="text"> | ||||||
|  |        <string><html><head/><body><p>Independent of speed or performance, how well does this game play from start to finish on this version of yuzu?</p></body></html></string> | ||||||
|  |       </property> | ||||||
|  |       <property name="wordWrap"> | ||||||
|  |        <bool>true</bool> | ||||||
|  |       </property> | ||||||
|  |      </widget> | ||||||
|  |     </item> | ||||||
|  |     <item row="1" column="0" colspan="2"> | ||||||
|  |      <spacer name="verticalSpacer"> | ||||||
|  |       <property name="orientation"> | ||||||
|  |        <enum>Qt::Vertical</enum> | ||||||
|  |       </property> | ||||||
|  |       <property name="sizeHint" stdset="0"> | ||||||
|  |        <size> | ||||||
|  |         <width>20</width> | ||||||
|  |         <height>0</height> | ||||||
|  |        </size> | ||||||
|  |       </property> | ||||||
|  |      </spacer> | ||||||
|  |     </item> | ||||||
|  |    </layout> | ||||||
|  |   </widget> | ||||||
|  |   <widget class="QWizardPage" name="wizard_ThankYou"> | ||||||
|  |    <property name="title"> | ||||||
|  |     <string>Thank you for your submission!</string> | ||||||
|  |    </property> | ||||||
|  |    <attribute name="pageId"> | ||||||
|  |     <string notr="true">2</string> | ||||||
|  |    </attribute> | ||||||
|  |   </widget> | ||||||
|  |  </widget> | ||||||
|  |  <resources/> | ||||||
|  |  <connections/> | ||||||
|  | </ui> | ||||||
| @@ -136,8 +136,18 @@ void Config::ReadValues() { | |||||||
|     Settings::values.gdbstub_port = qt_config->value("gdbstub_port", 24689).toInt(); |     Settings::values.gdbstub_port = qt_config->value("gdbstub_port", 24689).toInt(); | ||||||
|     qt_config->endGroup(); |     qt_config->endGroup(); | ||||||
|  |  | ||||||
|  |     qt_config->beginGroup("WebService"); | ||||||
|  |     Settings::values.enable_telemetry = qt_config->value("enable_telemetry", true).toBool(); | ||||||
|  |     Settings::values.web_api_url = | ||||||
|  |         qt_config->value("web_api_url", "https://api.yuzu-emu.org").toString().toStdString(); | ||||||
|  |     Settings::values.yuzu_username = qt_config->value("yuzu_username").toString().toStdString(); | ||||||
|  |     Settings::values.yuzu_token = qt_config->value("yuzu_token").toString().toStdString(); | ||||||
|  |     qt_config->endGroup(); | ||||||
|  |  | ||||||
|     qt_config->beginGroup("UI"); |     qt_config->beginGroup("UI"); | ||||||
|     UISettings::values.theme = qt_config->value("theme", UISettings::themes[0].second).toString(); |     UISettings::values.theme = qt_config->value("theme", UISettings::themes[0].second).toString(); | ||||||
|  |     UISettings::values.enable_discord_presence = | ||||||
|  |         qt_config->value("enable_discord_presence", true).toBool(); | ||||||
|  |  | ||||||
|     qt_config->beginGroup("UIGameList"); |     qt_config->beginGroup("UIGameList"); | ||||||
|     UISettings::values.show_unknown = qt_config->value("show_unknown", true).toBool(); |     UISettings::values.show_unknown = qt_config->value("show_unknown", true).toBool(); | ||||||
| @@ -261,8 +271,16 @@ void Config::SaveValues() { | |||||||
|     qt_config->setValue("gdbstub_port", Settings::values.gdbstub_port); |     qt_config->setValue("gdbstub_port", Settings::values.gdbstub_port); | ||||||
|     qt_config->endGroup(); |     qt_config->endGroup(); | ||||||
|  |  | ||||||
|  |     qt_config->beginGroup("WebService"); | ||||||
|  |     qt_config->setValue("enable_telemetry", Settings::values.enable_telemetry); | ||||||
|  |     qt_config->setValue("web_api_url", QString::fromStdString(Settings::values.web_api_url)); | ||||||
|  |     qt_config->setValue("yuzu_username", QString::fromStdString(Settings::values.yuzu_username)); | ||||||
|  |     qt_config->setValue("yuzu_token", QString::fromStdString(Settings::values.yuzu_token)); | ||||||
|  |     qt_config->endGroup(); | ||||||
|  |  | ||||||
|     qt_config->beginGroup("UI"); |     qt_config->beginGroup("UI"); | ||||||
|     qt_config->setValue("theme", UISettings::values.theme); |     qt_config->setValue("theme", UISettings::values.theme); | ||||||
|  |     qt_config->setValue("enable_discord_presence", UISettings::values.enable_discord_presence); | ||||||
|  |  | ||||||
|     qt_config->beginGroup("UIGameList"); |     qt_config->beginGroup("UIGameList"); | ||||||
|     qt_config->setValue("show_unknown", UISettings::values.show_unknown); |     qt_config->setValue("show_unknown", UISettings::values.show_unknown); | ||||||
|   | |||||||
| @@ -54,6 +54,11 @@ | |||||||
|        <string>Debug</string> |        <string>Debug</string> | ||||||
|       </attribute> |       </attribute> | ||||||
|      </widget> |      </widget> | ||||||
|  |       <widget class="ConfigureWeb" name="webTab"> | ||||||
|  |         <attribute name="title"> | ||||||
|  |           <string>Web</string> | ||||||
|  |         </attribute> | ||||||
|  |       </widget> | ||||||
|     </widget> |     </widget> | ||||||
|    </item> |    </item> | ||||||
|    <item> |    <item> | ||||||
| @@ -108,6 +113,12 @@ | |||||||
|    <header>configuration/configure_graphics.h</header> |    <header>configuration/configure_graphics.h</header> | ||||||
|    <container>1</container> |    <container>1</container> | ||||||
|   </customwidget> |   </customwidget> | ||||||
|  |    <customwidget> | ||||||
|  |      <class>ConfigureWeb</class> | ||||||
|  |      <extends>QWidget</extends> | ||||||
|  |      <header>configuration/configure_web.h</header> | ||||||
|  |      <container>1</container> | ||||||
|  |    </customwidget> | ||||||
|  </customwidgets> |  </customwidgets> | ||||||
|  <resources/> |  <resources/> | ||||||
|  <connections> |  <connections> | ||||||
|   | |||||||
| @@ -27,5 +27,6 @@ void ConfigureDialog::applyConfiguration() { | |||||||
|     ui->graphicsTab->applyConfiguration(); |     ui->graphicsTab->applyConfiguration(); | ||||||
|     ui->audioTab->applyConfiguration(); |     ui->audioTab->applyConfiguration(); | ||||||
|     ui->debugTab->applyConfiguration(); |     ui->debugTab->applyConfiguration(); | ||||||
|  |     ui->webTab->applyConfiguration(); | ||||||
|     Settings::Apply(); |     Settings::Apply(); | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										119
									
								
								src/yuzu/configuration/configure_web.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										119
									
								
								src/yuzu/configuration/configure_web.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,119 @@ | |||||||
|  | // Copyright 2017 Citra Emulator Project | ||||||
|  | // Licensed under GPLv2 or any later version | ||||||
|  | // Refer to the license.txt file included. | ||||||
|  |  | ||||||
|  | #include <QIcon> | ||||||
|  | #include <QMessageBox> | ||||||
|  | #include <QtConcurrent/QtConcurrentRun> | ||||||
|  | #include "core/settings.h" | ||||||
|  | #include "core/telemetry_session.h" | ||||||
|  | #include "ui_configure_web.h" | ||||||
|  | #include "yuzu/configuration/configure_web.h" | ||||||
|  | #include "yuzu/ui_settings.h" | ||||||
|  |  | ||||||
|  | ConfigureWeb::ConfigureWeb(QWidget* parent) | ||||||
|  |     : QWidget(parent), ui(std::make_unique<Ui::ConfigureWeb>()) { | ||||||
|  |     ui->setupUi(this); | ||||||
|  |     connect(ui->button_regenerate_telemetry_id, &QPushButton::clicked, this, | ||||||
|  |             &ConfigureWeb::RefreshTelemetryID); | ||||||
|  |     connect(ui->button_verify_login, &QPushButton::clicked, this, &ConfigureWeb::VerifyLogin); | ||||||
|  |     connect(&verify_watcher, &QFutureWatcher<bool>::finished, this, &ConfigureWeb::OnLoginVerified); | ||||||
|  |  | ||||||
|  | #ifndef USE_DISCORD_PRESENCE | ||||||
|  |     ui->discord_group->setVisible(false); | ||||||
|  | #endif | ||||||
|  |     this->setConfiguration(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | ConfigureWeb::~ConfigureWeb() = default; | ||||||
|  |  | ||||||
|  | void ConfigureWeb::setConfiguration() { | ||||||
|  |     ui->web_credentials_disclaimer->setWordWrap(true); | ||||||
|  |     ui->telemetry_learn_more->setOpenExternalLinks(true); | ||||||
|  |     ui->telemetry_learn_more->setText( | ||||||
|  |         tr("<a href='https://yuzu-emu.org/help/features/telemetry/'><span style=\"text-decoration: " | ||||||
|  |            "underline; color:#039be5;\">Learn more</span></a>")); | ||||||
|  |  | ||||||
|  |     ui->web_signup_link->setOpenExternalLinks(true); | ||||||
|  |     ui->web_signup_link->setText( | ||||||
|  |         tr("<a href='https://profile.yuzu-emu.org/'><span style=\"text-decoration: underline; " | ||||||
|  |            "color:#039be5;\">Sign up</span></a>")); | ||||||
|  |     ui->web_token_info_link->setOpenExternalLinks(true); | ||||||
|  |     ui->web_token_info_link->setText( | ||||||
|  |         tr("<a href='https://yuzu-emu.org/wiki/yuzu-web-service/'><span style=\"text-decoration: " | ||||||
|  |            "underline; color:#039be5;\">What is my token?</span></a>")); | ||||||
|  |  | ||||||
|  |     ui->toggle_telemetry->setChecked(Settings::values.enable_telemetry); | ||||||
|  |     ui->edit_username->setText(QString::fromStdString(Settings::values.yuzu_username)); | ||||||
|  |     ui->edit_token->setText(QString::fromStdString(Settings::values.yuzu_token)); | ||||||
|  |     // Connect after setting the values, to avoid calling OnLoginChanged now | ||||||
|  |     connect(ui->edit_token, &QLineEdit::textChanged, this, &ConfigureWeb::OnLoginChanged); | ||||||
|  |     connect(ui->edit_username, &QLineEdit::textChanged, this, &ConfigureWeb::OnLoginChanged); | ||||||
|  |     ui->label_telemetry_id->setText( | ||||||
|  |         tr("Telemetry ID: 0x%1").arg(QString::number(Core::GetTelemetryId(), 16).toUpper())); | ||||||
|  |     user_verified = true; | ||||||
|  |  | ||||||
|  |     ui->toggle_discordrpc->setChecked(UISettings::values.enable_discord_presence); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | 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.yuzu_username = ui->edit_username->text().toStdString(); | ||||||
|  |         Settings::values.yuzu_token = ui->edit_token->text().toStdString(); | ||||||
|  |     } else { | ||||||
|  |         QMessageBox::warning(this, tr("Username and token not verified"), | ||||||
|  |                              tr("Username and token were not verified. The changes to your " | ||||||
|  |                                 "username and/or token have not been saved.")); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void ConfigureWeb::RefreshTelemetryID() { | ||||||
|  |     const u64 new_telemetry_id{Core::RegenerateTelemetryId()}; | ||||||
|  |     ui->label_telemetry_id->setText( | ||||||
|  |         tr("Telemetry ID: 0x%1").arg(QString::number(new_telemetry_id, 16).toUpper())); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void ConfigureWeb::OnLoginChanged() { | ||||||
|  |     if (ui->edit_username->text().isEmpty() && ui->edit_token->text().isEmpty()) { | ||||||
|  |         user_verified = true; | ||||||
|  |         ui->label_username_verified->setPixmap(QIcon::fromTheme("checked").pixmap(16)); | ||||||
|  |         ui->label_token_verified->setPixmap(QIcon::fromTheme("checked").pixmap(16)); | ||||||
|  |     } else { | ||||||
|  |         user_verified = false; | ||||||
|  |         ui->label_username_verified->setPixmap(QIcon::fromTheme("failed").pixmap(16)); | ||||||
|  |         ui->label_token_verified->setPixmap(QIcon::fromTheme("failed").pixmap(16)); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void ConfigureWeb::VerifyLogin() { | ||||||
|  |     ui->button_verify_login->setDisabled(true); | ||||||
|  |     ui->button_verify_login->setText(tr("Verifying")); | ||||||
|  |     verify_watcher.setFuture( | ||||||
|  |         QtConcurrent::run([this, username = ui->edit_username->text().toStdString(), | ||||||
|  |                            token = ui->edit_token->text().toStdString()]() { | ||||||
|  |             return Core::VerifyLogin(username, token); | ||||||
|  |         })); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void ConfigureWeb::OnLoginVerified() { | ||||||
|  |     ui->button_verify_login->setEnabled(true); | ||||||
|  |     ui->button_verify_login->setText(tr("Verify")); | ||||||
|  |     if (verify_watcher.result()) { | ||||||
|  |         user_verified = true; | ||||||
|  |         ui->label_username_verified->setPixmap(QIcon::fromTheme("checked").pixmap(16)); | ||||||
|  |         ui->label_token_verified->setPixmap(QIcon::fromTheme("checked").pixmap(16)); | ||||||
|  |     } else { | ||||||
|  |         ui->label_username_verified->setPixmap(QIcon::fromTheme("failed").pixmap(16)); | ||||||
|  |         ui->label_token_verified->setPixmap(QIcon::fromTheme("failed").pixmap(16)); | ||||||
|  |         QMessageBox::critical( | ||||||
|  |             this, tr("Verification failed"), | ||||||
|  |             tr("Verification failed. Check that you have entered your username and token " | ||||||
|  |                "correctly, and that your internet connection is working.")); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void ConfigureWeb::retranslateUi() { | ||||||
|  |     ui->retranslateUi(this); | ||||||
|  | } | ||||||
							
								
								
									
										38
									
								
								src/yuzu/configuration/configure_web.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								src/yuzu/configuration/configure_web.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,38 @@ | |||||||
|  | // Copyright 2017 Citra Emulator Project | ||||||
|  | // Licensed under GPLv2 or any later version | ||||||
|  | // Refer to the license.txt file included. | ||||||
|  |  | ||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | #include <memory> | ||||||
|  | #include <QFutureWatcher> | ||||||
|  | #include <QWidget> | ||||||
|  |  | ||||||
|  | namespace Ui { | ||||||
|  | class ConfigureWeb; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class ConfigureWeb : public QWidget { | ||||||
|  |     Q_OBJECT | ||||||
|  |  | ||||||
|  | public: | ||||||
|  |     explicit ConfigureWeb(QWidget* parent = nullptr); | ||||||
|  |     ~ConfigureWeb(); | ||||||
|  |  | ||||||
|  |     void applyConfiguration(); | ||||||
|  |     void retranslateUi(); | ||||||
|  |  | ||||||
|  | public slots: | ||||||
|  |     void RefreshTelemetryID(); | ||||||
|  |     void OnLoginChanged(); | ||||||
|  |     void VerifyLogin(); | ||||||
|  |     void OnLoginVerified(); | ||||||
|  |  | ||||||
|  | private: | ||||||
|  |     void setConfiguration(); | ||||||
|  |  | ||||||
|  |     bool user_verified = true; | ||||||
|  |     QFutureWatcher<bool> verify_watcher; | ||||||
|  |  | ||||||
|  |     std::unique_ptr<Ui::ConfigureWeb> ui; | ||||||
|  | }; | ||||||
							
								
								
									
										206
									
								
								src/yuzu/configuration/configure_web.ui
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										206
									
								
								src/yuzu/configuration/configure_web.ui
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,206 @@ | |||||||
|  | <?xml version="1.0" encoding="UTF-8"?> | ||||||
|  | <ui version="4.0"> | ||||||
|  |  <class>ConfigureWeb</class> | ||||||
|  |  <widget class="QWidget" name="ConfigureWeb"> | ||||||
|  |   <property name="geometry"> | ||||||
|  |    <rect> | ||||||
|  |     <x>0</x> | ||||||
|  |     <y>0</y> | ||||||
|  |     <width>926</width> | ||||||
|  |     <height>561</height> | ||||||
|  |    </rect> | ||||||
|  |   </property> | ||||||
|  |   <property name="windowTitle"> | ||||||
|  |    <string>Form</string> | ||||||
|  |   </property> | ||||||
|  |   <layout class="QVBoxLayout" name="verticalLayout"> | ||||||
|  |    <item> | ||||||
|  |     <layout class="QVBoxLayout" name="verticalLayout_3"> | ||||||
|  |      <item> | ||||||
|  |       <widget class="QGroupBox" name="groupBoxWebConfig"> | ||||||
|  |        <property name="title"> | ||||||
|  |         <string>yuzu Web Service</string> | ||||||
|  |        </property> | ||||||
|  |        <layout class="QVBoxLayout" name="verticalLayoutYuzuWebService"> | ||||||
|  |         <item> | ||||||
|  |          <widget class="QLabel" name="web_credentials_disclaimer"> | ||||||
|  |           <property name="text"> | ||||||
|  |            <string>By providing your username and token, you agree to allow yuzu to collect additional usage data, which may include user identifying information.</string> | ||||||
|  |           </property> | ||||||
|  |          </widget> | ||||||
|  |         </item> | ||||||
|  |         <item> | ||||||
|  |          <layout class="QGridLayout" name="gridLayoutYuzuUsername"> | ||||||
|  |           <item row="2" column="3"> | ||||||
|  |            <widget class="QPushButton" name="button_verify_login"> | ||||||
|  |             <property name="sizePolicy"> | ||||||
|  |              <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> | ||||||
|  |               <horstretch>0</horstretch> | ||||||
|  |               <verstretch>0</verstretch> | ||||||
|  |              </sizepolicy> | ||||||
|  |             </property> | ||||||
|  |             <property name="layoutDirection"> | ||||||
|  |              <enum>Qt::RightToLeft</enum> | ||||||
|  |             </property> | ||||||
|  |             <property name="text"> | ||||||
|  |              <string>Verify</string> | ||||||
|  |             </property> | ||||||
|  |            </widget> | ||||||
|  |           </item> | ||||||
|  |           <item row="2" column="0"> | ||||||
|  |            <widget class="QLabel" name="web_signup_link"> | ||||||
|  |             <property name="text"> | ||||||
|  |              <string>Sign up</string> | ||||||
|  |             </property> | ||||||
|  |            </widget> | ||||||
|  |           </item> | ||||||
|  |           <item row="0" column="1" colspan="3"> | ||||||
|  |            <widget class="QLineEdit" name="edit_username"> | ||||||
|  |             <property name="maxLength"> | ||||||
|  |              <number>36</number> | ||||||
|  |             </property> | ||||||
|  |            </widget> | ||||||
|  |           </item> | ||||||
|  |           <item row="1" column="0"> | ||||||
|  |            <widget class="QLabel" name="label_token"> | ||||||
|  |             <property name="text"> | ||||||
|  |              <string>Token: </string> | ||||||
|  |             </property> | ||||||
|  |            </widget> | ||||||
|  |           </item> | ||||||
|  |           <item row="1" column="4"> | ||||||
|  |            <widget class="QLabel" name="label_token_verified"> | ||||||
|  |            </widget> | ||||||
|  |           </item> | ||||||
|  |           <item row="0" column="0"> | ||||||
|  |            <widget class="QLabel" name="label_username"> | ||||||
|  |             <property name="text"> | ||||||
|  |              <string>Username: </string> | ||||||
|  |             </property> | ||||||
|  |            </widget> | ||||||
|  |           </item> | ||||||
|  |           <item row="0" column="4"> | ||||||
|  |            <widget class="QLabel" name="label_username_verified"> | ||||||
|  |            </widget> | ||||||
|  |           </item> | ||||||
|  |           <item row="1" column="1" colspan="3"> | ||||||
|  |            <widget class="QLineEdit" name="edit_token"> | ||||||
|  |             <property name="maxLength"> | ||||||
|  |              <number>36</number> | ||||||
|  |             </property> | ||||||
|  |             <property name="echoMode"> | ||||||
|  |              <enum>QLineEdit::Password</enum> | ||||||
|  |             </property> | ||||||
|  |            </widget> | ||||||
|  |           </item> | ||||||
|  |           <item row="2" column="1"> | ||||||
|  |            <widget class="QLabel" name="web_token_info_link"> | ||||||
|  |             <property name="text"> | ||||||
|  |              <string>What is my token?</string> | ||||||
|  |             </property> | ||||||
|  |            </widget> | ||||||
|  |           </item> | ||||||
|  |           <item row="2" column="2"> | ||||||
|  |            <spacer name="horizontalSpacer"> | ||||||
|  |             <property name="orientation"> | ||||||
|  |              <enum>Qt::Horizontal</enum> | ||||||
|  |             </property> | ||||||
|  |             <property name="sizeHint" stdset="0"> | ||||||
|  |              <size> | ||||||
|  |               <width>40</width> | ||||||
|  |               <height>20</height> | ||||||
|  |              </size> | ||||||
|  |             </property> | ||||||
|  |            </spacer> | ||||||
|  |           </item> | ||||||
|  |          </layout> | ||||||
|  |         </item> | ||||||
|  |        </layout> | ||||||
|  |       </widget> | ||||||
|  |      </item> | ||||||
|  |      <item> | ||||||
|  |       <widget class="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 yuzu team</string> | ||||||
|  |           </property> | ||||||
|  |          </widget> | ||||||
|  |         </item> | ||||||
|  |         <item> | ||||||
|  |          <widget class="QLabel" name="telemetry_learn_more"> | ||||||
|  |           <property name="text"> | ||||||
|  |            <string>Learn more</string> | ||||||
|  |           </property> | ||||||
|  |          </widget> | ||||||
|  |         </item> | ||||||
|  |         <item> | ||||||
|  |          <layout class="QGridLayout" name="gridLayoutTelemetryId"> | ||||||
|  |           <item row="0" column="0"> | ||||||
|  |            <widget class="QLabel" name="label_telemetry_id"> | ||||||
|  |             <property name="text"> | ||||||
|  |              <string>Telemetry ID:</string> | ||||||
|  |             </property> | ||||||
|  |            </widget> | ||||||
|  |           </item> | ||||||
|  |           <item row="0" column="1"> | ||||||
|  |            <widget class="QPushButton" name="button_regenerate_telemetry_id"> | ||||||
|  |             <property name="sizePolicy"> | ||||||
|  |              <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> | ||||||
|  |               <horstretch>0</horstretch> | ||||||
|  |               <verstretch>0</verstretch> | ||||||
|  |              </sizepolicy> | ||||||
|  |             </property> | ||||||
|  |             <property name="layoutDirection"> | ||||||
|  |              <enum>Qt::RightToLeft</enum> | ||||||
|  |             </property> | ||||||
|  |             <property name="text"> | ||||||
|  |              <string>Regenerate</string> | ||||||
|  |             </property> | ||||||
|  |            </widget> | ||||||
|  |           </item> | ||||||
|  |          </layout> | ||||||
|  |         </item> | ||||||
|  |        </layout> | ||||||
|  |       </widget> | ||||||
|  |      </item> | ||||||
|  |     </layout> | ||||||
|  |    </item> | ||||||
|  |    <item> | ||||||
|  |      <widget class="QGroupBox" name="discord_group"> | ||||||
|  |       <property name="title"> | ||||||
|  |        <string>Discord Presence</string> | ||||||
|  |       </property> | ||||||
|  |       <layout class="QVBoxLayout" name="verticalLayout_21"> | ||||||
|  |        <item> | ||||||
|  |         <widget class="QCheckBox" name="toggle_discordrpc"> | ||||||
|  |          <property name="text"> | ||||||
|  |           <string>Show Current Game in your Discord Status</string> | ||||||
|  |          </property> | ||||||
|  |         </widget> | ||||||
|  |        </item> | ||||||
|  |       </layout> | ||||||
|  |      </widget> | ||||||
|  |    </item> | ||||||
|  |    <item> | ||||||
|  |     <spacer name="verticalSpacer"> | ||||||
|  |      <property name="orientation"> | ||||||
|  |       <enum>Qt::Vertical</enum> | ||||||
|  |      </property> | ||||||
|  |      <property name="sizeHint" stdset="0"> | ||||||
|  |       <size> | ||||||
|  |        <width>20</width> | ||||||
|  |        <height>40</height> | ||||||
|  |       </size> | ||||||
|  |      </property> | ||||||
|  |     </spacer> | ||||||
|  |    </item> | ||||||
|  |   </layout> | ||||||
|  |  </widget> | ||||||
|  |  <resources/> | ||||||
|  |  <connections/> | ||||||
|  | </ui> | ||||||
							
								
								
									
										25
									
								
								src/yuzu/discord.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								src/yuzu/discord.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,25 @@ | |||||||
|  | // Copyright 2018 Citra Emulator Project | ||||||
|  | // Licensed under GPLv2 or any later version | ||||||
|  | // Refer to the license.txt file included. | ||||||
|  |  | ||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | namespace DiscordRPC { | ||||||
|  |  | ||||||
|  | class DiscordInterface { | ||||||
|  | public: | ||||||
|  |     virtual ~DiscordInterface() = default; | ||||||
|  |  | ||||||
|  |     virtual void Pause() = 0; | ||||||
|  |     virtual void Update() = 0; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | class NullImpl : public DiscordInterface { | ||||||
|  | public: | ||||||
|  |     ~NullImpl() = default; | ||||||
|  |  | ||||||
|  |     void Pause() override {} | ||||||
|  |     void Update() override {} | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | } // namespace DiscordRPC | ||||||
							
								
								
									
										52
									
								
								src/yuzu/discord_impl.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								src/yuzu/discord_impl.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,52 @@ | |||||||
|  | // Copyright 2018 Citra Emulator Project | ||||||
|  | // Licensed under GPLv2 or any later version | ||||||
|  | // Refer to the license.txt file included. | ||||||
|  |  | ||||||
|  | #include <chrono> | ||||||
|  | #include <string> | ||||||
|  | #include <discord_rpc.h> | ||||||
|  | #include "common/common_types.h" | ||||||
|  | #include "core/core.h" | ||||||
|  | #include "core/loader/loader.h" | ||||||
|  | #include "yuzu/discord_impl.h" | ||||||
|  | #include "yuzu/ui_settings.h" | ||||||
|  |  | ||||||
|  | namespace DiscordRPC { | ||||||
|  |  | ||||||
|  | DiscordImpl::DiscordImpl() { | ||||||
|  |     DiscordEventHandlers handlers{}; | ||||||
|  |  | ||||||
|  |     // The number is the client ID for yuzu, it's used for images and the | ||||||
|  |     // application name | ||||||
|  |     Discord_Initialize("471872241299226636", &handlers, 1, nullptr); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | DiscordImpl::~DiscordImpl() { | ||||||
|  |     Discord_ClearPresence(); | ||||||
|  |     Discord_Shutdown(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void DiscordImpl::Pause() { | ||||||
|  |     Discord_ClearPresence(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void DiscordImpl::Update() { | ||||||
|  |     s64 start_time = std::chrono::duration_cast<std::chrono::seconds>( | ||||||
|  |                          std::chrono::system_clock::now().time_since_epoch()) | ||||||
|  |                          .count(); | ||||||
|  |     std::string title; | ||||||
|  |     if (Core::System::GetInstance().IsPoweredOn()) | ||||||
|  |         Core::System::GetInstance().GetAppLoader().ReadTitle(title); | ||||||
|  |     DiscordRichPresence presence{}; | ||||||
|  |     presence.largeImageKey = "yuzu_logo"; | ||||||
|  |     presence.largeImageText = "yuzu is an emulator for the Nintendo Switch"; | ||||||
|  |     if (Core::System::GetInstance().IsPoweredOn()) { | ||||||
|  |         presence.state = title.c_str(); | ||||||
|  |         presence.details = "Currently in game"; | ||||||
|  |     } else { | ||||||
|  |         presence.details = "Not in game"; | ||||||
|  |     } | ||||||
|  |     presence.startTimestamp = start_time; | ||||||
|  |     Discord_UpdatePresence(&presence); | ||||||
|  | } | ||||||
|  | } // namespace DiscordRPC | ||||||
							
								
								
									
										20
									
								
								src/yuzu/discord_impl.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								src/yuzu/discord_impl.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,20 @@ | |||||||
|  | // Copyright 2018 Citra Emulator Project | ||||||
|  | // Licensed under GPLv2 or any later version | ||||||
|  | // Refer to the license.txt file included. | ||||||
|  |  | ||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | #include "yuzu/discord.h" | ||||||
|  |  | ||||||
|  | namespace DiscordRPC { | ||||||
|  |  | ||||||
|  | class DiscordImpl : public DiscordInterface { | ||||||
|  | public: | ||||||
|  |     DiscordImpl(); | ||||||
|  |     ~DiscordImpl() override; | ||||||
|  |  | ||||||
|  |     void Pause() override; | ||||||
|  |     void Update() override; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | } // namespace DiscordRPC | ||||||
| @@ -35,6 +35,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual | |||||||
| #include <QtWidgets> | #include <QtWidgets> | ||||||
| #include <fmt/format.h> | #include <fmt/format.h> | ||||||
| #include "common/common_paths.h" | #include "common/common_paths.h" | ||||||
|  | #include "common/detached_tasks.h" | ||||||
| #include "common/file_util.h" | #include "common/file_util.h" | ||||||
| #include "common/logging/backend.h" | #include "common/logging/backend.h" | ||||||
| #include "common/logging/filter.h" | #include "common/logging/filter.h" | ||||||
| @@ -65,6 +66,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual | |||||||
| #include "video_core/debug_utils/debug_utils.h" | #include "video_core/debug_utils/debug_utils.h" | ||||||
| #include "yuzu/about_dialog.h" | #include "yuzu/about_dialog.h" | ||||||
| #include "yuzu/bootmanager.h" | #include "yuzu/bootmanager.h" | ||||||
|  | #include "yuzu/compatdb.h" | ||||||
| #include "yuzu/compatibility_list.h" | #include "yuzu/compatibility_list.h" | ||||||
| #include "yuzu/configuration/config.h" | #include "yuzu/configuration/config.h" | ||||||
| #include "yuzu/configuration/configure_dialog.h" | #include "yuzu/configuration/configure_dialog.h" | ||||||
| @@ -73,12 +75,17 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual | |||||||
| #include "yuzu/debugger/graphics/graphics_surface.h" | #include "yuzu/debugger/graphics/graphics_surface.h" | ||||||
| #include "yuzu/debugger/profiler.h" | #include "yuzu/debugger/profiler.h" | ||||||
| #include "yuzu/debugger/wait_tree.h" | #include "yuzu/debugger/wait_tree.h" | ||||||
|  | #include "yuzu/discord.h" | ||||||
| #include "yuzu/game_list.h" | #include "yuzu/game_list.h" | ||||||
| #include "yuzu/game_list_p.h" | #include "yuzu/game_list_p.h" | ||||||
| #include "yuzu/hotkeys.h" | #include "yuzu/hotkeys.h" | ||||||
| #include "yuzu/main.h" | #include "yuzu/main.h" | ||||||
| #include "yuzu/ui_settings.h" | #include "yuzu/ui_settings.h" | ||||||
|  |  | ||||||
|  | #ifdef USE_DISCORD_PRESENCE | ||||||
|  | #include "yuzu/discord_impl.h" | ||||||
|  | #endif | ||||||
|  |  | ||||||
| #ifdef QT_STATICPLUGIN | #ifdef QT_STATICPLUGIN | ||||||
| Q_IMPORT_PLUGIN(QWindowsIntegrationPlugin); | Q_IMPORT_PLUGIN(QWindowsIntegrationPlugin); | ||||||
| #endif | #endif | ||||||
| @@ -102,23 +109,22 @@ enum class CalloutFlag : uint32_t { | |||||||
|     DRDDeprecation = 0x2, |     DRDDeprecation = 0x2, | ||||||
| }; | }; | ||||||
|  |  | ||||||
| static void ShowCalloutMessage(const QString& message, CalloutFlag flag) { | void GMainWindow::ShowTelemetryCallout() { | ||||||
|     if (UISettings::values.callout_flags & static_cast<uint32_t>(flag)) { |     if (UISettings::values.callout_flags & static_cast<uint32_t>(CalloutFlag::Telemetry)) { | ||||||
|         return; |         return; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     UISettings::values.callout_flags |= static_cast<uint32_t>(flag); |     UISettings::values.callout_flags |= static_cast<uint32_t>(CalloutFlag::Telemetry); | ||||||
|  |     const QString telemetry_message = | ||||||
|     QMessageBox msg; |         tr("<a href='https://yuzu-emu.org/help/features/telemetry/'>Anonymous " | ||||||
|     msg.setText(message); |            "data is collected</a> to help improve yuzu. " | ||||||
|     msg.setStandardButtons(QMessageBox::Ok); |            "<br/><br/>Would you like to share your usage data with us?"); | ||||||
|     msg.setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); |     if (QMessageBox::question(this, tr("Telemetry"), telemetry_message) != QMessageBox::Yes) { | ||||||
|     msg.setStyleSheet("QLabel{min-width: 900px;}"); |         Settings::values.enable_telemetry = false; | ||||||
|     msg.exec(); |         Settings::Apply(); | ||||||
|  |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| void GMainWindow::ShowCallouts() {} |  | ||||||
|  |  | ||||||
| const int GMainWindow::max_recent_files_item; | const int GMainWindow::max_recent_files_item; | ||||||
|  |  | ||||||
| static void InitializeLogging() { | static void InitializeLogging() { | ||||||
| @@ -145,6 +151,9 @@ GMainWindow::GMainWindow() | |||||||
|     default_theme_paths = QIcon::themeSearchPaths(); |     default_theme_paths = QIcon::themeSearchPaths(); | ||||||
|     UpdateUITheme(); |     UpdateUITheme(); | ||||||
|  |  | ||||||
|  |     SetDiscordEnabled(UISettings::values.enable_discord_presence); | ||||||
|  |     discord_rpc->Update(); | ||||||
|  |  | ||||||
|     InitializeWidgets(); |     InitializeWidgets(); | ||||||
|     InitializeDebugWidgets(); |     InitializeDebugWidgets(); | ||||||
|     InitializeRecentFileMenuActions(); |     InitializeRecentFileMenuActions(); | ||||||
| @@ -168,7 +177,7 @@ GMainWindow::GMainWindow() | |||||||
|     game_list->PopulateAsync(UISettings::values.gamedir, UISettings::values.gamedir_deepscan); |     game_list->PopulateAsync(UISettings::values.gamedir, UISettings::values.gamedir_deepscan); | ||||||
|  |  | ||||||
|     // Show one-time "callout" messages to the user |     // Show one-time "callout" messages to the user | ||||||
|     ShowCallouts(); |     ShowTelemetryCallout(); | ||||||
|  |  | ||||||
|     QStringList args = QApplication::arguments(); |     QStringList args = QApplication::arguments(); | ||||||
|     if (args.length() >= 2) { |     if (args.length() >= 2) { | ||||||
| @@ -183,6 +192,9 @@ GMainWindow::~GMainWindow() { | |||||||
| } | } | ||||||
|  |  | ||||||
| void GMainWindow::InitializeWidgets() { | void GMainWindow::InitializeWidgets() { | ||||||
|  | #ifdef YUZU_ENABLE_COMPATIBILITY_REPORTING | ||||||
|  |     ui.action_Report_Compatibility->setVisible(true); | ||||||
|  | #endif | ||||||
|     render_window = new GRenderWindow(this, emu_thread.get()); |     render_window = new GRenderWindow(this, emu_thread.get()); | ||||||
|     render_window->hide(); |     render_window->hide(); | ||||||
|  |  | ||||||
| @@ -411,6 +423,8 @@ void GMainWindow::ConnectMenuEvents() { | |||||||
|     connect(ui.action_Start, &QAction::triggered, this, &GMainWindow::OnStartGame); |     connect(ui.action_Start, &QAction::triggered, this, &GMainWindow::OnStartGame); | ||||||
|     connect(ui.action_Pause, &QAction::triggered, this, &GMainWindow::OnPauseGame); |     connect(ui.action_Pause, &QAction::triggered, this, &GMainWindow::OnPauseGame); | ||||||
|     connect(ui.action_Stop, &QAction::triggered, this, &GMainWindow::OnStopGame); |     connect(ui.action_Stop, &QAction::triggered, this, &GMainWindow::OnStopGame); | ||||||
|  |     connect(ui.action_Report_Compatibility, &QAction::triggered, this, | ||||||
|  |             &GMainWindow::OnMenuReportCompatibility); | ||||||
|     connect(ui.action_Restart, &QAction::triggered, this, [this] { BootGame(QString(game_path)); }); |     connect(ui.action_Restart, &QAction::triggered, this, [this] { BootGame(QString(game_path)); }); | ||||||
|     connect(ui.action_Configure, &QAction::triggered, this, &GMainWindow::OnConfigure); |     connect(ui.action_Configure, &QAction::triggered, this, &GMainWindow::OnConfigure); | ||||||
|  |  | ||||||
| @@ -647,6 +661,7 @@ void GMainWindow::BootGame(const QString& filename) { | |||||||
| } | } | ||||||
|  |  | ||||||
| void GMainWindow::ShutdownGame() { | void GMainWindow::ShutdownGame() { | ||||||
|  |     discord_rpc->Pause(); | ||||||
|     emu_thread->RequestStop(); |     emu_thread->RequestStop(); | ||||||
|  |  | ||||||
|     emit EmulationStopping(); |     emit EmulationStopping(); | ||||||
| @@ -655,6 +670,8 @@ void GMainWindow::ShutdownGame() { | |||||||
|     emu_thread->wait(); |     emu_thread->wait(); | ||||||
|     emu_thread = nullptr; |     emu_thread = nullptr; | ||||||
|  |  | ||||||
|  |     discord_rpc->Update(); | ||||||
|  |  | ||||||
|     // The emulation is stopped, so closing the window or not does not matter anymore |     // The emulation is stopped, so closing the window or not does not matter anymore | ||||||
|     disconnect(render_window, &GRenderWindow::Closed, this, &GMainWindow::OnStopGame); |     disconnect(render_window, &GRenderWindow::Closed, this, &GMainWindow::OnStopGame); | ||||||
|  |  | ||||||
| @@ -664,6 +681,7 @@ void GMainWindow::ShutdownGame() { | |||||||
|     ui.action_Pause->setEnabled(false); |     ui.action_Pause->setEnabled(false); | ||||||
|     ui.action_Stop->setEnabled(false); |     ui.action_Stop->setEnabled(false); | ||||||
|     ui.action_Restart->setEnabled(false); |     ui.action_Restart->setEnabled(false); | ||||||
|  |     ui.action_Report_Compatibility->setEnabled(false); | ||||||
|     render_window->hide(); |     render_window->hide(); | ||||||
|     game_list->show(); |     game_list->show(); | ||||||
|     game_list->setFilterFocus(); |     game_list->setFilterFocus(); | ||||||
| @@ -1147,6 +1165,9 @@ void GMainWindow::OnStartGame() { | |||||||
|     ui.action_Pause->setEnabled(true); |     ui.action_Pause->setEnabled(true); | ||||||
|     ui.action_Stop->setEnabled(true); |     ui.action_Stop->setEnabled(true); | ||||||
|     ui.action_Restart->setEnabled(true); |     ui.action_Restart->setEnabled(true); | ||||||
|  |     ui.action_Report_Compatibility->setEnabled(true); | ||||||
|  |  | ||||||
|  |     discord_rpc->Update(); | ||||||
| } | } | ||||||
|  |  | ||||||
| void GMainWindow::OnPauseGame() { | void GMainWindow::OnPauseGame() { | ||||||
| @@ -1161,6 +1182,20 @@ void GMainWindow::OnStopGame() { | |||||||
|     ShutdownGame(); |     ShutdownGame(); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | void GMainWindow::OnMenuReportCompatibility() { | ||||||
|  |     if (!Settings::values.yuzu_token.empty() && !Settings::values.yuzu_username.empty()) { | ||||||
|  |         CompatDB compatdb{this}; | ||||||
|  |         compatdb.exec(); | ||||||
|  |     } else { | ||||||
|  |         QMessageBox::critical( | ||||||
|  |             this, tr("Missing yuzu Account"), | ||||||
|  |             tr("In order to submit a game compatibility test case, you must link your yuzu " | ||||||
|  |                "account.<br><br/>To link your yuzu account, go to Emulation > Configuration " | ||||||
|  |                "> " | ||||||
|  |                "Web.")); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
| void GMainWindow::ToggleFullscreen() { | void GMainWindow::ToggleFullscreen() { | ||||||
|     if (!emulation_running) { |     if (!emulation_running) { | ||||||
|         return; |         return; | ||||||
| @@ -1224,11 +1259,14 @@ void GMainWindow::ToggleWindowMode() { | |||||||
| void GMainWindow::OnConfigure() { | void GMainWindow::OnConfigure() { | ||||||
|     ConfigureDialog configureDialog(this, hotkey_registry); |     ConfigureDialog configureDialog(this, hotkey_registry); | ||||||
|     auto old_theme = UISettings::values.theme; |     auto old_theme = UISettings::values.theme; | ||||||
|  |     const bool old_discord_presence = UISettings::values.enable_discord_presence; | ||||||
|     auto result = configureDialog.exec(); |     auto result = configureDialog.exec(); | ||||||
|     if (result == QDialog::Accepted) { |     if (result == QDialog::Accepted) { | ||||||
|         configureDialog.applyConfiguration(); |         configureDialog.applyConfiguration(); | ||||||
|         if (UISettings::values.theme != old_theme) |         if (UISettings::values.theme != old_theme) | ||||||
|             UpdateUITheme(); |             UpdateUITheme(); | ||||||
|  |         if (UISettings::values.enable_discord_presence != old_discord_presence) | ||||||
|  |             SetDiscordEnabled(UISettings::values.enable_discord_presence); | ||||||
|         game_list->PopulateAsync(UISettings::values.gamedir, UISettings::values.gamedir_deepscan); |         game_list->PopulateAsync(UISettings::values.gamedir, UISettings::values.gamedir_deepscan); | ||||||
|         config->Save(); |         config->Save(); | ||||||
|     } |     } | ||||||
| @@ -1443,11 +1481,25 @@ void GMainWindow::UpdateUITheme() { | |||||||
|     emit UpdateThemedIcons(); |     emit UpdateThemedIcons(); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | void GMainWindow::SetDiscordEnabled(bool state) { | ||||||
|  | #ifdef USE_DISCORD_PRESENCE | ||||||
|  |     if (state) { | ||||||
|  |         discord_rpc = std::make_unique<DiscordRPC::DiscordImpl>(); | ||||||
|  |     } else { | ||||||
|  |         discord_rpc = std::make_unique<DiscordRPC::NullImpl>(); | ||||||
|  |     } | ||||||
|  | #else | ||||||
|  |     discord_rpc = std::make_unique<DiscordRPC::NullImpl>(); | ||||||
|  | #endif | ||||||
|  |     discord_rpc->Update(); | ||||||
|  | } | ||||||
|  |  | ||||||
| #ifdef main | #ifdef main | ||||||
| #undef main | #undef main | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
| int main(int argc, char* argv[]) { | int main(int argc, char* argv[]) { | ||||||
|  |     Common::DetachedTasks detached_tasks; | ||||||
|     MicroProfileOnThreadCreate("Frontend"); |     MicroProfileOnThreadCreate("Frontend"); | ||||||
|     SCOPE_EXIT({ MicroProfileShutdown(); }); |     SCOPE_EXIT({ MicroProfileShutdown(); }); | ||||||
|  |  | ||||||
| @@ -1465,5 +1517,7 @@ int main(int argc, char* argv[]) { | |||||||
|     GMainWindow main_window; |     GMainWindow main_window; | ||||||
|     // After settings have been loaded by GMainWindow, apply the filter |     // After settings have been loaded by GMainWindow, apply the filter | ||||||
|     main_window.show(); |     main_window.show(); | ||||||
|     return app.exec(); |     int result = app.exec(); | ||||||
|  |     detached_tasks.WaitForAllTasks(); | ||||||
|  |     return result; | ||||||
| } | } | ||||||
|   | |||||||
| @@ -41,6 +41,10 @@ enum class EmulatedDirectoryTarget { | |||||||
|     SDMC, |     SDMC, | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | namespace DiscordRPC { | ||||||
|  | class DiscordInterface; | ||||||
|  | } | ||||||
|  |  | ||||||
| class GMainWindow : public QMainWindow { | class GMainWindow : public QMainWindow { | ||||||
|     Q_OBJECT |     Q_OBJECT | ||||||
|  |  | ||||||
| @@ -61,6 +65,8 @@ public: | |||||||
|     GMainWindow(); |     GMainWindow(); | ||||||
|     ~GMainWindow() override; |     ~GMainWindow() override; | ||||||
|  |  | ||||||
|  |     std::unique_ptr<DiscordRPC::DiscordInterface> discord_rpc; | ||||||
|  |  | ||||||
| signals: | signals: | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
| @@ -99,7 +105,8 @@ private: | |||||||
|     void BootGame(const QString& filename); |     void BootGame(const QString& filename); | ||||||
|     void ShutdownGame(); |     void ShutdownGame(); | ||||||
|  |  | ||||||
|     void ShowCallouts(); |     void ShowTelemetryCallout(); | ||||||
|  |     void SetDiscordEnabled(bool state); | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * Stores the filename in the recently loaded files list. |      * Stores the filename in the recently loaded files list. | ||||||
| @@ -135,6 +142,7 @@ private slots: | |||||||
|     void OnStartGame(); |     void OnStartGame(); | ||||||
|     void OnPauseGame(); |     void OnPauseGame(); | ||||||
|     void OnStopGame(); |     void OnStopGame(); | ||||||
|  |     void OnMenuReportCompatibility(); | ||||||
|     /// Called whenever a user selects a game in the game list widget. |     /// Called whenever a user selects a game in the game list widget. | ||||||
|     void OnGameListLoadFile(QString game_path); |     void OnGameListLoadFile(QString game_path); | ||||||
|     void OnGameListOpenFolder(u64 program_id, GameListOpenTarget target); |     void OnGameListOpenFolder(u64 program_id, GameListOpenTarget target); | ||||||
|   | |||||||
| @@ -45,7 +45,7 @@ | |||||||
|      <x>0</x> |      <x>0</x> | ||||||
|      <y>0</y> |      <y>0</y> | ||||||
|      <width>1081</width> |      <width>1081</width> | ||||||
|      <height>19</height> |      <height>21</height> | ||||||
|     </rect> |     </rect> | ||||||
|    </property> |    </property> | ||||||
|    <widget class="QMenu" name="menu_File"> |    <widget class="QMenu" name="menu_File"> | ||||||
| @@ -101,6 +101,8 @@ | |||||||
|     <property name="title"> |     <property name="title"> | ||||||
|      <string>&Help</string> |      <string>&Help</string> | ||||||
|     </property> |     </property> | ||||||
|  |     <addaction name="action_Report_Compatibility"/> | ||||||
|  |     <addaction name="separator"/> | ||||||
|     <addaction name="action_About"/> |     <addaction name="action_About"/> | ||||||
|    </widget> |    </widget> | ||||||
|    <addaction name="menu_File"/> |    <addaction name="menu_File"/> | ||||||
| @@ -239,6 +241,18 @@ | |||||||
|        <string>Restart</string> |        <string>Restart</string> | ||||||
|      </property> |      </property> | ||||||
|    </action> |    </action> | ||||||
|  |    <action name="action_Report_Compatibility"> | ||||||
|  |      <property name="enabled"> | ||||||
|  |        <bool>false</bool> | ||||||
|  |      </property> | ||||||
|  |      <property name="text"> | ||||||
|  |        <string>Report Compatibility</string> | ||||||
|  |      </property> | ||||||
|  |      <property name="visible"> | ||||||
|  |        <bool>false</bool> | ||||||
|  |      </property> | ||||||
|  |    </action> | ||||||
|   </widget> |   </widget> | ||||||
|  <resources/> |  <resources/> | ||||||
|  |  <connections/> | ||||||
| </ui> | </ui> | ||||||
|   | |||||||
| @@ -38,6 +38,9 @@ struct Values { | |||||||
|     bool confirm_before_closing; |     bool confirm_before_closing; | ||||||
|     bool first_start; |     bool first_start; | ||||||
|  |  | ||||||
|  |     // Discord RPC | ||||||
|  |     bool enable_discord_presence; | ||||||
|  |  | ||||||
|     QString roms_path; |     QString roms_path; | ||||||
|     QString symbols_path; |     QString symbols_path; | ||||||
|     QString gamedir; |     QString gamedir; | ||||||
|   | |||||||
| @@ -138,6 +138,14 @@ void Config::ReadValues() { | |||||||
|     Settings::values.use_gdbstub = sdl2_config->GetBoolean("Debugging", "use_gdbstub", false); |     Settings::values.use_gdbstub = sdl2_config->GetBoolean("Debugging", "use_gdbstub", false); | ||||||
|     Settings::values.gdbstub_port = |     Settings::values.gdbstub_port = | ||||||
|         static_cast<u16>(sdl2_config->GetInteger("Debugging", "gdbstub_port", 24689)); |         static_cast<u16>(sdl2_config->GetInteger("Debugging", "gdbstub_port", 24689)); | ||||||
|  |  | ||||||
|  |     // Web Service | ||||||
|  |     Settings::values.enable_telemetry = | ||||||
|  |         sdl2_config->GetBoolean("WebService", "enable_telemetry", true); | ||||||
|  |     Settings::values.web_api_url = | ||||||
|  |         sdl2_config->Get("WebService", "web_api_url", "https://api.yuzu-emu.org"); | ||||||
|  |     Settings::values.yuzu_username = sdl2_config->Get("WebService", "yuzu_username", ""); | ||||||
|  |     Settings::values.yuzu_token = sdl2_config->Get("WebService", "yuzu_token", ""); | ||||||
| } | } | ||||||
|  |  | ||||||
| void Config::Reload() { | void Config::Reload() { | ||||||
|   | |||||||
| @@ -202,12 +202,10 @@ gdbstub_port=24689 | |||||||
| # Whether or not to enable telemetry | # Whether or not to enable telemetry | ||||||
| # 0: No, 1 (default): Yes | # 0: No, 1 (default): Yes | ||||||
| enable_telemetry = | enable_telemetry = | ||||||
| # Endpoint URL for submitting telemetry data | # URL for Web API | ||||||
| telemetry_endpoint_url = | web_api_url = https://api.yuzu-emu.org | ||||||
| # Endpoint URL to verify the username and token |  | ||||||
| verify_endpoint_url = |  | ||||||
| # Username and token for yuzu Web Service | # Username and token for yuzu Web Service | ||||||
| # See https://services.citra-emu.org/ for more info | # See https://profile.yuzu-emu.org/ for more info | ||||||
| yuzu_username = | yuzu_username = | ||||||
| yuzu_token = | yuzu_token = | ||||||
| )"; | )"; | ||||||
|   | |||||||
| @@ -10,6 +10,7 @@ | |||||||
| #include <fmt/ostream.h> | #include <fmt/ostream.h> | ||||||
|  |  | ||||||
| #include "common/common_paths.h" | #include "common/common_paths.h" | ||||||
|  | #include "common/detached_tasks.h" | ||||||
| #include "common/file_util.h" | #include "common/file_util.h" | ||||||
| #include "common/logging/backend.h" | #include "common/logging/backend.h" | ||||||
| #include "common/logging/filter.h" | #include "common/logging/filter.h" | ||||||
| @@ -78,6 +79,7 @@ static void InitializeLogging() { | |||||||
|  |  | ||||||
| /// Application entry point | /// Application entry point | ||||||
| int main(int argc, char** argv) { | int main(int argc, char** argv) { | ||||||
|  |     Common::DetachedTasks detached_tasks; | ||||||
|     Config config; |     Config config; | ||||||
|  |  | ||||||
|     int option_index = 0; |     int option_index = 0; | ||||||
| @@ -213,5 +215,6 @@ int main(int argc, char** argv) { | |||||||
|         system.RunLoop(); |         system.RunLoop(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     detached_tasks.WaitForAllTasks(); | ||||||
|     return 0; |     return 0; | ||||||
| } | } | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user