mirror of
				https://github.com/Atmosphere-NX/Atmosphere-libs.git
				synced 2025-10-30 19:15:47 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			332 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			332 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /*
 | |
|  * 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 "erpt_srv_forced_shutdown.hpp"
 | |
| #include "erpt_srv_context.hpp"
 | |
| #include "erpt_srv_context_record.hpp"
 | |
| #include "erpt_srv_reporter.hpp"
 | |
| #include "erpt_srv_stream.hpp"
 | |
| 
 | |
| namespace ams::erpt::srv {
 | |
| 
 | |
|     namespace {
 | |
| 
 | |
|         constexpr u32 ForcedShutdownContextBufferSize = 1_KB;
 | |
| 
 | |
|         constexpr u32 ForcedShutdownContextVersion = 1;
 | |
| 
 | |
|         struct ForcedShutdownContextHeader {
 | |
|             u32 version;
 | |
|             u32 num_contexts;
 | |
|         };
 | |
|         static_assert(sizeof(ForcedShutdownContextHeader) == 8);
 | |
| 
 | |
|         struct ForcedShutdownContextEntry {
 | |
|             u32 version;
 | |
|             CategoryId category;
 | |
|             u32 field_count;
 | |
|             u32 array_buffer_size;
 | |
|         };
 | |
|         static_assert(sizeof(ForcedShutdownContextEntry) == 16);
 | |
| 
 | |
|         os::Event g_forced_shutdown_update_event(os::EventClearMode_ManualClear);
 | |
| 
 | |
|         constinit ContextEntry g_forced_shutdown_contexts[] = {
 | |
|             { .category = CategoryId_RunningApplicationInfo,   },
 | |
|             { .category = CategoryId_RunningAppletInfo,        },
 | |
|             { .category = CategoryId_FocusedAppletHistoryInfo, },
 | |
|         };
 | |
| 
 | |
|         bool IsForceShutdownDetected() {
 | |
|             fs::DirectoryEntryType entry_type;
 | |
|             return R_SUCCEEDED(fs::GetEntryType(std::addressof(entry_type), ForcedShutdownContextFileName));
 | |
|         }
 | |
| 
 | |
|         Result CreateForcedShutdownContext() {
 | |
|             /* Create the context. */
 | |
|             {
 | |
|                 /* Create the stream. */
 | |
|                 Stream stream;
 | |
|                 R_TRY(stream.OpenStream(ForcedShutdownContextFileName, StreamMode_Write, 0));
 | |
| 
 | |
|                 /* Write a context header. */
 | |
|                 const ForcedShutdownContextHeader header = { .version = ForcedShutdownContextVersion, .num_contexts = 0, };
 | |
|                 R_TRY(stream.WriteStream(reinterpret_cast<const u8 *>(std::addressof(header)), sizeof(header)));
 | |
|             }
 | |
| 
 | |
|             /* Commit the context. */
 | |
|             R_TRY(Stream::CommitStream());
 | |
| 
 | |
|             R_SUCCEED();
 | |
|         }
 | |
| 
 | |
|         Result CreateReportForForcedShutdown() {
 | |
|             /* Create a new context record. */
 | |
|             /* NOTE: Nintendo does not check that this allocation succeeds. */
 | |
|             auto record = std::make_unique<ContextRecord>(CategoryId_ErrorInfo);
 | |
| 
 | |
|             /* Create error code for the report. */
 | |
|             char error_code_str[err::ErrorCode::StringLengthMax];
 | |
|             err::GetErrorCodeString(error_code_str, sizeof(error_code_str), err::ConvertResultToErrorCode(err::ResultForcedShutdownDetected()));
 | |
| 
 | |
|             /* Add error code to the context. */
 | |
|             R_TRY(record->Add(FieldId_ErrorCode, error_code_str, std::strlen(error_code_str)));
 | |
| 
 | |
|             /* Create report. */
 | |
|             R_TRY(Reporter::CreateReport(ReportType_Invisible, ResultSuccess(), std::move(record), nullptr, nullptr, 0, erpt::srv::MakeNoCreateReportOptionFlags(), nullptr));
 | |
| 
 | |
|             R_SUCCEED();
 | |
|         }
 | |
