Revert "hoc-clk: add live vdd2, live boost clock and basic pwm dimming"
This reverts commit 15b7df8ef1.
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -1,126 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <stratosphere.hpp>
|
||||
#include "fatal_config.hpp"
|
||||
|
||||
namespace ams::fatal::srv {
|
||||
|
||||
namespace {
|
||||
|
||||
/* Global config. */
|
||||
constinit os::SdkMutex g_config_mutex;
|
||||
constinit bool g_initialized_config;
|
||||
constinit util::TypedStorage<FatalConfig> g_config;
|
||||
|
||||
FatalConfig &GetFatalConfigImpl() {
|
||||
if (AMS_UNLIKELY(!g_initialized_config)) {
|
||||
std::scoped_lock lk(g_config_mutex);
|
||||
|
||||
if (AMS_LIKELY(!g_initialized_config)) {
|
||||
util::ConstructAt(g_config);
|
||||
|
||||
g_initialized_config = true;
|
||||
}
|
||||
}
|
||||
|
||||
return util::GetReference(g_config);
|
||||
}
|
||||
|
||||
|
||||
/* Event creator. */
|
||||
os::NativeHandle GetFatalDirtyEventReadableHandle() {
|
||||
Event evt;
|
||||
R_ABORT_UNLESS(setsysAcquireFatalDirtyFlagEventHandle(std::addressof(evt)));
|
||||
return evt.revent;
|
||||
}
|
||||
|
||||
/* Global event. */
|
||||
constinit os::SystemEventType g_fatal_dirty_event;
|
||||
constinit os::MultiWaitHolderType g_fatal_dirty_multi_wait_holder;
|
||||
constinit bool g_initialized_fatal_dirty_event;
|
||||
|
||||
}
|
||||
|
||||
os::MultiWaitHolderType *GetFatalDirtyMultiWaitHolder() {
|
||||
if (AMS_UNLIKELY(!g_initialized_fatal_dirty_event)) {
|
||||
os::AttachReadableHandleToSystemEvent(std::addressof(g_fatal_dirty_event), GetFatalDirtyEventReadableHandle(), true, os::EventClearMode_ManualClear);
|
||||
os::InitializeMultiWaitHolder(std::addressof(g_fatal_dirty_multi_wait_holder), std::addressof(g_fatal_dirty_event));
|
||||
os::SetMultiWaitHolderUserData(std::addressof(g_fatal_dirty_multi_wait_holder), reinterpret_cast<uintptr_t>(std::addressof(g_fatal_dirty_multi_wait_holder)));
|
||||
g_initialized_fatal_dirty_event = true;
|
||||
}
|
||||
return std::addressof(g_fatal_dirty_multi_wait_holder);
|
||||
}
|
||||
|
||||
void OnFatalDirtyEvent() {
|
||||
os::ClearSystemEvent(std::addressof(g_fatal_dirty_event));
|
||||
|
||||
u64 flags_0, flags_1;
|
||||
if (R_SUCCEEDED(setsysGetFatalDirtyFlags(std::addressof(flags_0), std::addressof(flags_1))) && (flags_0 & 1)) {
|
||||
GetFatalConfigImpl().UpdateLanguageCode();
|
||||
}
|
||||
}
|
||||
|
||||
FatalConfig::FatalConfig() {
|
||||
/* Get information from set. */
|
||||
settings::system::GetSerialNumber(std::addressof(m_serial_number));
|
||||
settings::system::GetFirmwareVersion(std::addressof(m_firmware_version));
|
||||
setsysGetQuestFlag(std::addressof(m_quest_flag));
|
||||
this->UpdateLanguageCode();
|
||||
|
||||
/* Read information from settings. */
|
||||
settings::fwdbg::GetSettingsItemValue(std::addressof(m_transition_to_fatal), sizeof(m_transition_to_fatal), "fatal", "transition_to_fatal");
|
||||
settings::fwdbg::GetSettingsItemValue(std::addressof(m_show_extra_info), sizeof(m_show_extra_info), "fatal", "show_extra_info");
|
||||
|
||||
u64 quest_interval_second;
|
||||
settings::fwdbg::GetSettingsItemValue(std::addressof(quest_interval_second), sizeof(quest_interval_second), "fatal", "quest_reboot_interval_second");
|
||||
m_quest_reboot_interval = TimeSpan::FromSeconds(quest_interval_second);
|
||||
|
||||
/* Atmosphere extension for automatic reboot. */
|
||||
u64 auto_reboot_ms;
|
||||
if (settings::fwdbg::GetSettingsItemValue(std::addressof(auto_reboot_ms), sizeof(auto_reboot_ms), "atmosphere", "fatal_auto_reboot_interval") == sizeof(auto_reboot_ms)) {
|
||||
m_fatal_auto_reboot_interval = TimeSpan::FromMilliSeconds(auto_reboot_ms);
|
||||
m_fatal_auto_reboot_enabled = auto_reboot_ms != 0;
|
||||
}
|
||||
|
||||
/* Setup messages. */
|
||||
{
|
||||
m_error_msg = "Error Code: 2%03d-%04d (0x%x)\n";
|
||||
|
||||
m_error_desc = "An error has occurred.\n\n"
|
||||
"Please press the POWER Button to restart the console normally, or a VOL button\n"
|
||||
"to reboot to a payload (or RCM, if none is present). If you are unable to\n"
|
||||
"restart the console, hold the POWER Button for 12 seconds to turn the console off.\n\n"
|
||||
"If the problem persists, refer to the Nintendo Support Website.\n"
|
||||
"support.nintendo.com/switch/error\n";
|
||||
|
||||
/* If you're running Atmosphere on a quest unit for some reason, talk to me on discord. */
|
||||
m_quest_desc = "Please call 1-800-875-1852 for service.\n\n"
|
||||
"Also, please be aware that running Atmosphere on a Quest device is not fully\n"
|
||||
"supported. Perhaps try booting your device without Atmosphere before calling\n"
|
||||
"an official Nintendo service hotline. If you encounter further issues, please\n"
|
||||
"contact SciresM#0524 on Discord, or via some other means.\n";
|
||||
|
||||
/* TODO: Try to load dynamically? */
|
||||
/* FsStorage message_storage; */
|
||||
/* TODO: if (R_SUCCEEDED(fsOpenDataStorageByDataId(0x010000000000081D, "fatal_msg"))) { ... } */
|
||||
}
|
||||
}
|
||||
|
||||
const FatalConfig &GetFatalConfig() {
|
||||
return GetFatalConfigImpl();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,95 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
namespace ams::fatal::srv {
|
||||
|
||||
class FatalConfig {
|
||||
private:
|
||||
settings::system::SerialNumber m_serial_number{};
|
||||
settings::system::FirmwareVersion m_firmware_version{};
|
||||
u64 m_language_code{};
|
||||
TimeSpan m_quest_reboot_interval{};
|
||||
bool m_transition_to_fatal{};
|
||||
bool m_show_extra_info{};
|
||||
bool m_quest_flag{};
|
||||
const char *m_error_msg{};
|
||||
const char *m_error_desc{};
|
||||
const char *m_quest_desc{};
|
||||
TimeSpan m_fatal_auto_reboot_interval{};
|
||||
bool m_fatal_auto_reboot_enabled{};
|
||||
public:
|
||||
FatalConfig();
|
||||
|
||||
const settings::system::SerialNumber &GetSerialNumber() const {
|
||||
return m_serial_number;
|
||||
}
|
||||
|
||||
const settings::system::FirmwareVersion &GetFirmwareVersion() const {
|
||||
return m_firmware_version;
|
||||
}
|
||||
|
||||
void UpdateLanguageCode() {
|
||||
setGetLanguageCode(&m_language_code);
|
||||
}
|
||||
|
||||
u64 GetLanguageCode() const {
|
||||
return m_language_code;
|
||||
}
|
||||
|
||||
bool ShouldTransitionToFatal() const {
|
||||
return m_transition_to_fatal;
|
||||
}
|
||||
|
||||
bool ShouldShowExtraInfo() const {
|
||||
return m_show_extra_info;
|
||||
}
|
||||
|
||||
bool IsQuest() const {
|
||||
return m_quest_flag;
|
||||
}
|
||||
|
||||
bool IsFatalRebootEnabled() const {
|
||||
return m_fatal_auto_reboot_enabled;
|
||||
}
|
||||
|
||||
TimeSpan GetQuestRebootTimeoutInterval() const {
|
||||
return m_quest_reboot_interval;
|
||||
}
|
||||
|
||||
TimeSpan GetFatalRebootTimeoutInterval() const {
|
||||
return m_fatal_auto_reboot_interval;
|
||||
}
|
||||
|
||||
const char *GetErrorMessage() const {
|
||||
return m_error_msg;
|
||||
}
|
||||
|
||||
const char *GetErrorDescription() const {
|
||||
if (this->IsQuest()) {
|
||||
return m_quest_desc;
|
||||
} else {
|
||||
return m_error_desc;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
os::MultiWaitHolderType *GetFatalDirtyMultiWaitHolder();
|
||||
void OnFatalDirtyEvent();
|
||||
const FatalConfig &GetFatalConfig();
|
||||
|
||||
}
|
||||
@@ -1,331 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <stratosphere.hpp>
|
||||
#include "fatal_debug.hpp"
|
||||
#include "fatal_config.hpp"
|
||||
|
||||
namespace ams::fatal::srv {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr u32 SvcSendSyncRequestInstruction = 0xD4000421;
|
||||
|
||||
struct StackFrame {
|
||||
u64 fp;
|
||||
u64 lr;
|
||||
};
|
||||
|
||||
constexpr inline size_t MaxThreads = 0x60;
|
||||
|
||||
template<size_t MaxThreadCount>
|
||||
class ThreadTlsMapImpl {
|
||||
private:
|
||||
std::pair<u64, u64> m_map[MaxThreadCount];
|
||||
size_t m_index;
|
||||
public:
|
||||
constexpr ThreadTlsMapImpl() : m_map(), m_index(0) { /* ... */ }
|
||||
|
||||
constexpr void ResetThreadTlsMap() {
|
||||
m_index = 0;
|
||||
}
|
||||
|
||||
constexpr void SetThreadTls(u64 thread_id, u64 tls) {
|
||||
if (m_index < util::size(m_map)) {
|
||||
m_map[m_index++] = std::make_pair(thread_id, tls);
|
||||
}
|
||||
}
|
||||
|
||||
constexpr bool GetThreadTls(u64 *out, u64 thread_id) const {
|
||||
for (size_t i = 0; i < m_index; ++i) {
|
||||
if (m_map[i].first == thread_id) {
|
||||
*out = m_map[i].second;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
using ThreadTlsMap = ThreadTlsMapImpl<MaxThreads>;
|
||||
|
||||
constinit ThreadTlsMap g_thread_id_to_tls_map;
|
||||
|
||||
bool IsThreadFatalCaller(Result result, os::NativeHandle debug_handle, u64 thread_id, u64 thread_tls_addr, svc::ThreadContext *thread_ctx) {
|
||||
/* Verify that the thread is running or waiting. */
|
||||
{
|
||||
u64 _;
|
||||
u32 _thread_state;
|
||||
if (R_FAILED(svc::GetDebugThreadParam(&_, &_thread_state, debug_handle, thread_id, svc::DebugThreadParam_State))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto thread_state = static_cast<svc::ThreadState>(_thread_state);
|
||||
if (thread_state != svc::ThreadState_Waiting && thread_state != svc::ThreadState_Running) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/* Get the thread context. */
|
||||
if (R_FAILED(svc::GetDebugThreadContext(thread_ctx, debug_handle, thread_id, svc::ThreadContextFlag_All))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Try to read the current instruction. */
|
||||
u32 insn;
|
||||
if (R_FAILED(svc::ReadDebugProcessMemory(reinterpret_cast<uintptr_t>(std::addressof(insn)), debug_handle, thread_ctx->pc, sizeof(insn)))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* If the instruction isn't svc::SendSyncRequest, it's not the fatal caller. */
|
||||
if (insn != SvcSendSyncRequestInstruction) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Read in the fatal caller's TLS. */
|
||||
u8 thread_tls[sizeof(svc::ThreadLocalRegion::message_buffer)];
|
||||
if (R_FAILED(svc::ReadDebugProcessMemory(reinterpret_cast<uintptr_t>(thread_tls), debug_handle, thread_tls_addr, sizeof(thread_tls)))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* We want to parse the command the fatal caller sent. */
|
||||
{
|
||||
const auto request = hipcParseRequest(thread_tls);
|
||||
|
||||
const struct {
|
||||
CmifInHeader header;
|
||||
Result result;
|
||||
} *in_data = decltype(in_data)(request.data.data_words);
|
||||
static_assert(sizeof(*in_data) == 0x14, "InData!");
|
||||
|
||||
/* Fatal command takes in a PID, only one buffer max. */
|
||||
if ((request.meta.type != CmifCommandType_Request && request.meta.type != CmifCommandType_RequestWithContext) ||
|
||||
!request.meta.send_pid ||
|
||||
request.meta.num_send_statics ||
|
||||
request.meta.num_recv_statics ||
|
||||
request.meta.num_recv_buffers ||
|
||||
request.meta.num_exch_buffers ||
|
||||
request.meta.num_copy_handles ||
|
||||
request.meta.num_move_handles ||
|
||||
request.meta.num_data_words < ((sizeof(*in_data) + 0x10) / sizeof(u32)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (in_data->header.magic != CMIF_IN_HEADER_MAGIC) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (in_data->header.version > 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (in_data->header.command_id) {
|
||||
case 0:
|
||||
case 1:
|
||||
if (request.meta.num_send_buffers != 0) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
if (request.meta.num_send_buffers != 1) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
if (in_data->result.GetValue() != result.GetValue()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/* We found our caller. */
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TryGuessBaseAddress(u64 *out_base_address, os::NativeHandle debug_handle, u64 guess) {
|
||||
svc::MemoryInfo mi;
|
||||
svc::PageInfo pi;
|
||||
if (R_FAILED(svc::QueryDebugProcessMemory(std::addressof(mi), std::addressof(pi), debug_handle, guess)) || mi.permission != svc::MemoryPermission_ReadExecute) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Iterate backwards until we find the memory before the code region. */
|
||||
while (mi.base_address > 0) {
|
||||
if (R_FAILED(svc::QueryDebugProcessMemory(std::addressof(mi), std::addressof(pi), debug_handle, guess))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (mi.state == svc::MemoryState_Free) {
|
||||
/* Code region will be at the end of the unmapped region preceding it. */
|
||||
*out_base_address = mi.base_address + mi.size;
|
||||
return true;
|
||||
}
|
||||
|
||||
guess = mi.base_address - 4;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
u64 GetBaseAddress(const ThrowContext *throw_ctx, const svc::ThreadContext *thread_ctx, os::NativeHandle debug_handle) {
|
||||
u64 base_address = 0;
|
||||
|
||||
if (TryGuessBaseAddress(std::addressof(base_address), debug_handle, thread_ctx->pc)) {
|
||||
return base_address;
|
||||
}
|
||||
|
||||
if (TryGuessBaseAddress(std::addressof(base_address), debug_handle, thread_ctx->lr)) {
|
||||
return base_address;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < throw_ctx->cpu_ctx.aarch64_ctx.stack_trace_size; i++) {
|
||||
if (TryGuessBaseAddress(std::addressof(base_address), debug_handle, throw_ctx->cpu_ctx.aarch64_ctx.stack_trace[i])) {
|
||||
return base_address;
|
||||
}
|
||||
}
|
||||
|
||||
return base_address;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void TryCollectDebugInformation(ThrowContext *ctx, os::ProcessId process_id) {
|
||||
/* Try to debug the process. This may fail, if we called into ourself. */
|
||||
os::NativeHandle debug_handle;
|
||||
if (R_FAILED(svc::DebugActiveProcess(std::addressof(debug_handle), process_id.value))) {
|
||||
return;
|
||||
}
|
||||
ON_SCOPE_EXIT { os::CloseNativeHandle(debug_handle); };
|
||||
|
||||
/* First things first, check if process is 64 bits, and get list of thread infos. */
|
||||
g_thread_id_to_tls_map.ResetThreadTlsMap();
|
||||
{
|
||||
bool got_create_process = false;
|
||||
svc::DebugEventInfo d;
|
||||
while (R_SUCCEEDED(svc::GetDebugEvent(std::addressof(d), debug_handle))) {
|
||||
switch (d.type) {
|
||||
case svc::DebugEvent_CreateProcess:
|
||||
ctx->cpu_ctx.architecture = (d.info.create_process.flags & 1) ? CpuContext::Architecture_Aarch64 : CpuContext::Architecture_Aarch32;
|
||||
std::memcpy(ctx->proc_name, d.info.create_process.name, sizeof(d.info.create_process.name));
|
||||
got_create_process = true;
|
||||
break;
|
||||
case svc::DebugEvent_CreateThread:
|
||||
g_thread_id_to_tls_map.SetThreadTls(d.info.create_thread.thread_id, d.info.create_thread.tls_address);
|
||||
break;
|
||||
case svc::DebugEvent_Exception:
|
||||
case svc::DebugEvent_ExitProcess:
|
||||
case svc::DebugEvent_ExitThread:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!got_create_process) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* TODO: Try to collect information on 32-bit fatals. This shouldn't really matter for any real use case. */
|
||||
if (ctx->cpu_ctx.architecture == CpuContext::Architecture_Aarch32) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Welcome to hell. Here, we try to identify which thread called into fatal. */
|
||||
bool found_fatal_caller = false;
|
||||
u64 thread_id = 0;
|
||||
u64 thread_tls = 0;
|
||||
svc::ThreadContext thread_ctx;
|
||||
{
|
||||
/* We start by trying to get a list of threads. */
|
||||
s32 thread_count;
|
||||
u64 thread_ids[0x60];
|
||||
if (R_FAILED(svc::GetThreadList(std::addressof(thread_count), thread_ids, 0x60, debug_handle))) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* We need to locate the thread that's called fatal. */
|
||||
for (s32 i = 0; i < thread_count; i++) {
|
||||
const u64 cur_thread_id = thread_ids[i];
|
||||
u64 cur_thread_tls;
|
||||
if (!g_thread_id_to_tls_map.GetThreadTls(std::addressof(cur_thread_tls), cur_thread_id)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (IsThreadFatalCaller(ctx->result, debug_handle, cur_thread_id, cur_thread_tls, std::addressof(thread_ctx))) {
|
||||
thread_id = cur_thread_id;
|
||||
thread_tls = cur_thread_tls;
|
||||
found_fatal_caller = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found_fatal_caller) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (R_FAILED(svc::GetDebugThreadContext(std::addressof(thread_ctx), debug_handle, thread_id, svc::ThreadContextFlag_All))) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Set register states. */
|
||||
ctx->cpu_ctx.aarch64_ctx.SetRegisterValue(aarch64::RegisterName_FP, thread_ctx.fp);
|
||||
ctx->cpu_ctx.aarch64_ctx.SetRegisterValue(aarch64::RegisterName_LR, thread_ctx.lr);
|
||||
ctx->cpu_ctx.aarch64_ctx.SetRegisterValue(aarch64::RegisterName_SP, thread_ctx.sp);
|
||||
ctx->cpu_ctx.aarch64_ctx.SetRegisterValue(aarch64::RegisterName_PC, thread_ctx.pc);
|
||||
|
||||
/* Parse a stack trace. */
|
||||
u64 cur_fp = thread_ctx.fp;
|
||||
ctx->cpu_ctx.aarch64_ctx.stack_trace_size = 0;
|
||||
for (unsigned int i = 0; i < aarch64::CpuContext::MaxStackTraceDepth; i++) {
|
||||
/* Validate the current frame. */
|
||||
if (cur_fp == 0 || (cur_fp & 0xF)) {
|
||||
break;
|
||||
}
|
||||
|
||||
/* Read a new frame. */
|
||||
StackFrame cur_frame = {};
|
||||
if (R_FAILED(svc::ReadDebugProcessMemory(reinterpret_cast<uintptr_t>(std::addressof(cur_frame)), debug_handle, cur_fp, sizeof(StackFrame)))) {
|
||||
break;
|
||||
}
|
||||
|
||||
/* Advance to the next frame. */
|
||||
ctx->cpu_ctx.aarch64_ctx.stack_trace[ctx->cpu_ctx.aarch64_ctx.stack_trace_size++] = cur_frame.lr;
|
||||
cur_fp = cur_frame.fp;
|
||||
}
|
||||
|
||||
/* Try to read up to 0x100 of stack. */
|
||||
ctx->stack_dump_base = 0;
|
||||
for (size_t sz = 0x100; sz > 0; sz -= 0x10) {
|
||||
if (R_SUCCEEDED(svc::ReadDebugProcessMemory(reinterpret_cast<uintptr_t>(ctx->stack_dump), debug_handle, thread_ctx.sp, sz))) {
|
||||
ctx->stack_dump_base = thread_ctx.sp;
|
||||
ctx->stack_dump_size = sz;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Try to read the first 0x100 of TLS. */
|
||||
if (R_SUCCEEDED(svc::ReadDebugProcessMemory(reinterpret_cast<uintptr_t>(ctx->tls_dump), debug_handle, thread_tls, sizeof(ctx->tls_dump)))) {
|
||||
ctx->tls_address = thread_tls;
|
||||
} else {
|
||||
ctx->tls_address = 0;
|
||||
std::memset(ctx->tls_dump, 0xCC, sizeof(ctx->tls_dump));
|
||||
}
|
||||
|
||||
/* Parse the base address. */
|
||||
ctx->cpu_ctx.aarch64_ctx.SetBaseAddress(GetBaseAddress(ctx, std::addressof(thread_ctx), debug_handle));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
namespace ams::fatal::srv {
|
||||
|
||||
void TryCollectDebugInformation(ThrowContext *ctx, os::ProcessId process_id);
|
||||
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <stratosphere.hpp>
|
||||
#include "fatal_event_manager.hpp"
|
||||
|
||||
namespace ams::fatal::srv {
|
||||
|
||||
FatalEventManager::FatalEventManager() : m_lock() {
|
||||
/* Just create all the events. */
|
||||
for (size_t i = 0; i < FatalEventManager::NumFatalEvents; i++) {
|
||||
R_ABORT_UNLESS(os::CreateSystemEvent(std::addressof(m_events[i]), os::EventClearMode_AutoClear, true));
|
||||
}
|
||||
}
|
||||
|
||||
Result FatalEventManager::GetEvent(const os::SystemEventType **out) {
|
||||
std::scoped_lock lk{m_lock};
|
||||
|
||||
/* Only allow GetEvent to succeed NumFatalEvents times. */
|
||||
R_UNLESS(m_num_events_gotten < FatalEventManager::NumFatalEvents, fatal::ResultTooManyEvents());
|
||||
|
||||
*out = std::addressof(m_events[m_num_events_gotten++]);
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
void FatalEventManager::SignalEvents() {
|
||||
for (size_t i = 0; i < FatalEventManager::NumFatalEvents; i++) {
|
||||
os::SignalSystemEvent(std::addressof(m_events[i]));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
namespace ams::fatal::srv {
|
||||
|
||||
class FatalEventManager {
|
||||
NON_COPYABLE(FatalEventManager);
|
||||
NON_MOVEABLE(FatalEventManager);
|
||||
public:
|
||||
static constexpr size_t NumFatalEvents = 3;
|
||||
private:
|
||||
os::SdkMutex m_lock;
|
||||
size_t m_num_events_gotten = 0;
|
||||
os::SystemEventType m_events[NumFatalEvents];
|
||||
public:
|
||||
FatalEventManager();
|
||||
Result GetEvent(const os::SystemEventType **out);
|
||||
void SignalEvents();
|
||||
};
|
||||
|
||||
}
|
||||
@@ -1,260 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <stratosphere.hpp>
|
||||
#include "fatal_config.hpp"
|
||||
#include "fatal_font.hpp"
|
||||
|
||||
namespace ams::fatal::srv::font {
|
||||
|
||||
constinit lmem::HeapHandle g_font_heap_handle;
|
||||
|
||||
void SetHeapMemory(void *memory, size_t memory_size) {
|
||||
g_font_heap_handle = lmem::CreateExpHeap(memory, memory_size, lmem::CreateOption_None);
|
||||
}
|
||||
|
||||
void *AllocateForFont(size_t size) {
|
||||
return lmem::AllocateFromExpHeap(g_font_heap_handle, size);
|
||||
}
|
||||
|
||||
void DeallocateForFont(void *p) {
|
||||
if (p != nullptr) {
|
||||
return lmem::FreeToExpHeap(g_font_heap_handle, p);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
#define STBTT_assert(x) AMS_ASSERT(x)
|
||||
#define STBTT_malloc(x,u) ((void)(u),ams::fatal::srv::font::AllocateForFont(x))
|
||||
#define STBTT_free(x,u) ((void)(u),ams::fatal::srv::font::DeallocateForFont(x))
|
||||
|
||||
#define STBTT_STATIC
|
||||
#define STB_TRUETYPE_IMPLEMENTATION
|
||||
#include "stb_truetype.h"
|
||||
#undef STBTT_STATIC
|
||||
#undef STB_TRUETYPE_IMPLEMENTATION
|
||||
|
||||
#undef STBTT_malloc
|
||||
#undef STBTT_free
|
||||
#undef STBTT_assert
|
||||
|
||||
/* Define color conversion macros. */
|
||||
#define RGB888_TO_RGB565(r, g, b) ((((r >> 3) << 11) & 0xF800) | (((g >> 2) << 5) & 0x7E0) | ((b >> 3) & 0x1F))
|
||||
#define RGB565_GET_R8(c) ((((c >> 11) & 0x1F) << 3) | ((c >> 13) & 7))
|
||||
#define RGB565_GET_G8(c) ((((c >> 5) & 0x3F) << 2) | ((c >> 9) & 3))
|
||||
#define RGB565_GET_B8(c) ((((c >> 0) & 0x1F) << 3) | ((c >> 2) & 7))
|
||||
|
||||
namespace ams::fatal::srv::font {
|
||||
|
||||
namespace {
|
||||
|
||||
/* Font state globals. */
|
||||
u16 *g_frame_buffer = nullptr;
|
||||
u32 (*g_unswizzle_func)(u32, u32) = nullptr;
|
||||
u16 g_font_color = 0xFFFF; /* White. */
|
||||
float g_font_line_pixels = 16.0f;
|
||||
float g_font_size = 16.0f;
|
||||
u32 g_line_x = 0, g_cur_x = 0, g_cur_y = 0;
|
||||
|
||||
u32 g_mono_adv = 0;
|
||||
|
||||
PlFontData g_font;
|
||||
|
||||
stbtt_fontinfo g_stb_font;
|
||||
|
||||
/* Helpers. */
|
||||
u16 Blend(u16 color, u16 bg, u8 alpha) {
|
||||
const u32 c_r = RGB565_GET_R8(color);
|
||||
const u32 c_g = RGB565_GET_G8(color);
|
||||
const u32 c_b = RGB565_GET_B8(color);
|
||||
const u32 b_r = RGB565_GET_R8(bg);
|
||||
const u32 b_g = RGB565_GET_G8(bg);
|
||||
const u32 b_b = RGB565_GET_B8(bg);
|
||||
|
||||
const u32 r = ((alpha * c_r) + ((0xFF - alpha) * b_r)) / 0xFF;
|
||||
const u32 g = ((alpha * c_g) + ((0xFF - alpha) * b_g)) / 0xFF;
|
||||
const u32 b = ((alpha * c_b) + ((0xFF - alpha) * b_b)) / 0xFF;
|
||||
|
||||
return RGB888_TO_RGB565(r, g, b);
|
||||
}
|
||||
|
||||
void DrawCodePoint(u32 codepoint, u32 x, u32 y) {
|
||||
int width = 0, height = 0;
|
||||
u8* imageptr = stbtt_GetCodepointBitmap(std::addressof(g_stb_font), g_font_size, g_font_size, codepoint, std::addressof(width), std::addressof(height), 0, 0);
|
||||
ON_SCOPE_EXIT { DeallocateForFont(imageptr); };
|
||||
|
||||
for (int tmpy = 0; tmpy < height; tmpy++) {
|
||||
for (int tmpx = 0; tmpx < width; tmpx++) {
|
||||
/* Implement very simple blending, as the bitmap value is an alpha value. */
|
||||
u16 *ptr = g_frame_buffer + g_unswizzle_func(x + tmpx, y + tmpy);
|
||||
*ptr = Blend(g_font_color, *ptr, imageptr[width * tmpy + tmpx]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DrawString(const char *str, bool add_line, bool mono = false) {
|
||||
const size_t len = strlen(str);
|
||||
|
||||
u32 cur_x = g_cur_x, cur_y = g_cur_y;
|
||||
|
||||
u32 prev_char = 0;
|
||||
for (u32 i = 0; i < len; ) {
|
||||
u32 cur_char;
|
||||
ssize_t unit_count = decode_utf8(std::addressof(cur_char), reinterpret_cast<const u8 *>(str + i));
|
||||
if (unit_count <= 0) break;
|
||||
|
||||
if (!g_mono_adv && i > 0) {
|
||||
cur_x += g_font_size * stbtt_GetCodepointKernAdvance(std::addressof(g_stb_font), prev_char, cur_char);
|
||||
}
|
||||
|
||||
i += unit_count;
|
||||
|
||||
if (cur_char == '\n') {
|
||||
cur_x = g_line_x;
|
||||
cur_y += g_font_line_pixels;
|
||||
continue;
|
||||
}
|
||||
|
||||
int adv_width, left_side_bearing;
|
||||
stbtt_GetCodepointHMetrics(std::addressof(g_stb_font), cur_char, std::addressof(adv_width), std::addressof(left_side_bearing));
|
||||
const u32 cur_width = static_cast<u32>(adv_width) * g_font_size;
|
||||
|
||||
int x0, y0, x1, y1;
|
||||
stbtt_GetCodepointBitmapBoxSubpixel(std::addressof(g_stb_font), cur_char, g_font_size, g_font_size, 0, 0, std::addressof(x0), std::addressof(y0), std::addressof(x1), std::addressof(y1));
|
||||
|
||||
DrawCodePoint(cur_char, cur_x + x0 + ((mono && g_mono_adv > cur_width) ? ((g_mono_adv - cur_width) / 2) : 0), cur_y + y0);
|
||||
|
||||
cur_x += (mono ? g_mono_adv : cur_width);
|
||||
|
||||
prev_char = cur_char;
|
||||
}
|
||||
|
||||
if (add_line) {
|
||||
/* Advance to next line. */
|
||||
g_cur_x = g_line_x;
|
||||
g_cur_y = cur_y + g_font_line_pixels;
|
||||
} else {
|
||||
g_cur_x = cur_x;
|
||||
g_cur_y = cur_y;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void PrintLine(const char *str) {
|
||||
return DrawString(str, true);
|
||||
}
|
||||
|
||||
void PrintFormatLine(const char *format, ...) {
|
||||
char char_buf[0x400];
|
||||
|
||||
std::va_list va_arg;
|
||||
va_start(va_arg, format);
|
||||
util::VSNPrintf(char_buf, sizeof(char_buf), format, va_arg);
|
||||
va_end(va_arg);
|
||||
|
||||
PrintLine(char_buf);
|
||||
}
|
||||
|
||||
void Print(const char *str) {
|
||||
return DrawString(str, false);
|
||||
}
|
||||
|
||||
void PrintFormat(const char *format, ...) {
|
||||
char char_buf[0x400];
|
||||
|
||||
std::va_list va_arg;
|
||||
va_start(va_arg, format);
|
||||
util::VSNPrintf(char_buf, sizeof(char_buf), format, va_arg);
|
||||
va_end(va_arg);
|
||||
|
||||
Print(char_buf);
|
||||
}
|
||||
|
||||
void PrintMonospaceU64(u64 x) {
|
||||
char char_buf[0x400];
|
||||
util::SNPrintf(char_buf, sizeof(char_buf), "%016lX", x);
|
||||
|
||||
DrawString(char_buf, false, true);
|
||||
}
|
||||
|
||||
void PrintMonospaceU32(u32 x) {
|
||||
char char_buf[0x400];
|
||||
util::SNPrintf(char_buf, sizeof(char_buf), "%08X", x);
|
||||
|
||||
DrawString(char_buf, false, true);
|
||||
}
|
||||
|
||||
void PrintMonospaceBlank(u32 width) {
|
||||
char char_buf[0x400] = {0};
|
||||
std::memset(char_buf, ' ', std::min(size_t(width), sizeof(char_buf)));
|
||||
|
||||
DrawString(char_buf, false, true);
|
||||
}
|
||||
|
||||
void SetFontColor(u16 color) {
|
||||
g_font_color = color;
|
||||
}
|
||||
|
||||
void SetPosition(u32 x, u32 y) {
|
||||
g_line_x = x;
|
||||
g_cur_x = x;
|
||||
g_cur_y = y;
|
||||
}
|
||||
|
||||
u32 GetX() {
|
||||
return g_cur_x;
|
||||
}
|
||||
|
||||
u32 GetY() {
|
||||
return g_cur_y;
|
||||
}
|
||||
|
||||
void SetFontSize(float fsz) {
|
||||
g_font_size = stbtt_ScaleForPixelHeight(std::addressof(g_stb_font), fsz * 1.375);
|
||||
|
||||
int ascent;
|
||||
stbtt_GetFontVMetrics(std::addressof(g_stb_font), std::addressof(ascent),0,0);
|
||||
g_font_line_pixels = ascent * g_font_size * 1.125;
|
||||
|
||||
int adv_width, left_side_bearing;
|
||||
stbtt_GetCodepointHMetrics(std::addressof(g_stb_font), 'A', std::addressof(adv_width), std::addressof(left_side_bearing));
|
||||
|
||||
g_mono_adv = adv_width * g_font_size;
|
||||
}
|
||||
|
||||
void AddSpacingLines(float num_lines) {
|
||||
g_cur_x = g_line_x;
|
||||
g_cur_y += static_cast<u32>(g_font_line_pixels * num_lines);
|
||||
}
|
||||
|
||||
void ConfigureFontFramebuffer(u16 *fb, u32 (*unswizzle_func)(u32, u32)) {
|
||||
g_frame_buffer = fb;
|
||||
g_unswizzle_func = unswizzle_func;
|
||||
}
|
||||
|
||||
Result InitializeSharedFont() {
|
||||
R_TRY(plGetSharedFontByType(std::addressof(g_font), PlSharedFontType_Standard));
|
||||
|
||||
u8 *font_buffer = reinterpret_cast<u8 *>(g_font.address);
|
||||
stbtt_InitFont(std::addressof(g_stb_font), font_buffer, stbtt_GetFontOffsetForIndex(font_buffer, 0));
|
||||
|
||||
SetFontSize(16.0f);
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
namespace ams::fatal::srv::font {
|
||||
|
||||
Result InitializeSharedFont();
|
||||
void ConfigureFontFramebuffer(u16 *fb, u32 (*unswizzle_func)(u32, u32));
|
||||
void SetHeapMemory(void *memory, size_t memory_size);
|
||||
|
||||
void SetFontColor(u16 color);
|
||||
void SetPosition(u32 x, u32 y);
|
||||
u32 GetX();
|
||||
u32 GetY();
|
||||
void SetFontSize(float fsz);
|
||||
void AddSpacingLines(float num_lines);
|
||||
void PrintLine(const char *str);
|
||||
void PrintFormatLine(const char *format, ...);
|
||||
void Print(const char *str);
|
||||
void PrintFormat(const char *format, ...);
|
||||
void PrintMonospaceU64(u64 x);
|
||||
void PrintMonospaceU32(u32 x);
|
||||
void PrintMonospaceBlank(u32 width);
|
||||
|
||||
}
|
||||
@@ -1,192 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <stratosphere.hpp>
|
||||
#include "fatal_service.hpp"
|
||||
#include "fatal_config.hpp"
|
||||
#include "fatal_repair.hpp"
|
||||
#include "fatal_font.hpp"
|
||||
|
||||
/* Set libnx graphics globals. */
|
||||
extern "C" {
|
||||
|
||||
u32 __nx_nv_transfermem_size = 0x40000;
|
||||
ViLayerFlags __nx_vi_stray_layer_flags = (ViLayerFlags)0;
|
||||
|
||||
}
|
||||
|
||||
namespace ams {
|
||||
|
||||
namespace fatal::srv {
|
||||
|
||||
namespace {
|
||||
|
||||
constinit u8 g_fs_heap_memory[2_KB];
|
||||
lmem::HeapHandle g_fs_heap_handle;
|
||||
|
||||
void *AllocateForFs(size_t size) {
|
||||
return lmem::AllocateFromExpHeap(g_fs_heap_handle, size);
|
||||
}
|
||||
|
||||
void DeallocateForFs(void *p, size_t size) {
|
||||
AMS_UNUSED(size);
|
||||
return lmem::FreeToExpHeap(g_fs_heap_handle, p);
|
||||
}
|
||||
|
||||
void InitializeFsHeap() {
|
||||
g_fs_heap_handle = lmem::CreateExpHeap(g_fs_heap_memory, sizeof(g_fs_heap_memory), lmem::CreateOption_ThreadSafe);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
using ServerOptions = sf::hipc::DefaultServerManagerOptions;
|
||||
|
||||
constexpr sm::ServiceName UserServiceName = sm::ServiceName::Encode("fatal:u");
|
||||
constexpr size_t UserMaxSessions = 4;
|
||||
|
||||
constexpr sm::ServiceName PrivateServiceName = sm::ServiceName::Encode("fatal:p");
|
||||
constexpr size_t PrivateMaxSessions = 4;
|
||||
|
||||
/* fatal:u, fatal:p. */
|
||||
constexpr size_t NumServers = 2;
|
||||
constexpr size_t NumSessions = UserMaxSessions + PrivateMaxSessions;
|
||||
|
||||
sf::hipc::ServerManager<NumServers, ServerOptions, NumSessions> g_server_manager;
|
||||
|
||||
constinit sf::UnmanagedServiceObject<fatal::impl::IService, fatal::srv::Service> g_user_service_object;
|
||||
constinit sf::UnmanagedServiceObject<fatal::impl::IPrivateService, fatal::srv::Service> g_private_service_object;
|
||||
|
||||
void InitializeAndLoopIpcServer() {
|
||||
/* Create services. */
|
||||
R_ABORT_UNLESS(g_server_manager.RegisterObjectForServer(g_user_service_object.GetShared(), UserServiceName, UserMaxSessions));
|
||||
R_ABORT_UNLESS(g_server_manager.RegisterObjectForServer(g_private_service_object.GetShared(), PrivateServiceName, PrivateMaxSessions));
|
||||
|
||||
/* Add dirty event holder. */
|
||||
auto *dirty_event_holder = fatal::srv::GetFatalDirtyMultiWaitHolder();
|
||||
g_server_manager.AddUserMultiWaitHolder(dirty_event_holder);
|
||||
|
||||
/* Loop forever, servicing our services. */
|
||||
/* Because fatal has a user wait holder, we need to specify how to process manually. */
|
||||
while (auto *signaled_holder = g_server_manager.WaitSignaled()) {
|
||||
if (signaled_holder == dirty_event_holder) {
|
||||
/* Dirty event holder was signaled. */
|
||||
fatal::srv::OnFatalDirtyEvent();
|
||||
g_server_manager.AddUserMultiWaitHolder(signaled_holder);
|
||||
} else {
|
||||
/* A server/session was signaled. Have the manager handle it. */
|
||||
R_ABORT_UNLESS(g_server_manager.Process(signaled_holder));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
namespace init {
|
||||
|
||||
namespace {
|
||||
|
||||
Result InitializePlatformServiceWithoutSessionCountForHomebrewCompatibility() {
|
||||
/* NOTE: This implements a hack, to keep a session to pl:u without counting against the global limit. */
|
||||
/* This is done because as of 16.0.0, pl:u is the only way to get shared font access, and there are */
|
||||
/* not enough sessions for all reasonable the clients when homebrew gets involved. */
|
||||
/* Please do not do similar things for other services; this is a hack which may not always work, */
|
||||
/* and which could cause problems in other contexts where the ServerManager doesn't have enough */
|
||||
/* slots in truth. */
|
||||
|
||||
/* Initialize pl. */
|
||||
R_ABORT_UNLESS(plInitialize(::PlServiceType_User));
|
||||
|
||||
/* Get the service session for pl. */
|
||||
Service *srv = plGetServiceSession();
|
||||
|
||||
/* Next, create a clone. */
|
||||
/* Because this doesn't go through sm, this does not count against the session limit. */
|
||||
Service clone;
|
||||
R_TRY(serviceClone(srv, std::addressof(clone)));
|
||||
|
||||
/* Next, close the pl service session from sm. */
|
||||
/* This decrements the used session count by one, since the session is from sm. */
|
||||
serviceClose(srv);
|
||||
|
||||
/* HACK: replace the session with the clone we made, to restore functionality. */
|
||||
*srv = clone;
|
||||
|
||||
return 0;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
void InitializeSystemModule() {
|
||||
/* Initialize heap. */
|
||||
fatal::srv::InitializeFsHeap();
|
||||
|
||||
/* Initialize our connection to sm. */
|
||||
R_ABORT_UNLESS(sm::Initialize());
|
||||
|
||||
/* Initialize fs. */
|
||||
fs::InitializeForSystem();
|
||||
fs::SetAllocator(fatal::srv::AllocateForFs, fatal::srv::DeallocateForFs);
|
||||
fs::SetEnabledAutoAbort(false);
|
||||
|
||||
/* Initialize other services we need. */
|
||||
R_ABORT_UNLESS(setInitialize());
|
||||
R_ABORT_UNLESS(setsysInitialize());
|
||||
R_ABORT_UNLESS(pminfoInitialize());
|
||||
R_ABORT_UNLESS(i2cInitialize());
|
||||
R_ABORT_UNLESS(bpcInitialize());
|
||||
|
||||
if (hos::GetVersion() >= hos::Version_8_0_0) {
|
||||
R_ABORT_UNLESS(clkrstInitialize());
|
||||
} else {
|
||||
R_ABORT_UNLESS(pcvInitialize());
|
||||
}
|
||||
|
||||
R_ABORT_UNLESS(psmInitialize());
|
||||
R_ABORT_UNLESS(spsmInitialize());
|
||||
R_ABORT_UNLESS(InitializePlatformServiceWithoutSessionCountForHomebrewCompatibility());
|
||||
gpio::Initialize();
|
||||
|
||||
/* Mount the SD card. */
|
||||
R_ABORT_UNLESS(fs::MountSdCard("sdmc"));
|
||||
}
|
||||
|
||||
void FinalizeSystemModule() { /* ... */ }
|
||||
|
||||
void Startup() { /* ... */ }
|
||||
|
||||
}
|
||||
|
||||
void Main() {
|
||||
/* Set thread name. */
|
||||
os::SetThreadNamePointer(os::GetCurrentThread(), AMS_GET_SYSTEM_THREAD_NAME(fatal, Main));
|
||||
AMS_ASSERT(os::GetThreadPriority(os::GetCurrentThread()) == AMS_GET_SYSTEM_THREAD_PRIORITY(fatal, Main));
|
||||
|
||||
/* Load shared font. */
|
||||
R_ABORT_UNLESS(fatal::srv::font::InitializeSharedFont());
|
||||
|
||||
/* Check whether we should throw fatal due to repair process. */
|
||||
fatal::srv::CheckRepairStatus();
|
||||
|
||||
/* Loop processing the IPC server. */
|
||||
fatal::srv::InitializeAndLoopIpcServer();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,116 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <stratosphere.hpp>
|
||||
#include "fatal_repair.hpp"
|
||||
#include "fatal_service_for_self.hpp"
|
||||
|
||||
namespace ams::fatal::srv {
|
||||
|
||||
namespace {
|
||||
|
||||
bool IsInRepair() {
|
||||
/* Before firmware 3.0.0, this wasn't implemented. */
|
||||
if (hos::GetVersion() < hos::Version_3_0_0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool in_repair;
|
||||
return R_SUCCEEDED(setsysGetInRepairProcessEnableFlag(std::addressof(in_repair))) && in_repair;
|
||||
}
|
||||
|
||||
bool IsInRepairWithoutVolHeld() {
|
||||
if (IsInRepair()) {
|
||||
gpio::GpioPadSession vol_btn;
|
||||
if (R_FAILED(gpio::OpenSession(std::addressof(vol_btn), gpio::DeviceCode_ButtonVolUp))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Ensure we close even on early return. */
|
||||
ON_SCOPE_EXIT { gpio::CloseSession(std::addressof(vol_btn)); };
|
||||
|
||||
/* Set direction input. */
|
||||
gpio::SetDirection(std::addressof(vol_btn), gpio::Direction_Input);
|
||||
|
||||
/* Ensure that we're holding the volume button for a full second. */
|
||||
auto start = os::GetSystemTick();
|
||||
do {
|
||||
if (gpio::GetValue(std::addressof(vol_btn)) != gpio::GpioValue_Low) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Sleep for 100 ms. */
|
||||
os::SleepThread(TimeSpan::FromMilliSeconds(100));
|
||||
} while (os::ConvertToTimeSpan(os::GetSystemTick() - start) < TimeSpan::FromSeconds(1));
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool NeedsRunTimeReviser() {
|
||||
/* Before firmware 5.0.0, this wasn't implemented. */
|
||||
if (hos::GetVersion() < hos::Version_5_0_0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool requires_time_reviser;
|
||||
return R_SUCCEEDED(setsysGetRequiresRunRepairTimeReviser(std::addressof(requires_time_reviser))) && requires_time_reviser;
|
||||
}
|
||||
|
||||
bool IsTimeReviserCartridgeInserted() {
|
||||
FsGameCardHandle gc_hnd;
|
||||
u8 gc_attr;
|
||||
{
|
||||
FsDeviceOperator devop;
|
||||
if (R_FAILED(fsOpenDeviceOperator(std::addressof(devop)))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Ensure we close even on early return. */
|
||||
ON_SCOPE_EXIT { fsDeviceOperatorClose(std::addressof(devop)); };
|
||||
|
||||
/* Check that a gamecard is inserted. */
|
||||
bool inserted;
|
||||
if (R_FAILED(fsDeviceOperatorIsGameCardInserted(std::addressof(devop), std::addressof(inserted))) || !inserted) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Check that we can retrieve the gamecard's attributes. */
|
||||
if (R_FAILED(fsDeviceOperatorGetGameCardHandle(std::addressof(devop), std::addressof(gc_hnd))) || R_FAILED(fsDeviceOperatorGetGameCardAttribute(std::addressof(devop), std::addressof(gc_hnd), std::addressof(gc_attr)))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/* Check that the gamecard is a repair tool. */
|
||||
return (gc_attr & FsGameCardAttribute_RepairToolFlag);
|
||||
}
|
||||
|
||||
bool IsInRepairWithoutTimeReviserCartridge() {
|
||||
return NeedsRunTimeReviser() && IsTimeReviserCartridgeInserted();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void CheckRepairStatus() {
|
||||
if (IsInRepairWithoutVolHeld()) {
|
||||
ThrowFatalForSelf(ResultInRepairWithoutVolHeld());
|
||||
}
|
||||
|
||||
if (IsInRepairWithoutTimeReviserCartridge()) {
|
||||
ThrowFatalForSelf(ResultInRepairWithoutTimeReviserCartridge());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
namespace ams::fatal::srv {
|
||||
|
||||
void CheckRepairStatus();
|
||||
|
||||
}
|
||||
@@ -1,98 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <stratosphere.hpp>
|
||||
#include "fatal_scoped_file.hpp"
|
||||
|
||||
namespace ams::fatal::srv {
|
||||
|
||||
namespace {
|
||||
|
||||
/* Convenience definitions. */
|
||||
constexpr size_t MaximumLineLength = 0x20;
|
||||
|
||||
constinit os::SdkMutex g_format_lock;
|
||||
constinit char g_format_buffer[2 * os::MemoryPageSize];
|
||||
|
||||
}
|
||||
|
||||
void ScopedFile::WriteString(const char *str) {
|
||||
this->Write(str, std::strlen(str));
|
||||
}
|
||||
|
||||
void ScopedFile::WriteFormat(const char *fmt, ...) {
|
||||
/* Acquire exclusive access to the format buffer. */
|
||||
std::scoped_lock lk(g_format_lock);
|
||||
|
||||
/* Format to the buffer. */
|
||||
{
|
||||
std::va_list vl;
|
||||
va_start(vl, fmt);
|
||||
util::VSNPrintf(g_format_buffer, sizeof(g_format_buffer), fmt, vl);
|
||||
va_end(vl);
|
||||
}
|
||||
|
||||
/* Write data. */
|
||||
this->WriteString(g_format_buffer);
|
||||
}
|
||||
|
||||
void ScopedFile::DumpMemory(const char *prefix, const void *data, size_t size) {
|
||||
const u8 *data_u8 = reinterpret_cast<const u8 *>(data);
|
||||
const int prefix_len = std::strlen(prefix);
|
||||
size_t data_ofs = 0;
|
||||
size_t remaining = size;
|
||||
bool first = true;
|
||||
|
||||
while (remaining) {
|
||||
const size_t cur_size = std::min(MaximumLineLength, remaining);
|
||||
|
||||
/* Print the line prefix. */
|
||||
if (first) {
|
||||
this->WriteFormat("%s", prefix);
|
||||
first = false;
|
||||
} else {
|
||||
this->WriteFormat("%*s", prefix_len, "");
|
||||
}
|
||||
|
||||
/* Dump up to 0x20 of hex memory. */
|
||||
{
|
||||
char hex[MaximumLineLength * 2 + 2] = {};
|
||||
for (size_t i = 0; i < cur_size; i++) {
|
||||
util::SNPrintf(hex + i * 2, 3, "%02X", data_u8[data_ofs++]);
|
||||
}
|
||||
hex[cur_size * 2 + 0] = '\n';
|
||||
hex[cur_size * 2 + 1] = '\x00';
|
||||
|
||||
this->WriteString(hex);
|
||||
}
|
||||
|
||||
/* Continue. */
|
||||
remaining -= cur_size;
|
||||
}
|
||||
}
|
||||
|
||||
void ScopedFile::Write(const void *data, size_t size) {
|
||||
/* If we're not open, we can't write. */
|
||||
if (!this->IsOpen()) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Advance, if we write successfully. */
|
||||
if (R_SUCCEEDED(fs::WriteFile(m_file, m_offset, data, size, fs::WriteOption::Flush))) {
|
||||
m_offset += size;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
namespace ams::fatal::srv {
|
||||
|
||||
class ScopedFile {
|
||||
NON_COPYABLE(ScopedFile);
|
||||
NON_MOVEABLE(ScopedFile);
|
||||
private:
|
||||
fs::FileHandle m_file;
|
||||
s64 m_offset;
|
||||
bool m_opened;
|
||||
public:
|
||||
ScopedFile(const char *path) : m_file(), m_offset(), m_opened(false) {
|
||||
if (R_SUCCEEDED(fs::CreateFile(path, 0))) {
|
||||
m_opened = R_SUCCEEDED(fs::OpenFile(std::addressof(m_file), path, fs::OpenMode_Write | fs::OpenMode_AllowAppend));
|
||||
}
|
||||
}
|
||||
|
||||
~ScopedFile() {
|
||||
if (m_opened) {
|
||||
fs::CloseFile(m_file);
|
||||
}
|
||||
}
|
||||
|
||||
bool IsOpen() const {
|
||||
return m_opened;
|
||||
}
|
||||
|
||||
void WriteString(const char *str);
|
||||
void WriteFormat(const char *fmt, ...) __attribute__((format(printf, 2, 3)));
|
||||
void DumpMemory(const char *prefix, const void *data, size_t size);
|
||||
|
||||
void Write(const void *data, size_t size);
|
||||
};
|
||||
|
||||
}
|
||||
@@ -1,173 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <stratosphere.hpp>
|
||||
#include "fatal_config.hpp"
|
||||
#include "fatal_debug.hpp"
|
||||
#include "fatal_service.hpp"
|
||||
#include "fatal_service_for_self.hpp"
|
||||
#include "fatal_event_manager.hpp"
|
||||
#include "fatal_task.hpp"
|
||||
|
||||
namespace ams::fatal::srv {
|
||||
|
||||
namespace {
|
||||
|
||||
/* Service Context. */
|
||||
class ServiceContext {
|
||||
private:
|
||||
os::Event m_erpt_event;
|
||||
os::Event m_battery_event;
|
||||
ThrowContext m_context;
|
||||
FatalEventManager m_event_manager;
|
||||
bool m_has_thrown;
|
||||
private:
|
||||
Result TrySetHasThrown() {
|
||||
R_UNLESS(!m_has_thrown, fatal::ResultAlreadyThrown());
|
||||
m_has_thrown = true;
|
||||
R_SUCCEED();
|
||||
}
|
||||
public:
|
||||
ServiceContext()
|
||||
: m_erpt_event(os::EventClearMode_ManualClear), m_battery_event(os::EventClearMode_ManualClear),
|
||||
m_context(std::addressof(m_erpt_event), std::addressof(m_battery_event)), m_has_thrown(false)
|
||||
{
|
||||
/* ... */
|
||||
}
|
||||
|
||||
Result GetEvent(const os::SystemEventType **out) {
|
||||
R_RETURN(m_event_manager.GetEvent(out));
|
||||
}
|
||||
|
||||
Result GetThrowContext(Result *out_error, ncm::ProgramId *out_program_id, FatalPolicy *out_policy, CpuContext *out_ctx) {
|
||||
/* Set the output. */
|
||||
*out_error = m_context.result;
|
||||
*out_program_id = m_context.throw_program_id;
|
||||
*out_policy = m_context.policy;
|
||||
*out_ctx = m_context.cpu_ctx;
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result ThrowFatal(Result result, os::ProcessId process_id) {
|
||||
R_RETURN(this->ThrowFatalWithCpuContext(result, process_id, FatalPolicy_ErrorReportAndErrorScreen, {}));
|
||||
}
|
||||
|
||||
Result ThrowFatalWithPolicy(Result result, os::ProcessId process_id, FatalPolicy policy) {
|
||||
R_RETURN(this->ThrowFatalWithCpuContext(result, process_id, policy, {}));
|
||||
}
|
||||
|
||||
Result ThrowFatalWithCpuContext(Result result, os::ProcessId process_id, FatalPolicy policy, const CpuContext &cpu_ctx);
|
||||
};
|
||||
|
||||
/* Context global. */
|
||||
ServiceContext g_context;
|
||||
|
||||
/* Throw implementation. */
|
||||
Result ServiceContext::ThrowFatalWithCpuContext(Result result, os::ProcessId process_id, FatalPolicy policy, const CpuContext &cpu_ctx) {
|
||||
/* We don't support Error-Report-only fatals. */
|
||||
R_SUCCEED_IF(policy == FatalPolicy_ErrorReport);
|
||||
|
||||
/* Note that we've thrown fatal. */
|
||||
R_TRY(this->TrySetHasThrown());
|
||||
|
||||
/* At this point we have exclusive access to m_context. */
|
||||
m_context.result = result;
|
||||
m_context.cpu_ctx = cpu_ctx;
|
||||
m_context.policy = policy;
|
||||
|
||||
/* Cap the stack trace to a sane limit. */
|
||||
if (cpu_ctx.architecture == CpuContext::Architecture_Aarch64) {
|
||||
m_context.cpu_ctx.aarch64_ctx.stack_trace_size = std::max(size_t(m_context.cpu_ctx.aarch64_ctx.stack_trace_size), aarch64::CpuContext::MaxStackTraceDepth);
|
||||
} else {
|
||||
m_context.cpu_ctx.aarch32_ctx.stack_trace_size = std::max(size_t(m_context.cpu_ctx.aarch32_ctx.stack_trace_size), aarch32::CpuContext::MaxStackTraceDepth);
|
||||
}
|
||||
|
||||
/* Get program id. */
|
||||
pm::info::GetProgramId(std::addressof(m_context.throw_program_id), process_id);
|
||||
m_context.is_creport = (m_context.throw_program_id == ncm::SystemProgramId::Creport);
|
||||
|
||||
|
||||
if (!m_context.is_creport) {
|
||||
m_context.program_id = m_context.throw_program_id;
|
||||
|
||||
/* On firmware version 2.0.0, use debugging SVCs to collect information. */
|
||||
if (hos::GetVersion() >= hos::Version_2_0_0) {
|
||||
fatal::srv::TryCollectDebugInformation(std::addressof(m_context), process_id);
|
||||
}
|
||||
} else {
|
||||
/* We received info from creport. Parse program id from afsr0. */
|
||||
if (cpu_ctx.architecture == CpuContext::Architecture_Aarch64) {
|
||||
m_context.program_id = cpu_ctx.aarch64_ctx.GetProgramIdForAtmosphere();
|
||||
} else {
|
||||
m_context.program_id = cpu_ctx.aarch32_ctx.GetProgramIdForAtmosphere();
|
||||
}
|
||||
}
|
||||
|
||||
/* Decide whether to generate a report. */
|
||||
m_context.generate_error_report = (policy == FatalPolicy_ErrorReportAndErrorScreen);
|
||||
|
||||
/* Adjust error code (ResultSuccess()/2000-0000 -> err::ResultSystemProgramAbort()/2162-0002). */
|
||||
if (R_SUCCEEDED(m_context.result)) {
|
||||
m_context.result = err::ResultSystemProgramAbort();
|
||||
}
|
||||
|
||||
switch (policy) {
|
||||
case FatalPolicy_ErrorReportAndErrorScreen:
|
||||
case FatalPolicy_ErrorScreen:
|
||||
/* Signal that we're throwing. */
|
||||
m_event_manager.SignalEvents();
|
||||
|
||||
if (GetFatalConfig().ShouldTransitionToFatal()) {
|
||||
RunTasks(std::addressof(m_context));
|
||||
}
|
||||
break;
|
||||
/* N aborts here. Should we just return an error code? */
|
||||
AMS_UNREACHABLE_DEFAULT_CASE();
|
||||
}
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Result ThrowFatalForSelf(Result result) {
|
||||
R_RETURN(g_context.ThrowFatalWithPolicy(result, os::GetCurrentProcessId(), FatalPolicy_ErrorScreen));
|
||||
}
|
||||
|
||||
Result Service::ThrowFatal(Result result, const sf::ClientProcessId &client_pid) {
|
||||
R_RETURN(g_context.ThrowFatal(result, client_pid.GetValue()));
|
||||
}
|
||||
|
||||
Result Service::ThrowFatalWithPolicy(Result result, const sf::ClientProcessId &client_pid, FatalPolicy policy) {
|
||||
R_RETURN(g_context.ThrowFatalWithPolicy(result, client_pid.GetValue(), policy));
|
||||
}
|
||||
|
||||
Result Service::ThrowFatalWithCpuContext(Result result, const sf::ClientProcessId &client_pid, FatalPolicy policy, const CpuContext &cpu_ctx) {
|
||||
R_RETURN(g_context.ThrowFatalWithCpuContext(result, client_pid.GetValue(), policy, cpu_ctx));
|
||||
}
|
||||
|
||||
Result Service::GetFatalEvent(sf::OutCopyHandle out_h) {
|
||||
const os::SystemEventType *event;
|
||||
R_TRY(g_context.GetEvent(std::addressof(event)));
|
||||
out_h.SetValue(os::GetReadableHandleOfSystemEvent(event), false);
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result Service::GetFatalContext(sf::Out<Result> out_error, sf::Out<ncm::ProgramId> out_program_id, sf::Out<fatal::FatalPolicy> out_policy, sf::Out<fatal::CpuContext> out_ctx) {
|
||||
R_RETURN(g_context.GetThrowContext(out_error.GetPointer(), out_program_id.GetPointer(), out_policy.GetPointer(), out_ctx.GetPointer()));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
namespace ams::fatal::srv {
|
||||
|
||||
class Service {
|
||||
public:
|
||||
Result ThrowFatal(Result error, const sf::ClientProcessId &client_pid);
|
||||
Result ThrowFatalWithPolicy(Result error, const sf::ClientProcessId &client_pid, FatalPolicy policy);
|
||||
Result ThrowFatalWithCpuContext(Result error, const sf::ClientProcessId &client_pid, FatalPolicy policy, const CpuContext &cpu_ctx);
|
||||
Result GetFatalEvent(sf::OutCopyHandle out_h);
|
||||
Result GetFatalContext(sf::Out<Result> out_error, sf::Out<ncm::ProgramId> out_program_id, sf::Out<fatal::FatalPolicy> out_policy, sf::Out<fatal::CpuContext> out_ctx);
|
||||
};
|
||||
static_assert(fatal::impl::IsIService<Service>);
|
||||
static_assert(fatal::impl::IsIPrivateService<Service>);
|
||||
|
||||
}
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
namespace ams::fatal::srv {
|
||||
|
||||
Result ThrowFatalForSelf(Result error_code);
|
||||
|
||||
}
|
||||
@@ -1,81 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <stratosphere.hpp>
|
||||
#include "fatal_task.hpp"
|
||||
|
||||
#include "fatal_task_error_report.hpp"
|
||||
#include "fatal_task_screen.hpp"
|
||||
#include "fatal_task_sound.hpp"
|
||||
#include "fatal_task_clock.hpp"
|
||||
#include "fatal_task_power.hpp"
|
||||
|
||||
namespace ams::fatal::srv {
|
||||
|
||||
namespace {
|
||||
|
||||
class TaskThread {
|
||||
NON_COPYABLE(TaskThread);
|
||||
private:
|
||||
os::ThreadType m_thread;
|
||||
private:
|
||||
static void RunTaskImpl(void *arg) {
|
||||
ITask *task = reinterpret_cast<ITask *>(arg);
|
||||
|
||||
if (R_FAILED(task->Run())) {
|
||||
/* TODO: Log task failure, somehow? */
|
||||
}
|
||||
}
|
||||
public:
|
||||
TaskThread() { /* ... */ }
|
||||
void StartTask(ITask *task) {
|
||||
R_ABORT_UNLESS(os::CreateThread(std::addressof(m_thread), RunTaskImpl, task, task->GetStack(), task->GetStackSize(), AMS_GET_SYSTEM_THREAD_PRIORITY(fatalsrv, FatalTaskThread), 3));
|
||||
os::SetThreadNamePointer(std::addressof(m_thread), AMS_GET_SYSTEM_THREAD_NAME(fatalsrv, FatalTaskThread));
|
||||
os::StartThread(std::addressof(m_thread));
|
||||
}
|
||||
};
|
||||
|
||||
class TaskManager {
|
||||
NON_COPYABLE(TaskManager);
|
||||
private:
|
||||
static constexpr size_t MaxTasks = 8;
|
||||
private:
|
||||
TaskThread m_task_threads[MaxTasks];
|
||||
size_t m_task_count = 0;
|
||||
public:
|
||||
TaskManager() { /* ... */ }
|
||||
void StartTask(ITask *task) {
|
||||
AMS_ABORT_UNLESS(m_task_count < MaxTasks);
|
||||
m_task_threads[m_task_count++].StartTask(task);
|
||||
}
|
||||
};
|
||||
|
||||
/* Global task manager. */
|
||||
TaskManager g_task_manager;
|
||||
|
||||
}
|
||||
|
||||
void RunTasks(const ThrowContext *ctx) {
|
||||
g_task_manager.StartTask(GetErrorReportTask(ctx));
|
||||
g_task_manager.StartTask(GetPowerControlTask(ctx));
|
||||
g_task_manager.StartTask(GetShowFatalTask(ctx));
|
||||
g_task_manager.StartTask(GetStopSoundTask(ctx));
|
||||
g_task_manager.StartTask(GetBacklightControlTask(ctx));
|
||||
g_task_manager.StartTask(GetAdjustClockTask(ctx));
|
||||
g_task_manager.StartTask(GetPowerButtonObserveTask(ctx));
|
||||
g_task_manager.StartTask(GetStateTransitionStopTask(ctx));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include "fatal_service.hpp"
|
||||
|
||||
namespace ams::fatal::srv {
|
||||
|
||||
class ITask {
|
||||
protected:
|
||||
const ThrowContext *m_context = nullptr;
|
||||
public:
|
||||
void Initialize(const ThrowContext *context) {
|
||||
m_context = context;
|
||||
}
|
||||
|
||||
virtual Result Run() = 0;
|
||||
virtual const char *GetName() const = 0;
|
||||
virtual u8 *GetStack() = 0;
|
||||
virtual size_t GetStackSize() const = 0;
|
||||
};
|
||||
|
||||
template<size_t _StackSize>
|
||||
class ITaskWithStack : public ITask {
|
||||
public:
|
||||
static constexpr size_t StackSize = _StackSize;
|
||||
static_assert(util::IsAligned(StackSize, os::MemoryPageSize), "StackSize alignment");
|
||||
protected:
|
||||
alignas(os::MemoryPageSize) u8 m_stack_mem[StackSize] = {};
|
||||
public:
|
||||
virtual u8 *GetStack() override final {
|
||||
return m_stack_mem;
|
||||
}
|
||||
|
||||
virtual size_t GetStackSize() const override final {
|
||||
return StackSize;
|
||||
}
|
||||
};
|
||||
|
||||
using ITaskWithDefaultStack = ITaskWithStack<0x2000>;
|
||||
|
||||
void RunTasks(const ThrowContext *ctx);
|
||||
|
||||
}
|
||||
@@ -1,83 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <stratosphere.hpp>
|
||||
#include "fatal_task_clock.hpp"
|
||||
|
||||
namespace ams::fatal::srv {
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
/* Task definition. */
|
||||
class AdjustClockTask : public ITaskWithDefaultStack {
|
||||
private:
|
||||
Result AdjustClockForModule(PcvModule module, u32 hz);
|
||||
Result AdjustClock();
|
||||
public:
|
||||
virtual Result Run() override;
|
||||
virtual const char *GetName() const override {
|
||||
return "AdjustClockTask";
|
||||
}
|
||||
};
|
||||
|
||||
/* Task global. */
|
||||
AdjustClockTask g_adjust_clock_task;
|
||||
|
||||
/* Task implementation. */
|
||||
Result AdjustClockTask::AdjustClockForModule(PcvModule module, u32 hz) {
|
||||
if (hos::GetVersion() >= hos::Version_8_0_0) {
|
||||
/* On 8.0.0+, convert to module id + use clkrst API. */
|
||||
PcvModuleId module_id;
|
||||
R_TRY(pcvGetModuleId(std::addressof(module_id), module));
|
||||
|
||||
ClkrstSession session;
|
||||
R_TRY(clkrstOpenSession(std::addressof(session), module_id, 3));
|
||||
ON_SCOPE_EXIT { clkrstCloseSession(std::addressof(session)); };
|
||||
|
||||
R_TRY(clkrstSetClockRate(std::addressof(session), hz));
|
||||
} else {
|
||||
/* On 1.0.0-7.0.1, use pcv API. */
|
||||
R_TRY(pcvSetClockRate(module, hz));
|
||||
}
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result AdjustClockTask::AdjustClock() {
|
||||
/* Fatal sets the CPU to 1020MHz, the GPU to 307 MHz, and the EMC to 1331MHz. */
|
||||
constexpr u32 CPU_CLOCK_1020MHZ = 0x3CCBF700L;
|
||||
constexpr u32 GPU_CLOCK_307MHZ = 0x124F8000L;
|
||||
constexpr u32 EMC_CLOCK_1331MHZ = 0x4F588000L;
|
||||
|
||||
R_TRY(AdjustClockForModule(PcvModule_CpuBus, CPU_CLOCK_1020MHZ));
|
||||
R_TRY(AdjustClockForModule(PcvModule_GPU, GPU_CLOCK_307MHZ));
|
||||
R_TRY(AdjustClockForModule(PcvModule_EMC, EMC_CLOCK_1331MHZ));
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result AdjustClockTask::Run() {
|
||||
R_RETURN(AdjustClock());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ITask *GetAdjustClockTask(const ThrowContext *ctx) {
|
||||
g_adjust_clock_task.Initialize(ctx);
|
||||
return std::addressof(g_adjust_clock_task);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include "fatal_task.hpp"
|
||||
|
||||
namespace ams::fatal::srv {
|
||||
|
||||
ITask *GetAdjustClockTask(const ThrowContext *ctx);
|
||||
|
||||
}
|
||||
@@ -1,178 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <stratosphere.hpp>
|
||||
#include "fatal_config.hpp"
|
||||
#include "fatal_task_error_report.hpp"
|
||||
#include "fatal_scoped_file.hpp"
|
||||
|
||||
namespace ams::fatal::srv {
|
||||
|
||||
namespace {
|
||||
|
||||
/* Helpers. */
|
||||
void TryEnsureReportDirectories() {
|
||||
fs::EnsureDirectory("sdmc:/atmosphere/fatal_reports/dumps");
|
||||
}
|
||||
|
||||
bool TryGetCurrentTimestamp(u64 *out) {
|
||||
/* Clear output. */
|
||||
*out = 0;
|
||||
|
||||
/* Check if we have time service. */
|
||||
{
|
||||
bool has_time_service = false;
|
||||
if (R_FAILED(sm::HasService(std::addressof(has_time_service), sm::ServiceName::Encode("time:s"))) || !has_time_service) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/* Try to get the current time. */
|
||||
{
|
||||
if (R_FAILED(::timeInitialize())) {
|
||||
return false;
|
||||
}
|
||||
ON_SCOPE_EXIT { ::timeExit(); };
|
||||
|
||||
return R_SUCCEEDED(::timeGetCurrentTime(TimeType_LocalSystemClock, out));
|
||||
}
|
||||
}
|
||||
|
||||
/* Task definition. */
|
||||
class ErrorReportTask : public ITaskWithDefaultStack {
|
||||
private:
|
||||
void SaveReportToSdCard();
|
||||
public:
|
||||
virtual Result Run() override;
|
||||
virtual const char *GetName() const override {
|
||||
return "WriteErrorReport";
|
||||
}
|
||||
};
|
||||
|
||||
/* Task globals. */
|
||||
ErrorReportTask g_error_report_task;
|
||||
|
||||
/* Task Implementation. */
|
||||
void ErrorReportTask::SaveReportToSdCard() {
|
||||
char file_path[fs::EntryNameLengthMax + 1];
|
||||
|
||||
/* Try to Ensure path exists. */
|
||||
TryEnsureReportDirectories();
|
||||
|
||||
/* Get a timestamp. */
|
||||
u64 timestamp;
|
||||
if (!TryGetCurrentTimestamp(std::addressof(timestamp))) {
|
||||
timestamp = os::GetSystemTick().GetInt64Value();
|
||||
}
|
||||
|
||||
/* Open report file. */
|
||||
{
|
||||
util::SNPrintf(file_path, sizeof(file_path) - 1, "sdmc:/atmosphere/fatal_reports/%011lu_%016lx.log", timestamp, static_cast<u64>(m_context->program_id));
|
||||
ScopedFile file(file_path);
|
||||
if (file.IsOpen()) {
|
||||
file.WriteFormat("Atmosphère Fatal Report (v1.1):\n");
|
||||
file.WriteFormat("Result: 0x%X (2%03d-%04d)\n\n", m_context->result.GetValue(), m_context->result.GetModule(), m_context->result.GetDescription());
|
||||
file.WriteFormat("Program ID: %016lx\n", static_cast<u64>(m_context->program_id));
|
||||
if (strlen(m_context->proc_name)) {
|
||||
file.WriteFormat("Process Name: %s\n", m_context->proc_name);
|
||||
}
|
||||
file.WriteFormat("Firmware: %s (Atmosphère %u.%u.%u-%s)\n", GetFatalConfig().GetFirmwareVersion().display_version, ATMOSPHERE_RELEASE_VERSION, ams::GetGitRevision());
|
||||
|
||||
if (m_context->cpu_ctx.architecture == CpuContext::Architecture_Aarch32) {
|
||||
file.WriteFormat("General Purpose Registers:\n");
|
||||
for (size_t i = 0; i <= aarch32::RegisterName_PC; i++) {
|
||||
if (m_context->cpu_ctx.aarch32_ctx.HasRegisterValue(static_cast<aarch32::RegisterName>(i))) {
|
||||
file.WriteFormat( " %3s: %08x\n", aarch32::CpuContext::RegisterNameStrings[i], m_context->cpu_ctx.aarch32_ctx.r[i]);
|
||||
}
|
||||
}
|
||||
file.WriteFormat("Start Address: %08x\n", m_context->cpu_ctx.aarch32_ctx.base_address);
|
||||
file.WriteFormat("Stack Trace:\n");
|
||||
for (unsigned int i = 0; i < m_context->cpu_ctx.aarch32_ctx.stack_trace_size; i++) {
|
||||
file.WriteFormat(" ReturnAddress[%02u]: %08x\n", i, m_context->cpu_ctx.aarch32_ctx.stack_trace[i]);
|
||||
}
|
||||
} else {
|
||||
file.WriteFormat("General Purpose Registers:\n");
|
||||
for (size_t i = 0; i <= aarch64::RegisterName_PC; i++) {
|
||||
if (m_context->cpu_ctx.aarch64_ctx.HasRegisterValue(static_cast<aarch64::RegisterName>(i))) {
|
||||
file.WriteFormat( " %3s: %016lx\n", aarch64::CpuContext::RegisterNameStrings[i], m_context->cpu_ctx.aarch64_ctx.x[i]);
|
||||
}
|
||||
}
|
||||
file.WriteFormat("Start Address: %016lx\n", m_context->cpu_ctx.aarch64_ctx.base_address);
|
||||
file.WriteFormat("Stack Trace:\n");
|
||||
for (unsigned int i = 0; i < m_context->cpu_ctx.aarch64_ctx.stack_trace_size; i++) {
|
||||
file.WriteFormat(" ReturnAddress[%02u]: %016lx\n", i, m_context->cpu_ctx.aarch64_ctx.stack_trace[i]);
|
||||
}
|
||||
}
|
||||
|
||||
if (m_context->stack_dump_size != 0) {
|
||||
file.WriteFormat("Stack Dump: 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f\n");
|
||||
for (size_t i = 0; i < 0x10; i++) {
|
||||
const size_t ofs = i * 0x10;
|
||||
file.WriteFormat(" %012lx %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x\n",
|
||||
m_context->stack_dump_base + ofs, m_context->stack_dump[ofs + 0], m_context->stack_dump[ofs + 1], m_context->stack_dump[ofs + 2], m_context->stack_dump[ofs + 3], m_context->stack_dump[ofs + 4], m_context->stack_dump[ofs + 5], m_context->stack_dump[ofs + 6], m_context->stack_dump[ofs + 7],
|
||||
m_context->stack_dump[ofs + 8], m_context->stack_dump[ofs + 9], m_context->stack_dump[ofs + 10], m_context->stack_dump[ofs + 11], m_context->stack_dump[ofs + 12], m_context->stack_dump[ofs + 13], m_context->stack_dump[ofs + 14], m_context->stack_dump[ofs + 15]);
|
||||
}
|
||||
}
|
||||
|
||||
if (m_context->tls_address != 0) {
|
||||
file.WriteFormat("TLS Address: %016lx\n", m_context->tls_address);
|
||||
file.WriteFormat("TLS Dump: 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f\n");
|
||||
for (size_t i = 0; i < 0x10; i++) {
|
||||
const size_t ofs = i * 0x10;
|
||||
file.WriteFormat(" %012lx %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x\n",
|
||||
m_context->tls_address + ofs, m_context->tls_dump[ofs + 0], m_context->tls_dump[ofs + 1], m_context->tls_dump[ofs + 2], m_context->tls_dump[ofs + 3], m_context->tls_dump[ofs + 4], m_context->tls_dump[ofs + 5], m_context->tls_dump[ofs + 6], m_context->tls_dump[ofs + 7],
|
||||
m_context->tls_dump[ofs + 8], m_context->tls_dump[ofs + 9], m_context->tls_dump[ofs + 10], m_context->tls_dump[ofs + 11], m_context->tls_dump[ofs + 12], m_context->tls_dump[ofs + 13], m_context->tls_dump[ofs + 14], m_context->tls_dump[ofs + 15]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Dump data to file. */
|
||||
{
|
||||
util::SNPrintf(file_path, sizeof(file_path) - 1, "sdmc:/atmosphere/fatal_reports/dumps/%011lu_%016lx.bin", timestamp, static_cast<u64>(m_context->program_id));
|
||||
ScopedFile file(file_path);
|
||||
if (file.IsOpen()) {
|
||||
file.Write(m_context->tls_dump, sizeof(m_context->tls_dump));
|
||||
if (m_context->stack_dump_size) {
|
||||
file.Write(m_context->stack_dump, m_context->stack_dump_size);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Result ErrorReportTask::Run() {
|
||||
if (m_context->generate_error_report) {
|
||||
/* Here, Nintendo creates an error report with erpt. AMS will not do that. */
|
||||
}
|
||||
|
||||
/* Save report to SD card. */
|
||||
if (!m_context->is_creport) {
|
||||
this->SaveReportToSdCard();
|
||||
}
|
||||
|
||||
/* Signal we're done with our job. */
|
||||
m_context->erpt_event->Signal();
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ITask *GetErrorReportTask(const ThrowContext *ctx) {
|
||||
g_error_report_task.Initialize(ctx);
|
||||
return std::addressof(g_error_report_task);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include "fatal_task.hpp"
|
||||
|
||||
namespace ams::fatal::srv {
|
||||
|
||||
ITask *GetErrorReportTask(const ThrowContext *ctx);
|
||||
|
||||
}
|
||||
@@ -1,241 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <stratosphere.hpp>
|
||||
#include "fatal_config.hpp"
|
||||
#include "fatal_task_power.hpp"
|
||||
|
||||
namespace ams::fatal::srv {
|
||||
|
||||
namespace {
|
||||
|
||||
/* Task types. */
|
||||
class PowerControlTask : public ITaskWithDefaultStack {
|
||||
private:
|
||||
bool TryShutdown();
|
||||
void MonitorBatteryState();
|
||||
public:
|
||||
virtual Result Run() override;
|
||||
virtual const char *GetName() const override {
|
||||
return "PowerControlTask";
|
||||
}
|
||||
};
|
||||
|
||||
class PowerButtonObserveTask : public ITaskWithDefaultStack {
|
||||
private:
|
||||
void WaitForPowerButton();
|
||||
public:
|
||||
virtual Result Run() override;
|
||||
virtual const char *GetName() const override {
|
||||
return "PowerButtonObserveTask";
|
||||
}
|
||||
};
|
||||
|
||||
class StateTransitionStopTask : public ITaskWithDefaultStack {
|
||||
public:
|
||||
virtual Result Run() override;
|
||||
virtual const char *GetName() const override {
|
||||
return "StateTransitionStopTask";
|
||||
}
|
||||
};
|
||||
|
||||
class RebootTimingObserver {
|
||||
private:
|
||||
os::Tick m_start_tick;
|
||||
TimeSpan m_interval;
|
||||
bool m_flag;
|
||||
public:
|
||||
RebootTimingObserver(bool flag, TimeSpan iv) : m_start_tick(os::GetSystemTick()), m_interval(iv), m_flag(flag) {
|
||||
/* ... */
|
||||
}
|
||||
|
||||
bool IsRebootTiming() const {
|
||||
auto current_tick = os::GetSystemTick();
|
||||
return m_flag && (current_tick - m_start_tick).ToTimeSpan() >= m_interval;
|
||||
}
|
||||
};
|
||||
|
||||
/* Task globals. */
|
||||
PowerControlTask g_power_control_task;
|
||||
PowerButtonObserveTask g_power_button_observe_task;
|
||||
StateTransitionStopTask g_state_transition_stop_task;
|
||||
|
||||
/* Task Implementations. */
|
||||
bool PowerControlTask::TryShutdown() {
|
||||
/* Set a timeout of 30 seconds. */
|
||||
constexpr auto MaxShutdownWaitInterval = TimeSpan::FromSeconds(30);
|
||||
|
||||
auto start_tick = os::GetSystemTick();
|
||||
|
||||
bool perform_shutdown = true;
|
||||
PsmBatteryVoltageState bv_state = PsmBatteryVoltageState_Normal;
|
||||
|
||||
while (true) {
|
||||
auto cur_tick = os::GetSystemTick();
|
||||
if ((cur_tick - start_tick).ToTimeSpan() > MaxShutdownWaitInterval) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (R_FAILED(psmGetBatteryVoltageState(std::addressof(bv_state))) || bv_state == PsmBatteryVoltageState_NeedsShutdown) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (bv_state == PsmBatteryVoltageState_Normal) {
|
||||
perform_shutdown = false;
|
||||
break;
|
||||
}
|
||||
|
||||
/* Query voltage state every 1 seconds, for 30 seconds. */
|
||||
os::SleepThread(TimeSpan::FromSeconds(1));
|
||||
}
|
||||
|
||||
if (perform_shutdown) {
|
||||
bpcShutdownSystem();
|
||||
}
|
||||
|
||||
return perform_shutdown;
|
||||
}
|
||||
|
||||
void PowerControlTask::MonitorBatteryState() {
|
||||
PsmBatteryVoltageState bv_state = PsmBatteryVoltageState_Normal;
|
||||
|
||||
/* Check the battery state, and shutdown on low voltage. */
|
||||
if (R_FAILED(psmGetBatteryVoltageState(std::addressof(bv_state))) || bv_state == PsmBatteryVoltageState_NeedsShutdown) {
|
||||
/* Wait a second for the error report task to finish. */
|
||||
m_context->erpt_event->TimedWait(TimeSpan::FromSeconds(1));
|
||||
this->TryShutdown();
|
||||
return;
|
||||
}
|
||||
|
||||
/* Signal we've checked the battery at least once. */
|
||||
m_context->battery_event->Signal();
|
||||
|
||||
/* Loop querying voltage state every 5 seconds. */
|
||||
while (true) {
|
||||
if (R_FAILED(psmGetBatteryVoltageState(std::addressof(bv_state)))) {
|
||||
bv_state = PsmBatteryVoltageState_NeedsShutdown;
|
||||
}
|
||||
|
||||
switch (bv_state) {
|
||||
case PsmBatteryVoltageState_NeedsShutdown:
|
||||
case PsmBatteryVoltageState_NeedsSleep:
|
||||
{
|
||||
bool shutdown = this->TryShutdown();
|
||||
if (shutdown) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
os::SleepThread(TimeSpan::FromSeconds(5));
|
||||
}
|
||||
}
|
||||
|
||||
bool IsPowerButtonHeld() {
|
||||
if (hos::GetVersion() >= hos::Version_14_0_0) {
|
||||
bool held = false;
|
||||
return R_SUCCEEDED(bpcGetPowerButton(std::addressof(held))) && held;
|
||||
} else if (hos::GetVersion() >= hos::Version_2_0_0) {
|
||||
BpcSleepButtonState state;
|
||||
return R_SUCCEEDED(bpcGetSleepButtonState(std::addressof(state))) && state == BpcSleepButtonState_Held;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void PowerButtonObserveTask::WaitForPowerButton() {
|
||||
/* Wait up to a second for error report generation to finish. */
|
||||
m_context->erpt_event->TimedWait(TimeSpan::FromSeconds(1));
|
||||
|
||||
/* Force a reboot after some time if kiosk unit. */
|
||||
const auto &config = GetFatalConfig();
|
||||
RebootTimingObserver quest_reboot_helper(config.IsQuest(), config.GetQuestRebootTimeoutInterval());
|
||||
RebootTimingObserver fatal_reboot_helper(config.IsFatalRebootEnabled(), config.GetFatalRebootTimeoutInterval());
|
||||
|
||||
bool check_vol_up = true, check_vol_down = true;
|
||||
gpio::GpioPadSession vol_up_btn, vol_down_btn;
|
||||
if (R_FAILED(gpio::OpenSession(std::addressof(vol_up_btn), gpio::DeviceCode_ButtonVolUp))) {
|
||||
check_vol_up = false;
|
||||
}
|
||||
if (R_FAILED(gpio::OpenSession(std::addressof(vol_down_btn), gpio::DeviceCode_ButtonVolDn))) {
|
||||
check_vol_down = false;
|
||||
}
|
||||
|
||||
/* Ensure we close on early return. */
|
||||
ON_SCOPE_EXIT { if (check_vol_up) { gpio::CloseSession(std::addressof(vol_up_btn)); } };
|
||||
ON_SCOPE_EXIT { if (check_vol_down) { gpio::CloseSession(std::addressof(vol_down_btn)); } };
|
||||
|
||||
/* Set direction input. */
|
||||
if (check_vol_up) {
|
||||
gpio::SetDirection(std::addressof(vol_up_btn), gpio::Direction_Input);
|
||||
}
|
||||
if (check_vol_down) {
|
||||
gpio::SetDirection(std::addressof(vol_down_btn), gpio::Direction_Input);
|
||||
}
|
||||
|
||||
while (true) {
|
||||
if (fatal_reboot_helper.IsRebootTiming() || (quest_reboot_helper.IsRebootTiming()) ||
|
||||
(check_vol_up && gpio::GetValue(std::addressof(vol_up_btn)) == gpio::GpioValue_Low) ||
|
||||
(check_vol_down && gpio::GetValue(std::addressof(vol_down_btn)) == gpio::GpioValue_Low) ||
|
||||
IsPowerButtonHeld())
|
||||
{
|
||||
/* If any of the above conditions succeeded, we should reboot. */
|
||||
bpcRebootSystem();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
/* Wait 100 ms between button checks. */
|
||||
os::SleepThread(TimeSpan::FromMilliSeconds(100));
|
||||
}
|
||||
}
|
||||
|
||||
Result PowerControlTask::Run() {
|
||||
this->MonitorBatteryState();
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result PowerButtonObserveTask::Run() {
|
||||
this->WaitForPowerButton();
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result StateTransitionStopTask::Run() {
|
||||
/* Nintendo ignores the output of this call... */
|
||||
spsmPutErrorState();
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ITask *GetPowerControlTask(const ThrowContext *ctx) {
|
||||
g_power_control_task.Initialize(ctx);
|
||||
return std::addressof(g_power_control_task);
|
||||
}
|
||||
|
||||
ITask *GetPowerButtonObserveTask(const ThrowContext *ctx) {
|
||||
g_power_button_observe_task.Initialize(ctx);
|
||||
return std::addressof(g_power_button_observe_task);
|
||||
}
|
||||
|
||||
ITask *GetStateTransitionStopTask(const ThrowContext *ctx) {
|
||||
g_state_transition_stop_task.Initialize(ctx);
|
||||
return std::addressof(g_state_transition_stop_task);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include "fatal_task.hpp"
|
||||
|
||||
namespace ams::fatal::srv {
|
||||
|
||||
ITask *GetPowerControlTask(const ThrowContext *ctx);
|
||||
ITask *GetPowerButtonObserveTask(const ThrowContext *ctx);
|
||||
ITask *GetStateTransitionStopTask(const ThrowContext *ctx);
|
||||
|
||||
}
|
||||
@@ -1,567 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <stratosphere.hpp>
|
||||
#include "fatal_task_screen.hpp"
|
||||
#include "fatal_config.hpp"
|
||||
#include "fatal_font.hpp"
|
||||
|
||||
namespace ams::fatal::srv {
|
||||
|
||||
/* Include Atmosphere logo into its own anonymous namespace. */
|
||||
namespace {
|
||||
|
||||
#include "fatal_ams_logo.inc"
|
||||
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
/* Screen definitions. */
|
||||
constexpr u32 FatalScreenWidth = 1280;
|
||||
constexpr u32 FatalScreenHeight = 720;
|
||||
constexpr u32 FatalScreenBpp = 2;
|
||||
constexpr u32 FatalLayerZ = 100;
|
||||
|
||||
constexpr u32 FatalScreenWidthAlignedBytes = util::AlignUp(FatalScreenWidth * FatalScreenBpp, 64);
|
||||
constexpr u32 FatalScreenWidthAligned = FatalScreenWidthAlignedBytes / FatalScreenBpp;
|
||||
|
||||
/* There should only be a single transfer memory (for nv). */
|
||||
alignas(os::MemoryPageSize) constinit u8 g_nv_transfer_memory[0x40000];
|
||||
|
||||
/* There should only be a single (1280*768) framebuffer. */
|
||||
constexpr size_t FrameBufferRequiredSizeBytes = FatalScreenWidthAlignedBytes * util::AlignUp(FatalScreenHeight, 128);
|
||||
constexpr size_t FrameBufferRequiredSizePageAligned = util::AlignUp(FrameBufferRequiredSizeBytes, os::MemoryPageSize);
|
||||
constexpr size_t FrameBufferRequiredSizeHeapAligned = util::AlignUp(FrameBufferRequiredSizeBytes, os::MemoryHeapUnitSize);
|
||||
|
||||
constinit u8 *g_framebuffer_pointer = nullptr;
|
||||
|
||||
void InitializeFrameBufferPointer() {
|
||||
/* Try to get a framebuffer from heap. */
|
||||
{
|
||||
if (R_SUCCEEDED(os::SetMemoryHeapSize(FrameBufferRequiredSizeHeapAligned))) {
|
||||
g_framebuffer_pointer = reinterpret_cast<u8 *>(os::GetMemoryHeapAddress());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* We couldn't use heap, so try insecure memory, from the system nonsecure pool. */
|
||||
{
|
||||
uintptr_t address = 0;
|
||||
if (R_SUCCEEDED(os::AllocateInsecureMemory(std::addressof(address), FrameBufferRequiredSizePageAligned))) {
|
||||
g_framebuffer_pointer = reinterpret_cast<u8 *>(address);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* Neither heap nor insecure is available, so we're going to have to try to raid the unsafe pool. */
|
||||
{
|
||||
/* First, increase the limit to an extremely high value. */
|
||||
size_t large_size = std::max(128_MB, FrameBufferRequiredSizeHeapAligned);
|
||||
while (svc::ResultLimitReached::Includes(svc::SetUnsafeLimit(large_size))) {
|
||||
large_size *= 2;
|
||||
}
|
||||
|
||||
/* Next, map some unsafe memory. */
|
||||
uintptr_t address = 0;
|
||||
if (R_SUCCEEDED(os::AllocateUnsafeMemory(std::addressof(address), FrameBufferRequiredSizePageAligned))) {
|
||||
g_framebuffer_pointer = reinterpret_cast<u8 *>(address);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extern "C" ::Result __nx_nv_create_tmem(TransferMemory *t, u32 *out_size, Permission perm) {
|
||||
*out_size = sizeof(ams::fatal::srv::g_nv_transfer_memory);
|
||||
return tmemCreateFromMemory(t, ams::fatal::srv::g_nv_transfer_memory, sizeof(ams::fatal::srv::g_nv_transfer_memory), perm);
|
||||
}
|
||||
|
||||
namespace ams::fatal::srv {
|
||||
|
||||
namespace {
|
||||
|
||||
/* Pixel calculation helper. */
|
||||
constexpr u32 GetPixelOffset(u32 x, u32 y) {
|
||||
u32 tmp_pos = ((y & 127) / 16) + (x/32*8) + ((y/16/8)*(((FatalScreenWidthAligned/2)/16*8)));
|
||||
tmp_pos *= 16*16 * 4;
|
||||
|
||||
tmp_pos += ((y%16)/8)*512 + ((x%32)/16)*256 + ((y%8)/2)*64 + ((x%16)/8)*32 + (y%2)*16 + (x%8)*2;//This line is a modified version of code from the Tegra X1 datasheet.
|
||||
|
||||
return tmp_pos / 2;
|
||||
}
|
||||
|
||||
/* Task definitions. */
|
||||
class ShowFatalTask : public ITaskWithStack<0x8000> {
|
||||
private:
|
||||
ViDisplay m_display;
|
||||
ViLayer m_layer;
|
||||
NWindow m_win;
|
||||
NvMap m_map;
|
||||
private:
|
||||
Result SetupDisplayInternal();
|
||||
Result SetupDisplayExternal();
|
||||
Result PrepareScreenForDrawing();
|
||||
void PreRenderFrameBuffer();
|
||||
Result InitializeNativeWindow();
|
||||
void DisplayPreRenderedFrame();
|
||||
Result ShowFatal();
|
||||
public:
|
||||
virtual Result Run() override;
|
||||
virtual const char *GetName() const override {
|
||||
return "ShowFatal";
|
||||
}
|
||||
};
|
||||
|
||||
class BacklightControlTask : public ITaskWithDefaultStack {
|
||||
private:
|
||||
void TurnOnBacklight();
|
||||
public:
|
||||
virtual Result Run() override;
|
||||
virtual const char *GetName() const override {
|
||||
return "BacklightControlTask";
|
||||
}
|
||||
};
|
||||
|
||||
/* Task globals. */
|
||||
ShowFatalTask g_show_fatal_task;
|
||||
BacklightControlTask g_backlight_control_task;
|
||||
|
||||
/* Task implementations. */
|
||||
Result ShowFatalTask::SetupDisplayInternal() {
|
||||
ViDisplay temp_display;
|
||||
/* Try to open the display. */
|
||||
R_TRY_CATCH(viOpenDisplay("Internal", std::addressof(temp_display))) {
|
||||
R_CONVERT(vi::ResultNotFound, ResultSuccess());
|
||||
} R_END_TRY_CATCH;
|
||||
|
||||
/* Guarantee we close the display. */
|
||||
ON_SCOPE_EXIT { viCloseDisplay(std::addressof(temp_display)); };
|
||||
|
||||
/* Turn on the screen. */
|
||||
if (hos::GetVersion() >= hos::Version_3_0_0) {
|
||||
R_TRY(viSetDisplayPowerState(std::addressof(temp_display), ViPowerState_On));
|
||||
} else {
|
||||
/* Prior to 3.0.0, the ViPowerState enum was different (0 = Off, 1 = On). */
|
||||
R_TRY(viSetDisplayPowerState(std::addressof(temp_display), ViPowerState_On_Deprecated));
|
||||
}
|
||||
|
||||
/* Set alpha to 1.0f. */
|
||||
R_TRY(viSetDisplayAlpha(std::addressof(temp_display), 1.0f));
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result ShowFatalTask::SetupDisplayExternal() {
|
||||
ViDisplay temp_display;
|
||||
/* Try to open the display. */
|
||||
R_TRY_CATCH(viOpenDisplay("External", std::addressof(temp_display))) {
|
||||
R_CONVERT(vi::ResultNotFound, ResultSuccess());
|
||||
} R_END_TRY_CATCH;
|
||||
|
||||
/* Guarantee we close the display. */
|
||||
ON_SCOPE_EXIT { viCloseDisplay(std::addressof(temp_display)); };
|
||||
|
||||
/* Set alpha to 1.0f. */
|
||||
R_TRY(viSetDisplayAlpha(std::addressof(temp_display), 1.0f));
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result ShowFatalTask::PrepareScreenForDrawing() {
|
||||
/* Connect to vi. */
|
||||
R_TRY(viInitialize(ViServiceType_Manager));
|
||||
|
||||
/* Close other content. */
|
||||
viSetContentVisibility(false);
|
||||
|
||||
/* Setup the two displays. */
|
||||
R_TRY(SetupDisplayInternal());
|
||||
R_TRY(SetupDisplayExternal());
|
||||
|
||||
/* Open the default display. */
|
||||
R_TRY(viOpenDefaultDisplay(std::addressof(m_display)));
|
||||
|
||||
/* Reset the display magnification to its default value. */
|
||||
s32 display_width, display_height;
|
||||
R_TRY(viGetDisplayLogicalResolution(std::addressof(m_display), std::addressof(display_width), std::addressof(display_height)));
|
||||
|
||||
/* viSetDisplayMagnification was added in 3.0.0. */
|
||||
if (hos::GetVersion() >= hos::Version_3_0_0) {
|
||||
R_TRY(viSetDisplayMagnification(std::addressof(m_display), 0, 0, display_width, display_height));
|
||||
}
|
||||
|
||||
/* Create layer to draw to. */
|
||||
R_TRY(viCreateLayer(std::addressof(m_display), std::addressof(m_layer)));
|
||||
|
||||
/* Setup the layer. */
|
||||
{
|
||||
/* Display a layer of 1280 x 720 at 1.5x magnification */
|
||||
/* NOTE: N uses 2 (770x400) RGBA4444 buffers (tiled buffer + linear). */
|
||||
/* We use a single 1280x720 tiled RGB565 buffer. */
|
||||
constexpr s32 RawWidth = FatalScreenWidth;
|
||||
constexpr s32 RawHeight = FatalScreenHeight;
|
||||
constexpr s32 LayerWidth = ((RawWidth) * 3) / 2;
|
||||
constexpr s32 LayerHeight = ((RawHeight) * 3) / 2;
|
||||
|
||||
const float layer_x = static_cast<float>((display_width - LayerWidth) / 2);
|
||||
const float layer_y = static_cast<float>((display_height - LayerHeight) / 2);
|
||||
|
||||
R_TRY(viSetLayerSize(std::addressof(m_layer), LayerWidth, LayerHeight));
|
||||
|
||||
/* Set the layer's Z at display maximum, to be above everything else .*/
|
||||
R_TRY(viSetLayerZ(std::addressof(m_layer), FatalLayerZ));
|
||||
|
||||
/* Center the layer in the screen. */
|
||||
R_TRY(viSetLayerPosition(std::addressof(m_layer), layer_x, layer_y));
|
||||
|
||||
/* Create framebuffer. */
|
||||
R_TRY(nwindowCreateFromLayer(std::addressof(m_win), std::addressof(m_layer)));
|
||||
R_TRY(this->InitializeNativeWindow());
|
||||
}
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
void ShowFatalTask::PreRenderFrameBuffer() {
|
||||
const FatalConfig &config = GetFatalConfig();
|
||||
|
||||
/* Allocate a frame buffer. */
|
||||
InitializeFrameBufferPointer();
|
||||
AMS_ABORT_UNLESS(g_framebuffer_pointer != nullptr);
|
||||
|
||||
/* Pre-render the image into the static framebuffer. */
|
||||
u16 *tiled_buf = reinterpret_cast<u16 *>(g_framebuffer_pointer);
|
||||
|
||||
/* Temporarily use the NV transfer memory as font backing heap. */
|
||||
font::SetHeapMemory(g_nv_transfer_memory, sizeof(g_nv_transfer_memory));
|
||||
ON_SCOPE_EXIT { std::memset(g_nv_transfer_memory, 0, sizeof(g_nv_transfer_memory)); };
|
||||
|
||||
/* Let the font manager know about our framebuffer. */
|
||||
font::ConfigureFontFramebuffer(tiled_buf, GetPixelOffset);
|
||||
font::SetFontColor(0xFFFF);
|
||||
|
||||
/* Draw a background. */
|
||||
for (size_t i = 0; i < FrameBufferRequiredSizeBytes / sizeof(*tiled_buf); i++) {
|
||||
tiled_buf[i] = AtmosphereLogoData[0];
|
||||
}
|
||||
|
||||
/* Draw the atmosphere logo in the upper right corner. */
|
||||
const u32 start_x = 32, start_y = 64;
|
||||
for (size_t y = 0; y < AtmosphereLogoHeight; y++) {
|
||||
for (size_t x = 0; x < AtmosphereLogoWidth; x++) {
|
||||
tiled_buf[GetPixelOffset(FatalScreenWidth - AtmosphereLogoWidth - start_x + x, start_x + y)] = AtmosphereLogoData[y * AtmosphereLogoWidth + x];
|
||||
}
|
||||
}
|
||||
|
||||
/* Draw error message and firmware. */
|
||||
font::SetPosition(start_x, start_y);
|
||||
font::SetFontSize(16.0f);
|
||||
font::PrintFormat(config.GetErrorMessage(), m_context->result.GetModule(), m_context->result.GetDescription(), m_context->result.GetValue());
|
||||
font::AddSpacingLines(0.5f);
|
||||
font::PrintFormatLine( "Program: %016lX", static_cast<u64>(m_context->program_id));
|
||||
font::AddSpacingLines(0.5f);
|
||||
|
||||
font::PrintFormatLine("Firmware: %s (Atmosphère %u.%u.%u-%s)", config.GetFirmwareVersion().display_version, ATMOSPHERE_RELEASE_VERSION, ams::GetGitRevision());
|
||||
font::AddSpacingLines(1.5f);
|
||||
if (!exosphere::ResultVersionMismatch::Includes(m_context->result)) {
|
||||
font::Print(config.GetErrorDescription());
|
||||
} else {
|
||||
/* Print a special message for atmosphere version mismatch. */
|
||||
font::Print("Atmosphère version mismatch detected.\n\n"
|
||||
"Please press the POWER Button to restart the console normally, or a VOL button\n"
|
||||
"to reboot to a payload (or RCM, if none is present). If you are unable to\n"
|
||||
"restart the console, hold the POWER Button for 12 seconds to turn the console off.\n\n"
|
||||
"Please ensure that all Atmosphère components are updated.\n"
|
||||
"github.com/Atmosphere-NX/Atmosphere/releases\n");
|
||||
}
|
||||
|
||||
/* Add a line. */
|
||||
for (size_t x = start_x; x < FatalScreenWidth - start_x; x++) {
|
||||
tiled_buf[GetPixelOffset(x, font::GetY())] = 0xFFFF;
|
||||
}
|
||||
|
||||
font::AddSpacingLines(1.5f);
|
||||
|
||||
u32 backtrace_y = font::GetY();
|
||||
u32 backtrace_x = 0;
|
||||
u32 pc_x = 0;
|
||||
|
||||
/* Note architecutre. */
|
||||
const bool is_aarch32 = m_context->cpu_ctx.architecture == CpuContext::Architecture_Aarch32;
|
||||
|
||||
/* Print GPRs. */
|
||||
font::SetFontSize(14.0f);
|
||||
font::Print("General Purpose Registers ");
|
||||
font::PrintLine("");
|
||||
font::SetPosition(start_x, font::GetY());
|
||||
font::AddSpacingLines(0.5f);
|
||||
if (is_aarch32) {
|
||||
for (size_t i = 0; i < (aarch32::RegisterName_GeneralPurposeCount / 2); i++) {
|
||||
u32 x = font::GetX();
|
||||
font::PrintFormat("%s:", aarch32::CpuContext::RegisterNameStrings[i]);
|
||||
font::SetPosition(x + 47, font::GetY());
|
||||
if (m_context->cpu_ctx.aarch32_ctx.HasRegisterValue(static_cast<aarch32::RegisterName>(i))) {
|
||||
font::PrintMonospaceU32(m_context->cpu_ctx.aarch32_ctx.r[i]);
|
||||
font::PrintMonospaceBlank(8);
|
||||
} else {
|
||||
font::PrintMonospaceBlank(16);
|
||||
}
|
||||
font::Print(" ");
|
||||
pc_x = font::GetX();
|
||||
font::PrintFormat("%s:", aarch32::CpuContext::RegisterNameStrings[i + (aarch32::RegisterName_GeneralPurposeCount / 2)]);
|
||||
font::SetPosition(pc_x + 47, font::GetY());
|
||||
if (m_context->cpu_ctx.aarch32_ctx.HasRegisterValue(static_cast<aarch32::RegisterName>(i + (aarch32::RegisterName_GeneralPurposeCount / 2)))) {
|
||||
font::PrintMonospaceU32(m_context->cpu_ctx.aarch32_ctx.r[i + (aarch32::RegisterName_GeneralPurposeCount / 2)]);
|
||||
font::PrintMonospaceBlank(8);
|
||||
} else {
|
||||
font::PrintMonospaceBlank(16);
|
||||
}
|
||||
|
||||
if (i == (aarch32::RegisterName_GeneralPurposeCount / 2) - 1) {
|
||||
font::Print(" ");
|
||||
backtrace_x = font::GetX();
|
||||
}
|
||||
|
||||
font::PrintLine("");
|
||||
font::SetPosition(start_x, font::GetY());
|
||||
}
|
||||
} else {
|
||||
for (size_t i = 0; i < aarch64::RegisterName_GeneralPurposeCount / 2; i++) {
|
||||
u32 x = font::GetX();
|
||||
font::PrintFormat("%s:", aarch64::CpuContext::RegisterNameStrings[i]);
|
||||
font::SetPosition(x + 47, font::GetY());
|
||||
if (m_context->cpu_ctx.aarch64_ctx.HasRegisterValue(static_cast<aarch64::RegisterName>(i))) {
|
||||
font::PrintMonospaceU64(m_context->cpu_ctx.aarch64_ctx.x[i]);
|
||||
} else {
|
||||
font::PrintMonospaceBlank(16);
|
||||
}
|
||||
font::Print(" ");
|
||||
pc_x = font::GetX();
|
||||
font::PrintFormat("%s:", aarch64::CpuContext::RegisterNameStrings[i + (aarch64::RegisterName_GeneralPurposeCount / 2)]);
|
||||
font::SetPosition(pc_x + 47, font::GetY());
|
||||
if (m_context->cpu_ctx.aarch64_ctx.HasRegisterValue(static_cast<aarch64::RegisterName>(i + (aarch64::RegisterName_GeneralPurposeCount / 2)))) {
|
||||
font::PrintMonospaceU64(m_context->cpu_ctx.aarch64_ctx.x[i + (aarch64::RegisterName_GeneralPurposeCount / 2)]);
|
||||
} else {
|
||||
font::PrintMonospaceBlank(16);
|
||||
}
|
||||
|
||||
if (i == (aarch64::RegisterName_GeneralPurposeCount / 2) - 1) {
|
||||
font::Print(" ");
|
||||
backtrace_x = font::GetX();
|
||||
}
|
||||
|
||||
font::PrintLine("");
|
||||
font::SetPosition(start_x, font::GetY());
|
||||
}
|
||||
}
|
||||
|
||||
/* Print PC. */
|
||||
{
|
||||
font::SetPosition(pc_x, backtrace_y);
|
||||
const u32 x = font::GetX();
|
||||
font::Print("PC: ");
|
||||
font::SetPosition(x + 47, font::GetY());
|
||||
}
|
||||
if (is_aarch32) {
|
||||
font::PrintMonospaceU32(m_context->cpu_ctx.aarch32_ctx.pc);
|
||||
} else {
|
||||
font::PrintMonospaceU64(m_context->cpu_ctx.aarch64_ctx.pc);
|
||||
}
|
||||
|
||||
/* Print Backtrace. */
|
||||
u32 bt_size;
|
||||
if (is_aarch32) {
|
||||
bt_size = m_context->cpu_ctx.aarch32_ctx.stack_trace_size;
|
||||
} else {
|
||||
bt_size = m_context->cpu_ctx.aarch64_ctx.stack_trace_size;
|
||||
}
|
||||
|
||||
|
||||
font::SetPosition(backtrace_x, backtrace_y);
|
||||
if (bt_size == 0) {
|
||||
if (is_aarch32) {
|
||||
font::Print("Start Address: ");
|
||||
font::PrintMonospaceU32(m_context->cpu_ctx.aarch32_ctx.base_address);
|
||||
font::PrintLine("");
|
||||
} else {
|
||||
font::Print("Start Address: ");
|
||||
font::PrintMonospaceU64(m_context->cpu_ctx.aarch64_ctx.base_address);
|
||||
font::PrintLine("");
|
||||
}
|
||||
} else {
|
||||
if (is_aarch32) {
|
||||
font::Print("Backtrace - Start Address: ");
|
||||
font::PrintMonospaceU32(m_context->cpu_ctx.aarch32_ctx.base_address);
|
||||
font::PrintLine("");
|
||||
font::AddSpacingLines(0.5f);
|
||||
for (u32 i = 0; i < aarch32::CpuContext::MaxStackTraceDepth / 2; i++) {
|
||||
u32 bt_cur = 0, bt_next = 0;
|
||||
if (i < m_context->cpu_ctx.aarch32_ctx.stack_trace_size) {
|
||||
bt_cur = m_context->cpu_ctx.aarch32_ctx.stack_trace[i];
|
||||
}
|
||||
if (i + aarch32::CpuContext::MaxStackTraceDepth / 2 < m_context->cpu_ctx.aarch32_ctx.stack_trace_size) {
|
||||
bt_next = m_context->cpu_ctx.aarch32_ctx.stack_trace[i + aarch32::CpuContext::MaxStackTraceDepth / 2];
|
||||
}
|
||||
|
||||
if (i < m_context->cpu_ctx.aarch32_ctx.stack_trace_size) {
|
||||
u32 x = font::GetX();
|
||||
font::PrintFormat("BT[%02d]: ", i);
|
||||
font::SetPosition(x + 72, font::GetY());
|
||||
font::PrintMonospaceU32(bt_cur);
|
||||
font::PrintMonospaceBlank(8);
|
||||
font::Print(" ");
|
||||
}
|
||||
|
||||
if (i + aarch32::CpuContext::MaxStackTraceDepth / 2 < m_context->cpu_ctx.aarch32_ctx.stack_trace_size) {
|
||||
u32 x = font::GetX();
|
||||
font::PrintFormat("BT[%02d]: ", i + aarch32::CpuContext::MaxStackTraceDepth / 2);
|
||||
font::SetPosition(x + 72, font::GetY());
|
||||
font::PrintMonospaceU32(bt_next);
|
||||
font::PrintMonospaceBlank(8);
|
||||
}
|
||||
|
||||
font::PrintLine("");
|
||||
font::SetPosition(backtrace_x, font::GetY());
|
||||
}
|
||||
} else {
|
||||
font::Print("Backtrace - Start Address: ");
|
||||
font::PrintMonospaceU64(m_context->cpu_ctx.aarch64_ctx.base_address);
|
||||
font::PrintLine("");
|
||||
font::AddSpacingLines(0.5f);
|
||||
for (u32 i = 0; i < aarch64::CpuContext::MaxStackTraceDepth / 2; i++) {
|
||||
u64 bt_cur = 0, bt_next = 0;
|
||||
if (i < m_context->cpu_ctx.aarch64_ctx.stack_trace_size) {
|
||||
bt_cur = m_context->cpu_ctx.aarch64_ctx.stack_trace[i];
|
||||
}
|
||||
if (i + aarch64::CpuContext::MaxStackTraceDepth / 2 < m_context->cpu_ctx.aarch64_ctx.stack_trace_size) {
|
||||
bt_next = m_context->cpu_ctx.aarch64_ctx.stack_trace[i + aarch64::CpuContext::MaxStackTraceDepth / 2];
|
||||
}
|
||||
|
||||
if (i < m_context->cpu_ctx.aarch64_ctx.stack_trace_size) {
|
||||
u32 x = font::GetX();
|
||||
font::PrintFormat("BT[%02d]: ", i);
|
||||
font::SetPosition(x + 72, font::GetY());
|
||||
font::PrintMonospaceU64(bt_cur);
|
||||
font::Print(" ");
|
||||
}
|
||||
|
||||
if (i + aarch64::CpuContext::MaxStackTraceDepth / 2 < m_context->cpu_ctx.aarch64_ctx.stack_trace_size) {
|
||||
u32 x = font::GetX();
|
||||
font::PrintFormat("BT[%02d]: ", i + aarch64::CpuContext::MaxStackTraceDepth / 2);
|
||||
font::SetPosition(x + 72, font::GetY());
|
||||
font::PrintMonospaceU64(bt_next);
|
||||
}
|
||||
|
||||
font::PrintLine("");
|
||||
font::SetPosition(backtrace_x, font::GetY());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Result ShowFatalTask::InitializeNativeWindow() {
|
||||
/* Setup nv driver. */
|
||||
R_TRY(nvInitialize());
|
||||
R_TRY(nvMapInit());
|
||||
R_TRY(nvFenceInit());
|
||||
|
||||
/* Create nvmap. */
|
||||
R_TRY(nvMapCreate(std::addressof(m_map), g_framebuffer_pointer, FrameBufferRequiredSizeBytes, 0x20000, NvKind_Pitch, true));
|
||||
|
||||
/* Setup graphics buffer. */
|
||||
{
|
||||
NvGraphicBuffer grbuf = {};
|
||||
grbuf.header.num_ints = (sizeof(NvGraphicBuffer) - sizeof(NativeHandle)) / 4;
|
||||
grbuf.unk0 = -1;
|
||||
grbuf.magic = 0xDAFFCAFF;
|
||||
grbuf.pid = 42;
|
||||
grbuf.usage = GRALLOC_USAGE_HW_COMPOSER | GRALLOC_USAGE_HW_RENDER | GRALLOC_USAGE_HW_TEXTURE;
|
||||
grbuf.format = PIXEL_FORMAT_RGB_565;
|
||||
grbuf.ext_format = PIXEL_FORMAT_RGB_565;
|
||||
grbuf.num_planes = 1;
|
||||
grbuf.planes[0].width = FatalScreenWidth;
|
||||
grbuf.planes[0].height = FatalScreenHeight;
|
||||
grbuf.planes[0].color_format = NvColorFormat_R5G6B5;
|
||||
grbuf.planes[0].layout = NvLayout_BlockLinear;
|
||||
grbuf.planes[0].kind = NvKind_Generic_16BX2;
|
||||
grbuf.planes[0].block_height_log2 = 4;
|
||||
grbuf.nvmap_id = nvMapGetId(std::addressof(m_map));
|
||||
grbuf.stride = FatalScreenWidthAligned;
|
||||
grbuf.total_size = FrameBufferRequiredSizeBytes;
|
||||
grbuf.planes[0].pitch = FatalScreenWidthAlignedBytes;
|
||||
grbuf.planes[0].size = FrameBufferRequiredSizeBytes;
|
||||
grbuf.planes[0].offset = 0;
|
||||
|
||||
R_TRY(nwindowConfigureBuffer(std::addressof(m_win), 0, std::addressof(grbuf)));
|
||||
}
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
void ShowFatalTask::DisplayPreRenderedFrame() {
|
||||
s32 slot;
|
||||
R_ABORT_UNLESS(nwindowDequeueBuffer(std::addressof(m_win), std::addressof(slot), nullptr));
|
||||
dd::FlushDataCache(g_framebuffer_pointer, FrameBufferRequiredSizeBytes);
|
||||
R_ABORT_UNLESS(nwindowQueueBuffer(std::addressof(m_win), m_win.cur_slot, NULL));
|
||||
}
|
||||
|
||||
Result ShowFatalTask::ShowFatal() {
|
||||
/* Pre-render the framebuffer. */
|
||||
PreRenderFrameBuffer();
|
||||
|
||||
/* Prepare screen for drawing. */
|
||||
R_ABORT_UNLESS(PrepareScreenForDrawing());
|
||||
|
||||
/* Display the pre-rendered frame. */
|
||||
this->DisplayPreRenderedFrame();
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result ShowFatalTask::Run() {
|
||||
/* Don't show the fatal error screen until we've verified the battery is okay. */
|
||||
m_context->battery_event->Wait();
|
||||
|
||||
R_RETURN(ShowFatal());
|
||||
}
|
||||
|
||||
void BacklightControlTask::TurnOnBacklight() {
|
||||
R_ABORT_UNLESS(::lblInitialize());
|
||||
::lblSwitchBacklightOn(0);
|
||||
::lblExit();
|
||||
}
|
||||
|
||||
Result BacklightControlTask::Run() {
|
||||
TurnOnBacklight();
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ITask *GetShowFatalTask(const ThrowContext *ctx) {
|
||||
g_show_fatal_task.Initialize(ctx);
|
||||
return std::addressof(g_show_fatal_task);
|
||||
}
|
||||
|
||||
ITask *GetBacklightControlTask(const ThrowContext *ctx) {
|
||||
g_backlight_control_task.Initialize(ctx);
|
||||
return std::addressof(g_backlight_control_task);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include "fatal_task.hpp"
|
||||
|
||||
namespace ams::fatal::srv {
|
||||
|
||||
ITask *GetShowFatalTask(const ThrowContext *ctx);
|
||||
ITask *GetBacklightControlTask(const ThrowContext *ctx);
|
||||
|
||||
}
|
||||
@@ -1,95 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <stratosphere.hpp>
|
||||
#include "fatal_task_sound.hpp"
|
||||
|
||||
namespace ams::fatal::srv {
|
||||
|
||||
namespace {
|
||||
|
||||
/* Task definition. */
|
||||
class StopSoundTask : public ITaskWithDefaultStack {
|
||||
private:
|
||||
void StopSound();
|
||||
public:
|
||||
virtual Result Run() override;
|
||||
virtual const char *GetName() const override {
|
||||
return "SoundTask";
|
||||
}
|
||||
};
|
||||
|
||||
/* Task global. */
|
||||
StopSoundTask g_stop_sound_task;
|
||||
|
||||
/* Task implementation. */
|
||||
void StopSoundTask::StopSound() {
|
||||
/* Talk to the ALC5639 over I2C, and disable audio output. */
|
||||
{
|
||||
I2cSession audio;
|
||||
if (R_SUCCEEDED(i2cOpenSession(std::addressof(audio), I2cDevice_Alc5639))) {
|
||||
ON_SCOPE_EXIT { i2csessionClose(std::addressof(audio)); };
|
||||
|
||||
struct {
|
||||
u8 reg;
|
||||
u16 val;
|
||||
} __attribute__((packed)) cmd;
|
||||
static_assert(sizeof(cmd) == 3, "I2C command definition!");
|
||||
|
||||
cmd.reg = 0x01;
|
||||
cmd.val = 0xC8C8;
|
||||
i2csessionSendAuto(std::addressof(audio), std::addressof(cmd), sizeof(cmd), I2cTransactionOption_All);
|
||||
|
||||
cmd.reg = 0x02;
|
||||
cmd.val = 0xC8C8;
|
||||
i2csessionSendAuto(std::addressof(audio), std::addressof(cmd), sizeof(cmd), I2cTransactionOption_All);
|
||||
|
||||
for (u8 reg = 97; reg <= 102; reg++) {
|
||||
cmd.reg = reg;
|
||||
cmd.val = 0;
|
||||
i2csessionSendAuto(std::addressof(audio), std::addressof(cmd), sizeof(cmd), I2cTransactionOption_All);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Talk to the ALC5639 over GPIO, and disable audio output */
|
||||
{
|
||||
gpio::GpioPadSession audio;
|
||||
if (R_SUCCEEDED(gpio::OpenSession(std::addressof(audio), gpio::DeviceCode_CodecLdoEnTemp))) {
|
||||
ON_SCOPE_EXIT { gpio::CloseSession(std::addressof(audio)); };
|
||||
|
||||
/* Set direction output, sleep 200 ms so it can take effect. */
|
||||
gpio::SetDirection(std::addressof(audio), gpio::Direction_Output);
|
||||
os::SleepThread(TimeSpan::FromMilliSeconds(200));
|
||||
|
||||
/* Pull audio codec low. */
|
||||
gpio::SetValue(std::addressof(audio), gpio::GpioValue_Low);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Result StopSoundTask::Run() {
|
||||
StopSound();
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ITask *GetStopSoundTask(const ThrowContext *ctx) {
|
||||
g_stop_sound_task.Initialize(ctx);
|
||||
return std::addressof(g_stop_sound_task);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include "fatal_task.hpp"
|
||||
|
||||
namespace ams::fatal::srv {
|
||||
|
||||
ITask *GetStopSoundTask(const ThrowContext *ctx);
|
||||
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user