diff options
-rw-r--r-- | src/core/hle/service/fatal/fatal.cpp | 141 | ||||
-rw-r--r-- | src/core/hle/service/fatal/fatal.h | 1 | ||||
-rw-r--r-- | src/core/hle/service/fatal/fatal_u.cpp | 2 |
3 files changed, 140 insertions, 4 deletions
diff --git a/src/core/hle/service/fatal/fatal.cpp b/src/core/hle/service/fatal/fatal.cpp index b436ce4e6..6de7edf9e 100644 --- a/src/core/hle/service/fatal/fatal.cpp +++ b/src/core/hle/service/fatal/fatal.cpp @@ -2,8 +2,17 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include <array> +#include <cstring> +#include <ctime> +#include <fmt/time.h> +#include "common/common_paths.h" +#include "common/file_util.h" #include "common/logging/log.h" +#include "common/scm_rev.h" +#include "common/swap.h" #include "core/hle/ipc_helpers.h" +#include "core/hle/kernel/process.h" #include "core/hle/service/fatal/fatal.h" #include "core/hle/service/fatal/fatal_p.h" #include "core/hle/service/fatal/fatal_u.h" @@ -15,16 +24,142 @@ Module::Interface::Interface(std::shared_ptr<Module> module, const char* name) Module::Interface::~Interface() = default; +struct FatalInfo { + std::array<u64_le, 31> registers{}; // TODO(ogniK): See if this actually is registers or + // not(find a game which has non zero valeus) + u64_le unk0{}; + u64_le unk1{}; + u64_le unk2{}; + u64_le unk3{}; + u64_le unk4{}; + u64_le unk5{}; + u64_le unk6{}; + + std::array<u64_le, 32> backtrace{}; + u64_le unk7{}; + u64_le unk8{}; + u32_le backtrace_size{}; + u32_le unk9{}; + u32_le unk10{}; // TODO(ogniK): Is this even used or is it just padding? +}; +static_assert(sizeof(FatalInfo) == 0x250, "FatalInfo is an invalid size"); + +enum class FatalType : u32 { + ErrorReportAndScreen = 0, + ErrorReport = 1, + ErrorScreen = 2, +}; + +static void GenerateErrorReport(ResultCode error_code, const FatalInfo& info) { + const auto title_id = Core::CurrentProcess()->program_id; + std::string crash_report = + fmt::format("Yuzu {}-{} crash report\n" + "Title ID: {:016x}\n" + "Result: 0x{:X} ({:04}-{:04d})\n" + "\n", + Common::g_scm_branch, Common::g_scm_desc, title_id, error_code.raw, + 2000 + static_cast<u32>(error_code.module.Value()), + static_cast<u32>(error_code.description.Value()), info.unk8, info.unk7); + if (info.backtrace_size != 0x0) { + crash_report += "Registers:\n"; + // TODO(ogniK): This is just a guess, find a game which actually has non zero values + for (size_t i = 0; i < info.registers.size(); i++) { + crash_report += + fmt::format(" X[{:02d}]: {:016x}\n", i, info.registers[i]); + } + crash_report += fmt::format(" Unknown 0: {:016x}\n", info.unk0); + crash_report += fmt::format(" Unknown 1: {:016x}\n", info.unk1); + crash_report += fmt::format(" Unknown 2: {:016x}\n", info.unk2); + crash_report += fmt::format(" Unknown 3: {:016x}\n", info.unk3); + crash_report += fmt::format(" Unknown 4: {:016x}\n", info.unk4); + crash_report += fmt::format(" Unknown 5: {:016x}\n", info.unk5); + crash_report += fmt::format(" Unknown 6: {:016x}\n", info.unk6); + crash_report += "\nBacktrace:\n"; + for (size_t i = 0; i < info.backtrace_size; i++) { + crash_report += + fmt::format(" Backtrace[{:02d}]: {:016x}\n", i, info.backtrace[i]); + } + crash_report += fmt::format("\nUnknown 7: 0x{:016x}\n", info.unk7); + crash_report += fmt::format("Unknown 8: 0x{:016x}\n", info.unk8); + crash_report += fmt::format("Unknown 9: 0x{:016x}\n", info.unk9); + crash_report += fmt::format("Unknown 10: 0x{:016x}\n", info.unk10); + } + + 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); + } +} + +static void ThrowFatalError(ResultCode error_code, FatalType fatal_type, const FatalInfo& info) { + LOG_ERROR(Service_Fatal, "Threw fatal error type {}", static_cast<u32>(fatal_type)); + switch (fatal_type) { + case FatalType::ErrorReportAndScreen: + GenerateErrorReport(error_code, info); + [[fallthrough]]; + case FatalType::ErrorScreen: + // Since we have no fatal:u error screen. We should just kill execution instead + ASSERT(false); + break; + // Should not throw a fatal screen but should generate an error report + case FatalType::ErrorReport: + GenerateErrorReport(error_code, info); + break; + }; +} + +void Module::Interface::ThrowFatal(Kernel::HLERequestContext& ctx) { + LOG_ERROR(Service_Fatal, "called"); + IPC::RequestParser rp{ctx}; + auto error_code = rp.Pop<ResultCode>(); + + ThrowFatalError(error_code, FatalType::ErrorScreen, {}); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); +} + void Module::Interface::ThrowFatalWithPolicy(Kernel::HLERequestContext& ctx) { + LOG_ERROR(Service_Fatal, "called"); IPC::RequestParser rp(ctx); - u32 error_code = rp.Pop<u32>(); - LOG_WARNING(Service_Fatal, "(STUBBED) called, error_code=0x{:X}", error_code); + auto error_code = rp.Pop<ResultCode>(); + auto fatal_type = rp.PopEnum<FatalType>(); + + ThrowFatalError(error_code, fatal_type, {}); // No info is passed with ThrowFatalWithPolicy IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); } void Module::Interface::ThrowFatalWithCpuContext(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_Fatal, "(STUBBED) called"); + LOG_ERROR(Service_Fatal, "called"); + IPC::RequestParser rp(ctx); + auto error_code = rp.Pop<ResultCode>(); + auto fatal_type = rp.PopEnum<FatalType>(); + auto fatal_info = ctx.ReadBuffer(); + FatalInfo info{}; + + ASSERT_MSG(fatal_info.size() == sizeof(FatalInfo), "Invalid fatal info buffer size!"); + std::memcpy(&info, fatal_info.data(), sizeof(FatalInfo)); + + ThrowFatalError(error_code, fatal_type, info); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); } diff --git a/src/core/hle/service/fatal/fatal.h b/src/core/hle/service/fatal/fatal.h index 4d9a5be52..09371ff7f 100644 --- a/src/core/hle/service/fatal/fatal.h +++ b/src/core/hle/service/fatal/fatal.h @@ -15,6 +15,7 @@ public: explicit Interface(std::shared_ptr<Module> module, const char* name); ~Interface() override; + void ThrowFatal(Kernel::HLERequestContext& ctx); void ThrowFatalWithPolicy(Kernel::HLERequestContext& ctx); void ThrowFatalWithCpuContext(Kernel::HLERequestContext& ctx); diff --git a/src/core/hle/service/fatal/fatal_u.cpp b/src/core/hle/service/fatal/fatal_u.cpp index befc307cf..1572a2051 100644 --- a/src/core/hle/service/fatal/fatal_u.cpp +++ b/src/core/hle/service/fatal/fatal_u.cpp @@ -8,7 +8,7 @@ namespace Service::Fatal { Fatal_U::Fatal_U(std::shared_ptr<Module> module) : Module::Interface(std::move(module), "fatal:u") { static const FunctionInfo functions[] = { - {0, nullptr, "ThrowFatal"}, + {0, &Fatal_U::ThrowFatal, "ThrowFatal"}, {1, &Fatal_U::ThrowFatalWithPolicy, "ThrowFatalWithPolicy"}, {2, &Fatal_U::ThrowFatalWithCpuContext, "ThrowFatalWithCpuContext"}, }; |