| 
 | |
|         Result LoadForcedShutdownContext() {
 | |
|             /* Create the stream to read the context. */
 | |
|             Stream stream;
 | |
|             R_TRY(stream.OpenStream(ForcedShutdownContextFileName, StreamMode_Read, ForcedShutdownContextBufferSize));
 | |
| 
 | |
|             /* Read the header. */
 | |
|             u32 read_size;
 | |
|             ForcedShutdownContextHeader header;
 | |
|             R_TRY(stream.ReadStream(std::addressof(read_size), reinterpret_cast<u8 *>(std::addressof(header)), sizeof(header)));
 | |
| 
 | |
|             /* Validate the header. */
 | |
|             R_SUCCEED_IF(read_size != sizeof(header));
 | |
|             R_SUCCEED_IF(ForcedShutdownContextVersion);
 | |
|             R_SUCCEED_IF(header.num_contexts == 0);
 | |
| 
 | |
|             /* Read out the contexts. */
 | |
|             for (u32 i = 0; i < header.num_contexts; ++i) {
 | |
|                 /* Read the context entry header. */
 | |
|                 ForcedShutdownContextEntry entry_header;
 | |
|                 R_TRY(stream.ReadStream(std::addressof(read_size), reinterpret_cast<u8 *>(std::addressof(entry_header)), sizeof(entry_header)));
 | |
| 
 | |
|                 if (read_size != sizeof(entry_header)) {
 | |
|                     break;
 | |
|                 }
 | |
| 
 | |
|                 if (entry_header.field_count == 0) {
 | |
|                     continue;
 | |
|                 }
 | |
| 
 | |
|                 /* Read the saved data into a context entry. */
 | |
|                 ContextEntry ctx = {
 | |
|                     .version      = entry_header.version,
 | |
|                     .field_count  = entry_header.field_count,
 | |
|                     .category     = entry_header.category,
 | |
|                 };
 | |
| 
 | |
|                 /* Check that the field count is valid. */
 | |
|                 AMS_ABORT_UNLESS(entry_header.field_count <= util::size(ctx.fields));
 | |
| 
 | |
|                 /* Read the fields. */
 | |
|                 R_TRY(stream.ReadStream(std::addressof(read_size), reinterpret_cast<u8 *>(std::addressof(ctx.fields)), entry_header.field_count * sizeof(ctx.fields[0])));
 | |
|                 if (read_size != entry_header.field_count * sizeof(ctx.fields[0])) {
 | |
|                     break;
 | |
|                 }
 | |
| 
 | |
|                 /* Allocate an array buffer. */
 | |
|                 u8 *array_buffer = static_cast<u8 *>(Allocate(entry_header.array_buffer_size));
 | |
|                 if (array_buffer == nullptr) {
 | |
|                     break;
 | |
|                 }
 | |
|                 ON_SCOPE_EXIT { Deallocate(array_buffer); };
 | |
| 
 | |
|                 /* Read the array buffer data. */
 | |
|                 R_TRY(stream.ReadStream(std::addressof(read_size), array_buffer, entry_header.array_buffer_size));
 | |
|                 if (read_size != entry_header.array_buffer_size) {
 | |
|                     break;
 | |
|                 }
 | |
| 
 | |
|                 /* Create a record for the context. */
 | |
|                 auto record = std::make_unique<ContextRecord>();
 | |
|                 if (record == nullptr) {
 | |
|                     break;
 | |
|                 }
 | |
| 
 | |
|                 /* Initialize the record. */
 | |
|                 R_TRY(record->Initialize(std::addressof(ctx), array_buffer, entry_header.array_buffer_size));
 | |
| 
 | |
|                 /* Submit the record. */
 | |
|                 R_TRY(Context::SubmitContextRecord(std::move(record)));
 | |
|             }
 | |
| 
 | |
|             R_SUCCEED();
 | |
|         }
 | |
