// SPDX-FileCopyrightText: 2017 Citra Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include #include #include #include "common/assert.h" #include "common/common_types.h" #include "common/fs/file.h" #include "common/fs/fs.h" #include "common/fs/path_util.h" #include "common/logging/log.h" #include "common/settings.h" #include "common/settings_enums.h" #include "core/file_sys/control_metadata.h" #include "core/file_sys/patch_manager.h" #include "core/loader/loader.h" #include "core/telemetry_session.h" #ifdef ENABLE_WEB_SERVICE #include "web_service/telemetry_json.h" #include "web_service/verify_login.h" #endif namespace Core { namespace Telemetry = Common::Telemetry; static u64 GenerateTelemetryId() { u64 telemetry_id{}; mbedtls_entropy_context entropy; mbedtls_entropy_init(&entropy); mbedtls_ctr_drbg_context ctr_drbg; static constexpr std::array personalization{{"suyu Telemetry ID"}}; mbedtls_ctr_drbg_init(&ctr_drbg); ASSERT(mbedtls_ctr_drbg_seed(&ctr_drbg, mbedtls_entropy_func, &entropy, reinterpret_cast(personalization.data()), personalization.size()) == 0); ASSERT(mbedtls_ctr_drbg_random(&ctr_drbg, reinterpret_cast(&telemetry_id), sizeof(u64)) == 0); mbedtls_ctr_drbg_free(&ctr_drbg); mbedtls_entropy_free(&entropy); return telemetry_id; } static const char* TranslateRenderer(Settings::RendererBackend backend) { switch (backend) { case Settings::RendererBackend::OpenGL: return "OpenGL"; case Settings::RendererBackend::Vulkan: return "Vulkan"; case Settings::RendererBackend::Null: return "Null"; } return "Unknown"; } static const char* TranslateGPUAccuracyLevel(Settings::GpuAccuracy backend) { switch (backend) { case Settings::GpuAccuracy::Normal: return "Normal"; case Settings::GpuAccuracy::High: return "High"; case Settings::GpuAccuracy::Extreme: return "Extreme"; } return "Unknown"; } static const char* TranslateNvdecEmulation(Settings::NvdecEmulation backend) { switch (backend) { case Settings::NvdecEmulation::Off: return "Off"; case Settings::NvdecEmulation::Cpu: return "CPU"; case Settings::NvdecEmulation::Gpu: return "GPU"; } return "Unknown"; } static constexpr const char* TranslateVSyncMode(Settings::VSyncMode mode) { switch (mode) { case Settings::VSyncMode::Immediate: return "Immediate"; case Settings::VSyncMode::Mailbox: return "Mailbox"; case Settings::VSyncMode::Fifo: return "FIFO"; case Settings::VSyncMode::FifoRelaxed: return "FIFO Relaxed"; } return "Unknown"; } static constexpr const char* TranslateASTCDecodeMode(Settings::AstcDecodeMode mode) { switch (mode) { case Settings::AstcDecodeMode::Cpu: return "CPU"; case Settings::AstcDecodeMode::Gpu: return "GPU"; case Settings::AstcDecodeMode::CpuAsynchronous: return "CPU Asynchronous"; } return "Unknown"; } u64 GetTelemetryId() { u64 telemetry_id{}; const auto filename = Common::FS::GetsuyuPath(Common::FS::suyuPath::ConfigDir) / "telemetry_id"; bool generate_new_id = !Common::FS::Exists(filename); if (!generate_new_id) { Common::FS::IOFile file{filename, Common::FS::FileAccessMode::Read, Common::FS::FileType::BinaryFile}; if (!file.IsOpen()) { LOG_ERROR(Core, "failed to open telemetry_id: {}", Common::FS::PathToUTF8String(filename)); return {}; } if (!file.ReadObject(telemetry_id) || telemetry_id == 0) { LOG_ERROR(Frontend, "telemetry_id is 0. Generating a new one.", telemetry_id); generate_new_id = true; } } if (generate_new_id) { Common::FS::IOFile file{filename, Common::FS::FileAccessMode::Write, Common::FS::FileType::BinaryFile}; if (!file.IsOpen()) { LOG_ERROR(Core, "failed to open telemetry_id: {}", Common::FS::PathToUTF8String(filename)); return {}; } telemetry_id = GenerateTelemetryId(); if (!file.WriteObject(telemetry_id)) { LOG_ERROR(Core, "Failed to write telemetry_id to file."); } } return telemetry_id; } u64 RegenerateTelemetryId() { const u64 new_telemetry_id{GenerateTelemetryId()}; const auto filename = Common::FS::GetsuyuPath(Common::FS::suyuPath::ConfigDir) / "telemetry_id"; Common::FS::IOFile file{filename, Common::FS::FileAccessMode::Write, Common::FS::FileType::BinaryFile}; if (!file.IsOpen()) { LOG_ERROR(Core, "failed to open telemetry_id: {}", Common::FS::PathToUTF8String(filename)); return {}; } if (!file.WriteObject(new_telemetry_id)) { LOG_ERROR(Core, "Failed to write telemetry_id to file."); } return new_telemetry_id; } bool VerifyLogin(const std::string& username, const std::string& token) { #ifdef ENABLE_WEB_SERVICE return WebService::VerifyLogin(Settings::values.web_api_url.GetValue(), username, token); #else return false; #endif } TelemetrySession::TelemetrySession() = default; TelemetrySession::~TelemetrySession() { // Log one-time session end information const s64 shutdown_time{std::chrono::duration_cast( std::chrono::system_clock::now().time_since_epoch()) .count()}; AddField(Telemetry::FieldType::Session, "Shutdown_Time", shutdown_time); #ifdef ENABLE_WEB_SERVICE auto backend = std::make_unique( Settings::values.web_api_url.GetValue(), Settings::values.suyu_username.GetValue(), Settings::values.suyu_token.GetValue()); #else auto backend = std::make_unique(); #endif // Complete the session, submitting to the web service backend if necessary field_collection.Accept(*backend); if (Settings::values.enable_telemetry) { backend->Complete(); } } void TelemetrySession::AddInitialInfo(Loader::AppLoader& app_loader, const Service::FileSystem::FileSystemController& fsc, const FileSys::ContentProvider& content_provider) { // Log one-time top-level information AddField(Telemetry::FieldType::None, "TelemetryId", GetTelemetryId()); // Log one-time session start information const s64 init_time{std::chrono::duration_cast( std::chrono::system_clock::now().time_since_epoch()) .count()}; AddField(Telemetry::FieldType::Session, "Init_Time", init_time); u64 program_id{}; const Loader::ResultStatus res{app_loader.ReadProgramId(program_id)}; if (res == Loader::ResultStatus::Success) { const std::string formatted_program_id{fmt::format("{:016X}", program_id)}; AddField(Telemetry::FieldType::Session, "ProgramId", formatted_program_id); std::string name; app_loader.ReadTitle(name); if (name.empty()) { const auto metadata = [&content_provider, &fsc, program_id] { const FileSys::PatchManager pm{program_id, fsc, content_provider}; return pm.GetControlMetadata(); }(); if (metadata.first != nullptr) { name = metadata.first->GetApplicationName(); } } if (!name.empty()) { AddField(Telemetry::FieldType::Session, "ProgramName", name); } } AddField(Telemetry::FieldType::Session, "ProgramFormat", static_cast(app_loader.GetFileType())); // Log application information Telemetry::AppendBuildInfo(field_collection); // Log user system information Telemetry::AppendCPUInfo(field_collection); Telemetry::AppendOSInfo(field_collection); // Log user configuration information constexpr auto field_type = Telemetry::FieldType::UserConfig; AddField(field_type, "Audio_SinkId", Settings::CanonicalizeEnum(Settings::values.sink_id.GetValue())); AddField(field_type, "Core_UseMultiCore", Settings::values.use_multi_core.GetValue()); AddField(field_type, "Renderer_Backend", TranslateRenderer(Settings::values.renderer_backend.GetValue())); AddField(field_type, "Renderer_UseSpeedLimit", Settings::values.use_speed_limit.GetValue()); AddField(field_type, "Renderer_SpeedLimit", Settings::values.speed_limit.GetValue()); AddField(field_type, "Renderer_UseDiskShaderCache", Settings::values.use_disk_shader_cache.GetValue()); AddField(field_type, "Renderer_GPUAccuracyLevel", TranslateGPUAccuracyLevel(Settings::values.gpu_accuracy.GetValue())); AddField(field_type, "Renderer_UseAsynchronousGpuEmulation", Settings::values.use_asynchronous_gpu_emulation.GetValue()); AddField(field_type, "Renderer_NvdecEmulation", TranslateNvdecEmulation(Settings::values.nvdec_emulation.GetValue())); AddField(field_type, "Renderer_AccelerateASTC", TranslateASTCDecodeMode(Settings::values.accelerate_astc.GetValue())); AddField(field_type, "Renderer_UseVsync", TranslateVSyncMode(Settings::values.vsync_mode.GetValue())); AddField(field_type, "Renderer_ShaderBackend", static_cast(Settings::values.shader_backend.GetValue())); AddField(field_type, "Renderer_UseAsynchronousShaders", Settings::values.use_asynchronous_shaders.GetValue()); AddField(field_type, "System_UseDockedMode", Settings::IsDockedMode()); } bool TelemetrySession::SubmitTestcase() { #ifdef ENABLE_WEB_SERVICE auto backend = std::make_unique( Settings::values.web_api_url.GetValue(), Settings::values.suyu_username.GetValue(), Settings::values.suyu_token.GetValue()); field_collection.Accept(*backend); return backend->SubmitTestcase(); #else return false; #endif } } // namespace Core