Browse Source
Merge pull request #1332 from FearlessTobi/port-web-backend
Merge pull request #1332 from FearlessTobi/port-web-backend
Port web_service from Citrance_cpp
committed by
GitHub
55 changed files with 21637 additions and 40 deletions
-
6.gitmodules
-
4.travis/common/travis-ci.env
-
2.travis/linux/build.sh
-
2.travis/linux/docker.sh
-
2.travis/macos/build.sh
-
4CMakeLists.txt
-
5appveyor.yml
-
25externals/CMakeLists.txt
-
1externals/discord-rpc
-
15externals/httplib/README.md
-
2344externals/httplib/httplib.h
-
9externals/json/README.md
-
17300externals/json/json.hpp
-
1externals/libressl
-
8externals/lurlparser/CMakeLists.txt
-
265externals/lurlparser/LUrlParser.cpp
-
78externals/lurlparser/LUrlParser.h
-
19externals/lurlparser/README.md
-
0git
-
3src/CMakeLists.txt
-
3src/common/CMakeLists.txt
-
41src/common/detached_tasks.cpp
-
40src/common/detached_tasks.h
-
24src/common/web_result.h
-
4src/core/CMakeLists.txt
-
6src/core/settings.h
-
52src/core/telemetry_session.cpp
-
5src/core/telemetry_session.h
-
16src/web_service/CMakeLists.txt
-
99src/web_service/telemetry_json.cpp
-
58src/web_service/telemetry_json.h
-
27src/web_service/verify_login.cpp
-
22src/web_service/verify_login.h
-
149src/web_service/web_backend.cpp
-
92src/web_service/web_backend.h
-
20src/yuzu/CMakeLists.txt
-
65src/yuzu/compatdb.cpp
-
26src/yuzu/compatdb.h
-
215src/yuzu/compatdb.ui
-
18src/yuzu/configuration/config.cpp
-
11src/yuzu/configuration/configure.ui
-
1src/yuzu/configuration/configure_dialog.cpp
-
119src/yuzu/configuration/configure_web.cpp
-
38src/yuzu/configuration/configure_web.h
-
206src/yuzu/configuration/configure_web.ui
-
25src/yuzu/discord.h
-
52src/yuzu/discord_impl.cpp
-
20src/yuzu/discord_impl.h
-
82src/yuzu/main.cpp
-
10src/yuzu/main.h
-
16src/yuzu/main.ui
-
3src/yuzu/ui_settings.h
-
8src/yuzu_cmd/config.cpp
-
8src/yuzu_cmd/default_ini.h
-
3src/yuzu_cmd/yuzu.cpp
@ -1,4 +1,4 @@ |
|||
#!/bin/bash -ex |
|||
|
|||
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 |
|||
@ -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
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View 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
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View 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}) |
|||
@ -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; |
|||
} |
|||
@ -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 |
|||
@ -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++. |
|||
@ -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
|
|||
@ -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 |
|||
@ -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 |
|||
@ -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) |
|||
@ -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
|
|||
@ -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 |
|||
@ -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
|
|||
@ -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 |
|||
@ -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
|
|||
@ -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 |
|||
@ -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); |
|||
} |
|||
@ -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(); |
|||
}; |
|||
@ -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> |
|||
@ -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); |
|||
} |
|||
@ -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; |
|||
}; |
|||
@ -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> |
|||
@ -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 |
|||
@ -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
|
|||
@ -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 |
|||
Write
Preview
Loading…
Cancel
Save
Reference in new issue