| 
 | |
|         u32 GetForcedShutdownContextCount() {
 | |
|             u32 count = 0;
 | |
|             for (const auto &ctx : g_forced_shutdown_contexts) {
 | |
|                 if (ctx.field_count != 0) {
 | |
|                     ++count;
 | |
|                 }
 | |
|             }
 | |
|             return count;
 | |
|         }
 | |
| 
 | |
|         Result SaveForcedShutdownContextImpl() {
 | |
|             /* Save context to file. */
 | |
|             {
 | |
|                 /* Create the stream to write the context. */
 | |
|                 Stream stream;
 | |
|                 R_TRY(stream.OpenStream(ForcedShutdownContextFileName, StreamMode_Write, ForcedShutdownContextBufferSize));
 | |
| 
 | |
|                 /* Write a context header. */
 | |
|                 const ForcedShutdownContextHeader header = { .version = ForcedShutdownContextVersion, .num_contexts = GetForcedShutdownContextCount(), };
 | |
|                 R_TRY(stream.WriteStream(reinterpret_cast<const u8 *>(std::addressof(header)), sizeof(header)));
 | |
| 
 | |
|                 /* Write each context. */
 | |
|                 for (const auto &ctx : g_forced_shutdown_contexts) {
 | |
|                     /* If the context has no fields, continue. */
 | |
|                     if (ctx.field_count == 0) {
 | |
|                         continue;
 | |
|                     }
 | |
| 
 | |
|                     /* Write a context entry header. */
 | |
|                     const ForcedShutdownContextEntry entry_header = {
 | |
|                         .version           = ctx.version,
 | |
|                         .category          = ctx.category,
 | |
|                         .field_count       = ctx.field_count,
 | |
|                         .array_buffer_size = ctx.array_buffer_size,
 | |
|                     };
 | |
|                     R_TRY(stream.WriteStream(reinterpret_cast<const u8 *>(std::addressof(entry_header)), sizeof(entry_header)));
 | |
| 
 | |
|                     /* Write all fields. */
 | |
|                     for (u32 i = 0; i < ctx.field_count; ++i) {
 | |
|                         R_TRY(stream.WriteStream(reinterpret_cast<const u8 *>(ctx.fields + i), sizeof(ctx.fields[0])));
 | |
|                     }
 | |
| 
 | |
|                     /* Write the array buffer. */
 | |
|                     R_TRY(stream.WriteStream(ctx.array_buffer, ctx.array_buffer_size));
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             /* Commit the context. */
 | |
|             R_TRY(Stream::CommitStream());
 | |
| 
 | |
|             R_SUCCEED();
 | |
|         }
 | |
| 
 | |
|     }
 | |
| 
 | |
|     os::Event *GetForcedShutdownUpdateEvent() {
 | |
|         return std::addressof(g_forced_shutdown_update_event);
 | |
|     }
 | |
| 
 | |
|     void InitializeForcedShutdownDetection() {
 | |
|         /* Check if the forced shutdown context exists; if it doesn't, we should create an empty one. */
 | |
|         if (!IsForceShutdownDetected()) {
 | |
|             /* NOTE: Nintendo does not check result here. */
 | |
|             CreateForcedShutdownContext();
 | |
|             return;
 | |
|         }
 | |
| 
 | |
|         /* Load the forced shutdown context. */
 | |
|         /* NOTE: Nintendo does not check that this succeeds. */
 | |
|         LoadForcedShutdownContext();
 | |
| 
 | |
|         /* Create report for the forced shutdown. */
 | |
|         /* NOTE: Nintendo does not check that this succeeds. */
 | |
|         CreateReportForForcedShutdown();
 | |
| 
 | |
|         /* Clear the forced shutdown categories. */
 | |
|         /* NOTE: Nintendo does not check that this succeeds. */
 | |
|         Context::ClearContext(CategoryId_RunningApplicationInfo);
 | |
|         Context::ClearContext(CategoryId_RunningAppletInfo);
 | |
|         Context::ClearContext(CategoryId_FocusedAppletHistoryInfo);
 | |
| 
 | |
|         /* Save the forced shutdown context. */
 | |
|         /* NOTE: Nintendo does not check that this succeeds. */
 | |
|         SaveForcedShutdownContext();
 | |
|     }
 | |
| 
 | |
|     void FinalizeForcedShutdownDetection() {
 | |
|         /* Try to delete the context. */
 | |
|         const Result result = Stream::DeleteStream(ForcedShutdownContextFileName);
 | |
|         if (!fs::ResultPathNotFound::Includes(result)) {
 | |
|             /* We must have succeeded, if the file existed. */
 | |
|             R_ABORT_UNLESS(result);
 | |
| 
 | |
|             /* Commit the deletion. */
 | |
|             R_ABORT_UNLESS(Stream::CommitStream());
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     void SaveForcedShutdownContext() {
 | |
|         /* NOTE: Nintendo does not check that saving the report succeeds. */
 | |
|         SaveForcedShutdownContextImpl();
 | |
|     }
 | |
| 
 | |
|     void SubmitContextForForcedShutdownDetection(const ContextEntry *entry, const u8 *data, u32 data_size) {
 | |
|         /* If the context entry matches one of our tracked categories, update our stored category. */
 | |
|         for (auto &ctx : g_forced_shutdown_contexts) {
 | |
|             /* Check for a match. */
 | |
|             if (ctx.category != entry->category) {
 | |
|                 continue;
 | |
|             }
 | |
| 
 | |
|             /* If we have an existing array buffer, free it. */
 | |
|             if (ctx.array_buffer != nullptr) {
 | |
|                 Deallocate(ctx.array_buffer);
 | |
|                 ctx.array_buffer      = nullptr;
 | |
|                 ctx.array_buffer_size = 0;
 | |
|                 ctx.array_free_count  = 0;
 | |
|             }
 | |
| 
 | |
|             /* Copy in the context. */
 | |
|             ctx = *entry;
 | |
| 
 | |
|             /* Add the submitted data. */
 | |
|             if (data != nullptr && data_size > 0) {
 | |
|                 /* Allocate new array buffer. */
 | |
|                 ctx.array_buffer = static_cast<u8 *>(Allocate(data_size));
 | |
|                 if (ctx.array_buffer == nullptr) {
 | |
|                     /* We failed to allocate; this is okay, but clear our field count. */
 | |
|                     ctx.field_count = 0;
 | |
|                     break;
 | |
|                 }
 | |
| 
 | |
|                 /* Copy in the data. */
 | |
|                 std::memcpy(ctx.array_buffer, data, data_size);
 | |
| 
 | |
|                 /* Set buffer extents. */
 | |
|                 ctx.array_buffer_size = data_size;
 | |
|                 ctx.array_free_count  = 0;
 | |
|             } else {
 | |
|                 ctx.array_buffer      = nullptr;
 | |
|                 ctx.array_buffer_size = 0;
 | |
|                 ctx.array_free_count  = 0;
 | |
|             }
 | |
| 
 | |
|             /* Signal, to notify that we had an update. */
 | |
|             g_forced_shutdown_update_event.Signal();
 | |
| 
 | |
|             /* We're done processing, since we found a match. */
 | |
|             break;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     Result InvalidateForcedShutdownDetection() {
 | |
|         /* Delete the forced shutdown context. */
 | |
|         R_TRY(Stream::DeleteStream(ForcedShutdownContextFileName));
 | |
| 
 | |
|         /* Commit the deletion. */
 | |
|         R_TRY(Stream::CommitStream());
 | |
| 
 | |
|         R_SUCCEED();
 | |
|     }
 | |
| 
 | |
| 
 | |
| }
 |