mirror of
				https://git.suyu.dev/suyu/suyu
				synced 2025-11-03 16:39:01 -06:00 
			
		
		
		
	feat: Removed telemetry
This commit is contained in:
		@@ -136,8 +136,6 @@ add_library(common STATIC
 | 
			
		||||
    string_util.cpp
 | 
			
		||||
    string_util.h
 | 
			
		||||
    swap.h
 | 
			
		||||
    telemetry.cpp
 | 
			
		||||
    telemetry.h
 | 
			
		||||
    thread.cpp
 | 
			
		||||
    thread.h
 | 
			
		||||
    thread_queue_list.h
 | 
			
		||||
 
 | 
			
		||||
@@ -1,119 +0,0 @@
 | 
			
		||||
// SPDX-FileCopyrightText: 2017 Citra Emulator Project
 | 
			
		||||
// SPDX-License-Identifier: GPL-2.0-or-later
 | 
			
		||||
 | 
			
		||||
#include <algorithm>
 | 
			
		||||
#include <cstring>
 | 
			
		||||
#include "common/scm_rev.h"
 | 
			
		||||
#include "common/telemetry.h"
 | 
			
		||||
 | 
			
		||||
#ifdef ARCHITECTURE_x86_64
 | 
			
		||||
#include "common/x64/cpu_detect.h"
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
namespace Common::Telemetry {
 | 
			
		||||
 | 
			
		||||
void FieldCollection::Accept(VisitorInterface& visitor) const {
 | 
			
		||||
    for (const auto& field : fields) {
 | 
			
		||||
        field.second->Accept(visitor);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void FieldCollection::AddField(std::unique_ptr<FieldInterface> field) {
 | 
			
		||||
    fields[field->GetName()] = std::move(field);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
template <class T>
 | 
			
		||||
void Field<T>::Accept(VisitorInterface& visitor) const {
 | 
			
		||||
    visitor.Visit(*this);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
template class Field<bool>;
 | 
			
		||||
template class Field<double>;
 | 
			
		||||
template class Field<float>;
 | 
			
		||||
template class Field<u8>;
 | 
			
		||||
template class Field<u16>;
 | 
			
		||||
template class Field<u32>;
 | 
			
		||||
template class Field<u64>;
 | 
			
		||||
template class Field<s8>;
 | 
			
		||||
template class Field<s16>;
 | 
			
		||||
template class Field<s32>;
 | 
			
		||||
template class Field<s64>;
 | 
			
		||||
template class Field<std::string>;
 | 
			
		||||
template class Field<const char*>;
 | 
			
		||||
template class Field<std::chrono::microseconds>;
 | 
			
		||||
 | 
			
		||||
void AppendBuildInfo(FieldCollection& fc) {
 | 
			
		||||
    const bool is_git_dirty{std::strstr(Common::g_scm_desc, "dirty") != nullptr};
 | 
			
		||||
    fc.AddField(FieldType::App, "Git_IsDirty", is_git_dirty);
 | 
			
		||||
    fc.AddField(FieldType::App, "Git_Branch", Common::g_scm_branch);
 | 
			
		||||
    fc.AddField(FieldType::App, "Git_Revision", Common::g_scm_rev);
 | 
			
		||||
    fc.AddField(FieldType::App, "BuildDate", Common::g_build_date);
 | 
			
		||||
    fc.AddField(FieldType::App, "BuildName", Common::g_build_name);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AppendCPUInfo(FieldCollection& fc) {
 | 
			
		||||
#ifdef ARCHITECTURE_x86_64
 | 
			
		||||
 | 
			
		||||
    const auto& caps = Common::GetCPUCaps();
 | 
			
		||||
    const auto add_field = [&fc](std::string_view field_name, const auto& field_value) {
 | 
			
		||||
        fc.AddField(FieldType::UserSystem, field_name, field_value);
 | 
			
		||||
    };
 | 
			
		||||
    add_field("CPU_Model", caps.cpu_string);
 | 
			
		||||
    add_field("CPU_BrandString", caps.brand_string);
 | 
			
		||||
 | 
			
		||||
    add_field("CPU_Extension_x64_SSE", caps.sse);
 | 
			
		||||
    add_field("CPU_Extension_x64_SSE2", caps.sse2);
 | 
			
		||||
    add_field("CPU_Extension_x64_SSE3", caps.sse3);
 | 
			
		||||
    add_field("CPU_Extension_x64_SSSE3", caps.ssse3);
 | 
			
		||||
    add_field("CPU_Extension_x64_SSE41", caps.sse4_1);
 | 
			
		||||
    add_field("CPU_Extension_x64_SSE42", caps.sse4_2);
 | 
			
		||||
 | 
			
		||||
    add_field("CPU_Extension_x64_AVX", caps.avx);
 | 
			
		||||
    add_field("CPU_Extension_x64_AVX_VNNI", caps.avx_vnni);
 | 
			
		||||
    add_field("CPU_Extension_x64_AVX2", caps.avx2);
 | 
			
		||||
 | 
			
		||||
    // Skylake-X/SP level AVX512, for compatibility with the previous telemetry field
 | 
			
		||||
    add_field("CPU_Extension_x64_AVX512",
 | 
			
		||||
              caps.avx512f && caps.avx512cd && caps.avx512vl && caps.avx512dq && caps.avx512bw);
 | 
			
		||||
 | 
			
		||||
    add_field("CPU_Extension_x64_AVX512F", caps.avx512f);
 | 
			
		||||
    add_field("CPU_Extension_x64_AVX512CD", caps.avx512cd);
 | 
			
		||||
    add_field("CPU_Extension_x64_AVX512VL", caps.avx512vl);
 | 
			
		||||
    add_field("CPU_Extension_x64_AVX512DQ", caps.avx512dq);
 | 
			
		||||
    add_field("CPU_Extension_x64_AVX512BW", caps.avx512bw);
 | 
			
		||||
    add_field("CPU_Extension_x64_AVX512BITALG", caps.avx512bitalg);
 | 
			
		||||
    add_field("CPU_Extension_x64_AVX512VBMI", caps.avx512vbmi);
 | 
			
		||||
 | 
			
		||||
    add_field("CPU_Extension_x64_AES", caps.aes);
 | 
			
		||||
    add_field("CPU_Extension_x64_BMI1", caps.bmi1);
 | 
			
		||||
    add_field("CPU_Extension_x64_BMI2", caps.bmi2);
 | 
			
		||||
    add_field("CPU_Extension_x64_F16C", caps.f16c);
 | 
			
		||||
    add_field("CPU_Extension_x64_FMA", caps.fma);
 | 
			
		||||
    add_field("CPU_Extension_x64_FMA4", caps.fma4);
 | 
			
		||||
    add_field("CPU_Extension_x64_GFNI", caps.gfni);
 | 
			
		||||
    add_field("CPU_Extension_x64_INVARIANT_TSC", caps.invariant_tsc);
 | 
			
		||||
    add_field("CPU_Extension_x64_LZCNT", caps.lzcnt);
 | 
			
		||||
    add_field("CPU_Extension_x64_MONITORX", caps.monitorx);
 | 
			
		||||
    add_field("CPU_Extension_x64_MOVBE", caps.movbe);
 | 
			
		||||
    add_field("CPU_Extension_x64_PCLMULQDQ", caps.pclmulqdq);
 | 
			
		||||
    add_field("CPU_Extension_x64_POPCNT", caps.popcnt);
 | 
			
		||||
    add_field("CPU_Extension_x64_SHA", caps.sha);
 | 
			
		||||
    add_field("CPU_Extension_x64_WAITPKG", caps.waitpkg);
 | 
			
		||||
#else
 | 
			
		||||
    fc.AddField(FieldType::UserSystem, "CPU_Model", "Other");
 | 
			
		||||
#endif
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AppendOSInfo(FieldCollection& fc) {
 | 
			
		||||
#ifdef __APPLE__
 | 
			
		||||
    fc.AddField(FieldType::UserSystem, "OsPlatform", "Apple");
 | 
			
		||||
#elif defined(_WIN32)
 | 
			
		||||
    fc.AddField(FieldType::UserSystem, "OsPlatform", "Windows");
 | 
			
		||||
#elif defined(__linux__) || defined(linux) || defined(__linux)
 | 
			
		||||
    fc.AddField(FieldType::UserSystem, "OsPlatform", "Linux");
 | 
			
		||||
#else
 | 
			
		||||
    fc.AddField(FieldType::UserSystem, "OsPlatform", "Unknown");
 | 
			
		||||
#endif
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
} // namespace Common::Telemetry
 | 
			
		||||
@@ -1,209 +0,0 @@
 | 
			
		||||
// SPDX-FileCopyrightText: 2017 Citra Emulator Project & 2024 suyu Emulator Project
 | 
			
		||||
// SPDX-License-Identifier: GPL-2.0-or-later
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <chrono>
 | 
			
		||||
#include <map>
 | 
			
		||||
#include <memory>
 | 
			
		||||
#include <string>
 | 
			
		||||
#include "common/common_funcs.h"
 | 
			
		||||
#include "common/common_types.h"
 | 
			
		||||
 | 
			
		||||
namespace Common::Telemetry {
 | 
			
		||||
 | 
			
		||||
/// Field type, used for grouping fields together in the final submitted telemetry log
 | 
			
		||||
enum class FieldType : u8 {
 | 
			
		||||
    None = 0,     ///< No specified field group
 | 
			
		||||
    App,          ///< suyu application fields (e.g. version, branch, etc.)
 | 
			
		||||
    Session,      ///< Emulated session fields (e.g. title ID, log, etc.)
 | 
			
		||||
    Performance,  ///< Emulated performance (e.g. fps, emulated CPU speed, etc.)
 | 
			
		||||
    UserFeedback, ///< User submitted feedback (e.g. star rating, user notes, etc.)
 | 
			
		||||
    UserConfig,   ///< User configuration fields (e.g. emulated CPU core, renderer, etc.)
 | 
			
		||||
    UserSystem,   ///< User system information (e.g. host CPU type, RAM, etc.)
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct VisitorInterface;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Interface class for telemetry data fields.
 | 
			
		||||
 */
 | 
			
		||||
class FieldInterface {
 | 
			
		||||
public:
 | 
			
		||||
    virtual ~FieldInterface() = default;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Accept method for the visitor pattern.
 | 
			
		||||
     * @param visitor Reference to the visitor that will visit this field.
 | 
			
		||||
     */
 | 
			
		||||
    virtual void Accept(VisitorInterface& visitor) const = 0;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Gets the name of this field.
 | 
			
		||||
     * @returns Name of this field as a string.
 | 
			
		||||
     */
 | 
			
		||||
    virtual const std::string& GetName() const = 0;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Represents a telemetry data field, i.e. a unit of data that gets logged and submitted to our
 | 
			
		||||
 * telemetry web service.
 | 
			
		||||
 */
 | 
			
		||||
template <typename T>
 | 
			
		||||
class Field : public FieldInterface {
 | 
			
		||||
public:
 | 
			
		||||
    SUYU_NON_COPYABLE(Field);
 | 
			
		||||
 | 
			
		||||
    Field(FieldType type_, std::string_view name_, T value_)
 | 
			
		||||
        : name(name_), type(type_), value(std::move(value_)) {}
 | 
			
		||||
 | 
			
		||||
    ~Field() override = default;
 | 
			
		||||
 | 
			
		||||
    Field(Field&&) noexcept = default;
 | 
			
		||||
    Field& operator=(Field&& other) noexcept = default;
 | 
			
		||||
 | 
			
		||||
    void Accept(VisitorInterface& visitor) const override;
 | 
			
		||||
 | 
			
		||||
    [[nodiscard]] const std::string& GetName() const override {
 | 
			
		||||
        return name;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the type of the field.
 | 
			
		||||
     */
 | 
			
		||||
    [[nodiscard]] FieldType GetType() const {
 | 
			
		||||
        return type;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the value of the field.
 | 
			
		||||
     */
 | 
			
		||||
    [[nodiscard]] const T& GetValue() const {
 | 
			
		||||
        return value;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    [[nodiscard]] bool operator==(const Field& other) const {
 | 
			
		||||
        return (type == other.type) && (name == other.name) && (value == other.value);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    [[nodiscard]] bool operator!=(const Field& other) const {
 | 
			
		||||
        return !operator==(other);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
    std::string name; ///< Field name, must be unique
 | 
			
		||||
    FieldType type{}; ///< Field type, used for grouping fields together
 | 
			
		||||
    T value;          ///< Field value
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Collection of data fields that have been logged.
 | 
			
		||||
 */
 | 
			
		||||
class FieldCollection final {
 | 
			
		||||
public:
 | 
			
		||||
    SUYU_NON_COPYABLE(FieldCollection);
 | 
			
		||||
 | 
			
		||||
    FieldCollection() = default;
 | 
			
		||||
    ~FieldCollection() = default;
 | 
			
		||||
 | 
			
		||||
    FieldCollection(FieldCollection&&) noexcept = default;
 | 
			
		||||
    FieldCollection& operator=(FieldCollection&&) noexcept = default;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Accept method for the visitor pattern, visits each field in the collection.
 | 
			
		||||
     * @param visitor Reference to the visitor that will visit each field.
 | 
			
		||||
     */
 | 
			
		||||
    void Accept(VisitorInterface& visitor) const;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Creates a new field and adds it to the field collection.
 | 
			
		||||
     * @param type Type of the field to add.
 | 
			
		||||
     * @param name Name of the field to add.
 | 
			
		||||
     * @param value Value for the field to add.
 | 
			
		||||
     */
 | 
			
		||||
    template <typename T>
 | 
			
		||||
    void AddField(FieldType type, std::string_view name, T value) {
 | 
			
		||||
        return AddField(std::make_unique<Field<T>>(type, name, std::move(value)));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Adds a new field to the field collection.
 | 
			
		||||
     * @param field Field to add to the field collection.
 | 
			
		||||
     */
 | 
			
		||||
    void AddField(std::unique_ptr<FieldInterface> field);
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
    std::map<std::string, std::unique_ptr<FieldInterface>> fields;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Telemetry fields visitor interface class. A backend to log to a web service should implement
 | 
			
		||||
 * this interface.
 | 
			
		||||
 */
 | 
			
		||||
struct VisitorInterface {
 | 
			
		||||
    virtual ~VisitorInterface() = default;
 | 
			
		||||
 | 
			
		||||
    virtual void Visit(const Field<bool>& field) = 0;
 | 
			
		||||
    virtual void Visit(const Field<double>& field) = 0;
 | 
			
		||||
    virtual void Visit(const Field<float>& field) = 0;
 | 
			
		||||
    virtual void Visit(const Field<u8>& field) = 0;
 | 
			
		||||
    virtual void Visit(const Field<u16>& field) = 0;
 | 
			
		||||
    virtual void Visit(const Field<u32>& field) = 0;
 | 
			
		||||
    virtual void Visit(const Field<u64>& field) = 0;
 | 
			
		||||
    virtual void Visit(const Field<s8>& field) = 0;
 | 
			
		||||
    virtual void Visit(const Field<s16>& field) = 0;
 | 
			
		||||
    virtual void Visit(const Field<s32>& field) = 0;
 | 
			
		||||
    virtual void Visit(const Field<s64>& field) = 0;
 | 
			
		||||
    virtual void Visit(const Field<std::string>& field) = 0;
 | 
			
		||||
    virtual void Visit(const Field<const char*>& field) = 0;
 | 
			
		||||
    virtual void Visit(const Field<std::chrono::microseconds>& field) = 0;
 | 
			
		||||
 | 
			
		||||
    /// Completion method, called once all fields have been visited
 | 
			
		||||
    virtual void Complete() = 0;
 | 
			
		||||
    virtual bool SubmitTestcase() = 0;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Empty implementation of VisitorInterface that drops all fields. Used when a functional
 | 
			
		||||
 * backend implementation is not available.
 | 
			
		||||
 */
 | 
			
		||||
struct NullVisitor final : public VisitorInterface {
 | 
			
		||||
    SUYU_NON_COPYABLE(NullVisitor);
 | 
			
		||||
 | 
			
		||||
    NullVisitor() = default;
 | 
			
		||||
    ~NullVisitor() override = default;
 | 
			
		||||
 | 
			
		||||
    void Visit(const Field<bool>& /*field*/) override {}
 | 
			
		||||
    void Visit(const Field<double>& /*field*/) override {}
 | 
			
		||||
    void Visit(const Field<float>& /*field*/) override {}
 | 
			
		||||
    void Visit(const Field<u8>& /*field*/) override {}
 | 
			
		||||
    void Visit(const Field<u16>& /*field*/) override {}
 | 
			
		||||
    void Visit(const Field<u32>& /*field*/) override {}
 | 
			
		||||
    void Visit(const Field<u64>& /*field*/) override {}
 | 
			
		||||
    void Visit(const Field<s8>& /*field*/) override {}
 | 
			
		||||
    void Visit(const Field<s16>& /*field*/) override {}
 | 
			
		||||
    void Visit(const Field<s32>& /*field*/) override {}
 | 
			
		||||
    void Visit(const Field<s64>& /*field*/) override {}
 | 
			
		||||
    void Visit(const Field<std::string>& /*field*/) override {}
 | 
			
		||||
    void Visit(const Field<const char*>& /*field*/) override {}
 | 
			
		||||
    void Visit(const Field<std::chrono::microseconds>& /*field*/) override {}
 | 
			
		||||
 | 
			
		||||
    void Complete() override {}
 | 
			
		||||
    bool SubmitTestcase() override {
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/// Appends build-specific information to the given FieldCollection,
 | 
			
		||||
/// such as branch name, revision hash, etc.
 | 
			
		||||
void AppendBuildInfo(FieldCollection& fc);
 | 
			
		||||
 | 
			
		||||
/// Appends CPU-specific information to the given FieldCollection,
 | 
			
		||||
/// such as instruction set extensions, etc.
 | 
			
		||||
void AppendCPUInfo(FieldCollection& fc);
 | 
			
		||||
 | 
			
		||||
/// Appends OS-specific information to the given FieldCollection,
 | 
			
		||||
/// such as platform name, etc.
 | 
			
		||||
void AppendOSInfo(FieldCollection& fc);
 | 
			
		||||
 | 
			
		||||
} // namespace Common::Telemetry
 | 
			
		||||
@@ -1136,8 +1136,6 @@ add_library(core STATIC
 | 
			
		||||
    precompiled_headers.h
 | 
			
		||||
    reporter.cpp
 | 
			
		||||
    reporter.h
 | 
			
		||||
    telemetry_session.cpp
 | 
			
		||||
    telemetry_session.h
 | 
			
		||||
    tools/freezer.cpp
 | 
			
		||||
    tools/freezer.h
 | 
			
		||||
    tools/renderdoc.cpp
 | 
			
		||||
 
 | 
			
		||||
@@ -55,7 +55,6 @@
 | 
			
		||||
#include "core/memory/cheat_engine.h"
 | 
			
		||||
#include "core/perf_stats.h"
 | 
			
		||||
#include "core/reporter.h"
 | 
			
		||||
#include "core/telemetry_session.h"
 | 
			
		||||
#include "core/tools/freezer.h"
 | 
			
		||||
#include "core/tools/renderdoc.h"
 | 
			
		||||
#include "hid_core/hid_core.h"
 | 
			
		||||
@@ -272,8 +271,6 @@ struct System::Impl {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    SystemResultStatus SetupForApplicationProcess(System& system, Frontend::EmuWindow& emu_window) {
 | 
			
		||||
        telemetry_session = std::make_unique<Core::TelemetrySession>();
 | 
			
		||||
 | 
			
		||||
        host1x_core = std::make_unique<Tegra::Host1x::Host1x>(system);
 | 
			
		||||
        gpu_core = VideoCore::CreateGPU(emu_window, system);
 | 
			
		||||
        if (!gpu_core) {
 | 
			
		||||
@@ -354,8 +351,6 @@ struct System::Impl {
 | 
			
		||||
            return init_result;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        telemetry_session->AddInitialInfo(*app_loader, fs_controller, *content_provider);
 | 
			
		||||
 | 
			
		||||
        // Initialize cheat engine
 | 
			
		||||
        if (cheat_engine) {
 | 
			
		||||
            cheat_engine->Initialize();
 | 
			
		||||
@@ -401,21 +396,6 @@ struct System::Impl {
 | 
			
		||||
    void ShutdownMainProcess() {
 | 
			
		||||
        SetShuttingDown(true);
 | 
			
		||||
 | 
			
		||||
        // Log last frame performance stats if game was loaded
 | 
			
		||||
        if (perf_stats) {
 | 
			
		||||
            const auto perf_results = GetAndResetPerfStats();
 | 
			
		||||
            constexpr auto performance = Common::Telemetry::FieldType::Performance;
 | 
			
		||||
 | 
			
		||||
            telemetry_session->AddField(performance, "Shutdown_EmulationSpeed",
 | 
			
		||||
                                        perf_results.emulation_speed * 100.0);
 | 
			
		||||
            telemetry_session->AddField(performance, "Shutdown_Framerate",
 | 
			
		||||
                                        perf_results.average_game_fps);
 | 
			
		||||
            telemetry_session->AddField(performance, "Shutdown_Frametime",
 | 
			
		||||
                                        perf_results.frametime * 1000.0);
 | 
			
		||||
            telemetry_session->AddField(performance, "Mean_Frametime_MS",
 | 
			
		||||
                                        perf_stats->GetMeanFrametime());
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        is_powered_on = false;
 | 
			
		||||
        exit_locked = false;
 | 
			
		||||
        exit_requested = false;
 | 
			
		||||
@@ -434,7 +414,6 @@ struct System::Impl {
 | 
			
		||||
        service_manager.reset();
 | 
			
		||||
        fs_controller.Reset();
 | 
			
		||||
        cheat_engine.reset();
 | 
			
		||||
        telemetry_session.reset();
 | 
			
		||||
        core_timing.ClearPendingEvents();
 | 
			
		||||
        app_loader.reset();
 | 
			
		||||
        audio_core.reset();
 | 
			
		||||
@@ -534,9 +513,6 @@ struct System::Impl {
 | 
			
		||||
    /// Services
 | 
			
		||||
    std::unique_ptr<Service::Services> services;
 | 
			
		||||
 | 
			
		||||
    /// Telemetry session for this emulation session
 | 
			
		||||
    std::unique_ptr<Core::TelemetrySession> telemetry_session;
 | 
			
		||||
 | 
			
		||||
    /// Network instance
 | 
			
		||||
    Network::NetworkInstance network_instance;
 | 
			
		||||
 | 
			
		||||
@@ -663,14 +639,6 @@ PerfStatsResults System::GetAndResetPerfStats() {
 | 
			
		||||
    return impl->GetAndResetPerfStats();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
TelemetrySession& System::TelemetrySession() {
 | 
			
		||||
    return *impl->telemetry_session;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const TelemetrySession& System::TelemetrySession() const {
 | 
			
		||||
    return *impl->telemetry_session;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Kernel::PhysicalCore& System::CurrentPhysicalCore() {
 | 
			
		||||
    return impl->kernel.CurrentPhysicalCore();
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -122,7 +122,6 @@ class GPUDirtyMemoryManager;
 | 
			
		||||
class PerfStats;
 | 
			
		||||
class Reporter;
 | 
			
		||||
class SpeedLimiter;
 | 
			
		||||
class TelemetrySession;
 | 
			
		||||
 | 
			
		||||
struct PerfStatsResults;
 | 
			
		||||
 | 
			
		||||
@@ -218,12 +217,6 @@ public:
 | 
			
		||||
     */
 | 
			
		||||
    [[nodiscard]] bool IsPoweredOn() const;
 | 
			
		||||
 | 
			
		||||
    /// Gets a reference to the telemetry session for this emulation session.
 | 
			
		||||
    [[nodiscard]] Core::TelemetrySession& TelemetrySession();
 | 
			
		||||
 | 
			
		||||
    /// Gets a reference to the telemetry session for this emulation session.
 | 
			
		||||
    [[nodiscard]] const Core::TelemetrySession& TelemetrySession() const;
 | 
			
		||||
 | 
			
		||||
    /// Prepare the core emulation for a reschedule
 | 
			
		||||
    void PrepareReschedule(u32 core_index);
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,294 +0,0 @@
 | 
			
		||||
// SPDX-FileCopyrightText: 2017 Citra Emulator Project & 2024 suyu Emulator Project
 | 
			
		||||
// SPDX-License-Identifier: GPL-2.0-or-later
 | 
			
		||||
 | 
			
		||||
#include <array>
 | 
			
		||||
 | 
			
		||||
#include <mbedtls/ctr_drbg.h>
 | 
			
		||||
#include <mbedtls/entropy.h>
 | 
			
		||||
 | 
			
		||||
#include "common/assert.h"
 | 
			
		||||
#include "common/common_types.h"
 | 
			
		||||
#include "common/fs/file.h"
 | 
			
		||||
#include "common/fs/fs.h"
 | 
			
		||||
#include "common/fs/path_util.h"
 | 
			
		||||
#include "common/logging/log.h"
 | 
			
		||||
 | 
			
		||||
#include "common/settings.h"
 | 
			
		||||
#include "common/settings_enums.h"
 | 
			
		||||
#include "core/file_sys/control_metadata.h"
 | 
			
		||||
#include "core/file_sys/patch_manager.h"
 | 
			
		||||
#include "core/loader/loader.h"
 | 
			
		||||
#include "core/telemetry_session.h"
 | 
			
		||||
 | 
			
		||||
#ifdef ENABLE_WEB_SERVICE
 | 
			
		||||
#include "web_service/telemetry_json.h"
 | 
			
		||||
#include "web_service/verify_login.h"
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
namespace Core {
 | 
			
		||||
 | 
			
		||||
namespace Telemetry = Common::Telemetry;
 | 
			
		||||
 | 
			
		||||
static u64 GenerateTelemetryId() {
 | 
			
		||||
    u64 telemetry_id{};
 | 
			
		||||
 | 
			
		||||
    mbedtls_entropy_context entropy;
 | 
			
		||||
    mbedtls_entropy_init(&entropy);
 | 
			
		||||
    mbedtls_ctr_drbg_context ctr_drbg;
 | 
			
		||||
    static constexpr std::array<char, 18> personalization{{"suyu Telemetry ID"}};
 | 
			
		||||
 | 
			
		||||
    mbedtls_ctr_drbg_init(&ctr_drbg);
 | 
			
		||||
    ASSERT(mbedtls_ctr_drbg_seed(&ctr_drbg, mbedtls_entropy_func, &entropy,
 | 
			
		||||
                                 reinterpret_cast<const unsigned char*>(personalization.data()),
 | 
			
		||||
                                 personalization.size()) == 0);
 | 
			
		||||
    ASSERT(mbedtls_ctr_drbg_random(&ctr_drbg, reinterpret_cast<unsigned char*>(&telemetry_id),
 | 
			
		||||
                                   sizeof(u64)) == 0);
 | 
			
		||||
 | 
			
		||||
    mbedtls_ctr_drbg_free(&ctr_drbg);
 | 
			
		||||
    mbedtls_entropy_free(&entropy);
 | 
			
		||||
 | 
			
		||||
    return telemetry_id;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static const char* TranslateRenderer(Settings::RendererBackend backend) {
 | 
			
		||||
    switch (backend) {
 | 
			
		||||
    case Settings::RendererBackend::OpenGL:
 | 
			
		||||
        return "OpenGL";
 | 
			
		||||
    case Settings::RendererBackend::Vulkan:
 | 
			
		||||
        return "Vulkan";
 | 
			
		||||
    case Settings::RendererBackend::Null:
 | 
			
		||||
        return "Null";
 | 
			
		||||
    }
 | 
			
		||||
    return "Unknown";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static const char* TranslateGPUAccuracyLevel(Settings::GpuAccuracy backend) {
 | 
			
		||||
    switch (backend) {
 | 
			
		||||
    case Settings::GpuAccuracy::Normal:
 | 
			
		||||
        return "Normal";
 | 
			
		||||
    case Settings::GpuAccuracy::High:
 | 
			
		||||
        return "High";
 | 
			
		||||
    case Settings::GpuAccuracy::Extreme:
 | 
			
		||||
        return "Extreme";
 | 
			
		||||
    }
 | 
			
		||||
    return "Unknown";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static const char* TranslateNvdecEmulation(Settings::NvdecEmulation backend) {
 | 
			
		||||
    switch (backend) {
 | 
			
		||||
    case Settings::NvdecEmulation::Off:
 | 
			
		||||
        return "Off";
 | 
			
		||||
    case Settings::NvdecEmulation::Cpu:
 | 
			
		||||
        return "CPU";
 | 
			
		||||
    case Settings::NvdecEmulation::Gpu:
 | 
			
		||||
        return "GPU";
 | 
			
		||||
    }
 | 
			
		||||
    return "Unknown";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static constexpr const char* TranslateVSyncMode(Settings::VSyncMode mode) {
 | 
			
		||||
    switch (mode) {
 | 
			
		||||
    case Settings::VSyncMode::Immediate:
 | 
			
		||||
        return "Immediate";
 | 
			
		||||
    case Settings::VSyncMode::Mailbox:
 | 
			
		||||
        return "Mailbox";
 | 
			
		||||
    case Settings::VSyncMode::Fifo:
 | 
			
		||||
        return "FIFO";
 | 
			
		||||
    case Settings::VSyncMode::FifoRelaxed:
 | 
			
		||||
        return "FIFO Relaxed";
 | 
			
		||||
    }
 | 
			
		||||
    return "Unknown";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static constexpr const char* TranslateASTCDecodeMode(Settings::AstcDecodeMode mode) {
 | 
			
		||||
    switch (mode) {
 | 
			
		||||
    case Settings::AstcDecodeMode::Cpu:
 | 
			
		||||
        return "CPU";
 | 
			
		||||
    case Settings::AstcDecodeMode::Gpu:
 | 
			
		||||
        return "GPU";
 | 
			
		||||
    case Settings::AstcDecodeMode::CpuAsynchronous:
 | 
			
		||||
        return "CPU Asynchronous";
 | 
			
		||||
    }
 | 
			
		||||
    return "Unknown";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
u64 GetTelemetryId() {
 | 
			
		||||
    u64 telemetry_id{};
 | 
			
		||||
    const auto filename = Common::FS::GetSuyuPath(Common::FS::SuyuPath::ConfigDir) / "telemetry_id";
 | 
			
		||||
 | 
			
		||||
    bool generate_new_id = !Common::FS::Exists(filename);
 | 
			
		||||
 | 
			
		||||
    if (!generate_new_id) {
 | 
			
		||||
        Common::FS::IOFile file{filename, Common::FS::FileAccessMode::Read,
 | 
			
		||||
                                Common::FS::FileType::BinaryFile};
 | 
			
		||||
 | 
			
		||||
        if (!file.IsOpen()) {
 | 
			
		||||
            LOG_ERROR(Core, "failed to open telemetry_id: {}",
 | 
			
		||||
                      Common::FS::PathToUTF8String(filename));
 | 
			
		||||
            return {};
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (!file.ReadObject(telemetry_id) || telemetry_id == 0) {
 | 
			
		||||
            LOG_ERROR(Frontend, "telemetry_id is 0. Generating a new one.", telemetry_id);
 | 
			
		||||
            generate_new_id = true;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (generate_new_id) {
 | 
			
		||||
        Common::FS::IOFile file{filename, Common::FS::FileAccessMode::Write,
 | 
			
		||||
                                Common::FS::FileType::BinaryFile};
 | 
			
		||||
 | 
			
		||||
        if (!file.IsOpen()) {
 | 
			
		||||
            LOG_ERROR(Core, "failed to open telemetry_id: {}",
 | 
			
		||||
                      Common::FS::PathToUTF8String(filename));
 | 
			
		||||
            return {};
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        telemetry_id = GenerateTelemetryId();
 | 
			
		||||
 | 
			
		||||
        if (!file.WriteObject(telemetry_id)) {
 | 
			
		||||
            LOG_ERROR(Core, "Failed to write telemetry_id to file.");
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return telemetry_id;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
u64 RegenerateTelemetryId() {
 | 
			
		||||
    const u64 new_telemetry_id{GenerateTelemetryId()};
 | 
			
		||||
    const auto filename = Common::FS::GetSuyuPath(Common::FS::SuyuPath::ConfigDir) / "telemetry_id";
 | 
			
		||||
 | 
			
		||||
    Common::FS::IOFile file{filename, Common::FS::FileAccessMode::Write,
 | 
			
		||||
                            Common::FS::FileType::BinaryFile};
 | 
			
		||||
 | 
			
		||||
    if (!file.IsOpen()) {
 | 
			
		||||
        LOG_ERROR(Core, "failed to open telemetry_id: {}", Common::FS::PathToUTF8String(filename));
 | 
			
		||||
        return {};
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (!file.WriteObject(new_telemetry_id)) {
 | 
			
		||||
        LOG_ERROR(Core, "Failed to write telemetry_id to file.");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return new_telemetry_id;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool VerifyLogin(const std::string& username, const std::string& token) {
 | 
			
		||||
#ifdef ENABLE_WEB_SERVICE
 | 
			
		||||
    return WebService::VerifyLogin(Settings::values.web_api_url.GetValue(), username, token);
 | 
			
		||||
#else
 | 
			
		||||
    return false;
 | 
			
		||||
#endif
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
TelemetrySession::TelemetrySession() = default;
 | 
			
		||||
 | 
			
		||||
TelemetrySession::~TelemetrySession() {
 | 
			
		||||
    // Log one-time session end information
 | 
			
		||||
    const s64 shutdown_time{std::chrono::duration_cast<std::chrono::milliseconds>(
 | 
			
		||||
                                std::chrono::system_clock::now().time_since_epoch())
 | 
			
		||||
                                .count()};
 | 
			
		||||
    AddField(Telemetry::FieldType::Session, "Shutdown_Time", shutdown_time);
 | 
			
		||||
 | 
			
		||||
#ifdef ENABLE_WEB_SERVICE
 | 
			
		||||
    auto backend = std::make_unique<WebService::TelemetryJson>(
 | 
			
		||||
        Settings::values.web_api_url.GetValue(), Settings::values.suyu_username.GetValue(),
 | 
			
		||||
        Settings::values.suyu_token.GetValue());
 | 
			
		||||
#else
 | 
			
		||||
    auto backend = std::make_unique<Telemetry::NullVisitor>();
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
    // Complete the session, submitting to the web service backend if necessary
 | 
			
		||||
    field_collection.Accept(*backend);
 | 
			
		||||
    if (Settings::values.enable_telemetry) {
 | 
			
		||||
        backend->Complete();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void TelemetrySession::AddInitialInfo(Loader::AppLoader& app_loader,
 | 
			
		||||
                                      const Service::FileSystem::FileSystemController& fsc,
 | 
			
		||||
                                      const FileSys::ContentProvider& content_provider) {
 | 
			
		||||
    // Log one-time top-level information
 | 
			
		||||
    AddField(Telemetry::FieldType::None, "TelemetryId", GetTelemetryId());
 | 
			
		||||
 | 
			
		||||
    // Log one-time session start information
 | 
			
		||||
    const s64 init_time{std::chrono::duration_cast<std::chrono::milliseconds>(
 | 
			
		||||
                            std::chrono::system_clock::now().time_since_epoch())
 | 
			
		||||
                            .count()};
 | 
			
		||||
    AddField(Telemetry::FieldType::Session, "Init_Time", init_time);
 | 
			
		||||
 | 
			
		||||
    u64 program_id{};
 | 
			
		||||
    const Loader::ResultStatus res{app_loader.ReadProgramId(program_id)};
 | 
			
		||||
    if (res == Loader::ResultStatus::Success) {
 | 
			
		||||
        const std::string formatted_program_id{fmt::format("{:016X}", program_id)};
 | 
			
		||||
        AddField(Telemetry::FieldType::Session, "ProgramId", formatted_program_id);
 | 
			
		||||
 | 
			
		||||
        std::string name;
 | 
			
		||||
        app_loader.ReadTitle(name);
 | 
			
		||||
 | 
			
		||||
        if (name.empty()) {
 | 
			
		||||
            const auto metadata = [&content_provider, &fsc, program_id] {
 | 
			
		||||
                const FileSys::PatchManager pm{program_id, fsc, content_provider};
 | 
			
		||||
                return pm.GetControlMetadata();
 | 
			
		||||
            }();
 | 
			
		||||
            if (metadata.first != nullptr) {
 | 
			
		||||
                name = metadata.first->GetApplicationName();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (!name.empty()) {
 | 
			
		||||
            AddField(Telemetry::FieldType::Session, "ProgramName", name);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    AddField(Telemetry::FieldType::Session, "ProgramFormat",
 | 
			
		||||
             static_cast<u8>(app_loader.GetFileType()));
 | 
			
		||||
 | 
			
		||||
    // Log application information
 | 
			
		||||
    Telemetry::AppendBuildInfo(field_collection);
 | 
			
		||||
 | 
			
		||||
    // Log user system information
 | 
			
		||||
    Telemetry::AppendCPUInfo(field_collection);
 | 
			
		||||
    Telemetry::AppendOSInfo(field_collection);
 | 
			
		||||
 | 
			
		||||
    // Log user configuration information
 | 
			
		||||
    constexpr auto field_type = Telemetry::FieldType::UserConfig;
 | 
			
		||||
    AddField(field_type, "Audio_SinkId",
 | 
			
		||||
             Settings::CanonicalizeEnum(Settings::values.sink_id.GetValue()));
 | 
			
		||||
    AddField(field_type, "Core_UseMultiCore", Settings::values.use_multi_core.GetValue());
 | 
			
		||||
    AddField(field_type, "Renderer_Backend",
 | 
			
		||||
             TranslateRenderer(Settings::values.renderer_backend.GetValue()));
 | 
			
		||||
    AddField(field_type, "Renderer_UseSpeedLimit", Settings::values.use_speed_limit.GetValue());
 | 
			
		||||
    AddField(field_type, "Renderer_SpeedLimit", Settings::values.speed_limit.GetValue());
 | 
			
		||||
    AddField(field_type, "Renderer_UseDiskShaderCache",
 | 
			
		||||
             Settings::values.use_disk_shader_cache.GetValue());
 | 
			
		||||
    AddField(field_type, "Renderer_GPUAccuracyLevel",
 | 
			
		||||
             TranslateGPUAccuracyLevel(Settings::values.gpu_accuracy.GetValue()));
 | 
			
		||||
    AddField(field_type, "Renderer_UseAsynchronousGpuEmulation",
 | 
			
		||||
             Settings::values.use_asynchronous_gpu_emulation.GetValue());
 | 
			
		||||
    AddField(field_type, "Renderer_NvdecEmulation",
 | 
			
		||||
             TranslateNvdecEmulation(Settings::values.nvdec_emulation.GetValue()));
 | 
			
		||||
    AddField(field_type, "Renderer_AccelerateASTC",
 | 
			
		||||
             TranslateASTCDecodeMode(Settings::values.accelerate_astc.GetValue()));
 | 
			
		||||
    AddField(field_type, "Renderer_UseVsync",
 | 
			
		||||
             TranslateVSyncMode(Settings::values.vsync_mode.GetValue()));
 | 
			
		||||
    AddField(field_type, "Renderer_ShaderBackend",
 | 
			
		||||
             static_cast<u32>(Settings::values.shader_backend.GetValue()));
 | 
			
		||||
    AddField(field_type, "Renderer_UseAsynchronousShaders",
 | 
			
		||||
             Settings::values.use_asynchronous_shaders.GetValue());
 | 
			
		||||
    AddField(field_type, "System_UseDockedMode", Settings::IsDockedMode());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool TelemetrySession::SubmitTestcase() {
 | 
			
		||||
#ifdef ENABLE_WEB_SERVICE
 | 
			
		||||
    auto backend = std::make_unique<WebService::TelemetryJson>(
 | 
			
		||||
        Settings::values.web_api_url.GetValue(), Settings::values.suyu_username.GetValue(),
 | 
			
		||||
        Settings::values.suyu_token.GetValue());
 | 
			
		||||
    field_collection.Accept(*backend);
 | 
			
		||||
    return backend->SubmitTestcase();
 | 
			
		||||
#else
 | 
			
		||||
    return false;
 | 
			
		||||
#endif
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
} // namespace Core
 | 
			
		||||
@@ -1,101 +0,0 @@
 | 
			
		||||
// SPDX-FileCopyrightText: 2017 Citra Emulator Project
 | 
			
		||||
// SPDX-License-Identifier: GPL-2.0-or-later
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <string>
 | 
			
		||||
#include "common/telemetry.h"
 | 
			
		||||
 | 
			
		||||
namespace FileSys {
 | 
			
		||||
class ContentProvider;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
namespace Loader {
 | 
			
		||||
class AppLoader;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
namespace Service::FileSystem {
 | 
			
		||||
class FileSystemController;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
namespace Core {
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Instruments telemetry for this emulation session. Creates a new set of telemetry fields on each
 | 
			
		||||
 * session, logging any one-time fields. Interfaces with the telemetry backend used for submitting
 | 
			
		||||
 * data to the web service. Submits session data on close.
 | 
			
		||||
 */
 | 
			
		||||
class TelemetrySession {
 | 
			
		||||
public:
 | 
			
		||||
    explicit TelemetrySession();
 | 
			
		||||
    ~TelemetrySession();
 | 
			
		||||
 | 
			
		||||
    TelemetrySession(const TelemetrySession&) = delete;
 | 
			
		||||
    TelemetrySession& operator=(const TelemetrySession&) = delete;
 | 
			
		||||
 | 
			
		||||
    TelemetrySession(TelemetrySession&&) = delete;
 | 
			
		||||
    TelemetrySession& operator=(TelemetrySession&&) = delete;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Adds the initial telemetry info necessary when starting up a title.
 | 
			
		||||
     *
 | 
			
		||||
     * This includes information such as:
 | 
			
		||||
     *   - Telemetry ID
 | 
			
		||||
     *   - Initialization time
 | 
			
		||||
     *   - Title ID
 | 
			
		||||
     *   - Title name
 | 
			
		||||
     *   - Title file format
 | 
			
		||||
     *   - Miscellaneous settings values.
 | 
			
		||||
     *
 | 
			
		||||
     * @param app_loader       The application loader to use to retrieve
 | 
			
		||||
     *                         title-specific information.
 | 
			
		||||
     * @param fsc              Filesystem controller to use to retrieve info.
 | 
			
		||||
     * @param content_provider Content provider to use to retrieve info.
 | 
			
		||||
     */
 | 
			
		||||
    void AddInitialInfo(Loader::AppLoader& app_loader,
 | 
			
		||||
                        const Service::FileSystem::FileSystemController& fsc,
 | 
			
		||||
                        const FileSys::ContentProvider& content_provider);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Wrapper around the Telemetry::FieldCollection::AddField method.
 | 
			
		||||
     * @param type Type of the field to add.
 | 
			
		||||
     * @param name Name of the field to add.
 | 
			
		||||
     * @param value Value for the field to add.
 | 
			
		||||
     */
 | 
			
		||||
    template <typename T>
 | 
			
		||||
    void AddField(Common::Telemetry::FieldType type, const char* name, T value) {
 | 
			
		||||
        field_collection.AddField(type, name, std::move(value));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Submits a Testcase.
 | 
			
		||||
     * @returns A bool indicating whether the submission succeeded
 | 
			
		||||
     */
 | 
			
		||||
    bool SubmitTestcase();
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
    /// Tracks all added fields for the session
 | 
			
		||||
    Common::Telemetry::FieldCollection field_collection;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Gets TelemetryId, a unique identifier used for the user's telemetry sessions.
 | 
			
		||||
 * @returns The current TelemetryId for the session.
 | 
			
		||||
 */
 | 
			
		||||
u64 GetTelemetryId();
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Regenerates TelemetryId, a unique identifier used for the user's telemetry sessions.
 | 
			
		||||
 * @returns The new TelemetryId that was generated.
 | 
			
		||||
 */
 | 
			
		||||
u64 RegenerateTelemetryId();
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Verifies the username and token.
 | 
			
		||||
 * @param username suyu username to use for authentication.
 | 
			
		||||
 * @param token suyu token to use for authentication.
 | 
			
		||||
 * @returns Future with bool indicating whether the verification succeeded
 | 
			
		||||
 */
 | 
			
		||||
bool VerifyLogin(const std::string& username, const std::string& token);
 | 
			
		||||
 | 
			
		||||
} // namespace Core
 | 
			
		||||
@@ -6,14 +6,12 @@
 | 
			
		||||
#include <QPushButton>
 | 
			
		||||
#include <QtConcurrent/qtconcurrentrun.h>
 | 
			
		||||
#include "common/logging/log.h"
 | 
			
		||||
#include "common/telemetry.h"
 | 
			
		||||
#include "core/telemetry_session.h"
 | 
			
		||||
#include "suyu/compatdb.h"
 | 
			
		||||
#include "ui_compatdb.h"
 | 
			
		||||
 | 
			
		||||
CompatDB::CompatDB(Core::TelemetrySession& telemetry_session_, QWidget* parent)
 | 
			
		||||
CompatDB::CompatDB(QWidget* parent)
 | 
			
		||||
    : QWizard(parent, Qt::WindowTitleHint | Qt::WindowCloseButtonHint | Qt::WindowSystemMenuHint),
 | 
			
		||||
      ui{std::make_unique<Ui::CompatDB>()}, telemetry_session{telemetry_session_} {
 | 
			
		||||
      ui{std::make_unique<Ui::CompatDB>()} {
 | 
			
		||||
    ui->setupUi(this);
 | 
			
		||||
 | 
			
		||||
    connect(ui->radioButton_GameBoot_Yes, &QRadioButton::clicked, this, &CompatDB::EnableNext);
 | 
			
		||||
@@ -114,15 +112,10 @@ void CompatDB::Submit() {
 | 
			
		||||
    case CompatDBPage::Final:
 | 
			
		||||
        back();
 | 
			
		||||
        LOG_INFO(Frontend, "Compatibility Rating: {}", compatibility);
 | 
			
		||||
        telemetry_session.AddField(Common::Telemetry::FieldType::UserFeedback, "Compatibility",
 | 
			
		||||
                                   compatibility);
 | 
			
		||||
 | 
			
		||||
        button(NextButton)->setEnabled(false);
 | 
			
		||||
        button(NextButton)->setText(tr("Submitting"));
 | 
			
		||||
        button(CancelButton)->setVisible(false);
 | 
			
		||||
 | 
			
		||||
        testcase_watcher.setFuture(
 | 
			
		||||
            QtConcurrent::run([this] { return telemetry_session.SubmitTestcase(); }));
 | 
			
		||||
        break;
 | 
			
		||||
    default:
 | 
			
		||||
        LOG_ERROR(Frontend, "Unexpected page: {}", currentId());
 | 
			
		||||
 
 | 
			
		||||
@@ -6,7 +6,6 @@
 | 
			
		||||
#include <memory>
 | 
			
		||||
#include <QFutureWatcher>
 | 
			
		||||
#include <QWizard>
 | 
			
		||||
#include "core/telemetry_session.h"
 | 
			
		||||
 | 
			
		||||
namespace Ui {
 | 
			
		||||
class CompatDB;
 | 
			
		||||
@@ -25,7 +24,7 @@ class CompatDB : public QWizard {
 | 
			
		||||
    Q_OBJECT
 | 
			
		||||
 | 
			
		||||
public:
 | 
			
		||||
    explicit CompatDB(Core::TelemetrySession& telemetry_session_, QWidget* parent = nullptr);
 | 
			
		||||
    explicit CompatDB(QWidget* parent = nullptr);
 | 
			
		||||
    ~CompatDB();
 | 
			
		||||
    int nextId() const override;
 | 
			
		||||
 | 
			
		||||
@@ -38,6 +37,4 @@ private:
 | 
			
		||||
    CompatibilityStatus CalculateCompatibility() const;
 | 
			
		||||
    void OnTestcaseSubmitted();
 | 
			
		||||
    void EnableNext();
 | 
			
		||||
 | 
			
		||||
    Core::TelemetrySession& telemetry_session;
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -5,7 +5,6 @@
 | 
			
		||||
#include <QMessageBox>
 | 
			
		||||
#include <QtConcurrent/QtConcurrentRun>
 | 
			
		||||
#include "common/settings.h"
 | 
			
		||||
#include "core/telemetry_session.h"
 | 
			
		||||
#include "suyu/configuration/configure_web.h"
 | 
			
		||||
#include "suyu/uisettings.h"
 | 
			
		||||
#include "ui_configure_web.h"
 | 
			
		||||
@@ -38,8 +37,6 @@ static std::string TokenFromDisplayToken(const std::string& display_token) {
 | 
			
		||||
ConfigureWeb::ConfigureWeb(QWidget* parent)
 | 
			
		||||
    : QWidget(parent), ui(std::make_unique<Ui::ConfigureWeb>()) {
 | 
			
		||||
    ui->setupUi(this);
 | 
			
		||||
    connect(ui->button_regenerate_telemetry_id, &QPushButton::clicked, this,
 | 
			
		||||
            &ConfigureWeb::RefreshTelemetryID);
 | 
			
		||||
    connect(ui->button_verify_login, &QPushButton::clicked, this, &ConfigureWeb::VerifyLogin);
 | 
			
		||||
    connect(&verify_watcher, &QFutureWatcher<bool>::finished, this, &ConfigureWeb::OnLoginVerified);
 | 
			
		||||
 | 
			
		||||
@@ -64,10 +61,6 @@ void ConfigureWeb::changeEvent(QEvent* event) {
 | 
			
		||||
void ConfigureWeb::RetranslateUI() {
 | 
			
		||||
    ui->retranslateUi(this);
 | 
			
		||||
 | 
			
		||||
    ui->telemetry_learn_more->setText(
 | 
			
		||||
        tr("<a href='https://suyu.dev/help/feature/telemetry/'><span style=\"text-decoration: "
 | 
			
		||||
           "underline; color:#039be5;\">Learn more</span></a>"));
 | 
			
		||||
 | 
			
		||||
    ui->web_signup_link->setText(
 | 
			
		||||
        tr("<a href='https://profile.suyu.dev/'><span style=\"text-decoration: underline; "
 | 
			
		||||
           "color:#039be5;\">Sign up</span></a>"));
 | 
			
		||||
@@ -75,15 +68,11 @@ void ConfigureWeb::RetranslateUI() {
 | 
			
		||||
    ui->web_token_info_link->setText(
 | 
			
		||||
        tr("<a href='https://suyu.dev/wiki/suyu-web-service/'><span style=\"text-decoration: "
 | 
			
		||||
           "underline; color:#039be5;\">What is my token?</span></a>"));
 | 
			
		||||
 | 
			
		||||
    ui->label_telemetry_id->setText(
 | 
			
		||||
        tr("Telemetry ID: 0x%1").arg(QString::number(Core::GetTelemetryId(), 16).toUpper()));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ConfigureWeb::SetConfiguration() {
 | 
			
		||||
    ui->web_credentials_disclaimer->setWordWrap(true);
 | 
			
		||||
 | 
			
		||||
    ui->telemetry_learn_more->setOpenExternalLinks(true);
 | 
			
		||||
    ui->web_signup_link->setOpenExternalLinks(true);
 | 
			
		||||
    ui->web_token_info_link->setOpenExternalLinks(true);
 | 
			
		||||
 | 
			
		||||
@@ -93,7 +82,6 @@ void ConfigureWeb::SetConfiguration() {
 | 
			
		||||
        ui->username->setText(QString::fromStdString(Settings::values.suyu_username.GetValue()));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    ui->toggle_telemetry->setChecked(Settings::values.enable_telemetry.GetValue());
 | 
			
		||||
    ui->edit_token->setText(QString::fromStdString(GenerateDisplayToken(
 | 
			
		||||
        Settings::values.suyu_username.GetValue(), Settings::values.suyu_token.GetValue())));
 | 
			
		||||
 | 
			
		||||
@@ -106,7 +94,6 @@ void ConfigureWeb::SetConfiguration() {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ConfigureWeb::ApplyConfiguration() {
 | 
			
		||||
    Settings::values.enable_telemetry = ui->toggle_telemetry->isChecked();
 | 
			
		||||
    UISettings::values.enable_discord_presence = ui->toggle_discordrpc->isChecked();
 | 
			
		||||
    if (user_verified) {
 | 
			
		||||
        Settings::values.suyu_username =
 | 
			
		||||
@@ -119,12 +106,6 @@ void ConfigureWeb::ApplyConfiguration() {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ConfigureWeb::RefreshTelemetryID() {
 | 
			
		||||
    const u64 new_telemetry_id{Core::RegenerateTelemetryId()};
 | 
			
		||||
    ui->label_telemetry_id->setText(
 | 
			
		||||
        tr("Telemetry ID: 0x%1").arg(QString::number(new_telemetry_id, 16).toUpper()));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ConfigureWeb::OnLoginChanged() {
 | 
			
		||||
    if (ui->edit_token->text().isEmpty()) {
 | 
			
		||||
        user_verified = true;
 | 
			
		||||
@@ -150,7 +131,12 @@ void ConfigureWeb::VerifyLogin() {
 | 
			
		||||
    verify_watcher.setFuture(QtConcurrent::run(
 | 
			
		||||
        [username = UsernameFromDisplayToken(ui->edit_token->text().toStdString()),
 | 
			
		||||
         token = TokenFromDisplayToken(ui->edit_token->text().toStdString())] {
 | 
			
		||||
            return Core::VerifyLogin(username, token);
 | 
			
		||||
#ifdef ENABLE_WEB_SERVICE
 | 
			
		||||
            return WebService::VerifyLogin(Settings::values.web_api_url.GetValue(), username,
 | 
			
		||||
                                           token);
 | 
			
		||||
#else
 | 
			
		||||
            return false;
 | 
			
		||||
#endif
 | 
			
		||||
        }));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -25,7 +25,6 @@ private:
 | 
			
		||||
    void changeEvent(QEvent* event) override;
 | 
			
		||||
    void RetranslateUI();
 | 
			
		||||
 | 
			
		||||
    void RefreshTelemetryID();
 | 
			
		||||
    void OnLoginChanged();
 | 
			
		||||
    void VerifyLogin();
 | 
			
		||||
    void OnLoginVerified();
 | 
			
		||||
 
 | 
			
		||||
@@ -125,56 +125,6 @@
 | 
			
		||||
       </property>
 | 
			
		||||
      </widget>
 | 
			
		||||
     </item>
 | 
			
		||||
     <item>
 | 
			
		||||
      <widget class="QGroupBox" name="groupBox">
 | 
			
		||||
       <property name="title">
 | 
			
		||||
        <string>Telemetry</string>
 | 
			
		||||
       </property>
 | 
			
		||||
       <layout class="QVBoxLayout" name="verticalLayout_2">
 | 
			
		||||
        <item>
 | 
			
		||||
         <widget class="QCheckBox" name="toggle_telemetry">
 | 
			
		||||
          <property name="text">
 | 
			
		||||
           <string>Share anonymous usage data with the suyu team</string>
 | 
			
		||||
          </property>
 | 
			
		||||
         </widget>
 | 
			
		||||
        </item>
 | 
			
		||||
        <item>
 | 
			
		||||
         <widget class="QLabel" name="telemetry_learn_more">
 | 
			
		||||
          <property name="text">
 | 
			
		||||
           <string>Learn more</string>
 | 
			
		||||
          </property>
 | 
			
		||||
         </widget>
 | 
			
		||||
        </item>
 | 
			
		||||
        <item>
 | 
			
		||||
         <layout class="QGridLayout" name="gridLayoutTelemetryId">
 | 
			
		||||
          <item row="0" column="0">
 | 
			
		||||
           <widget class="QLabel" name="label_telemetry_id">
 | 
			
		||||
            <property name="text">
 | 
			
		||||
             <string>Telemetry ID:</string>
 | 
			
		||||
            </property>
 | 
			
		||||
           </widget>
 | 
			
		||||
          </item>
 | 
			
		||||
          <item row="0" column="1">
 | 
			
		||||
           <widget class="QPushButton" name="button_regenerate_telemetry_id">
 | 
			
		||||
            <property name="sizePolicy">
 | 
			
		||||
             <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
 | 
			
		||||
              <horstretch>0</horstretch>
 | 
			
		||||
              <verstretch>0</verstretch>
 | 
			
		||||
             </sizepolicy>
 | 
			
		||||
            </property>
 | 
			
		||||
            <property name="layoutDirection">
 | 
			
		||||
             <enum>Qt::RightToLeft</enum>
 | 
			
		||||
            </property>
 | 
			
		||||
            <property name="text">
 | 
			
		||||
             <string>Regenerate</string>
 | 
			
		||||
            </property>
 | 
			
		||||
           </widget>
 | 
			
		||||
          </item>
 | 
			
		||||
         </layout>
 | 
			
		||||
        </item>
 | 
			
		||||
       </layout>
 | 
			
		||||
      </widget>
 | 
			
		||||
     </item>
 | 
			
		||||
    </layout>
 | 
			
		||||
   </item>
 | 
			
		||||
   <item>
 | 
			
		||||
 
 | 
			
		||||
@@ -100,7 +100,6 @@
 | 
			
		||||
#include "common/x64/cpu_detect.h"
 | 
			
		||||
#endif
 | 
			
		||||
#include "common/settings.h"
 | 
			
		||||
#include "common/telemetry.h"
 | 
			
		||||
#include "core/core.h"
 | 
			
		||||
#include "core/core_timing.h"
 | 
			
		||||
#include "core/crypto/key_manager.h"
 | 
			
		||||
@@ -119,7 +118,6 @@
 | 
			
		||||
#include "core/hle/service/sm/sm.h"
 | 
			
		||||
#include "core/loader/loader.h"
 | 
			
		||||
#include "core/perf_stats.h"
 | 
			
		||||
#include "core/telemetry_session.h"
 | 
			
		||||
#include "frontend_common/config.h"
 | 
			
		||||
#include "input_common/drivers/tas_input.h"
 | 
			
		||||
#include "input_common/drivers/virtual_amiibo.h"
 | 
			
		||||
@@ -1870,8 +1868,6 @@ bool GMainWindow::LoadROM(const QString& filename, Service::AM::FrontendAppletPa
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
    current_game_path = filename;
 | 
			
		||||
 | 
			
		||||
    system->TelemetrySession().AddField(Common::Telemetry::FieldType::App, "Frontend", "Qt");
 | 
			
		||||
    return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -3380,7 +3376,7 @@ void GMainWindow::OnMenuReportCompatibility() {
 | 
			
		||||
 | 
			
		||||
    if (!Settings::values.suyu_token.GetValue().empty() &&
 | 
			
		||||
        !Settings::values.suyu_username.GetValue().empty()) {
 | 
			
		||||
        CompatDB compatdb{system->TelemetrySession(), this};
 | 
			
		||||
        CompatDB compatdb{this};
 | 
			
		||||
        compatdb.exec();
 | 
			
		||||
    } else {
 | 
			
		||||
        QMessageBox::critical(
 | 
			
		||||
 
 | 
			
		||||
@@ -19,7 +19,6 @@
 | 
			
		||||
#include "common/scope_exit.h"
 | 
			
		||||
#include "common/settings.h"
 | 
			
		||||
#include "common/string_util.h"
 | 
			
		||||
#include "common/telemetry.h"
 | 
			
		||||
#include "core/core.h"
 | 
			
		||||
#include "core/core_timing.h"
 | 
			
		||||
#include "core/cpu_manager.h"
 | 
			
		||||
@@ -29,7 +28,6 @@
 | 
			
		||||
#include "core/hle/service/am/applet_manager.h"
 | 
			
		||||
#include "core/hle/service/filesystem/filesystem.h"
 | 
			
		||||
#include "core/loader/loader.h"
 | 
			
		||||
#include "core/telemetry_session.h"
 | 
			
		||||
#include "frontend_common/config.h"
 | 
			
		||||
#include "input_common/main.h"
 | 
			
		||||
#include "network/network.h"
 | 
			
		||||
@@ -403,8 +401,6 @@ int main(int argc, char** argv) {
 | 
			
		||||
        break;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    system.TelemetrySession().AddField(Common::Telemetry::FieldType::App, "Frontend", "SDL");
 | 
			
		||||
 | 
			
		||||
    if (use_multiplayer) {
 | 
			
		||||
        if (auto member = system.GetRoomNetwork().GetRoomMember().lock()) {
 | 
			
		||||
            member->BindOnChatMessageReceived(OnMessageReceived);
 | 
			
		||||
 
 | 
			
		||||
@@ -1,27 +1,19 @@
 | 
			
		||||
// SPDX-FileCopyrightText: 2014 Citra Emulator Project
 | 
			
		||||
// SPDX-License-Identifier: GPL-2.0-or-later
 | 
			
		||||
 | 
			
		||||
#include <algorithm>
 | 
			
		||||
#include <cstddef>
 | 
			
		||||
#include <cstdlib>
 | 
			
		||||
#include <memory>
 | 
			
		||||
 | 
			
		||||
#include <glad/glad.h>
 | 
			
		||||
 | 
			
		||||
#include "common/assert.h"
 | 
			
		||||
#include "common/logging/log.h"
 | 
			
		||||
#include "common/microprofile.h"
 | 
			
		||||
#include "common/settings.h"
 | 
			
		||||
#include "common/telemetry.h"
 | 
			
		||||
#include "core/core_timing.h"
 | 
			
		||||
#include "core/frontend/emu_window.h"
 | 
			
		||||
#include "core/telemetry_session.h"
 | 
			
		||||
#include "video_core/capture.h"
 | 
			
		||||
#include "video_core/present.h"
 | 
			
		||||
#include "video_core/renderer_opengl/gl_blit_screen.h"
 | 
			
		||||
#include "video_core/renderer_opengl/gl_rasterizer.h"
 | 
			
		||||
#include "video_core/renderer_opengl/gl_shader_manager.h"
 | 
			
		||||
#include "video_core/renderer_opengl/gl_shader_util.h"
 | 
			
		||||
#include "video_core/renderer_opengl/renderer_opengl.h"
 | 
			
		||||
#include "video_core/textures/decoders.h"
 | 
			
		||||
 | 
			
		||||
@@ -90,20 +82,18 @@ void APIENTRY DebugHandler(GLenum source, GLenum type, GLuint id, GLenum severit
 | 
			
		||||
}
 | 
			
		||||
} // Anonymous namespace
 | 
			
		||||
 | 
			
		||||
RendererOpenGL::RendererOpenGL(Core::TelemetrySession& telemetry_session_,
 | 
			
		||||
                               Core::Frontend::EmuWindow& emu_window_,
 | 
			
		||||
RendererOpenGL::RendererOpenGL(Core::Frontend::EmuWindow& emu_window_,
 | 
			
		||||
                               Tegra::MaxwellDeviceMemoryManager& device_memory_, Tegra::GPU& gpu_,
 | 
			
		||||
                               std::unique_ptr<Core::Frontend::GraphicsContext> context_)
 | 
			
		||||
    : RendererBase{emu_window_, std::move(context_)}, telemetry_session{telemetry_session_},
 | 
			
		||||
      emu_window{emu_window_}, device_memory{device_memory_}, gpu{gpu_}, device{emu_window_},
 | 
			
		||||
      state_tracker{}, program_manager{device},
 | 
			
		||||
    : RendererBase{emu_window_, std::move(context_)}, emu_window{emu_window_},
 | 
			
		||||
      device_memory{device_memory_}, gpu{gpu_}, device{emu_window_}, state_tracker{},
 | 
			
		||||
      program_manager{device},
 | 
			
		||||
      rasterizer(emu_window, gpu, device_memory, device, program_manager, state_tracker) {
 | 
			
		||||
    if (Settings::values.renderer_debug && GLAD_GL_KHR_debug) {
 | 
			
		||||
        glEnable(GL_DEBUG_OUTPUT);
 | 
			
		||||
        glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS);
 | 
			
		||||
        glDebugMessageCallback(DebugHandler, nullptr);
 | 
			
		||||
    }
 | 
			
		||||
    AddTelemetryFields();
 | 
			
		||||
 | 
			
		||||
    // Initialize default attributes to match hardware's disabled attributes
 | 
			
		||||
    GLint max_attribs{};
 | 
			
		||||
@@ -155,21 +145,6 @@ void RendererOpenGL::Composite(std::span<const Tegra::FramebufferConfig> framebu
 | 
			
		||||
    render_window.OnFrameDisplayed();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void RendererOpenGL::AddTelemetryFields() {
 | 
			
		||||
    const char* const gl_version{reinterpret_cast<char const*>(glGetString(GL_VERSION))};
 | 
			
		||||
    const char* const gpu_vendor{reinterpret_cast<char const*>(glGetString(GL_VENDOR))};
 | 
			
		||||
    const char* const gpu_model{reinterpret_cast<char const*>(glGetString(GL_RENDERER))};
 | 
			
		||||
 | 
			
		||||
    LOG_INFO(Render_OpenGL, "GL_VERSION: {}", gl_version);
 | 
			
		||||
    LOG_INFO(Render_OpenGL, "GL_VENDOR: {}", gpu_vendor);
 | 
			
		||||
    LOG_INFO(Render_OpenGL, "GL_RENDERER: {}", gpu_model);
 | 
			
		||||
 | 
			
		||||
    constexpr auto user_system = Common::Telemetry::FieldType::UserSystem;
 | 
			
		||||
    telemetry_session.AddField(user_system, "GPU_Vendor", std::string(gpu_vendor));
 | 
			
		||||
    telemetry_session.AddField(user_system, "GPU_Model", std::string(gpu_model));
 | 
			
		||||
    telemetry_session.AddField(user_system, "GPU_OpenGL_Version", std::string(gl_version));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void RendererOpenGL::RenderToBuffer(std::span<const Tegra::FramebufferConfig> framebuffers,
 | 
			
		||||
                                    const Layout::FramebufferLayout& layout, void* dst) {
 | 
			
		||||
    GLint old_read_fb;
 | 
			
		||||
 
 | 
			
		||||
@@ -34,8 +34,7 @@ class BlitScreen;
 | 
			
		||||
 | 
			
		||||
class RendererOpenGL final : public VideoCore::RendererBase {
 | 
			
		||||
public:
 | 
			
		||||
    explicit RendererOpenGL(Core::TelemetrySession& telemetry_session_,
 | 
			
		||||
                            Core::Frontend::EmuWindow& emu_window_,
 | 
			
		||||
    explicit RendererOpenGL(Core::Frontend::EmuWindow& emu_window_,
 | 
			
		||||
                            Tegra::MaxwellDeviceMemoryManager& device_memory_, Tegra::GPU& gpu_,
 | 
			
		||||
                            std::unique_ptr<Core::Frontend::GraphicsContext> context_);
 | 
			
		||||
    ~RendererOpenGL() override;
 | 
			
		||||
@@ -53,14 +52,11 @@ public:
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
    void AddTelemetryFields();
 | 
			
		||||
 | 
			
		||||
    void RenderToBuffer(std::span<const Tegra::FramebufferConfig> framebuffers,
 | 
			
		||||
                        const Layout::FramebufferLayout& layout, void* dst);
 | 
			
		||||
    void RenderScreenshot(std::span<const Tegra::FramebufferConfig> framebuffers);
 | 
			
		||||
    void RenderAppletCaptureLayer(std::span<const Tegra::FramebufferConfig> framebuffers);
 | 
			
		||||
 | 
			
		||||
    Core::TelemetrySession& telemetry_session;
 | 
			
		||||
    Core::Frontend::EmuWindow& emu_window;
 | 
			
		||||
    Tegra::MaxwellDeviceMemoryManager& device_memory;
 | 
			
		||||
    Tegra::GPU& gpu;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,24 +1,17 @@
 | 
			
		||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
 | 
			
		||||
// SPDX-License-Identifier: GPL-2.0-or-later
 | 
			
		||||
 | 
			
		||||
#include <algorithm>
 | 
			
		||||
#include <array>
 | 
			
		||||
#include <cstring>
 | 
			
		||||
#include <memory>
 | 
			
		||||
#include <optional>
 | 
			
		||||
#include <string>
 | 
			
		||||
#include <vector>
 | 
			
		||||
 | 
			
		||||
#include <fmt/format.h>
 | 
			
		||||
 | 
			
		||||
#include "common/logging/log.h"
 | 
			
		||||
#include "common/polyfill_ranges.h"
 | 
			
		||||
#include "common/scope_exit.h"
 | 
			
		||||
#include "common/settings.h"
 | 
			
		||||
#include "common/telemetry.h"
 | 
			
		||||
#include "core/core_timing.h"
 | 
			
		||||
#include "core/frontend/graphics_context.h"
 | 
			
		||||
#include "core/telemetry_session.h"
 | 
			
		||||
#include "video_core/capture.h"
 | 
			
		||||
#include "video_core/gpu.h"
 | 
			
		||||
#include "video_core/present.h"
 | 
			
		||||
@@ -53,37 +46,6 @@ constexpr VkExtent3D CaptureImageExtent{
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
constexpr VkFormat CaptureFormat = VK_FORMAT_A8B8G8R8_UNORM_PACK32;
 | 
			
		||||
 | 
			
		||||
std::string GetReadableVersion(u32 version) {
 | 
			
		||||
    return fmt::format("{}.{}.{}", VK_VERSION_MAJOR(version), VK_VERSION_MINOR(version),
 | 
			
		||||
                       VK_VERSION_PATCH(version));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::string GetDriverVersion(const Device& device) {
 | 
			
		||||
    // Extracted from
 | 
			
		||||
    // https://github.com/SaschaWillems/vulkan.gpuinfo.org/blob/5dddea46ea1120b0df14eef8f15ff8e318e35462/functions.php#L308-L314
 | 
			
		||||
    const u32 version = device.GetDriverVersion();
 | 
			
		||||
 | 
			
		||||
    if (device.GetDriverID() == VK_DRIVER_ID_NVIDIA_PROPRIETARY) {
 | 
			
		||||
        const u32 major = (version >> 22) & 0x3ff;
 | 
			
		||||
        const u32 minor = (version >> 14) & 0x0ff;
 | 
			
		||||
        const u32 secondary = (version >> 6) & 0x0ff;
 | 
			
		||||
        const u32 tertiary = version & 0x003f;
 | 
			
		||||
        return fmt::format("{}.{}.{}.{}", major, minor, secondary, tertiary);
 | 
			
		||||
    }
 | 
			
		||||
    if (device.GetDriverID() == VK_DRIVER_ID_INTEL_PROPRIETARY_WINDOWS) {
 | 
			
		||||
        const u32 major = version >> 14;
 | 
			
		||||
        const u32 minor = version & 0x3fff;
 | 
			
		||||
        return fmt::format("{}.{}", major, minor);
 | 
			
		||||
    }
 | 
			
		||||
    return GetReadableVersion(version);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::string BuildCommaSeparatedExtensions(
 | 
			
		||||
    const std::set<std::string, std::less<>>& available_extensions) {
 | 
			
		||||
    return fmt::format("{}", fmt::join(available_extensions, ","));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
} // Anonymous namespace
 | 
			
		||||
 | 
			
		||||
Device CreateDevice(const vk::Instance& instance, const vk::InstanceDispatch& dld,
 | 
			
		||||
@@ -98,12 +60,11 @@ Device CreateDevice(const vk::Instance& instance, const vk::InstanceDispatch& dl
 | 
			
		||||
    return Device(*instance, physical_device, surface, dld);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
RendererVulkan::RendererVulkan(Core::TelemetrySession& telemetry_session_,
 | 
			
		||||
                               Core::Frontend::EmuWindow& emu_window,
 | 
			
		||||
RendererVulkan::RendererVulkan(Core::Frontend::EmuWindow& emu_window,
 | 
			
		||||
                               Tegra::MaxwellDeviceMemoryManager& device_memory_, Tegra::GPU& gpu_,
 | 
			
		||||
                               std::unique_ptr<Core::Frontend::GraphicsContext> context_) try
 | 
			
		||||
    : RendererBase(emu_window, std::move(context_)), telemetry_session(telemetry_session_),
 | 
			
		||||
      device_memory(device_memory_), gpu(gpu_), library(OpenLibrary(context.get())),
 | 
			
		||||
    : RendererBase(emu_window, std::move(context_)), device_memory(device_memory_), gpu(gpu_),
 | 
			
		||||
      library(OpenLibrary(context.get())),
 | 
			
		||||
      instance(CreateInstance(*library, dld, VK_API_VERSION_1_1, render_window.GetWindowInfo().type,
 | 
			
		||||
                              Settings::values.renderer_debug.GetValue())),
 | 
			
		||||
      debug_messenger(Settings::values.renderer_debug ? CreateDebugUtilsCallback(instance)
 | 
			
		||||
@@ -128,7 +89,6 @@ RendererVulkan::RendererVulkan(Core::TelemetrySession& telemetry_session_,
 | 
			
		||||
        turbo_mode.emplace(instance, dld);
 | 
			
		||||
        scheduler.RegisterOnSubmit([this] { turbo_mode->QueueSubmitted(); });
 | 
			
		||||
    }
 | 
			
		||||
    Report();
 | 
			
		||||
} catch (const vk::Exception& exception) {
 | 
			
		||||
    LOG_ERROR(Render_Vulkan, "Vulkan initialization failed with error: {}", exception.what());
 | 
			
		||||
    throw std::runtime_error{fmt::format("Vulkan initialization error {}", exception.what())};
 | 
			
		||||
@@ -166,32 +126,6 @@ void RendererVulkan::Composite(std::span<const Tegra::FramebufferConfig> framebu
 | 
			
		||||
    rasterizer.TickFrame();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void RendererVulkan::Report() const {
 | 
			
		||||
    using namespace Common::Literals;
 | 
			
		||||
    const std::string vendor_name{device.GetVendorName()};
 | 
			
		||||
    const std::string model_name{device.GetModelName()};
 | 
			
		||||
    const std::string driver_version = GetDriverVersion(device);
 | 
			
		||||
    const std::string driver_name = fmt::format("{} {}", vendor_name, driver_version);
 | 
			
		||||
 | 
			
		||||
    const std::string api_version = GetReadableVersion(device.ApiVersion());
 | 
			
		||||
 | 
			
		||||
    const std::string extensions = BuildCommaSeparatedExtensions(device.GetAvailableExtensions());
 | 
			
		||||
 | 
			
		||||
    const auto available_vram = static_cast<f64>(device.GetDeviceLocalMemory()) / f64{1_GiB};
 | 
			
		||||
 | 
			
		||||
    LOG_INFO(Render_Vulkan, "Driver: {}", driver_name);
 | 
			
		||||
    LOG_INFO(Render_Vulkan, "Device: {}", model_name);
 | 
			
		||||
    LOG_INFO(Render_Vulkan, "Vulkan: {}", api_version);
 | 
			
		||||
    LOG_INFO(Render_Vulkan, "Available VRAM: {:.2f} GiB", available_vram);
 | 
			
		||||
 | 
			
		||||
    static constexpr auto field = Common::Telemetry::FieldType::UserSystem;
 | 
			
		||||
    telemetry_session.AddField(field, "GPU_Vendor", vendor_name);
 | 
			
		||||
    telemetry_session.AddField(field, "GPU_Model", model_name);
 | 
			
		||||
    telemetry_session.AddField(field, "GPU_Vulkan_Driver", driver_name);
 | 
			
		||||
    telemetry_session.AddField(field, "GPU_Vulkan_Version", api_version);
 | 
			
		||||
    telemetry_session.AddField(field, "GPU_Vulkan_Extensions", extensions);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
vk::Buffer RendererVulkan::RenderToBuffer(std::span<const Tegra::FramebufferConfig> framebuffers,
 | 
			
		||||
                                          const Layout::FramebufferLayout& layout, VkFormat format,
 | 
			
		||||
                                          VkDeviceSize buffer_size) {
 | 
			
		||||
 
 | 
			
		||||
@@ -40,8 +40,7 @@ Device CreateDevice(const vk::Instance& instance, const vk::InstanceDispatch& dl
 | 
			
		||||
 | 
			
		||||
class RendererVulkan final : public VideoCore::RendererBase {
 | 
			
		||||
public:
 | 
			
		||||
    explicit RendererVulkan(Core::TelemetrySession& telemtry_session,
 | 
			
		||||
                            Core::Frontend::EmuWindow& emu_window,
 | 
			
		||||
    explicit RendererVulkan(Core::Frontend::EmuWindow& emu_window,
 | 
			
		||||
                            Tegra::MaxwellDeviceMemoryManager& device_memory_, Tegra::GPU& gpu_,
 | 
			
		||||
                            std::unique_ptr<Core::Frontend::GraphicsContext> context_);
 | 
			
		||||
    ~RendererVulkan() override;
 | 
			
		||||
@@ -59,15 +58,12 @@ public:
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
    void Report() const;
 | 
			
		||||
 | 
			
		||||
    vk::Buffer RenderToBuffer(std::span<const Tegra::FramebufferConfig> framebuffers,
 | 
			
		||||
                              const Layout::FramebufferLayout& layout, VkFormat format,
 | 
			
		||||
                              VkDeviceSize buffer_size);
 | 
			
		||||
    void RenderScreenshot(std::span<const Tegra::FramebufferConfig> framebuffers);
 | 
			
		||||
    void RenderAppletCaptureLayer(std::span<const Tegra::FramebufferConfig> framebuffers);
 | 
			
		||||
 | 
			
		||||
    Core::TelemetrySession& telemetry_session;
 | 
			
		||||
    Tegra::MaxwellDeviceMemoryManager& device_memory;
 | 
			
		||||
    Tegra::GPU& gpu;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -6,7 +6,6 @@
 | 
			
		||||
#include "common/logging/log.h"
 | 
			
		||||
#include "common/settings.h"
 | 
			
		||||
#include "core/core.h"
 | 
			
		||||
#include "video_core/host1x/gpu_device_memory_manager.h"
 | 
			
		||||
#include "video_core/host1x/host1x.h"
 | 
			
		||||
#include "video_core/renderer_base.h"
 | 
			
		||||
#include "video_core/renderer_null/renderer_null.h"
 | 
			
		||||
@@ -19,16 +18,15 @@ namespace {
 | 
			
		||||
std::unique_ptr<VideoCore::RendererBase> CreateRenderer(
 | 
			
		||||
    Core::System& system, Core::Frontend::EmuWindow& emu_window, Tegra::GPU& gpu,
 | 
			
		||||
    std::unique_ptr<Core::Frontend::GraphicsContext> context) {
 | 
			
		||||
    auto& telemetry_session = system.TelemetrySession();
 | 
			
		||||
    auto& device_memory = system.Host1x().MemoryManager();
 | 
			
		||||
 | 
			
		||||
    switch (Settings::values.renderer_backend.GetValue()) {
 | 
			
		||||
    case Settings::RendererBackend::OpenGL:
 | 
			
		||||
        return std::make_unique<OpenGL::RendererOpenGL>(telemetry_session, emu_window,
 | 
			
		||||
                                                        device_memory, gpu, std::move(context));
 | 
			
		||||
        return std::make_unique<OpenGL::RendererOpenGL>(emu_window, device_memory, gpu,
 | 
			
		||||
                                                        std::move(context));
 | 
			
		||||
    case Settings::RendererBackend::Vulkan:
 | 
			
		||||
        return std::make_unique<Vulkan::RendererVulkan>(telemetry_session, emu_window,
 | 
			
		||||
                                                        device_memory, gpu, std::move(context));
 | 
			
		||||
        return std::make_unique<Vulkan::RendererVulkan>(emu_window, device_memory, gpu,
 | 
			
		||||
                                                        std::move(context));
 | 
			
		||||
    case Settings::RendererBackend::Null:
 | 
			
		||||
        return std::make_unique<Null::RendererNull>(emu_window, gpu, std::move(context));
 | 
			
		||||
    default:
 | 
			
		||||
 
 | 
			
		||||
@@ -5,8 +5,6 @@ add_library(web_service STATIC
 | 
			
		||||
    announce_room_json.cpp
 | 
			
		||||
    announce_room_json.h
 | 
			
		||||
    precompiled_headers.h
 | 
			
		||||
    telemetry_json.cpp
 | 
			
		||||
    telemetry_json.h
 | 
			
		||||
    verify_login.cpp
 | 
			
		||||
    verify_login.h
 | 
			
		||||
    verify_user_jwt.cpp
 | 
			
		||||
 
 | 
			
		||||
@@ -1,130 +0,0 @@
 | 
			
		||||
// SPDX-FileCopyrightText: 2017 Citra Emulator Project
 | 
			
		||||
// SPDX-License-Identifier: GPL-2.0-or-later
 | 
			
		||||
 | 
			
		||||
#include <nlohmann/json.hpp>
 | 
			
		||||
#include "common/detached_tasks.h"
 | 
			
		||||
#include "web_service/telemetry_json.h"
 | 
			
		||||
#include "web_service/web_backend.h"
 | 
			
		||||
#include "web_service/web_result.h"
 | 
			
		||||
 | 
			
		||||
namespace WebService {
 | 
			
		||||
 | 
			
		||||
namespace Telemetry = Common::Telemetry;
 | 
			
		||||
 | 
			
		||||
struct TelemetryJson::Impl {
 | 
			
		||||
    Impl(std::string host_, std::string username_, std::string token_)
 | 
			
		||||
        : host{std::move(host_)}, username{std::move(username_)}, token{std::move(token_)} {}
 | 
			
		||||
 | 
			
		||||
    nlohmann::json& TopSection() {
 | 
			
		||||
        return sections[static_cast<u8>(Telemetry::FieldType::None)];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const nlohmann::json& TopSection() const {
 | 
			
		||||
        return sections[static_cast<u8>(Telemetry::FieldType::None)];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    template <class T>
 | 
			
		||||
    void Serialize(Telemetry::FieldType type, const std::string& name, T value) {
 | 
			
		||||
        sections[static_cast<u8>(type)][name] = value;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void SerializeSection(Telemetry::FieldType type, const std::string& name) {
 | 
			
		||||
        TopSection()[name] = sections[static_cast<unsigned>(type)];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    nlohmann::json output;
 | 
			
		||||
    std::array<nlohmann::json, 7> sections;
 | 
			
		||||
    std::string host;
 | 
			
		||||
    std::string username;
 | 
			
		||||
    std::string token;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
TelemetryJson::TelemetryJson(std::string host, std::string username, std::string token)
 | 
			
		||||
    : impl{std::make_unique<Impl>(std::move(host), std::move(username), std::move(token))} {}
 | 
			
		||||
TelemetryJson::~TelemetryJson() = default;
 | 
			
		||||
 | 
			
		||||
void TelemetryJson::Visit(const Telemetry::Field<bool>& field) {
 | 
			
		||||
    impl->Serialize(field.GetType(), field.GetName(), field.GetValue());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void TelemetryJson::Visit(const Telemetry::Field<double>& field) {
 | 
			
		||||
    impl->Serialize(field.GetType(), field.GetName(), field.GetValue());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void TelemetryJson::Visit(const Telemetry::Field<float>& field) {
 | 
			
		||||
    impl->Serialize(field.GetType(), field.GetName(), field.GetValue());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void TelemetryJson::Visit(const Telemetry::Field<u8>& field) {
 | 
			
		||||
    impl->Serialize(field.GetType(), field.GetName(), field.GetValue());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void TelemetryJson::Visit(const Telemetry::Field<u16>& field) {
 | 
			
		||||
    impl->Serialize(field.GetType(), field.GetName(), field.GetValue());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void TelemetryJson::Visit(const Telemetry::Field<u32>& field) {
 | 
			
		||||
    impl->Serialize(field.GetType(), field.GetName(), field.GetValue());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void TelemetryJson::Visit(const Telemetry::Field<u64>& field) {
 | 
			
		||||
    impl->Serialize(field.GetType(), field.GetName(), field.GetValue());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void TelemetryJson::Visit(const Telemetry::Field<s8>& field) {
 | 
			
		||||
    impl->Serialize(field.GetType(), field.GetName(), field.GetValue());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void TelemetryJson::Visit(const Telemetry::Field<s16>& field) {
 | 
			
		||||
    impl->Serialize(field.GetType(), field.GetName(), field.GetValue());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void TelemetryJson::Visit(const Telemetry::Field<s32>& field) {
 | 
			
		||||
    impl->Serialize(field.GetType(), field.GetName(), field.GetValue());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void TelemetryJson::Visit(const Telemetry::Field<s64>& field) {
 | 
			
		||||
    impl->Serialize(field.GetType(), field.GetName(), field.GetValue());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void TelemetryJson::Visit(const Telemetry::Field<std::string>& field) {
 | 
			
		||||
    impl->Serialize(field.GetType(), field.GetName(), field.GetValue());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void TelemetryJson::Visit(const Telemetry::Field<const char*>& field) {
 | 
			
		||||
    impl->Serialize(field.GetType(), field.GetName(), std::string(field.GetValue()));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void TelemetryJson::Visit(const Telemetry::Field<std::chrono::microseconds>& field) {
 | 
			
		||||
    impl->Serialize(field.GetType(), field.GetName(), field.GetValue().count());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void TelemetryJson::Complete() {
 | 
			
		||||
    impl->SerializeSection(Telemetry::FieldType::App, "App");
 | 
			
		||||
    impl->SerializeSection(Telemetry::FieldType::Session, "Session");
 | 
			
		||||
    impl->SerializeSection(Telemetry::FieldType::Performance, "Performance");
 | 
			
		||||
    impl->SerializeSection(Telemetry::FieldType::UserConfig, "UserConfig");
 | 
			
		||||
    impl->SerializeSection(Telemetry::FieldType::UserSystem, "UserSystem");
 | 
			
		||||
 | 
			
		||||
    auto content = impl->TopSection().dump();
 | 
			
		||||
    // Send the telemetry async but don't handle the errors since they were written to the log
 | 
			
		||||
    Common::DetachedTasks::AddTask([host{impl->host}, content]() {
 | 
			
		||||
        Client{host, "", ""}.PostJson("/telemetry", content, true);
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool TelemetryJson::SubmitTestcase() {
 | 
			
		||||
    impl->SerializeSection(Telemetry::FieldType::App, "App");
 | 
			
		||||
    impl->SerializeSection(Telemetry::FieldType::Session, "Session");
 | 
			
		||||
    impl->SerializeSection(Telemetry::FieldType::UserFeedback, "UserFeedback");
 | 
			
		||||
    impl->SerializeSection(Telemetry::FieldType::UserSystem, "UserSystem");
 | 
			
		||||
    impl->SerializeSection(Telemetry::FieldType::UserConfig, "UserConfig");
 | 
			
		||||
 | 
			
		||||
    auto content = impl->TopSection().dump();
 | 
			
		||||
    Client client(impl->host, impl->username, impl->token);
 | 
			
		||||
    auto value = client.PostJson("/gamedb/testcase", content, false);
 | 
			
		||||
 | 
			
		||||
    return value.result_code == WebResult::Code::Success;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
} // namespace WebService
 | 
			
		||||
@@ -1,44 +0,0 @@
 | 
			
		||||
// SPDX-FileCopyrightText: 2017 Citra Emulator Project & 2024 suyu Emulator Project
 | 
			
		||||
// SPDX-License-Identifier: GPL-2.0-or-later
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <chrono>
 | 
			
		||||
#include <string>
 | 
			
		||||
#include "common/telemetry.h"
 | 
			
		||||
 | 
			
		||||
namespace WebService {
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Implementation of VisitorInterface that serialized telemetry into JSON, and submits it to the
 | 
			
		||||
 * suyu web service
 | 
			
		||||
 */
 | 
			
		||||
class TelemetryJson : public Common::Telemetry::VisitorInterface {
 | 
			
		||||
public:
 | 
			
		||||
    TelemetryJson(std::string host, std::string username, std::string token);
 | 
			
		||||
    ~TelemetryJson() override;
 | 
			
		||||
 | 
			
		||||
    void Visit(const Common::Telemetry::Field<bool>& field) override;
 | 
			
		||||
    void Visit(const Common::Telemetry::Field<double>& field) override;
 | 
			
		||||
    void Visit(const Common::Telemetry::Field<float>& field) override;
 | 
			
		||||
    void Visit(const Common::Telemetry::Field<u8>& field) override;
 | 
			
		||||
    void Visit(const Common::Telemetry::Field<u16>& field) override;
 | 
			
		||||
    void Visit(const Common::Telemetry::Field<u32>& field) override;
 | 
			
		||||
    void Visit(const Common::Telemetry::Field<u64>& field) override;
 | 
			
		||||
    void Visit(const Common::Telemetry::Field<s8>& field) override;
 | 
			
		||||
    void Visit(const Common::Telemetry::Field<s16>& field) override;
 | 
			
		||||
    void Visit(const Common::Telemetry::Field<s32>& field) override;
 | 
			
		||||
    void Visit(const Common::Telemetry::Field<s64>& field) override;
 | 
			
		||||
    void Visit(const Common::Telemetry::Field<std::string>& field) override;
 | 
			
		||||
    void Visit(const Common::Telemetry::Field<const char*>& field) override;
 | 
			
		||||
    void Visit(const Common::Telemetry::Field<std::chrono::microseconds>& field) override;
 | 
			
		||||
 | 
			
		||||
    void Complete() override;
 | 
			
		||||
    bool SubmitTestcase() override;
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
    struct Impl;
 | 
			
		||||
    std::unique_ptr<Impl> impl;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
} // namespace WebService
 | 
			
		||||
		Reference in New Issue
	
	Block a user