Просмотр исходного кода

Merge pull request #2482 from DarkLordZach/prepo

core: Add detailed local reporting feature for development
bunnei 7 лет назад
Родитель
Сommit
96412848a9

+ 3 - 1
src/core/CMakeLists.txt

@@ -459,6 +459,8 @@ add_library(core STATIC
     memory_setup.h
     perf_stats.cpp
     perf_stats.h
+    reporter.cpp
+    reporter.h
     settings.cpp
     settings.h
     telemetry_session.cpp
@@ -468,7 +470,7 @@ add_library(core STATIC
 create_target_directory_groups(core)
 
 target_link_libraries(core PUBLIC common PRIVATE audio_core video_core)
-target_link_libraries(core PUBLIC Boost::boost PRIVATE fmt mbedtls opus unicorn open_source_archives)
+target_link_libraries(core PUBLIC Boost::boost PRIVATE fmt json-headers mbedtls opus unicorn open_source_archives)
 if (ENABLE_WEB_SERVICE)
     target_compile_definitions(core PRIVATE -DENABLE_WEB_SERVICE)
     target_link_libraries(core PRIVATE web_service)

+ 194 - 7
src/core/arm/arm_interface.cpp

@@ -2,26 +2,213 @@
 // Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
+#include <map>
+#include <optional>
+#include "common/bit_field.h"
 #include "common/common_types.h"
 #include "common/logging/log.h"
 #include "core/arm/arm_interface.h"
+#include "core/core.h"
+#include "core/loader/loader.h"
 #include "core/memory.h"
 
 namespace Core {
-void ARM_Interface::LogBacktrace() const {
-    VAddr fp = GetReg(29);
-    VAddr lr = GetReg(30);
-    const VAddr sp = GetReg(13);
-    const VAddr pc = GetPC();
 
-    LOG_ERROR(Core_ARM, "Backtrace, sp={:016X}, pc={:016X}", sp, pc);
+namespace {
+
+constexpr u64 ELF_DYNAMIC_TAG_NULL = 0;
+constexpr u64 ELF_DYNAMIC_TAG_STRTAB = 5;
+constexpr u64 ELF_DYNAMIC_TAG_SYMTAB = 6;
+constexpr u64 ELF_DYNAMIC_TAG_SYMENT = 11;
+
+enum class ELFSymbolType : u8 {
+    None = 0,
+    Object = 1,
+    Function = 2,
+    Section = 3,
+    File = 4,
+    Common = 5,
+    TLS = 6,
+};
+
+enum class ELFSymbolBinding : u8 {
+    Local = 0,
+    Global = 1,
+    Weak = 2,
+};
+
+enum class ELFSymbolVisibility : u8 {
+    Default = 0,
+    Internal = 1,
+    Hidden = 2,
+    Protected = 3,
+};
+
+struct ELFSymbol {
+    u32 name_index;
+    union {
+        u8 info;
+
+        BitField<0, 4, ELFSymbolType> type;
+        BitField<4, 4, ELFSymbolBinding> binding;
+    };
+    ELFSymbolVisibility visibility;
+    u16 sh_index;
+    u64 value;
+    u64 size;
+};
+static_assert(sizeof(ELFSymbol) == 0x18, "ELFSymbol has incorrect size.");
+
+using Symbols = std::vector<std::pair<ELFSymbol, std::string>>;
+
+Symbols GetSymbols(VAddr text_offset) {
+    const auto mod_offset = text_offset + Memory::Read32(text_offset + 4);
+
+    if (mod_offset < text_offset || (mod_offset & 0b11) != 0 ||
+        Memory::Read32(mod_offset) != Common::MakeMagic('M', 'O', 'D', '0')) {
+        return {};
+    }
+
+    const auto dynamic_offset = Memory::Read32(mod_offset + 0x4) + mod_offset;
+
+    VAddr string_table_offset{};
+    VAddr symbol_table_offset{};
+    u64 symbol_entry_size{};
+
+    VAddr dynamic_index = dynamic_offset;
+    while (true) {
+        const auto tag = Memory::Read64(dynamic_index);
+        const auto value = Memory::Read64(dynamic_index + 0x8);
+        dynamic_index += 0x10;
+
+        if (tag == ELF_DYNAMIC_TAG_NULL) {
+            break;
+        }
+
+        if (tag == ELF_DYNAMIC_TAG_STRTAB) {
+            string_table_offset = value;
+        } else if (tag == ELF_DYNAMIC_TAG_SYMTAB) {
+            symbol_table_offset = value;
+        } else if (tag == ELF_DYNAMIC_TAG_SYMENT) {
+            symbol_entry_size = value;
+        }
+    }
+
+    if (string_table_offset == 0 || symbol_table_offset == 0 || symbol_entry_size == 0) {
+        return {};
+    }
+
+    const auto string_table_address = text_offset + string_table_offset;
+    const auto symbol_table_address = text_offset + symbol_table_offset;
+
+    Symbols out;
+
+    VAddr symbol_index = symbol_table_address;
+    while (symbol_index < string_table_address) {
+        ELFSymbol symbol{};
+        Memory::ReadBlock(symbol_index, &symbol, sizeof(ELFSymbol));
+
+        VAddr string_offset = string_table_address + symbol.name_index;
+        std::string name;
+        for (u8 c = Memory::Read8(string_offset); c != 0; c = Memory::Read8(++string_offset)) {
+            name += static_cast<char>(c);
+        }
+
+        symbol_index += symbol_entry_size;
+        out.push_back({symbol, name});
+    }
+
+    return out;
+}
+
+std::optional<std::string> GetSymbolName(const Symbols& symbols, VAddr func_address) {
+    const auto iter =
+        std::find_if(symbols.begin(), symbols.end(), [func_address](const auto& pair) {
+            const auto& [symbol, name] = pair;
+            const auto end_address = symbol.value + symbol.size;
+            return func_address >= symbol.value && func_address < end_address;
+        });
+
+    if (iter == symbols.end()) {
+        return std::nullopt;
+    }
+
+    return iter->second;
+}
+
+} // Anonymous namespace
+
+constexpr u64 SEGMENT_BASE = 0x7100000000ull;
+
+std::vector<ARM_Interface::BacktraceEntry> ARM_Interface::GetBacktrace() const {
+    std::vector<BacktraceEntry> out;
+
+    auto fp = GetReg(29);
+    auto lr = GetReg(30);
+
     while (true) {
-        LOG_ERROR(Core_ARM, "{:016X}", lr);
+        out.push_back({"", 0, lr, 0});
         if (!fp) {
             break;
         }
         lr = Memory::Read64(fp + 8) - 4;
         fp = Memory::Read64(fp);
     }
+
+    std::map<VAddr, std::string> modules;
+    auto& loader{System::GetInstance().GetAppLoader()};
+    if (loader.ReadNSOModules(modules) != Loader::ResultStatus::Success) {
+        return {};
+    }
+
+    std::map<std::string, Symbols> symbols;
+    for (const auto& module : modules) {
+        symbols.insert_or_assign(module.second, GetSymbols(module.first));
+    }
+
+    for (auto& entry : out) {
+        VAddr base = 0;
+        for (auto iter = modules.rbegin(); iter != modules.rend(); ++iter) {
+            const auto& module{*iter};
+            if (entry.original_address >= module.first) {
+                entry.module = module.second;
+                base = module.first;
+                break;
+            }
+        }
+
+        entry.offset = entry.original_address - base;
+        entry.address = SEGMENT_BASE + entry.offset;
+
+        if (entry.module.empty())
+            entry.module = "unknown";
+
+        const auto symbol_set = symbols.find(entry.module);
+        if (symbol_set != symbols.end()) {
+            const auto symbol = GetSymbolName(symbol_set->second, entry.offset);
+            if (symbol.has_value()) {
+                // TODO(DarkLordZach): Add demangling of symbol names.
+                entry.name = *symbol;
+            }
+        }
+    }
+
+    return out;
+}
+
+void ARM_Interface::LogBacktrace() const {
+    const VAddr sp = GetReg(13);
+    const VAddr pc = GetPC();
+    LOG_ERROR(Core_ARM, "Backtrace, sp={:016X}, pc={:016X}", sp, pc);
+    LOG_ERROR(Core_ARM, "{:20}{:20}{:20}{:20}{}", "Module Name", "Address", "Original Address",
+              "Offset", "Symbol");
+    LOG_ERROR(Core_ARM, "");
+
+    const auto backtrace = GetBacktrace();
+    for (const auto& entry : backtrace) {
+        LOG_ERROR(Core_ARM, "{:20}{:016X}    {:016X}    {:016X}    {}", entry.module, entry.address,
+                  entry.original_address, entry.offset, entry.name);
+    }
 }
+
 } // namespace Core

+ 11 - 0
src/core/arm/arm_interface.h

@@ -5,6 +5,7 @@
 #pragma once
 
 #include <array>
+#include <vector>
 #include "common/common_types.h"
 
 namespace Common {
@@ -152,6 +153,16 @@ public:
     /// Prepare core for thread reschedule (if needed to correctly handle state)
     virtual void PrepareReschedule() = 0;
 
+    struct BacktraceEntry {
+        std::string module;
+        u64 address;
+        u64 original_address;
+        u64 offset;
+        std::string name;
+    };
+
+    std::vector<BacktraceEntry> GetBacktrace() const;
+
     /// fp (= r29) points to the last frame record.
     /// Note that this is the frame record for the *previous* frame, not the current one.
     /// Note we need to subtract 4 from our last read to get the proper address

+ 8 - 1
src/core/core.cpp

@@ -29,6 +29,7 @@
 #include "core/hle/service/sm/sm.h"
 #include "core/loader/loader.h"
 #include "core/perf_stats.h"
+#include "core/reporter.h"
 #include "core/settings.h"
 #include "core/telemetry_session.h"
 #include "file_sys/cheat_engine.h"
@@ -74,7 +75,7 @@ FileSys::VirtualFile GetGameFileFromPath(const FileSys::VirtualFilesystem& vfs,
     return vfs->OpenFile(path, FileSys::Mode::Read);
 }
 struct System::Impl {
-    explicit Impl(System& system) : kernel{system}, cpu_core_manager{system} {}
+    explicit Impl(System& system) : kernel{system}, cpu_core_manager{system}, reporter{system} {}
 
     Cpu& CurrentCpuCore() {
         return cpu_core_manager.GetCurrentCore();
@@ -253,6 +254,8 @@ struct System::Impl {
     /// Telemetry session for this emulation session
     std::unique_ptr<Core::TelemetrySession> telemetry_session;
 
+    Reporter reporter;
+
     ResultStatus status = ResultStatus::Success;
     std::string status_details = "";
 
@@ -492,6 +495,10 @@ void System::ClearContentProvider(FileSys::ContentProviderUnionSlot slot) {
     impl->content_provider->ClearSlot(slot);
 }
 
+const Reporter& System::GetReporter() const {
+    return impl->reporter;
+}
+
 System::ResultStatus System::Init(Frontend::EmuWindow& emu_window) {
     return impl->Init(*this, emu_window);
 }

+ 4 - 0
src/core/core.h

@@ -8,6 +8,7 @@
 #include <memory>
 #include <string>
 
+#include <map>
 #include "common/common_types.h"
 #include "core/file_sys/vfs_types.h"
 #include "core/hle/kernel/object.h"
@@ -68,6 +69,7 @@ class Cpu;
 class ExclusiveMonitor;
 class FrameLimiter;
 class PerfStats;
+class Reporter;
 class TelemetrySession;
 
 struct PerfStatsResults;
@@ -284,6 +286,8 @@ public:
 
     void ClearContentProvider(FileSys::ContentProviderUnionSlot slot);
 
+    const Reporter& GetReporter() const;
+
 private:
     System();
 

+ 7 - 1
src/core/hle/kernel/svc.cpp

@@ -38,6 +38,7 @@
 #include "core/hle/result.h"
 #include "core/hle/service/service.h"
 #include "core/memory.h"
+#include "core/reporter.h"
 
 namespace Kernel {
 namespace {
@@ -594,6 +595,7 @@ struct BreakReason {
 static void Break(Core::System& system, u32 reason, u64 info1, u64 info2) {
     BreakReason break_reason{reason};
     bool has_dumped_buffer{};
+    std::vector<u8> debug_buffer;
 
     const auto handle_debug_buffer = [&](VAddr addr, u64 sz) {
         if (sz == 0 || addr == 0 || has_dumped_buffer) {
@@ -605,7 +607,7 @@ static void Break(Core::System& system, u32 reason, u64 info1, u64 info2) {
             LOG_CRITICAL(Debug_Emulated, "debug_buffer_err_code={:X}", Memory::Read32(addr));
         } else {
             // We don't know what's in here so we'll hexdump it
-            std::vector<u8> debug_buffer(sz);
+            debug_buffer.resize(sz);
             Memory::ReadBlock(addr, debug_buffer.data(), sz);
             std::string hexdump;
             for (std::size_t i = 0; i < debug_buffer.size(); i++) {
@@ -664,6 +666,10 @@ static void Break(Core::System& system, u32 reason, u64 info1, u64 info2) {
         break;
     }
 
+    system.GetReporter().SaveSvcBreakReport(
+        static_cast<u32>(break_reason.break_type.Value()), break_reason.signal_debugger, info1,
+        info2, has_dumped_buffer ? std::make_optional(debug_buffer) : std::nullopt);
+
     if (!break_reason.signal_debugger) {
         LOG_CRITICAL(
             Debug_Emulated,

+ 25 - 9
src/core/hle/service/am/applets/applets.cpp

@@ -35,12 +35,28 @@ AppletDataBroker::AppletDataBroker() {
 
 AppletDataBroker::~AppletDataBroker() = default;
 
+AppletDataBroker::RawChannelData AppletDataBroker::PeekDataToAppletForDebug() const {
+    std::vector<std::vector<u8>> out_normal;
+
+    for (const auto& storage : in_channel) {
+        out_normal.push_back(storage->GetData());
+    }
+
+    std::vector<std::vector<u8>> out_interactive;
+
+    for (const auto& storage : in_interactive_channel) {
+        out_interactive.push_back(storage->GetData());
+    }
+
+    return {std::move(out_normal), std::move(out_interactive)};
+}
+
 std::unique_ptr<IStorage> AppletDataBroker::PopNormalDataToGame() {
     if (out_channel.empty())
         return nullptr;
 
     auto out = std::move(out_channel.front());
-    out_channel.pop();
+    out_channel.pop_front();
     return out;
 }
 
@@ -49,7 +65,7 @@ std::unique_ptr<IStorage> AppletDataBroker::PopNormalDataToApplet() {
         return nullptr;
 
     auto out = std::move(in_channel.front());
-    in_channel.pop();
+    in_channel.pop_front();
     return out;
 }
 
@@ -58,7 +74,7 @@ std::unique_ptr<IStorage> AppletDataBroker::PopInteractiveDataToGame() {
         return nullptr;
 
     auto out = std::move(out_interactive_channel.front());
-    out_interactive_channel.pop();
+    out_interactive_channel.pop_front();
     return out;
 }
 
@@ -67,25 +83,25 @@ std::unique_ptr<IStorage> AppletDataBroker::PopInteractiveDataToApplet() {
         return nullptr;
 
     auto out = std::move(in_interactive_channel.front());
-    in_interactive_channel.pop();
+    in_interactive_channel.pop_front();
     return out;
 }
 
 void AppletDataBroker::PushNormalDataFromGame(IStorage storage) {
-    in_channel.push(std::make_unique<IStorage>(storage));
+    in_channel.push_back(std::make_unique<IStorage>(storage));
 }
 
 void AppletDataBroker::PushNormalDataFromApplet(IStorage storage) {
-    out_channel.push(std::make_unique<IStorage>(storage));
+    out_channel.push_back(std::make_unique<IStorage>(storage));
     pop_out_data_event.writable->Signal();
 }
 
 void AppletDataBroker::PushInteractiveDataFromGame(IStorage storage) {
-    in_interactive_channel.push(std::make_unique<IStorage>(storage));
+    in_interactive_channel.push_back(std::make_unique<IStorage>(storage));
 }
 
 void AppletDataBroker::PushInteractiveDataFromApplet(IStorage storage) {
-    out_interactive_channel.push(std::make_unique<IStorage>(storage));
+    out_interactive_channel.push_back(std::make_unique<IStorage>(storage));
     pop_interactive_out_data_event.writable->Signal();
 }
 
@@ -204,7 +220,7 @@ std::shared_ptr<Applet> AppletManager::GetApplet(AppletId id) const {
         UNIMPLEMENTED_MSG(
             "No backend implementation exists for applet_id={:02X}! Falling back to stub applet.",
             static_cast<u8>(id));
-        return std::make_shared<StubApplet>();
+        return std::make_shared<StubApplet>(id);
     }
 }
 

+ 12 - 4
src/core/hle/service/am/applets/applets.h

@@ -54,6 +54,14 @@ public:
     AppletDataBroker();
     ~AppletDataBroker();
 
+    struct RawChannelData {
+        std::vector<std::vector<u8>> normal;
+        std::vector<std::vector<u8>> interactive;
+    };
+
+    // Retrieves but does not pop the data sent to applet.
+    RawChannelData PeekDataToAppletForDebug() const;
+
     std::unique_ptr<IStorage> PopNormalDataToGame();
     std::unique_ptr<IStorage> PopNormalDataToApplet();
 
@@ -76,16 +84,16 @@ private:
     // Queues are named from applet's perspective
 
     // PopNormalDataToApplet and PushNormalDataFromGame
-    std::queue<std::unique_ptr<IStorage>> in_channel;
+    std::deque<std::unique_ptr<IStorage>> in_channel;
 
     // PopNormalDataToGame and PushNormalDataFromApplet
-    std::queue<std::unique_ptr<IStorage>> out_channel;
+    std::deque<std::unique_ptr<IStorage>> out_channel;
 
     // PopInteractiveDataToApplet and PushInteractiveDataFromGame
-    std::queue<std::unique_ptr<IStorage>> in_interactive_channel;
+    std::deque<std::unique_ptr<IStorage>> in_interactive_channel;
 
     // PopInteractiveDataToGame and PushInteractiveDataFromApplet
-    std::queue<std::unique_ptr<IStorage>> out_interactive_channel;
+    std::deque<std::unique_ptr<IStorage>> out_interactive_channel;
 
     Kernel::EventPair state_changed_event;
 

+ 14 - 5
src/core/hle/service/am/applets/error.cpp

@@ -9,8 +9,10 @@
 #include "common/string_util.h"
 #include "core/core.h"
 #include "core/frontend/applets/error.h"
+#include "core/hle/kernel/process.h"
 #include "core/hle/service/am/am.h"
 #include "core/hle/service/am/applets/error.h"
+#include "core/reporter.h"
 
 namespace Service::AM::Applets {
 
@@ -143,9 +145,12 @@ void Error::Execute() {
     }
 
     const auto callback = [this] { DisplayCompleted(); };
+    const auto title_id = Core::CurrentProcess()->GetTitleID();
+    const auto& reporter{Core::System::GetInstance().GetReporter()};
 
     switch (mode) {
     case ErrorAppletMode::ShowError:
+        reporter.SaveErrorReport(title_id, error_code);
         frontend.ShowError(error_code, callback);
         break;
     case ErrorAppletMode::ShowSystemError:
@@ -156,14 +161,18 @@ void Error::Execute() {
         const auto& detail_text =
             system ? args->system_error.detail_text : args->application_error.detail_text;
 
-        frontend.ShowCustomErrorText(
-            error_code,
-            Common::StringFromFixedZeroTerminatedBuffer(main_text.data(), main_text.size()),
-            Common::StringFromFixedZeroTerminatedBuffer(detail_text.data(), detail_text.size()),
-            callback);
+        const auto main_text_string =
+            Common::StringFromFixedZeroTerminatedBuffer(main_text.data(), main_text.size());
+        const auto detail_text_string =
+            Common::StringFromFixedZeroTerminatedBuffer(detail_text.data(), detail_text.size());
+
+        reporter.SaveErrorReport(title_id, error_code, main_text_string, detail_text_string);
+        frontend.ShowCustomErrorText(error_code, main_text_string, detail_text_string, callback);
         break;
     }
     case ErrorAppletMode::ShowErrorRecord:
+        reporter.SaveErrorReport(title_id, error_code,
+                                 fmt::format("{:016X}", args->error_record.posix_time));
         frontend.ShowErrorWithTimestamp(
             error_code, std::chrono::seconds{args->error_record.posix_time}, callback);
         break;

+ 9 - 1
src/core/hle/service/am/applets/general_backend.cpp

@@ -13,6 +13,7 @@
 #include "core/hle/result.h"
 #include "core/hle/service/am/am.h"
 #include "core/hle/service/am/applets/general_backend.h"
+#include "core/reporter.h"
 
 namespace Service::AM::Applets {
 
@@ -83,13 +84,20 @@ void PhotoViewer::ViewFinished() {
     broker.SignalStateChanged();
 }
 
-StubApplet::StubApplet() = default;
+StubApplet::StubApplet(AppletId id) : id(id) {}
 
 StubApplet::~StubApplet() = default;
 
 void StubApplet::Initialize() {
     LOG_WARNING(Service_AM, "called (STUBBED)");
     Applet::Initialize();
+
+    const auto data = broker.PeekDataToAppletForDebug();
+    Core::System::GetInstance().GetReporter().SaveUnimplementedAppletReport(
+        static_cast<u32>(id), common_args.arguments_version, common_args.library_version,
+        common_args.theme_color, common_args.play_startup_sound, common_args.system_tick,
+        data.normal, data.interactive);
+
     LogCurrentStorage(broker, "Initialize");
 }
 

+ 4 - 1
src/core/hle/service/am/applets/general_backend.h

@@ -34,7 +34,7 @@ private:
 
 class StubApplet final : public Applet {
 public:
-    StubApplet();
+    explicit StubApplet(AppletId id);
     ~StubApplet() override;
 
     void Initialize() override;
@@ -43,6 +43,9 @@ public:
     ResultCode GetStatus() const override;
     void ExecuteInteractive() override;
     void Execute() override;
+
+private:
+    AppletId id;
 };
 
 } // namespace Service::AM::Applets

+ 5 - 21
src/core/hle/service/fatal/fatal.cpp

@@ -16,6 +16,7 @@
 #include "core/hle/service/fatal/fatal.h"
 #include "core/hle/service/fatal/fatal_p.h"
 #include "core/hle/service/fatal/fatal_u.h"
+#include "core/reporter.h"
 
 namespace Service::Fatal {
 
@@ -100,27 +101,10 @@ static void GenerateErrorReport(ResultCode error_code, const FatalInfo& info) {
 
     LOG_ERROR(Service_Fatal, "{}", crash_report);
 
-    const std::string crashreport_dir =
-        FileUtil::GetUserPath(FileUtil::UserPath::LogDir) + "crash_logs";
-
-    if (!FileUtil::CreateFullPath(crashreport_dir)) {
-        LOG_ERROR(
-            Service_Fatal,
-            "Unable to create crash report directory. Possible log directory permissions issue.");
-        return;
-    }
-
-    const std::time_t t = std::time(nullptr);
-    const std::string crashreport_filename =
-        fmt::format("{}/{:016x}-{:%F-%H%M%S}.log", crashreport_dir, title_id, *std::localtime(&t));
-
-    auto file = FileUtil::IOFile(crashreport_filename, "wb");
-    if (file.IsOpen()) {
-        file.WriteString(crash_report);
-        LOG_ERROR(Service_Fatal, "Saving error report to {}", crashreport_filename);
-    } else {
-        LOG_ERROR(Service_Fatal, "Failed to save error report to {}", crashreport_filename);
-    }
+    Core::System::GetInstance().GetReporter().SaveCrashReport(
+        title_id, error_code, info.set_flags, info.program_entry_point, info.sp, info.pc,
+        info.pstate, info.afsr0, info.afsr1, info.esr, info.far, info.registers, info.backtrace,
+        info.backtrace_size, info.ArchAsString(), info.unk10);
 }
 
 static void ThrowFatalError(ResultCode error_code, FatalType fatal_type, const FatalInfo& info) {

+ 23 - 2
src/core/hle/service/prepo/prepo.cpp

@@ -2,10 +2,18 @@
 // Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
+#include <json.hpp>
+#include "common/file_util.h"
+#include "common/hex_util.h"
 #include "common/logging/log.h"
+#include "common/scm_rev.h"
 #include "core/hle/ipc_helpers.h"
+#include "core/hle/kernel/process.h"
+#include "core/hle/service/acc/profile_manager.h"
 #include "core/hle/service/prepo/prepo.h"
 #include "core/hle/service/service.h"
+#include "core/reporter.h"
+#include "core/settings.h"
 
 namespace Service::PlayReport {
 
@@ -40,8 +48,21 @@ public:
 
 private:
     void SaveReportWithUserOld(Kernel::HLERequestContext& ctx) {
-        // TODO(ogniK): Do we want to add play report?
-        LOG_WARNING(Service_PREPO, "(STUBBED) called");
+        IPC::RequestParser rp{ctx};
+        const auto user_id = rp.PopRaw<u128>();
+        const auto process_id = rp.PopRaw<u64>();
+
+        const auto data1 = ctx.ReadBuffer(0);
+        const auto data2 = ctx.ReadBuffer(1);
+
+        LOG_DEBUG(
+            Service_PREPO,
+            "called, user_id={:016X}{:016X}, unk1={:016X}, data1_size={:016X}, data2_size={:016X}",
+            user_id[1], user_id[0], process_id, data1.size(), data2.size());
+
+        const auto& reporter{Core::System::GetInstance().GetReporter()};
+        reporter.SavePlayReport(Core::CurrentProcess()->GetTitleID(), process_id, {data1, data2},
+                                user_id);
 
         IPC::ResponseBuilder rb{ctx, 2};
         rb.Push(RESULT_SUCCESS);

+ 3 - 0
src/core/hle/service/service.cpp

@@ -68,6 +68,7 @@
 #include "core/hle/service/usb/usb.h"
 #include "core/hle/service/vi/vi.h"
 #include "core/hle/service/wlan/wlan.h"
+#include "core/reporter.h"
 
 namespace Service {
 
@@ -148,6 +149,8 @@ void ServiceFrameworkBase::ReportUnimplementedFunction(Kernel::HLERequestContext
     }
     buf.push_back('}');
 
+    Core::System::GetInstance().GetReporter().SaveUnimplementedFunctionReport(
+        ctx, ctx.GetCommand(), function_name, service_name);
     UNIMPLEMENTED_MSG("Unknown / unimplemented {}", fmt::to_string(buf));
 }
 

+ 11 - 0
src/core/loader/deconstructed_rom_directory.cpp

@@ -141,6 +141,7 @@ AppLoader_DeconstructedRomDirectory::LoadResult AppLoader_DeconstructedRomDirect
     const FileSys::PatchManager pm(metadata.GetTitleID());
 
     // Load NSO modules
+    modules.clear();
     const VAddr base_address = process.VMManager().GetCodeRegionBaseAddress();
     VAddr next_load_addr = base_address;
     for (const auto& module : {"rtld", "main", "subsdk0", "subsdk1", "subsdk2", "subsdk3",
@@ -159,6 +160,7 @@ AppLoader_DeconstructedRomDirectory::LoadResult AppLoader_DeconstructedRomDirect
         }
 
         next_load_addr = *tentative_next_load_addr;
+        modules.insert_or_assign(load_addr, module);
         LOG_DEBUG(Loader, "loaded module {} @ 0x{:X}", module, load_addr);
         // Register module with GDBStub
         GDBStub::RegisterModule(module, load_addr, next_load_addr - 1, false);
@@ -212,4 +214,13 @@ bool AppLoader_DeconstructedRomDirectory::IsRomFSUpdatable() const {
     return false;
 }
 
+ResultStatus AppLoader_DeconstructedRomDirectory::ReadNSOModules(Modules& modules) {
+    if (!is_loaded) {
+        return ResultStatus::ErrorNotInitialized;
+    }
+
+    modules = this->modules;
+    return ResultStatus::Success;
+}
+
 } // namespace Loader

+ 4 - 0
src/core/loader/deconstructed_rom_directory.h

@@ -45,6 +45,8 @@ public:
     ResultStatus ReadTitle(std::string& title) override;
     bool IsRomFSUpdatable() const override;
 
+    ResultStatus ReadNSOModules(Modules& modules) override;
+
 private:
     FileSys::ProgramMetadata metadata;
     FileSys::VirtualFile romfs;
@@ -54,6 +56,8 @@ private:
     std::string name;
     u64 title_id{};
     bool override_update;
+
+    Modules modules;
 };
 
 } // namespace Loader

+ 6 - 0
src/core/loader/loader.h

@@ -267,6 +267,12 @@ public:
         return ResultStatus::ErrorNotImplemented;
     }
 
+    using Modules = std::map<VAddr, std::string>;
+
+    virtual ResultStatus ReadNSOModules(Modules& modules) {
+        return ResultStatus::ErrorNotImplemented;
+    }
+
 protected:
     FileSys::VirtualFile file;
     bool is_loaded = false;

+ 4 - 0
src/core/loader/nax.cpp

@@ -94,4 +94,8 @@ ResultStatus AppLoader_NAX::ReadLogo(std::vector<u8>& buffer) {
     return nca_loader->ReadLogo(buffer);
 }
 
+ResultStatus AppLoader_NAX::ReadNSOModules(Modules& modules) {
+    return nca_loader->ReadNSOModules(modules);
+}
+
 } // namespace Loader

+ 2 - 0
src/core/loader/nax.h

@@ -42,6 +42,8 @@ public:
     ResultStatus ReadBanner(std::vector<u8>& buffer) override;
     ResultStatus ReadLogo(std::vector<u8>& buffer) override;
 
+    ResultStatus ReadNSOModules(Modules& modules) override;
+
 private:
     std::unique_ptr<FileSys::NAX> nax;
     std::unique_ptr<AppLoader_NCA> nca_loader;

+ 9 - 0
src/core/loader/nca.cpp

@@ -105,4 +105,13 @@ ResultStatus AppLoader_NCA::ReadLogo(std::vector<u8>& buffer) {
     buffer = logo->GetFile("NintendoLogo.png")->ReadAllBytes();
     return ResultStatus::Success;
 }
+
+ResultStatus AppLoader_NCA::ReadNSOModules(Modules& modules) {
+    if (directory_loader == nullptr) {
+        return ResultStatus::ErrorNotInitialized;
+    }
+
+    return directory_loader->ReadNSOModules(modules);
+}
+
 } // namespace Loader

+ 2 - 0
src/core/loader/nca.h

@@ -42,6 +42,8 @@ public:
     ResultStatus ReadBanner(std::vector<u8>& buffer) override;
     ResultStatus ReadLogo(std::vector<u8>& buffer) override;
 
+    ResultStatus ReadNSOModules(Modules& modules) override;
+
 private:
     std::unique_ptr<FileSys::NCA> nca;
     std::unique_ptr<AppLoader_DeconstructedRomDirectory> directory_loader;

+ 9 - 0
src/core/loader/nso.cpp

@@ -172,11 +172,15 @@ AppLoader_NSO::LoadResult AppLoader_NSO::Load(Kernel::Process& process) {
         return {ResultStatus::ErrorAlreadyLoaded, {}};
     }
 
+    modules.clear();
+
     // Load module
     const VAddr base_address = process.VMManager().GetCodeRegionBaseAddress();
     if (!LoadModule(process, *file, base_address, true)) {
         return {ResultStatus::ErrorLoadingNSO, {}};
     }
+
+    modules.insert_or_assign(base_address, file->GetName());
     LOG_DEBUG(Loader, "loaded module {} @ 0x{:X}", file->GetName(), base_address);
 
     is_loaded = true;
@@ -184,4 +188,9 @@ AppLoader_NSO::LoadResult AppLoader_NSO::Load(Kernel::Process& process) {
             LoadParameters{Kernel::THREADPRIO_DEFAULT, Memory::DEFAULT_STACK_SIZE}};
 }
 
+ResultStatus AppLoader_NSO::ReadNSOModules(Modules& modules) {
+    modules = this->modules;
+    return ResultStatus::Success;
+}
+
 } // namespace Loader

+ 5 - 0
src/core/loader/nso.h

@@ -85,6 +85,11 @@ public:
                                            std::optional<FileSys::PatchManager> pm = {});
 
     LoadResult Load(Kernel::Process& process) override;
+
+    ResultStatus ReadNSOModules(Modules& modules) override;
+
+private:
+    Modules modules;
 };
 
 } // namespace Loader

+ 4 - 0
src/core/loader/nsp.cpp

@@ -183,4 +183,8 @@ ResultStatus AppLoader_NSP::ReadLogo(std::vector<u8>& buffer) {
     return secondary_loader->ReadLogo(buffer);
 }
 
+ResultStatus AppLoader_NSP::ReadNSOModules(Modules& modules) {
+    return secondary_loader->ReadNSOModules(modules);
+}
+
 } // namespace Loader

+ 2 - 0
src/core/loader/nsp.h

@@ -49,6 +49,8 @@ public:
     ResultStatus ReadBanner(std::vector<u8>& buffer) override;
     ResultStatus ReadLogo(std::vector<u8>& buffer) override;
 
+    ResultStatus ReadNSOModules(Modules& modules) override;
+
 private:
     std::unique_ptr<FileSys::NSP> nsp;
     std::unique_ptr<AppLoader> secondary_loader;

+ 4 - 0
src/core/loader/xci.cpp

@@ -149,4 +149,8 @@ ResultStatus AppLoader_XCI::ReadLogo(std::vector<u8>& buffer) {
     return nca_loader->ReadLogo(buffer);
 }
 
+ResultStatus AppLoader_XCI::ReadNSOModules(Modules& modules) {
+    return nca_loader->ReadNSOModules(modules);
+}
+
 } // namespace Loader

+ 2 - 0
src/core/loader/xci.h

@@ -49,6 +49,8 @@ public:
     ResultStatus ReadBanner(std::vector<u8>& buffer) override;
     ResultStatus ReadLogo(std::vector<u8>& buffer) override;
 
+    ResultStatus ReadNSOModules(Modules& modules) override;
+
 private:
     std::unique_ptr<FileSys::XCI> xci;
     std::unique_ptr<AppLoader_NCA> nca_loader;

+ 353 - 0
src/core/reporter.cpp

@@ -0,0 +1,353 @@
+// Copyright 2019 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <fstream>
+#include <json.hpp>
+#include "common/file_util.h"
+#include "common/hex_util.h"
+#include "common/scm_rev.h"
+#include "core/arm/arm_interface.h"
+#include "core/core.h"
+#include "core/hle/kernel/hle_ipc.h"
+#include "core/hle/kernel/process.h"
+#include "core/hle/result.h"
+#include "core/reporter.h"
+#include "core/settings.h"
+#include "fmt/time.h"
+
+namespace {
+
+std::string GetPath(std::string_view type, u64 title_id, std::string_view timestamp) {
+    return fmt::format("{}{}/{:016X}_{}.json", FileUtil::GetUserPath(FileUtil::UserPath::LogDir),
+                       type, title_id, timestamp);
+}
+
+std::string GetTimestamp() {
+    const auto time = std::time(nullptr);
+    return fmt::format("{:%FT%H-%M-%S}", *std::localtime(&time));
+}
+
+using namespace nlohmann;
+
+void SaveToFile(const json& json, const std::string& filename) {
+    if (!FileUtil::CreateFullPath(filename))
+        LOG_ERROR(Core, "Failed to create path for '{}' to save report!", filename);
+
+    std::ofstream file(
+        FileUtil::SanitizePath(filename, FileUtil::DirectorySeparator::PlatformDefault));
+    file << std::setw(4) << json << std::endl;
+}
+
+json GetYuzuVersionData() {
+    return {
+        {"scm_rev", std::string(Common::g_scm_rev)},
+        {"scm_branch", std::string(Common::g_scm_branch)},
+        {"scm_desc", std::string(Common::g_scm_desc)},
+        {"build_name", std::string(Common::g_build_name)},
+        {"build_date", std::string(Common::g_build_date)},
+        {"build_fullname", std::string(Common::g_build_fullname)},
+        {"build_version", std::string(Common::g_build_version)},
+        {"shader_cache_version", std::string(Common::g_shader_cache_version)},
+    };
+}
+
+json GetReportCommonData(u64 title_id, ResultCode result, const std::string& timestamp,
+                         std::optional<u128> user_id = {}) {
+    auto out = json{
+        {"title_id", fmt::format("{:016X}", title_id)},
+        {"result_raw", fmt::format("{:08X}", result.raw)},
+        {"result_module", fmt::format("{:08X}", static_cast<u32>(result.module.Value()))},
+        {"result_description", fmt::format("{:08X}", result.description.Value())},
+        {"timestamp", timestamp},
+    };
+    if (user_id.has_value())
+        out["user_id"] = fmt::format("{:016X}{:016X}", (*user_id)[1], (*user_id)[0]);
+    return out;
+}
+
+json GetProcessorStateData(const std::string& architecture, u64 entry_point, u64 sp, u64 pc,
+                           u64 pstate, std::array<u64, 31> registers,
+                           std::optional<std::array<u64, 32>> backtrace = {}) {
+    auto out = json{
+        {"entry_point", fmt::format("{:016X}", entry_point)},
+        {"sp", fmt::format("{:016X}", sp)},
+        {"pc", fmt::format("{:016X}", pc)},
+        {"pstate", fmt::format("{:016X}", pstate)},
+        {"architecture", architecture},
+    };
+
+    auto registers_out = json::object();
+    for (std::size_t i = 0; i < registers.size(); ++i) {
+        registers_out[fmt::format("X{:02d}", i)] = fmt::format("{:016X}", registers[i]);
+    }
+
+    out["registers"] = std::move(registers_out);
+
+    if (backtrace.has_value()) {
+        auto backtrace_out = json::array();
+        for (const auto& entry : *backtrace) {
+            backtrace_out.push_back(fmt::format("{:016X}", entry));
+        }
+        out["backtrace"] = std::move(backtrace_out);
+    }
+
+    return out;
+}
+
+json GetProcessorStateDataAuto(Core::System& system) {
+    const auto* process{system.CurrentProcess()};
+    const auto& vm_manager{process->VMManager()};
+    auto& arm{system.CurrentArmInterface()};
+
+    Core::ARM_Interface::ThreadContext context{};
+    arm.SaveContext(context);
+
+    return GetProcessorStateData(process->Is64BitProcess() ? "AArch64" : "AArch32",
+                                 vm_manager.GetCodeRegionBaseAddress(), context.sp, context.pc,
+                                 context.pstate, context.cpu_registers);
+}
+
+json GetBacktraceData(Core::System& system) {
+    auto out = json::array();
+    const auto& backtrace{system.CurrentArmInterface().GetBacktrace()};
+    for (const auto& entry : backtrace) {
+        out.push_back({
+            {"module", entry.module},
+            {"address", fmt::format("{:016X}", entry.address)},
+            {"original_address", fmt::format("{:016X}", entry.original_address)},
+            {"offset", fmt::format("{:016X}", entry.offset)},
+            {"symbol_name", entry.name},
+        });
+    }
+
+    return out;
+}
+
+json GetFullDataAuto(const std::string& timestamp, u64 title_id, Core::System& system) {
+    json out;
+
+    out["yuzu_version"] = GetYuzuVersionData();
+    out["report_common"] = GetReportCommonData(title_id, RESULT_SUCCESS, timestamp);
+    out["processor_state"] = GetProcessorStateDataAuto(system);
+    out["backtrace"] = GetBacktraceData(system);
+
+    return out;
+}
+
+template <bool read_value, typename DescriptorType>
+json GetHLEBufferDescriptorData(const std::vector<DescriptorType>& buffer) {
+    auto buffer_out = json::array();
+    for (const auto& desc : buffer) {
+        auto entry = json{
+            {"address", fmt::format("{:016X}", desc.Address())},
+            {"size", fmt::format("{:016X}", desc.Size())},
+        };
+
+        if constexpr (read_value) {
+            std::vector<u8> data(desc.Size());
+            Memory::ReadBlock(desc.Address(), data.data(), desc.Size());
+            entry["data"] = Common::HexVectorToString(data);
+        }
+
+        buffer_out.push_back(std::move(entry));
+    }
+
+    return buffer_out;
+}
+
+json GetHLERequestContextData(Kernel::HLERequestContext& ctx) {
+    json out;
+
+    auto cmd_buf = json::array();
+    for (std::size_t i = 0; i < IPC::COMMAND_BUFFER_LENGTH; ++i) {
+        cmd_buf.push_back(fmt::format("{:08X}", ctx.CommandBuffer()[i]));
+    }
+
+    out["command_buffer"] = std::move(cmd_buf);
+
+    out["buffer_descriptor_a"] = GetHLEBufferDescriptorData<true>(ctx.BufferDescriptorA());
+    out["buffer_descriptor_b"] = GetHLEBufferDescriptorData<false>(ctx.BufferDescriptorB());
+    out["buffer_descriptor_c"] = GetHLEBufferDescriptorData<false>(ctx.BufferDescriptorC());
+    out["buffer_descriptor_x"] = GetHLEBufferDescriptorData<true>(ctx.BufferDescriptorX());
+
+    return std::move(out);
+}
+
+} // Anonymous namespace
+
+namespace Core {
+
+Reporter::Reporter(Core::System& system) : system(system) {}
+
+Reporter::~Reporter() = default;
+
+void Reporter::SaveCrashReport(u64 title_id, ResultCode result, u64 set_flags, u64 entry_point,
+                               u64 sp, u64 pc, u64 pstate, u64 afsr0, u64 afsr1, u64 esr, u64 far,
+                               const std::array<u64, 31>& registers,
+                               const std::array<u64, 32>& backtrace, u32 backtrace_size,
+                               const std::string& arch, u32 unk10) const {
+    if (!IsReportingEnabled())
+        return;
+
+    const auto timestamp = GetTimestamp();
+    json out;
+
+    out["yuzu_version"] = GetYuzuVersionData();
+    out["report_common"] = GetReportCommonData(title_id, result, timestamp);
+
+    auto proc_out = GetProcessorStateData(arch, entry_point, sp, pc, pstate, registers, backtrace);
+    proc_out["set_flags"] = fmt::format("{:016X}", set_flags);
+    proc_out["afsr0"] = fmt::format("{:016X}", afsr0);
+    proc_out["afsr1"] = fmt::format("{:016X}", afsr1);
+    proc_out["esr"] = fmt::format("{:016X}", esr);
+    proc_out["far"] = fmt::format("{:016X}", far);
+    proc_out["backtrace_size"] = fmt::format("{:08X}", backtrace_size);
+    proc_out["unknown_10"] = fmt::format("{:08X}", unk10);
+
+    out["processor_state"] = std::move(proc_out);
+
+    SaveToFile(std::move(out), GetPath("crash_report", title_id, timestamp));
+}
+
+void Reporter::SaveSvcBreakReport(u32 type, bool signal_debugger, u64 info1, u64 info2,
+                                  std::optional<std::vector<u8>> resolved_buffer) const {
+    if (!IsReportingEnabled())
+        return;
+
+    const auto timestamp = GetTimestamp();
+    const auto title_id = system.CurrentProcess()->GetTitleID();
+    auto out = GetFullDataAuto(timestamp, title_id, system);
+
+    auto break_out = json{
+        {"type", fmt::format("{:08X}", type)},
+        {"signal_debugger", fmt::format("{}", signal_debugger)},
+        {"info1", fmt::format("{:016X}", info1)},
+        {"info2", fmt::format("{:016X}", info2)},
+    };
+
+    if (resolved_buffer.has_value()) {
+        break_out["debug_buffer"] = Common::HexVectorToString(*resolved_buffer);
+    }
+
+    out["svc_break"] = std::move(break_out);
+
+    SaveToFile(std::move(out), GetPath("svc_break_report", title_id, timestamp));
+}
+
+void Reporter::SaveUnimplementedFunctionReport(Kernel::HLERequestContext& ctx, u32 command_id,
+                                               const std::string& name,
+                                               const std::string& service_name) const {
+    if (!IsReportingEnabled())
+        return;
+
+    const auto timestamp = GetTimestamp();
+    const auto title_id = system.CurrentProcess()->GetTitleID();
+    auto out = GetFullDataAuto(timestamp, title_id, system);
+
+    auto function_out = GetHLERequestContextData(ctx);
+    function_out["command_id"] = command_id;
+    function_out["function_name"] = name;
+    function_out["service_name"] = service_name;
+
+    out["function"] = std::move(function_out);
+
+    SaveToFile(std::move(out), GetPath("unimpl_func_report", title_id, timestamp));
+}
+
+void Reporter::SaveUnimplementedAppletReport(
+    u32 applet_id, u32 common_args_version, u32 library_version, u32 theme_color,
+    bool startup_sound, u64 system_tick, std::vector<std::vector<u8>> normal_channel,
+    std::vector<std::vector<u8>> interactive_channel) const {
+    if (!IsReportingEnabled())
+        return;
+
+    const auto timestamp = GetTimestamp();
+    const auto title_id = system.CurrentProcess()->GetTitleID();
+    auto out = GetFullDataAuto(timestamp, title_id, system);
+
+    out["applet_common_args"] = {
+        {"applet_id", fmt::format("{:02X}", applet_id)},
+        {"common_args_version", fmt::format("{:08X}", common_args_version)},
+        {"library_version", fmt::format("{:08X}", library_version)},
+        {"theme_color", fmt::format("{:08X}", theme_color)},
+        {"startup_sound", fmt::format("{}", startup_sound)},
+        {"system_tick", fmt::format("{:016X}", system_tick)},
+    };
+
+    auto normal_out = json::array();
+    for (const auto& data : normal_channel) {
+        normal_out.push_back(Common::HexVectorToString(data));
+    }
+
+    auto interactive_out = json::array();
+    for (const auto& data : interactive_channel) {
+        interactive_out.push_back(Common::HexVectorToString(data));
+    }
+
+    out["applet_normal_data"] = std::move(normal_out);
+    out["applet_interactive_data"] = std::move(interactive_out);
+
+    SaveToFile(std::move(out), GetPath("unimpl_applet_report", title_id, timestamp));
+}
+
+void Reporter::SavePlayReport(u64 title_id, u64 process_id, std::vector<std::vector<u8>> data,
+                              std::optional<u128> user_id) const {
+    if (!IsReportingEnabled())
+        return;
+
+    const auto timestamp = GetTimestamp();
+    json out;
+
+    out["yuzu_version"] = GetYuzuVersionData();
+    out["report_common"] = GetReportCommonData(title_id, RESULT_SUCCESS, timestamp, user_id);
+
+    auto data_out = json::array();
+    for (const auto& d : data) {
+        data_out.push_back(Common::HexVectorToString(d));
+    }
+
+    out["play_report_process_id"] = fmt::format("{:016X}", process_id);
+    out["play_report_data"] = std::move(data_out);
+
+    SaveToFile(std::move(out), GetPath("play_report", title_id, timestamp));
+}
+
+void Reporter::SaveErrorReport(u64 title_id, ResultCode result,
+                               std::optional<std::string> custom_text_main,
+                               std::optional<std::string> custom_text_detail) const {
+    if (!IsReportingEnabled())
+        return;
+
+    const auto timestamp = GetTimestamp();
+    json out;
+
+    out["yuzu_version"] = GetYuzuVersionData();
+    out["report_common"] = GetReportCommonData(title_id, result, timestamp);
+    out["processor_state"] = GetProcessorStateDataAuto(system);
+    out["backtrace"] = GetBacktraceData(system);
+
+    out["error_custom_text"] = {
+        {"main", *custom_text_main},
+        {"detail", *custom_text_detail},
+    };
+
+    SaveToFile(std::move(out), GetPath("error_report", title_id, timestamp));
+}
+
+void Reporter::SaveUserReport() const {
+    if (!IsReportingEnabled())
+        return;
+
+    const auto timestamp = GetTimestamp();
+    const auto title_id = system.CurrentProcess()->GetTitleID();
+
+    SaveToFile(GetFullDataAuto(timestamp, title_id, system),
+               GetPath("user_report", title_id, timestamp));
+}
+
+bool Reporter::IsReportingEnabled() const {
+    return Settings::values.reporting_services;
+}
+
+} // namespace Core

+ 56 - 0
src/core/reporter.h

@@ -0,0 +1,56 @@
+// Copyright 2019 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <optional>
+#include <vector>
+#include "common/common_types.h"
+
+union ResultCode;
+
+namespace Kernel {
+class HLERequestContext;
+} // namespace Kernel
+
+namespace Core {
+
+class Reporter {
+public:
+    explicit Reporter(Core::System& system);
+    ~Reporter();
+
+    void SaveCrashReport(u64 title_id, ResultCode result, u64 set_flags, u64 entry_point, u64 sp,
+                         u64 pc, u64 pstate, u64 afsr0, u64 afsr1, u64 esr, u64 far,
+                         const std::array<u64, 31>& registers, const std::array<u64, 32>& backtrace,
+                         u32 backtrace_size, const std::string& arch, u32 unk10) const;
+
+    void SaveSvcBreakReport(u32 type, bool signal_debugger, u64 info1, u64 info2,
+                            std::optional<std::vector<u8>> resolved_buffer = {}) const;
+
+    void SaveUnimplementedFunctionReport(Kernel::HLERequestContext& ctx, u32 command_id,
+                                         const std::string& name,
+                                         const std::string& service_name) const;
+
+    void SaveUnimplementedAppletReport(u32 applet_id, u32 common_args_version, u32 library_version,
+                                       u32 theme_color, bool startup_sound, u64 system_tick,
+                                       std::vector<std::vector<u8>> normal_channel,
+                                       std::vector<std::vector<u8>> interactive_channel) const;
+
+    void SavePlayReport(u64 title_id, u64 process_id, std::vector<std::vector<u8>> data,
+                        std::optional<u128> user_id = {}) const;
+
+    void SaveErrorReport(u64 title_id, ResultCode result,
+                         std::optional<std::string> custom_text_main = {},
+                         std::optional<std::string> custom_text_detail = {}) const;
+
+    void SaveUserReport() const;
+
+private:
+    bool IsReportingEnabled() const;
+
+    Core::System& system;
+};
+
+} // namespace Core

+ 1 - 0
src/core/settings.h

@@ -415,6 +415,7 @@ struct Values {
     std::string program_args;
     bool dump_exefs;
     bool dump_nso;
+    bool reporting_services;
 
     // WebService
     bool enable_telemetry;

+ 3 - 1
src/yuzu/configuration/config.cpp

@@ -473,6 +473,8 @@ void Config::ReadDebuggingValues() {
         ReadSetting(QStringLiteral("program_args"), QStringLiteral("")).toString().toStdString();
     Settings::values.dump_exefs = ReadSetting(QStringLiteral("dump_exefs"), false).toBool();
     Settings::values.dump_nso = ReadSetting(QStringLiteral("dump_nso"), false).toBool();
+    Settings::values.reporting_services =
+        ReadSetting(QStringLiteral("reporting_services"), false).toBool();
 
     qt_config->endGroup();
 }
@@ -691,7 +693,7 @@ void Config::ReadValues() {
     ReadDataStorageValues();
     ReadSystemValues();
     ReadMiscellaneousValues();
-    ReadDebugValues();
+    ReadDebuggingValues();
     ReadWebServiceValues();
     ReadDisabledAddOnValues();
     ReadUIValues();

+ 2 - 0
src/yuzu/configuration/configure_debug.cpp

@@ -36,6 +36,7 @@ void ConfigureDebug::SetConfiguration() {
     ui->homebrew_args_edit->setText(QString::fromStdString(Settings::values.program_args));
     ui->dump_exefs->setChecked(Settings::values.dump_exefs);
     ui->dump_decompressed_nso->setChecked(Settings::values.dump_nso);
+    ui->reporting_services->setChecked(Settings::values.reporting_services);
 }
 
 void ConfigureDebug::ApplyConfiguration() {
@@ -46,6 +47,7 @@ void ConfigureDebug::ApplyConfiguration() {
     Settings::values.program_args = ui->homebrew_args_edit->text().toStdString();
     Settings::values.dump_exefs = ui->dump_exefs->isChecked();
     Settings::values.dump_nso = ui->dump_decompressed_nso->isChecked();
+    Settings::values.reporting_services = ui->reporting_services->isChecked();
     Debugger::ToggleConsole();
     Log::Filter filter;
     filter.ParseFilterString(Settings::values.log_filter);

+ 22 - 0
src/yuzu/configuration/configure_debug.ui

@@ -155,6 +155,28 @@
         </property>
        </widget>
       </item>
+      <item>
+       <widget class="QCheckBox" name="reporting_services">
+        <property name="text">
+         <string>Enable Verbose Reporting Services</string>
+        </property>
+       </widget>
+      </item>
+      <item>
+       <widget class="QLabel" name="label_3">
+        <property name="font">
+         <font>
+          <italic>true</italic>
+         </font>
+        </property>
+        <property name="text">
+         <string>This will be reset automatically when yuzu closes.</string>
+        </property>
+        <property name="indent">
+         <number>20</number>
+        </property>
+       </widget>
+      </item>
      </layout>
     </widget>
    </item>

+ 2 - 0
src/yuzu_cmd/config.cpp

@@ -381,6 +381,8 @@ void Config::ReadValues() {
     Settings::values.program_args = sdl2_config->Get("Debugging", "program_args", "");
     Settings::values.dump_exefs = sdl2_config->GetBoolean("Debugging", "dump_exefs", false);
     Settings::values.dump_nso = sdl2_config->GetBoolean("Debugging", "dump_nso", false);
+    Settings::values.reporting_services =
+        sdl2_config->GetBoolean("Debugging", "reporting_services", false);
 
     const auto title_list = sdl2_config->Get("AddOns", "title_ids", "");
     std::stringstream ss(title_list);