pgl: Reimplement the pgl sysmodule (#896)

* pgl: add skeleton folder to stratosphere

* pgl: Add service interface for IShellInterface

* pgl: begin skeletoning shell service, implement two commands.

* pgl: Implement three more commands.

* pgl: implement bool tracking commands

* pgl: Implement TriggerApplicationSnapShotDumper

* pgl: implement InitializeProcessControlTask

* pgl: Implement pgl::srv::Initialize

* pgl: Implement main()

* pgl: Implement (Get)ShellEventObserver

* pgl: implement LaunchProgramFromHost, GetHostContentMetaInfo

* pgl: Implement ProcessControlTask

* settings: fix duplicate object name

* pgl: fix minor bugs in impl
This commit is contained in:
SciresM 2020-04-16 19:55:47 -07:00 committed by GitHub
parent f2944d36ba
commit 98cc051387
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
42 changed files with 2218 additions and 57 deletions

View file

@ -16,5 +16,6 @@
#pragma once
#include "ldr/ldr_types.hpp"
#include "ldr/ldr_pm_api.hpp"
#include <stratosphere/ldr/ldr_types.hpp>
#include <stratosphere/ldr/ldr_shell_api.hpp>
#include <stratosphere/ldr/ldr_pm_api.hpp>

View file

@ -0,0 +1,31 @@
/*
* Copyright (c) 2018-2020 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 <vapours.hpp>
#include <stratosphere/ncm/ncm_ids.hpp>
#include <stratosphere/ldr/ldr_types.hpp>
namespace ams::ldr {
/* Shell API. */
Result InitializeForShell();
Result FinalizeForShell();
Result SetProgramArgument(ncm::ProgramId program_id, const void *arg, size_t size);
Result FlushArguments();
}

View file

@ -402,6 +402,34 @@ namespace ams::ncm {
return true;
}
struct SystemDebugAppletId {
u64 value;
constexpr operator ProgramId() const {
return { this->value };
}
static const SystemDebugAppletId Start;
static const SystemDebugAppletId SnapShotDumper;
static const SystemDebugAppletId End;
};
inline constexpr const SystemDebugAppletId SystemDebugAppletId::Start = { 0x0100000000002000ul };
inline constexpr const SystemDebugAppletId SystemDebugAppletId::SnapShotDumper = { 0x0100000000002071ul };
inline constexpr const SystemDebugAppletId SystemDebugAppletId::End = { 0x0100000000002FFFul };
inline constexpr bool IsSystemDebugAppletId(const ProgramId &program_id) {
return SystemDebugAppletId::Start <= program_id && program_id <= SystemDebugAppletId::End;
}
inline constexpr bool IsSystemDebugAppletId(const SystemDebugAppletId &program_id) {
return true;
}
struct LibraryAppletId {
u64 value;

View file

@ -27,6 +27,7 @@
#include <stratosphere/os/os_random.hpp>
#include <stratosphere/os/os_mutex.hpp>
#include <stratosphere/os/os_condition_variable.hpp>
#include <stratosphere/os/os_sdk_mutex.hpp>
#include <stratosphere/os/os_rw_lock.hpp>
#include <stratosphere/os/os_semaphore.hpp>
#include <stratosphere/os/os_event.hpp>

View file

@ -0,0 +1,60 @@
/*
* Copyright (c) 2018-2020 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 <vapours.hpp>
#include <stratosphere/os/impl/os_internal_critical_section.hpp>
namespace ams::os {
class SdkConditionVariable;
struct SdkMutexType {
union {
s32 _arr[sizeof(impl::InternalCriticalSectionStorage) / sizeof(s32)];
impl::InternalCriticalSectionStorage _storage;
};
};
static_assert(std::is_trivial<SdkMutexType>::value);
void InitializeSdkMutex(SdkMutexType *mutex);
void LockSdkMutex(SdkMutexType *mutex);
bool TryLockSdkMutex(SdkMutexType *mutex);
void UnlockSdkMutex(SdkMutexType *mutex);
bool IsSdkMutexLockedByCurrentThread(const SdkMutexType *mutex);
class SdkMutex {
private:
friend class SdkConditionVariable;
private:
SdkMutexType mutex;
public:
constexpr SdkMutex() : mutex{{0}} { /* ... */ }
ALWAYS_INLINE void Lock() { return os::LockSdkMutex(std::addressof(this->mutex)); }
ALWAYS_INLINE bool TryLock() { return os::TryLockSdkMutex(std::addressof(this->mutex)); }
ALWAYS_INLINE void Unlock() { return os::UnlockSdkMutex(std::addressof(this->mutex)); }
ALWAYS_INLINE bool IsLockedByCurrentThread() const { return os::IsSdkMutexLockedByCurrentThread(std::addressof(this->mutex)); }
ALWAYS_INLINE void lock() { return this->Lock(); }
ALWAYS_INLINE bool try_lock() { return this->TryLock(); }
ALWAYS_INLINE void unlock() { return this->Unlock(); }
};
}

View file

@ -19,3 +19,5 @@
#include <stratosphere/pgl/pgl_types.hpp>
#include <stratosphere/pgl/pgl_event_observer.hpp>
#include <stratosphere/pgl/pgl_shell_api.hpp>
#include <stratosphere/pgl/pgl_shell_api.hpp>
#include <stratosphere/pgl/srv/pgl_srv_api.hpp>

View file

@ -34,7 +34,7 @@ namespace ams::pgl {
Result EnableApplicationCrashReport(bool enabled);
Result IsApplicationCrashReportEnabled(bool *out);
Result EnableApplicationAllThreadDumpOnCrash(bool enabled);
Result TriggerSnapShotDumper(const char *arg, SnapShotDumpType dump_type);
Result TriggerApplicationSnapShotDumper(const char *arg, SnapShotDumpType dump_type);
Result GetEventObserver(pgl::EventObserver *out);

View file

@ -23,6 +23,7 @@
namespace ams::pgl {
enum LaunchFlags : u8 {
LaunchFlags_None = 0,
LaunchFlags_EnableDetailedCrashReport = (1 << 0),
LaunchFlags_EnableCrashReportScreenShotForProduction = (1 << 1),
LaunchFlags_EnableCrashReportScreenShotForDevelop = (1 << 2),

View file

@ -0,0 +1,74 @@
/*
* Copyright (c) 2018-2020 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 <vapours.hpp>
#include <stratosphere/os.hpp>
#include <stratosphere/pm.hpp>
#include <stratosphere/pgl/pgl_types.hpp>
#include <stratosphere/pgl/sf/pgl_sf_i_event_observer.hpp>
namespace ams::pgl::sf {
class IShellInterface : public ams::sf::IServiceObject {
protected:
enum class CommandId {
LaunchProgram = 0,
TerminateProcess = 1,
LaunchProgramFromHost = 2,
GetHostContentMetaInfo = 4,
GetApplicationProcessId = 5,
BoostSystemMemoryResourceLimit = 6,
IsProcessTracked = 7,
EnableApplicationCrashReport = 8,
IsApplicationCrashReportEnabled = 9,
EnableApplicationAllThreadDumpOnCrash = 10,
TriggerApplicationSnapShotDumper = 12,
GetShellEventObserver = 20,
};
public:
/* Actual commands. */
virtual Result LaunchProgram(ams::sf::Out<os::ProcessId> out, const ncm::ProgramLocation &loc, u32 pm_flags, u8 pgl_flags) = 0;
virtual Result TerminateProcess(os::ProcessId process_id) = 0;
virtual Result LaunchProgramFromHost(ams::sf::Out<os::ProcessId> out, const ams::sf::InBuffer &content_path, u32 pm_flags) = 0;
virtual Result GetHostContentMetaInfo(ams::sf::Out<pgl::ContentMetaInfo> out, const ams::sf::InBuffer &content_path) = 0;
virtual Result GetApplicationProcessId(ams::sf::Out<os::ProcessId> out) = 0;
virtual Result BoostSystemMemoryResourceLimit(u64 size) = 0;
virtual Result IsProcessTracked(ams::sf::Out<bool> out, os::ProcessId process_id) = 0;
virtual Result EnableApplicationCrashReport(bool enabled) = 0;
virtual Result IsApplicationCrashReportEnabled(ams::sf::Out<bool> out) = 0;
virtual Result EnableApplicationAllThreadDumpOnCrash(bool enabled) = 0;
virtual Result TriggerApplicationSnapShotDumper(SnapShotDumpType dump_type, const ams::sf::InBuffer &arg) = 0;
virtual Result GetShellEventObserver(ams::sf::Out<std::shared_ptr<pgl::sf::IEventObserver>> out) = 0;
public:
DEFINE_SERVICE_DISPATCH_TABLE {
MAKE_SERVICE_COMMAND_META(LaunchProgram),
MAKE_SERVICE_COMMAND_META(TerminateProcess),
MAKE_SERVICE_COMMAND_META(LaunchProgramFromHost),
MAKE_SERVICE_COMMAND_META(GetHostContentMetaInfo),
MAKE_SERVICE_COMMAND_META(GetApplicationProcessId),
MAKE_SERVICE_COMMAND_META(BoostSystemMemoryResourceLimit),
MAKE_SERVICE_COMMAND_META(IsProcessTracked),
MAKE_SERVICE_COMMAND_META(EnableApplicationCrashReport),
MAKE_SERVICE_COMMAND_META(IsApplicationCrashReportEnabled),
MAKE_SERVICE_COMMAND_META(EnableApplicationAllThreadDumpOnCrash),
MAKE_SERVICE_COMMAND_META(TriggerApplicationSnapShotDumper),
MAKE_SERVICE_COMMAND_META(GetShellEventObserver),
};
};
}

View file

@ -0,0 +1,25 @@
/*
* Copyright (c) 2018-2020 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 <vapours.hpp>
#include <stratosphere/pgl/pgl_types.hpp>
#include <stratosphere/pgl/srv/pgl_srv_shell_interface.hpp>
namespace ams::pgl::srv {
void Initialize(ShellInterface *interface, MemoryResource *mr);
}

View file

@ -0,0 +1,52 @@
/*
* Copyright (c) 2018-2020 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 <vapours.hpp>
#include <stratosphere/pgl/pgl_types.hpp>
#include <stratosphere/pgl/sf/pgl_sf_i_shell_interface.hpp>
namespace ams::pgl::srv {
class ShellInterface final : public pgl::sf::IShellInterface {
NON_COPYABLE(ShellInterface);
NON_MOVEABLE(ShellInterface);
private:
MemoryResource *memory_resource;
public:
constexpr ShellInterface() : memory_resource(nullptr) { /* ... */ }
void Initialize(MemoryResource *mr) {
AMS_ASSERT(this->memory_resource == nullptr);
this->memory_resource = mr;
}
public:
/* Interface commands. */
virtual Result LaunchProgram(ams::sf::Out<os::ProcessId> out, const ncm::ProgramLocation &loc, u32 pm_flags, u8 pgl_flags) override final;
virtual Result TerminateProcess(os::ProcessId process_id) override final;
virtual Result LaunchProgramFromHost(ams::sf::Out<os::ProcessId> out, const ams::sf::InBuffer &content_path, u32 pm_flags) override final;
virtual Result GetHostContentMetaInfo(ams::sf::Out<pgl::ContentMetaInfo> out, const ams::sf::InBuffer &content_path) override final;
virtual Result GetApplicationProcessId(ams::sf::Out<os::ProcessId> out) override final;
virtual Result BoostSystemMemoryResourceLimit(u64 size) override final;
virtual Result IsProcessTracked(ams::sf::Out<bool> out, os::ProcessId process_id) override final;
virtual Result EnableApplicationCrashReport(bool enabled) override final;
virtual Result IsApplicationCrashReportEnabled(ams::sf::Out<bool> out) override final;
virtual Result EnableApplicationAllThreadDumpOnCrash(bool enabled) override final;
virtual Result TriggerApplicationSnapShotDumper(SnapShotDumpType dump_type, const ams::sf::InBuffer &arg) override final;
virtual Result GetShellEventObserver(ams::sf::Out<std::shared_ptr<pgl::sf::IEventObserver>> out) override final;
};
}

View file

@ -23,6 +23,12 @@
namespace ams::pm::shell {
/* Shell API. */
Result LaunchProgram(os::ProcessId *out_process_id, const ncm::ProgramLocation &loc, u32 launch_flags);
Result LaunchProgram(os::ProcessId *out, const ncm::ProgramLocation &loc, u32 launch_flags);
Result TerminateProcess(os::ProcessId process_id);
Result GetProcessEventEvent(os::SystemEvent *out);
Result GetProcessEventInfo(ProcessEventInfo *out);
Result GetApplicationProcessIdForShell(os::ProcessId *out);
Result BoostSystemMemoryResourceLimit(u64 size);
Result EnableApplicationExtraThread();
}

View file

@ -32,9 +32,29 @@ namespace ams::pm {
ResourceLimitGroup_Count,
};
using LimitableResource = ::LimitableResource;
enum LaunchFlags : u32 {
LaunchFlags_None = 0,
LaunchFlags_SignalOnExit = (1 << 0),
LaunchFlags_SignalOnStart = (1 << 1),
LaunchFlags_SignalOnException = (1 << 2),
LaunchFlags_SignalOnDebugEvent = (1 << 3),
LaunchFlags_StartSuspended = (1 << 4),
LaunchFlags_DisableAslr = (1 << 5),
};
enum class ProcessEvent {
enum LaunchFlagsDeprecated : u32 {
LaunchFlagsDeprecated_None = 0,
LaunchFlagsDeprecated_SignalOnExit = (1 << 0),
LaunchFlagsDeprecated_StartSuspended = (1 << 1),
LaunchFlagsDeprecated_SignalOnException = (1 << 2),
LaunchFlagsDeprecated_DisableAslr = (1 << 3),
LaunchFlagsDeprecated_SignalOnDebugEvent = (1 << 4),
LaunchFlagsDeprecated_SignalOnStart = (1 << 5),
};
constexpr inline u32 LaunchFlagsMask = (1 << 6) - 1;
enum class ProcessEvent : u32 {
None = 0,
Exited = 1,
Started = 2,
@ -43,7 +63,7 @@ namespace ams::pm {
DebugBreak = 5,
};
enum class ProcessEventDeprecated {
enum class ProcessEventDeprecated : u32 {
None = 0,
Exception = 1,
Exited = 2,

View file

@ -19,6 +19,7 @@
#include "settings/settings_types.hpp"
#include "settings/settings_fwdbg_types.hpp"
#include "settings/settings_fwdbg_api.hpp"
#include "settings/system/settings_error_report.hpp"
#include "settings/system/settings_firmware_version.hpp"
#include "settings/system/settings_product_model.hpp"
#include "settings/system/settings_region.hpp"

View file

@ -0,0 +1,31 @@
/*
* Copyright (c) 2018-2020 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 <vapours.hpp>
#include <stratosphere/settings/settings_types.hpp>
namespace ams::settings::system {
enum ErrorReportSharePermission {
ErrorReportSharePermission_NotConfirmed = 0,
ErrorReportSharePermission_Granted = 1,
ErrorReportSharePermission_Denied = 2,
};
ErrorReportSharePermission GetErrorReportSharePermission();
}

View file

@ -16,15 +16,16 @@
#pragma once
#include "sf/sf_common.hpp"
#include "sf/sf_mem_utility.hpp"
#include "sf/sf_service_object.hpp"
#include "sf/hipc/sf_hipc_server_session_manager.hpp"
#include <stratosphere/sf/sf_common.hpp>
#include <stratosphere/sf/sf_lmem_utility.hpp>
#include <stratosphere/sf/sf_mem_utility.hpp>
#include <stratosphere/sf/sf_service_object.hpp>
#include <stratosphere/sf/hipc/sf_hipc_server_session_manager.hpp>
#include "sf/sf_out.hpp"
#include "sf/sf_buffers.hpp"
#include "sf/impl/sf_impl_command_serialization.hpp"
#include <stratosphere/sf/sf_out.hpp>
#include <stratosphere/sf/sf_buffers.hpp>
#include <stratosphere/sf/impl/sf_impl_command_serialization.hpp>
#include "sf/hipc/sf_hipc_server_manager.hpp"
#include <stratosphere/sf/hipc/sf_hipc_server_manager.hpp>
#include "sf/sf_mitm_dispatch.h"
#include <stratosphere/sf/sf_mitm_dispatch.h>

View file

@ -0,0 +1,67 @@
/*
* Copyright (c) 2018-2020 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/sf/sf_common.hpp>
#include <stratosphere/lmem.hpp>
namespace ams::sf {
class ExpHeapMemoryResource : public MemoryResource {
private:
lmem::HeapHandle handle;
public:
explicit ExpHeapMemoryResource(lmem::HeapHandle h) : handle(h) { /* ... */ }
lmem::HeapHandle GetHandle() const { return this->handle; }
private:
virtual void *AllocateImpl(size_t size, size_t alignment) override {
return lmem::AllocateFromExpHeap(this->handle, size, static_cast<int>(alignment));
}
virtual void DeallocateImpl(void *buffer, size_t size, size_t alignment) override {
return lmem::FreeToExpHeap(this->handle, buffer);
}
virtual bool IsEqualImpl(const MemoryResource &resource) const {
return this == std::addressof(resource);
}
};
class UnitHeapMemoryResource : public MemoryResource {
private:
lmem::HeapHandle handle;
public:
explicit UnitHeapMemoryResource(lmem::HeapHandle h) : handle(h) { /* ... */ }
lmem::HeapHandle GetHandle() const { return this->handle; }
private:
virtual void *AllocateImpl(size_t size, size_t alignment) override {
AMS_ASSERT(size <= lmem::GetUnitHeapUnitSize(this->handle));
AMS_ASSERT(alignment <= static_cast<size_t>(lmem::GetUnitHeapAlignment(this->handle)));
return lmem::AllocateFromUnitHeap(this->handle);
}
virtual void DeallocateImpl(void *buffer, size_t size, size_t alignment) override {
return lmem::FreeToUnitHeap(this->handle, buffer);
}
virtual bool IsEqualImpl(const MemoryResource &resource) const {
return this == std::addressof(resource);
}
};
}

View file

@ -44,7 +44,7 @@ namespace ams::diag {
inline void DebugLog(const char *format, ...) __attribute__((format(printf, 1, 2)));
#ifdef AMS_ENABLE_DEBUG_PRINT
os::Mutex g_debug_log_lock;
os::Mutex g_debug_log_lock(true);
char g_debug_buffer[0x400];
void DebugLogImpl(const char *format, ::std::va_list vl) {

View file

@ -169,7 +169,7 @@ namespace ams::fssystem {
if (this->size > ideal_size) {
/* If we do, we need to have a buffer allocated from the heap. */
AMS_ASSERT(this->buffer != nullptr);
AMS_ASSERT(g_heap.GetBlockSize(), HeapBlockSize);
AMS_ASSERT(g_heap.GetBlockSize() == HeapBlockSize);
const size_t new_size = util::AlignUp(ideal_size, HeapBlockSize);

View file

@ -0,0 +1,37 @@
/*
* Copyright (c) 2018-2020 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>
namespace ams::ldr {
Result InitializeForShell() {
return ::ldrShellInitialize();
}
Result FinalizeForShell() {
::ldrShellExit();
return ResultSuccess();
}
Result SetProgramArgument(ncm::ProgramId program_id, const void *arg, size_t size) {
return ::ldrShellSetProgramArguments(static_cast<u64>(program_id), arg, size);
}
Result FlushArguments() {
return ::ldrShellFlushArguments();
}
}

View file

@ -28,19 +28,19 @@ namespace ams::lr {
public:
/* Actual commands. */
virtual Result ResolveProgramPath(sf::Out<Path> out, ncm::ProgramId id) override {
return lrLrResolveProgramPath(std::addressof(this->srv), static_cast<u64>(id), out->str);
return lrLrResolveProgramPath(std::addressof(this->srv), id.value, out->str);
}
virtual Result RedirectProgramPath(const Path &path, ncm::ProgramId id) override {
return lrLrRedirectProgramPath(std::addressof(this->srv), static_cast<u64>(id), path.str);
return lrLrRedirectProgramPath(std::addressof(this->srv), id.value, path.str);
}
virtual Result ResolveApplicationControlPath(sf::Out<Path> out, ncm::ProgramId id) override {
return lrLrResolveApplicationControlPath(std::addressof(this->srv), static_cast<u64>(id), out->str);
return lrLrResolveApplicationControlPath(std::addressof(this->srv), id.value, out->str);
}
virtual Result ResolveApplicationHtmlDocumentPath(sf::Out<Path> out, ncm::ProgramId id) override {
return lrLrResolveApplicationHtmlDocumentPath(std::addressof(this->srv), static_cast<u64>(id), out->str);
return lrLrResolveApplicationHtmlDocumentPath(std::addressof(this->srv), id.value, out->str);
}
virtual Result ResolveDataPath(sf::Out<Path> out, ncm::DataId id) override {
@ -48,31 +48,31 @@ namespace ams::lr {
}
virtual Result RedirectApplicationControlPathDeprecated(const Path &path, ncm::ProgramId id) override {
return lrLrRedirectApplicationControlPath(std::addressof(this->srv), static_cast<u64>(id), 0, path.str);
return lrLrRedirectApplicationControlPath(std::addressof(this->srv), id.value, 0, path.str);
}
virtual Result RedirectApplicationControlPath(const Path &path, ncm::ProgramId id, ncm::ProgramId owner_id) override {
return lrLrRedirectApplicationControlPath(std::addressof(this->srv), static_cast<u64>(id), static_cast<u64>(owner_id), path.str);
return lrLrRedirectApplicationControlPath(std::addressof(this->srv), id.value, owner_id.value, path.str);
}
virtual Result RedirectApplicationHtmlDocumentPathDeprecated(const Path &path, ncm::ProgramId id) override {
return lrLrRedirectApplicationHtmlDocumentPath(std::addressof(this->srv), static_cast<u64>(id), 0, path.str);
return lrLrRedirectApplicationHtmlDocumentPath(std::addressof(this->srv), id.value, 0, path.str);
}
virtual Result RedirectApplicationHtmlDocumentPath(const Path &path, ncm::ProgramId id, ncm::ProgramId owner_id) override {
return lrLrRedirectApplicationHtmlDocumentPath(std::addressof(this->srv), static_cast<u64>(id), static_cast<u64>(owner_id), path.str);
return lrLrRedirectApplicationHtmlDocumentPath(std::addressof(this->srv), id.value, owner_id.value, path.str);
}
virtual Result ResolveApplicationLegalInformationPath(sf::Out<Path> out, ncm::ProgramId id) override {
return lrLrResolveApplicationLegalInformationPath(std::addressof(this->srv), static_cast<u64>(id), out->str);
return lrLrResolveApplicationLegalInformationPath(std::addressof(this->srv), id.value, out->str);
}
virtual Result RedirectApplicationLegalInformationPathDeprecated(const Path &path, ncm::ProgramId id) override {
return lrLrRedirectApplicationLegalInformationPath(std::addressof(this->srv), static_cast<u64>(id), 0, path.str);
return lrLrRedirectApplicationLegalInformationPath(std::addressof(this->srv), id.value, 0, path.str);
}
virtual Result RedirectApplicationLegalInformationPath(const Path &path, ncm::ProgramId id, ncm::ProgramId owner_id) override {
return lrLrRedirectApplicationLegalInformationPath(std::addressof(this->srv), static_cast<u64>(id), static_cast<u64>(owner_id), path.str);
return lrLrRedirectApplicationLegalInformationPath(std::addressof(this->srv), id.value, owner_id.value, path.str);
}
virtual Result Refresh() override {
@ -100,8 +100,7 @@ namespace ams::lr {
}
virtual Result EraseProgramRedirection(ncm::ProgramId id) override {
/* TODO: libnx bindings */
AMS_ABORT();
return lrLrEraseProgramRedirection(std::addressof(this->srv), id.value);
}
virtual Result EraseApplicationControlRedirection(ncm::ProgramId id) override {

View file

@ -0,0 +1,43 @@
/*
* Copyright (c) 2018-2020 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>
namespace ams::os {
void InitializeSdkMutex(SdkMutexType *mutex) {
GetReference(mutex->_storage).Initialize();
}
bool IsSdkMutexLockedByCurrentThread(const SdkMutexType *mutex) {
return GetReference(mutex->_storage).IsLockedByCurrentThread();
}
void LockSdkMutex(SdkMutexType *mutex) {
AMS_ABORT_UNLESS(!IsSdkMutexLockedByCurrentThread(mutex));
return GetReference(mutex->_storage).Enter();
}
bool TryLockSdkMutex(SdkMutexType *mutex) {
AMS_ABORT_UNLESS(!IsSdkMutexLockedByCurrentThread(mutex));
return GetReference(mutex->_storage).TryEnter();
}
void UnlockSdkMutex(SdkMutexType *mutex) {
AMS_ABORT_UNLESS(IsSdkMutexLockedByCurrentThread(mutex));
return GetReference(mutex->_storage).Leave();
}
}

View file

@ -71,8 +71,8 @@ namespace ams::pgl {
return ::pglEnableApplicationAllThreadDumpOnCrash(enabled);
}
Result TriggerSnapShotDumper(const char *arg, SnapShotDumpType dump_type) {
return ::pglTriggerSnapShotDumper(arg, static_cast<::PglSnapShotDumpType>(dump_type));
Result TriggerApplicationSnapShotDumper(const char *arg, SnapShotDumpType dump_type) {
return ::pglTriggerApplicationSnapShotDumper(static_cast<::PglSnapShotDumpType>(dump_type), arg);
}
Result GetEventObserver(pgl::EventObserver *out) {

View file

@ -0,0 +1,38 @@
/*
* Copyright (c) 2018-2020 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 "pgl_srv_shell.hpp"
namespace ams::pgl::srv {
void Initialize(ShellInterface *interface, MemoryResource *mr) {
/* Set the memory resource for the interface. */
interface->Initialize(mr);
/* Enable extra application threads, if we should. */
u8 enable_application_extra_thread;
const size_t sz = settings::fwdbg::GetSettingsItemValue(std::addressof(enable_application_extra_thread), sizeof(enable_application_extra_thread), "application_extra_thread", "enable_application_extra_thread");
if (sz == sizeof(enable_application_extra_thread) && enable_application_extra_thread != 0) {
/* NOTE: Nintendo does not check that this succeeds. */
pm::shell::EnableApplicationExtraThread();
}
/* Start the Process Tracking thread. */
pgl::srv::InitializeProcessControlTask();
}
}

View file

@ -0,0 +1,485 @@
/*
* Copyright (c) 2018-2020 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 "pgl_srv_shell.hpp"
#include "pgl_srv_shell_event_observer.hpp"
namespace ams::pgl::srv {
namespace {
constexpr inline size_t ProcessDataCount = 0x20;
struct ProcessData {
os::ProcessId process_id;
u32 flags;
};
static_assert(std::is_pod<ProcessData>::value);
enum ProcessDataFlag : u32 {
ProcessDataFlag_None = 0,
ProcessDataFlag_DetailedCrashReportAllowed = (1 << 0),
ProcessDataFlag_DetailedCrashReportEnabled = (1 << 1),
ProcessDataFlag_HasLogOption = (1 << 2),
ProcessDataFlag_OutputAllLog = (1 << 3),
ProcessDataFlag_EnableCrashReportScreenShot = (1 << 4),
};
bool g_is_production = true;
bool g_enable_crash_report_screenshot = true;
bool g_enable_jit_debug = false;
constexpr inline size_t ProcessControlTaskStackSize = 8_KB;
constexpr inline s32 ProcessControlTaskPriority = 21;
os::ThreadType g_process_control_task_thread;
alignas(os::ThreadStackAlignment) u8 g_process_control_task_stack[ProcessControlTaskStackSize];
os::SdkMutex g_observer_list_mutex;
util::IntrusiveListBaseTraits<ShellEventObserverHolder>::ListType g_observer_list;
os::SdkMutex g_process_data_mutex;
ProcessData g_process_data[ProcessDataCount];
ProcessData *FindProcessData(os::ProcessId process_id) {
for (auto &data : g_process_data) {
if (data.process_id == process_id) {
return std::addressof(data);
}
}
return nullptr;
}
u32 ConvertToProcessDataFlags(u8 pgl_flags) {
if ((pgl_flags & pgl::LaunchFlags_EnableDetailedCrashReport) == 0) {
/* If we shouldn't generate detailed crash reports, set no flags. */
return ProcessDataFlag_None;
} else {
/* We can and should generate detailed crash reports. */
u32 data_flags = ProcessDataFlag_DetailedCrashReportAllowed | ProcessDataFlag_DetailedCrashReportEnabled;
/* If we should enable crash report screenshots, check the correct flag. */
if (g_enable_crash_report_screenshot) {
const u32 test_flag = g_is_production ? pgl::LaunchFlags_EnableCrashReportScreenShotForProduction : pgl::LaunchFlags_EnableCrashReportScreenShotForDevelop;
if ((pgl_flags & test_flag) != 0) {
data_flags |= ProcessDataFlag_EnableCrashReportScreenShot;
}
}
return data_flags;
}
}
std::optional<os::ProcessId> GetRunningApplicationProcessId() {
os::ProcessId process_id;
if (R_SUCCEEDED(pm::shell::GetApplicationProcessIdForShell(std::addressof(process_id)))) {
return process_id;
} else {
return std::nullopt;
}
}
s32 ConvertDumpTypeToArgument(SnapShotDumpType dump_type) {
switch (dump_type) {
case SnapShotDumpType::None: return -1;
case SnapShotDumpType::Auto: return 0;
case SnapShotDumpType::Full: return 1;
AMS_UNREACHABLE_DEFAULT_CASE();
}
}
bool GetSnapShotDumpOutputAllLog(os::ProcessId process_id) {
/* Check if we have an option set for the process. */
{
std::scoped_lock lk(g_process_data_mutex);
if (ProcessData *data = FindProcessData(process_id); data != nullptr) {
if ((data->flags & ProcessDataFlag_HasLogOption) != 0) {
return ((data->flags & ProcessDataFlag_OutputAllLog) != 0);
}
}
}
/* If we don't have an option for the process, fall back to settings. */
u8 log_option;
const size_t option_size = settings::fwdbg::GetSettingsItemValue(std::addressof(log_option), sizeof(log_option), "snap_shot_dump", "output_all_log");
return (option_size == sizeof(log_option) && log_option != 0);
}
size_t CreateSnapShotDumpArguments(char *dst, size_t dst_size, os::ProcessId process_id, SnapShotDumpType dump_type, const char *str_arg) {
const s32 dump_arg = ConvertDumpTypeToArgument(dump_type);
const s32 log_arg = GetSnapShotDumpOutputAllLog(process_id) ? 1 : 0;
if (str_arg != nullptr) {
return std::snprintf(dst, dst_size, "D %010llu \"%s\" -log %d -dump %d", static_cast<unsigned long long>(static_cast<u64>(process_id)), str_arg, log_arg, dump_arg);
} else {
return std::snprintf(dst, dst_size, "D %010llu -log %d -dump %d", static_cast<unsigned long long>(static_cast<u64>(process_id)), log_arg, dump_arg);
}
}
Result TriggerSnapShotDumper(os::ProcessId process_id, SnapShotDumpType dump_type, const char *arg) {
/* Create the arguments. */
char process_arguments[800];
const size_t arg_len = CreateSnapShotDumpArguments(process_arguments, sizeof(process_arguments), process_id, dump_type, arg);
/* Set the arguments. */
R_TRY(ldr::SetProgramArgument(ncm::SystemDebugAppletId::SnapShotDumper, process_arguments, arg_len + 1));
/* Launch the process. */
os::ProcessId dummy_process_id;
return pm::shell::LaunchProgram(std::addressof(dummy_process_id), ncm::ProgramLocation::Make(ncm::SystemDebugAppletId::SnapShotDumper, ncm::StorageId::BuiltInSystem), pm::LaunchFlags_None);
}
bool ShouldSnapShotAutoDump() {
bool dump;
const size_t sz = settings::fwdbg::GetSettingsItemValue(std::addressof(dump), sizeof(dump), "snap_shot_dump", "auto_dump");
return sz == sizeof(dump) && dump;
}
bool ShouldSnapShotFullDump() {
bool dump;
const size_t sz = settings::fwdbg::GetSettingsItemValue(std::addressof(dump), sizeof(dump), "snap_shot_dump", "full_dump");
return sz == sizeof(dump) && dump;
}
SnapShotDumpType GetSnapShotDumpType() {
if (ShouldSnapShotAutoDump()) {
if (ShouldSnapShotFullDump()) {
return SnapShotDumpType::Full;
} else {
return SnapShotDumpType::Auto;
}
} else {
return SnapShotDumpType::None;
}
}
void TriggerSnapShotDumper(os::ProcessId process_id) {
TriggerSnapShotDumper(process_id, GetSnapShotDumpType(), nullptr);
}
s32 GetCrashReportDetailedArgument(u32 data_flags) {
if (((data_flags & ProcessDataFlag_DetailedCrashReportAllowed) != 0) && ((data_flags & ProcessDataFlag_DetailedCrashReportEnabled) != 0)) {
return 1;
} else {
return 0;
}
}
s32 GetCrashReportScreenShotArgument(u32 data_flags) {
if (settings::system::GetErrorReportSharePermission() == settings::system::ErrorReportSharePermission_Granted) {
return ((data_flags & ProcessDataFlag_EnableCrashReportScreenShot) != 0) ? 1 : 0;
} else {
return 0;
}
}
void TriggerCrashReport(os::ProcessId process_id) {
static os::ProcessId s_crashed_process_id = os::InvalidProcessId;
static os::ProcessId s_creport_process_id = os::InvalidProcessId;
/* If the program that crashed is creport, we should just terminate both processes and return. */
if (process_id == s_creport_process_id) {
TerminateProcess(s_crashed_process_id);
TerminateProcess(s_creport_process_id);
s_crashed_process_id = os::InvalidProcessId;
s_creport_process_id = os::InvalidProcessId;
return;
}
/* Get the data flags for the process. */
u32 data_flags;
{
std::scoped_lock lk(g_process_data_mutex);
if (auto *data = FindProcessData(process_id); data != nullptr) {
data_flags = data->flags;
} else {
data_flags = ProcessDataFlag_None;
}
}
/* Generate arguments. */
char arguments[0x40];
const size_t len = std::snprintf(arguments, sizeof(arguments), "%ld %d %d", static_cast<s64>(static_cast<u64>(process_id)), GetCrashReportDetailedArgument(data_flags), GetCrashReportScreenShotArgument(data_flags));
if (R_FAILED(ldr::SetProgramArgument(ncm::SystemProgramId::Creport, arguments, len + 1))) {
return;
}
/* Launch creport. */
os::ProcessId creport_process_id;
if (R_FAILED(pm::shell::LaunchProgram(std::addressof(creport_process_id), ncm::ProgramLocation::Make(ncm::SystemProgramId::Creport, ncm::StorageId::BuiltInSystem), pm::LaunchFlags_None))) {
return;
}
/* Set the globals. */
s_crashed_process_id = process_id;
s_creport_process_id = creport_process_id;
}
void HandleException(os::ProcessId process_id) {
if (g_enable_jit_debug) {
/* If jit debug is enabled, we want to try to launch snap shot dumper. */
ProcessData *data = nullptr;
{
std::scoped_lock lk(g_process_data_mutex);
data = FindProcessData(process_id);
}
/* If we're tracking the process, we can launch dumper. Otherwise we should just terminate. */
if (data != nullptr) {
TriggerSnapShotDumper(process_id);
} else {
TerminateProcess(process_id);
}
} else {
/* Otherwise, we want to launch creport. */
TriggerCrashReport(process_id);
}
}
void HandleExit(os::ProcessId process_id) {
std::scoped_lock lk(g_process_data_mutex);
if (auto *data = FindProcessData(process_id); data != nullptr) {
data->process_id = os::InvalidProcessId;
}
}
void OnProcessEvent(const pm::ProcessEventInfo &event_info) {
/* Determine if we're tracking the process. */
ProcessData *data = nullptr;
{
std::scoped_lock lk(g_process_data_mutex);
data = FindProcessData(event_info.process_id);
}
/* If we are, we're going to want to notify our listeners. */
if (data != nullptr) {
/* If we closed the process, note that. */
if (static_cast<pm::ProcessEvent>(event_info.event) == pm::ProcessEvent::Exited) {
HandleExit(event_info.process_id);
}
/* Notify all observers. */
std::scoped_lock lk(g_observer_list_mutex);
for (auto &observer : g_observer_list) {
observer.Notify(event_info);
}
}
/* If the process crashed, handle that. */
if (static_cast<pm::ProcessEvent>(event_info.event) == pm::ProcessEvent::Exception) {
HandleException(event_info.process_id);
}
}
void ProcessControlTask(void *) {
/* Get the process event event from pm. */
os::SystemEvent process_event;
R_ABORT_UNLESS(pm::shell::GetProcessEventEvent(std::addressof(process_event)));
while (true) {
/* Wait for an event to come in, and clear our signal. */
process_event.Wait();
process_event.Clear();
bool continue_getting_event = true;
while (continue_getting_event) {
/* Try to get an event info. */
pm::ProcessEventInfo event_info;
if (R_FAILED(pm::shell::GetProcessEventInfo(std::addressof(event_info)))) {
break;
}
/* Process the event. */
switch (static_cast<pm::ProcessEvent>(event_info.event)) {
case pm::ProcessEvent::None:
continue_getting_event = false;
break;
case pm::ProcessEvent::Exited:
case pm::ProcessEvent::Started:
case pm::ProcessEvent::Exception:
case pm::ProcessEvent::DebugRunning:
case pm::ProcessEvent::DebugBreak:
OnProcessEvent(event_info);
break;
AMS_UNREACHABLE_DEFAULT_CASE();
}
}
}
}
}
void InitializeProcessControlTask() {
/* Create the task thread. */
R_ABORT_UNLESS(os::CreateThread(std::addressof(g_process_control_task_thread), ProcessControlTask, nullptr, g_process_control_task_stack, sizeof(g_process_control_task_stack), ProcessControlTaskPriority));
/* Retrieve settings. */
settings::fwdbg::GetSettingsItemValue(std::addressof(g_enable_jit_debug), sizeof(g_enable_jit_debug), "jit_debug", "enable_jit_debug");
settings::fwdbg::GetSettingsItemValue(std::addressof(g_enable_crash_report_screenshot), sizeof(g_enable_crash_report_screenshot), "creport", "crash_screen_shot");
g_is_production = !settings::fwdbg::IsDebugModeEnabled();
/* Clear all process data. */
{
for (size_t i = 0; i < util::size(g_process_data); i++) {
g_process_data[i].process_id = os::InvalidProcessId;
}
}
/* Start the thread. */
os::StartThread(std::addressof(g_process_control_task_thread));
}
void RegisterShellEventObserver(ShellEventObserverHolder *holder) {
std::scoped_lock lk(g_observer_list_mutex);
g_observer_list.push_back(*holder);
}
void UnregisterShellEventObserver(ShellEventObserverHolder *holder) {
std::scoped_lock lk(g_observer_list_mutex);
for (auto &observer : g_observer_list) {
if (std::addressof(observer) == holder) {
g_observer_list.erase(g_observer_list.iterator_to(observer));
break;
}
}
}
Result LaunchProgram(os::ProcessId *out, const ncm::ProgramLocation &loc, u32 pm_flags, u8 pgl_flags) {
/* Convert the input flags to the internal format. */
const u32 data_flags = ConvertToProcessDataFlags(pgl_flags);
/* If jit debug is enabled, we want to be signaled on crash. */
if (g_enable_jit_debug) {
pm_flags |= pm::LaunchFlags_SignalOnException;
}
/* Launch the process. */
os::ProcessId process_id;
R_TRY(pm::shell::LaunchProgram(std::addressof(process_id), loc, pm_flags & pm::LaunchFlagsMask));
/* Create a ProcessData for the process. */
{
std::scoped_lock lk(g_process_data_mutex);
ProcessData *new_data = FindProcessData(os::InvalidProcessId);
AMS_ABORT_UNLESS(new_data != nullptr);
new_data->process_id = process_id;
new_data->flags = data_flags;
}
/* We succeeded. */
*out = process_id;
return ResultSuccess();
}
Result TerminateProcess(os::ProcessId process_id) {
/* Ask PM to terminate the process. */
return pm::shell::TerminateProcess(process_id);
}
Result GetApplicationProcessId(os::ProcessId *out) {
/* Get the application process id. */
auto application_process_id = GetRunningApplicationProcessId();
R_UNLESS(application_process_id, pgl::ResultApplicationNotRunning());
/* Return the id. */
*out = *application_process_id;
return ResultSuccess();
}
Result BoostSystemMemoryResourceLimit(u64 size) {
/* Ask PM to boost the limit. */
return pm::shell::BoostSystemMemoryResourceLimit(size);
}
bool IsProcessTracked(os::ProcessId process_id) {
/* Check whether a ProcessData exists for the process. */
std::scoped_lock lk(g_process_data_mutex);
return FindProcessData(process_id) != nullptr;
}
void EnableApplicationCrashReport(bool enabled) {
/* Get the application process id. */
auto application_process_id = GetRunningApplicationProcessId();
if (application_process_id) {
/* Find the data for the application process. */
std::scoped_lock lk(g_process_data_mutex);
ProcessData *data = FindProcessData(*application_process_id);
/* It's okay if we aren't tracking the process. */
if (data != nullptr) {
/* Set or clear the flag. */
if (enabled) {
data->flags |= ProcessDataFlag_DetailedCrashReportEnabled;
} else {
data->flags &= ~ProcessDataFlag_DetailedCrashReportEnabled;
}
}
}
}
bool IsApplicationCrashReportEnabled() {
/* Get the application process id. */
auto application_process_id = GetRunningApplicationProcessId();
if (!application_process_id) {
return false;
}
/* Find the data for the process. */
std::scoped_lock lk(g_process_data_mutex);
if (ProcessData *data = FindProcessData(*application_process_id); data != nullptr) {
return (data->flags & ProcessDataFlag_DetailedCrashReportEnabled) != 0;
} else {
return false;
}
}
void EnableApplicationAllThreadDumpOnCrash(bool enabled) {
/* Get the application process id. */
auto application_process_id = GetRunningApplicationProcessId();
if (application_process_id) {
/* Find the data for the application process. */
std::scoped_lock lk(g_process_data_mutex);
ProcessData *data = FindProcessData(*application_process_id);
/* It's okay if we aren't tracking the process. */
if (data != nullptr) {
/* Set or clear the flag. */
if (enabled) {
data->flags |= ProcessDataFlag_OutputAllLog;
} else {
data->flags &= ~ProcessDataFlag_OutputAllLog;
}
/* NOTE: Here Nintendo releases the lock, re-takes the lock, and re-finds the process data. */
/* This is unnecessary and less efficient, so we will not bother. */
/* Note that the flag bit has a meaningful value. */
data->flags |= ProcessDataFlag_HasLogOption;
}
}
}
Result TriggerApplicationSnapShotDumper(SnapShotDumpType dump_type, const char *arg) {
/* Try to get the application process id. */
os::ProcessId process_id;
R_TRY(pm::shell::GetApplicationProcessIdForShell(std::addressof(process_id)));
/* Launch the snapshot dumper. */
return TriggerSnapShotDumper(process_id, dump_type, arg);
}
}

View file

@ -0,0 +1,38 @@
/*
* Copyright (c) 2018-2020 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::pgl::srv {
void InitializeProcessControlTask();
class ShellEventObserverHolder;
void RegisterShellEventObserver(ShellEventObserverHolder *holder);
void UnregisterShellEventObserver(ShellEventObserverHolder *holder);
Result LaunchProgram(os::ProcessId *out, const ncm::ProgramLocation &loc, u32 pm_flags, u8 pgl_flags);
Result TerminateProcess(os::ProcessId process_id);
Result GetApplicationProcessId(os::ProcessId *out);
Result BoostSystemMemoryResourceLimit(u64 size);
bool IsProcessTracked(os::ProcessId process_id);
void EnableApplicationCrashReport(bool enabled);
bool IsApplicationCrashReportEnabled();
void EnableApplicationAllThreadDumpOnCrash(bool enabled);
Result TriggerApplicationSnapShotDumper(SnapShotDumpType dump_type, const char *arg);
}

View file

@ -0,0 +1,78 @@
/*
* Copyright (c) 2018-2020 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 "pgl_srv_shell_event_observer.hpp"
#include "pgl_srv_shell.hpp"
namespace ams::pgl::srv {
ShellEventObserver::ShellEventObserver() : message_queue(queue_buffer, QueueCapacity), event(os::EventClearMode_AutoClear, true) {
this->heap_handle = lmem::CreateUnitHeap(this->event_info_data, sizeof(this->event_info_data), sizeof(this->event_info_data[0]), lmem::CreateOption_ThreadSafe, 8, GetPointer(this->heap_head));
new (GetPointer(this->holder)) ShellEventObserverHolder(this);
RegisterShellEventObserver(GetPointer(this->holder));
}
ShellEventObserver::~ShellEventObserver() {
UnregisterShellEventObserver(GetPointer(this->holder));
GetReference(this->holder).~ShellEventObserverHolder();
}
Result ShellEventObserver::PopEventInfo(pm::ProcessEventInfo *out) {
/* Receive an info from the queue. */
uintptr_t info_address;
R_UNLESS(this->message_queue.TryReceive(std::addressof(info_address)), pgl::ResultNotAvailable());
pm::ProcessEventInfo *info = reinterpret_cast<pm::ProcessEventInfo *>(info_address);
/* Set the output. */
*out = *info;
/* Free the received info. */
lmem::FreeToUnitHeap(this->heap_handle, info);
return ResultSuccess();
}
void ShellEventObserver::Notify(const pm::ProcessEventInfo &info) {
/* Allocate a new info. */
auto allocated = reinterpret_cast<pm::ProcessEventInfo *>(lmem::AllocateFromUnitHeap(this->heap_handle));
if (!allocated) {
return;
}
/* Set it to the notification. */
*allocated = info;
/* Try to send it. */
if (!this->message_queue.TrySend(reinterpret_cast<uintptr_t>(allocated))) {
lmem::FreeToUnitHeap(this->heap_handle, allocated);
return;
}
/* Notify that we have a new info available. */
this->event.Signal();
}
Result EventObserverInterface::GetProcessEventHandle(ams::sf::OutCopyHandle out) {
out.SetValue(GetReference(this->observer).GetEvent().GetReadableHandle());
return ResultSuccess();
}
Result EventObserverInterface::GetProcessEventInfo(ams::sf::Out<pm::ProcessEventInfo> out) {
return GetReference(this->observer).PopEventInfo(out.GetPointer());
}
}

View file

@ -0,0 +1,78 @@
/*
* Copyright (c) 2018-2020 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::pgl::srv {
class IShellEventObserver {
public:
virtual void Notify(const pm::ProcessEventInfo &info) = 0;
};
class ShellEventObserverHolder : public util::IntrusiveListBaseNode<ShellEventObserverHolder> {
private:
IShellEventObserver *observer;
public:
explicit ShellEventObserverHolder(IShellEventObserver *observer) : observer(observer) { /* ... */ }
void Notify(const pm::ProcessEventInfo &info) {
this->observer->Notify(info);
}
};
class ShellEventObserver final : public IShellEventObserver {
private:
static constexpr size_t QueueCapacity = 0x20;
private:
os::MessageQueue message_queue;
uintptr_t queue_buffer[QueueCapacity];
os::SystemEvent event;
TYPED_STORAGE(lmem::HeapCommonHead) heap_head;
lmem::HeapHandle heap_handle;
pm::ProcessEventInfo event_info_data[QueueCapacity];
TYPED_STORAGE(ShellEventObserverHolder) holder;
public:
ShellEventObserver();
~ShellEventObserver();
os::SystemEvent &GetEvent() {
return this->event;
}
Result PopEventInfo(pm::ProcessEventInfo *out);
virtual void Notify(const pm::ProcessEventInfo &info) override final;
};
class EventObserverInterface final : public pgl::sf::IEventObserver {
private:
TYPED_STORAGE(ShellEventObserver) observer;
public:
EventObserverInterface() {
std::memset(std::addressof(this->observer), 0, sizeof(this->observer));
new (GetPointer(this->observer)) ShellEventObserver;
}
~EventObserverInterface() {
GetReference(this->observer).~ShellEventObserver();
}
public:
virtual Result GetProcessEventHandle(ams::sf::OutCopyHandle out) override final;
virtual Result GetProcessEventInfo(ams::sf::Out<pm::ProcessEventInfo> out) override final;
};
}

View file

@ -0,0 +1,355 @@
/*
* Copyright (c) 2018-2020 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 "pgl_srv_shell_host_utils.hpp"
#include "pgl_srv_shell.hpp"
namespace ams::pgl::srv {
namespace {
constexpr inline char HostPackageMountName[] = "HostPackageRead";
static_assert(sizeof(HostPackageMountName) - 1 <= fs::MountNameLengthMax);
struct CaseInsensitiveCharTraits : public std::char_traits<char> {
static char to_upper(char c) {
return std::toupper(static_cast<unsigned char>(c));
}
static bool eq(char c1, char c2) {
return to_upper(c1) == to_upper(c2);
}
static bool lt(char c1, char c2) {
return to_upper(c1) < to_upper(c2);
}
static int compare(const char *s1, const char *s2, size_t n) {
while ( n-- != 0 ) {
if ( to_upper(*s1) < to_upper(*s2) ) return -1;
if ( to_upper(*s1) > to_upper(*s2) ) return 1;
++s1; ++s2;
}
return 0;
}
static const char *find(const char *s, int n, char a) {
auto const ua (to_upper(a));
while ( n-- != 0 )
{
if (to_upper(*s) == ua)
return s;
s++;
}
return nullptr;
}
};
using PathView = std::basic_string_view<char, CaseInsensitiveCharTraits>;
enum class ExtensionType {
None = 0,
Nsp = 1,
Nspd = 2,
};
bool HasSuffix(const char *str, const char *suffix) {
const size_t suffix_len = std::strlen(suffix);
const size_t str_len = std::strlen(str);
if (suffix_len > str_len) {
return false;
}
return (PathView(str).substr(str_len - suffix_len) == PathView(suffix));
}
class HostPackageReader {
NON_COPYABLE(HostPackageReader);
NON_MOVEABLE(HostPackageReader);
private:
char content_path[fs::EntryNameLengthMax] = {};
ExtensionType extension_type = ExtensionType::None;
char mount_name[fs::MountNameLengthMax] = {};
bool is_mounted = false;
ncm::AutoBuffer content_meta_buffer;
ncm::ProgramId program_id = ncm::InvalidProgramId;
u32 program_version = 0;
ncm::ContentMetaType content_meta_type = static_cast<ncm::ContentMetaType>(0);
u8 program_index = 0;
public:
HostPackageReader() : content_meta_buffer() { /* ... */ }
~HostPackageReader() {
if (this->is_mounted) {
fs::Unmount(this->mount_name);
}
}
Result Initialize(const char *package, const char *mount) {
/* Copy in the content path. */
R_UNLESS(strlen(package) <= sizeof(this->content_path) - 1, pgl::ResultBufferNotEnough());
std::strcpy(this->content_path, package);
/* Set the extension type. */
R_TRY(this->SetExtensionType());
/* Copy in mount name. */
R_UNLESS(strlen(mount) <= sizeof(this->mount_name) - 1, pgl::ResultBufferNotEnough());
std::strcpy(this->mount_name, mount);
/* Mount the package. */
R_TRY(fs::MountApplicationPackage(this->mount_name, this->content_path));
this->is_mounted = true;
/* Set the content meta buffer. */
R_TRY(this->SetContentMetaBuffer());
/* Ensure we have a content meta buffer. */
R_UNLESS(this->content_meta_buffer.Get() != nullptr, pgl::ResultContentMetaNotFound());
return ResultSuccess();
}
Result ReadProgramInfo() {
/* First, read the program index. */
R_TRY(this->GetProgramIndex(std::addressof(this->program_index)));
/* Next, create a key for the rest of the fields. */
const auto key = ncm::PackagedContentMetaReader(this->content_meta_buffer.Get(), this->content_meta_buffer.GetSize()).GetKey();
/* Set fields. */
this->program_id = {key.id};
this->program_version = key.version;
this->content_meta_type = key.type;
return ResultSuccess();
}
Result GetContentPath(lr::Path *out, ncm::ContentType type, std::optional<u8> index) const {
switch (this->extension_type) {
case ExtensionType::Nsp: return this->GetContentPathInNsp(out, type, index);
case ExtensionType::Nspd: return this->GetContentPathInNspd(out, type, index);
AMS_UNREACHABLE_DEFAULT_CASE();
}
}
ncm::ProgramId GetProgramId() const {
return this->program_id;
}
u32 GetProgramVersion() const {
return this->program_version;
}
ncm::ContentMetaType GetContentMetaType() const {
return this->content_meta_type;
}
u8 GetProgramIndex() const {
return this->program_index;
}
private:
Result GetContentPathInNsp(lr::Path *out, ncm::ContentType type, std::optional<u8> index) const {
/* Create a reader. */
auto reader = ncm::PackagedContentMetaReader(this->content_meta_buffer.Get(), this->content_meta_buffer.GetSize());
/* Get the content info. */
const ncm::PackagedContentInfo *content_info = nullptr;
if (index) {
content_info = reader.GetContentInfo(type, *index);
} else {
content_info = reader.GetContentInfo(type);
}
R_UNLESS(content_info != nullptr, pgl::ResultApplicationContentNotFound());
/* Get the content id string. */
ncm::ContentIdString id_str;
ncm::GetStringFromContentId(id_str.data, sizeof(id_str.data), content_info->GetId());
/* Get the file name. */
char file_name[ncm::ContentIdStringLength + 5];
const size_t len = std::snprintf(file_name, sizeof(file_name), "%s.nca", id_str.data);
R_UNLESS(len + 1 == sizeof(file_name), pgl::ResultBufferNotEnough());
/* Ensure we have the content. */
bool has_content;
R_TRY(this->SearchContent(std::addressof(has_content), out, file_name, fs::OpenDirectoryMode_File));
R_UNLESS(has_content, pgl::ResultApplicationContentNotFound());
return ResultSuccess();
}
Result GetContentPathInNspd(lr::Path *out, ncm::ContentType type, std::optional<u8> index) const {
/* Get the content name. */
const char *content_name = nullptr;
switch (type) {
case ncm::ContentType::Program: content_name = "program"; break;
case ncm::ContentType::Control: content_name = "control"; break;
case ncm::ContentType::HtmlDocument: content_name = "htmlDocument"; break;
case ncm::ContentType::LegalInformation: content_name = "legalInformation"; break;
AMS_UNREACHABLE_DEFAULT_CASE();
}
/* Get the file name. */
/* NSPD does not support indexed content, so we always use 0 as the index. */
char file_name[0x20];
const size_t len = std::snprintf(file_name, sizeof(file_name), "%s%d.ncd", content_name, 0);
R_UNLESS(len + 1 <= sizeof(file_name), pgl::ResultBufferNotEnough());
/* Ensure we have the content. */
bool has_content;
R_TRY(this->SearchContent(std::addressof(has_content), out, file_name, fs::OpenDirectoryMode_Directory));
R_UNLESS(has_content, pgl::ResultApplicationContentNotFound());
return ResultSuccess();
}
Result GetProgramIndex(u8 *out) {
/* Nspd programs do not have indices. */
if (this->extension_type == ExtensionType::Nspd) {
*out = 0;
return ResultSuccess();
}
/* Create a reader. */
auto reader = ncm::PackagedContentMetaReader(this->content_meta_buffer.Get(), this->content_meta_buffer.GetSize());
/* Get the program content info. */
auto program_content_info = reader.GetContentInfo(ncm::ContentType::Program);
R_UNLESS(program_content_info, pgl::ResultApplicationContentNotFound());
/* Return the index. */
*out = program_content_info->GetIdOffset();
return ResultSuccess();
}
Result SetExtensionType() {
/* First, clear the suffix if the path is a program ncd. */
if (HasSuffix(this->content_path, "program0.ncd/")) {
this->content_path[strnlen(this->content_path, sizeof(this->content_path)) - std::strlen("program0.ncd/")] = 0;
}
if (HasSuffix(this->content_path, ".nsp")) {
this->extension_type = ExtensionType::Nsp;
return ResultSuccess();
} else if (HasSuffix(this->content_path, ".nspd") || HasSuffix(this->content_path, ".nspd/")) {
this->extension_type = ExtensionType::Nspd;
return ResultSuccess();
} else {
return fs::ResultPathNotFound();
}
}
Result SetContentMetaBuffer() {
constexpr const char ContentMetaFileExtension[] = ".cnmt.nca";
constexpr const char ContentMetaDirectoryExtension[] = "meta0.ncd";
/* Find the Content meta path. */
bool has_content = false;
lr::Path meta_path;
switch (this->extension_type) {
case ExtensionType::Nsp: R_TRY(this->SearchContent(std::addressof(has_content), std::addressof(meta_path), ContentMetaFileExtension, fs::OpenDirectoryMode_File)); break;
case ExtensionType::Nspd: R_TRY(this->SearchContent(std::addressof(has_content), std::addressof(meta_path), ContentMetaDirectoryExtension, fs::OpenDirectoryMode_Directory)); break;
AMS_UNREACHABLE_DEFAULT_CASE();
}
R_UNLESS(has_content, pgl::ResultContentMetaNotFound());
/* Read the content meta buffer. */
return ncm::ReadContentMetaPath(std::addressof(this->content_meta_buffer), meta_path.str);
}
Result SearchContent(bool *out, lr::Path *out_path, const char *extension, fs::OpenDirectoryMode mode) const {
/* Generate the root directory path. */
char root_dir[sizeof(this->mount_name) + 2];
std::snprintf(root_dir, sizeof(root_dir), "%s:/", this->mount_name);
/* Open the root directory. */
fs::DirectoryHandle dir;
R_TRY(fs::OpenDirectory(std::addressof(dir), root_dir, mode));
ON_SCOPE_EXIT { fs::CloseDirectory(dir); };
/* Iterate over directory entries. */
while (true) {
fs::DirectoryEntry entry;
s64 count;
R_TRY(fs::ReadDirectory(std::addressof(count), std::addressof(entry), dir, 1));
if (count == 0) {
break;
}
/* Check if we match the suffix. */
if (HasSuffix(entry.name, extension)) {
*out = true;
if (out_path) {
const size_t len = std::snprintf(out_path->str, sizeof(out_path->str), "%s/%s", this->content_path, entry.name);
R_UNLESS(len + 1 < sizeof(out_path->str), pgl::ResultBufferNotEnough());
if (entry.type == fs::DirectoryEntryType_Directory) {
out_path->str[len] = '/';
out_path->str[len + 1] = 0;
}
}
return ResultSuccess();
}
}
/* We didn't find a match. */
*out = false;
return ResultSuccess();
}
};
}
Result LaunchProgramFromHost(os::ProcessId *out, const char *package_path, u32 pm_flags) {
/* Read the package. */
HostPackageReader reader;
R_TRY(reader.Initialize(package_path, HostPackageMountName));
/* Read the program info. */
R_TRY(reader.ReadProgramInfo());
/* Open a host location resolver. */
lr::LocationResolver host_resolver;
R_TRY(lr::OpenLocationResolver(std::addressof(host_resolver), ncm::StorageId::Host));
/* Get the content path. */
lr::Path content_path;
R_TRY(reader.GetContentPath(std::addressof(content_path), ncm::ContentType::Program, reader.GetProgramIndex()));
/* Erase the program redirection. */
R_TRY(host_resolver.EraseProgramRedirection(reader.GetProgramId()));
/* Redirect the program path to point to the new path. */
host_resolver.RedirectProgramPath(content_path, reader.GetProgramId());
/* Launch the program. */
return pgl::srv::LaunchProgram(out, ncm::ProgramLocation::Make(reader.GetProgramId(), ncm::StorageId::Host), pm_flags, pgl::LaunchFlags_None);
}
Result GetHostContentMetaInfo(pgl::ContentMetaInfo *out, const char *package_path) {
/* Read the package. */
HostPackageReader reader;
R_TRY(reader.Initialize(package_path, HostPackageMountName));
/* Read the program info. */
R_TRY(reader.ReadProgramInfo());
/* Get the content meta info. */
*out = {
.id = reader.GetProgramId().value,
.version = reader.GetProgramVersion(),
.content_type = ncm::ContentType::Program,
.id_offset = reader.GetProgramIndex(),
};
return ResultSuccess();
}
}

View file

@ -0,0 +1,24 @@
/*
* Copyright (c) 2018-2020 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::pgl::srv {
Result LaunchProgramFromHost(os::ProcessId *out, const char *content_path, u32 pm_flags);
Result GetHostContentMetaInfo(pgl::ContentMetaInfo *out, const char *content_path);
}

View file

@ -0,0 +1,90 @@
/*
* Copyright (c) 2018-2020 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 "pgl_srv_shell.hpp"
#include "pgl_srv_shell_event_observer.hpp"
#include "pgl_srv_shell_host_utils.hpp"
namespace ams::pgl::srv {
Result ShellInterface::LaunchProgram(ams::sf::Out<os::ProcessId> out, const ncm::ProgramLocation &loc, u32 pm_flags, u8 pgl_flags) {
return pgl::srv::LaunchProgram(out.GetPointer(), loc, pm_flags, pgl_flags);
}
Result ShellInterface::TerminateProcess(os::ProcessId process_id) {
return pgl::srv::TerminateProcess(process_id);
}
Result ShellInterface::LaunchProgramFromHost(ams::sf::Out<os::ProcessId> out, const ams::sf::InBuffer &content_path, u32 pm_flags) {
return pgl::srv::LaunchProgramFromHost(out.GetPointer(), reinterpret_cast<const char *>(content_path.GetPointer()), pm_flags);
}
Result ShellInterface::GetHostContentMetaInfo(ams::sf::Out<pgl::ContentMetaInfo> out, const ams::sf::InBuffer &content_path) {
return pgl::srv::GetHostContentMetaInfo(out.GetPointer(), reinterpret_cast<const char *>(content_path.GetPointer()));
}
Result ShellInterface::GetApplicationProcessId(ams::sf::Out<os::ProcessId> out) {
return pgl::srv::GetApplicationProcessId(out.GetPointer());
}
Result ShellInterface::BoostSystemMemoryResourceLimit(u64 size) {
return pgl::srv::BoostSystemMemoryResourceLimit(size);
}
Result ShellInterface::IsProcessTracked(ams::sf::Out<bool> out, os::ProcessId process_id) {
out.SetValue(pgl::srv::IsProcessTracked(process_id));
return ResultSuccess();
}
Result ShellInterface::EnableApplicationCrashReport(bool enabled) {
pgl::srv::EnableApplicationCrashReport(enabled);
return ResultSuccess();
}
Result ShellInterface::IsApplicationCrashReportEnabled(ams::sf::Out<bool> out) {
out.SetValue(pgl::srv::IsApplicationCrashReportEnabled());
return ResultSuccess();
}
Result ShellInterface::EnableApplicationAllThreadDumpOnCrash(bool enabled) {
pgl::srv::EnableApplicationAllThreadDumpOnCrash(enabled);
return ResultSuccess();
}
Result ShellInterface::TriggerApplicationSnapShotDumper(SnapShotDumpType dump_type, const ams::sf::InBuffer &arg) {
return pgl::srv::TriggerApplicationSnapShotDumper(dump_type, reinterpret_cast<const char *>(arg.GetPointer()));
}
Result ShellInterface::GetShellEventObserver(ams::sf::Out<std::shared_ptr<pgl::sf::IEventObserver>> out) {
/* Allocate a new interface. */
auto *observer_memory = this->memory_resource->Allocate(sizeof(EventObserverInterface), alignof(EventObserverInterface));
AMS_ABORT_UNLESS(observer_memory != nullptr);
/* Create the interface object. */
new (observer_memory) EventObserverInterface;
/* Set the output. */
out.SetValue(std::shared_ptr<EventObserverInterface>(reinterpret_cast<EventObserverInterface *>(observer_memory), [&](EventObserverInterface *obj) {
/* Destroy the object. */
obj->~EventObserverInterface();
/* Custom deleter: use the memory resource to free. */
this->memory_resource->Deallocate(obj, sizeof(EventObserverInterface), alignof(EventObserverInterface));
}));
return ResultSuccess();
}
}

View file

@ -18,10 +18,39 @@
namespace ams::pm::shell {
/* Shell API. */
Result WEAK_SYMBOL LaunchProgram(os::ProcessId *out_process_id, const ncm::ProgramLocation &loc, u32 launch_flags) {
Result WEAK_SYMBOL LaunchProgram(os::ProcessId *out, const ncm::ProgramLocation &loc, u32 launch_flags) {
static_assert(sizeof(ncm::ProgramLocation) == sizeof(NcmProgramLocation));
static_assert(alignof(ncm::ProgramLocation) == alignof(NcmProgramLocation));
return pmshellLaunchProgram(launch_flags, reinterpret_cast<const NcmProgramLocation *>(&loc), reinterpret_cast<u64 *>(out_process_id));
return pmshellLaunchProgram(launch_flags, reinterpret_cast<const NcmProgramLocation *>(&loc), reinterpret_cast<u64 *>(out));
}
Result TerminateProcess(os::ProcessId process_id) {
return ::pmshellTerminateProcess(static_cast<u64>(process_id));
}
Result GetProcessEventEvent(os::SystemEvent *out) {
::Event evt;
R_TRY(::pmshellGetProcessEventHandle(std::addressof(evt)));
out->Attach(evt.revent, true, svc::InvalidHandle, false, os::EventClearMode_ManualClear);
return ResultSuccess();
}
Result GetProcessEventInfo(ProcessEventInfo *out) {
static_assert(sizeof(*out) == sizeof(::PmProcessEventInfo));
return ::pmshellGetProcessEventInfo(reinterpret_cast<::PmProcessEventInfo *>(out));
}
Result GetApplicationProcessIdForShell(os::ProcessId *out) {
static_assert(sizeof(*out) == sizeof(u64));
return ::pmshellGetApplicationProcessIdForShell(reinterpret_cast<u64 *>(out));
}
Result BoostSystemMemoryResourceLimit(u64 size) {
return ::pmshellBoostSystemMemoryResourceLimit(size);
}
Result EnableApplicationExtraThread() {
return ::pmshellEnableApplicationExtraThread();
}
}

View file

@ -0,0 +1,26 @@
/*
* Copyright (c) 2018-2020 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 "settings_error_report_impl.hpp"
namespace ams::settings::impl {
Result GetErrorReportSharePermission(s32 *out) {
static_assert(sizeof(*out) == sizeof(::SetSysErrorReportSharePermission));
return ::setsysGetErrorReportSharePermission(reinterpret_cast<::SetSysErrorReportSharePermission *>(out));
}
}

View file

@ -0,0 +1,23 @@
/*
* Copyright (c) 2018-2020 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::settings::impl {
Result GetErrorReportSharePermission(s32 *out);
}

View file

@ -0,0 +1,27 @@
/*
* Copyright (c) 2018-2020 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 "impl/settings_error_report_impl.hpp"
namespace ams::settings::system {
ErrorReportSharePermission GetErrorReportSharePermission() {
s32 perm = 0;
R_ABORT_UNLESS(settings::impl::GetErrorReportSharePermission(std::addressof(perm)));
return static_cast<ErrorReportSharePermission>(perm);
}
}

View file

@ -17,8 +17,11 @@
namespace ams::settings::fwdbg {
/* TODO: Implement when libnx wrapper is added. */
bool IsDebugModeEnabled();
bool IsDebugModeEnabled() {
bool value = false;
R_ABORT_UNLESS(::setsysGetDebugModeFlag(std::addressof(value)));
return value;
}
size_t WEAK_SYMBOL GetSettingsItemValueSize(const char *name, const char *key) {
u64 size = 0;

View file

@ -38,6 +38,7 @@
#include <vapours/results/lr_results.hpp>
#include <vapours/results/os_results.hpp>
#include <vapours/results/ncm_results.hpp>
#include <vapours/results/pgl_results.hpp>
#include <vapours/results/pm_results.hpp>
#include <vapours/results/psc_results.hpp>
#include <vapours/results/ro_results.hpp>

View file

@ -0,0 +1,30 @@
/*
* Copyright (c) 2018-2020 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 <vapours/results/results_common.hpp>
namespace ams::pgl {
R_DEFINE_NAMESPACE_RESULT_MODULE(228);
R_DEFINE_ERROR_RESULT(NotAvailable, 2);
R_DEFINE_ERROR_RESULT(ApplicationNotRunning, 3);
R_DEFINE_ERROR_RESULT(BufferNotEnough, 4);
R_DEFINE_ERROR_RESULT(ApplicationContentNotFound, 5);
R_DEFINE_ERROR_RESULT(ContentMetaNotFound, 6);
}

128
stratosphere/pgl/Makefile Normal file
View file

@ -0,0 +1,128 @@
#---------------------------------------------------------------------------------
# pull in common stratosphere sysmodule configuration
#---------------------------------------------------------------------------------
include $(dir $(abspath $(lastword $(MAKEFILE_LIST))))/../../libraries/config/templates/stratosphere.mk
#---------------------------------------------------------------------------------
# no real need to edit anything past this point unless you need to add additional
# rules for different file extensions
#---------------------------------------------------------------------------------
ifneq ($(BUILD),$(notdir $(CURDIR)))
#---------------------------------------------------------------------------------
export OUTPUT := $(CURDIR)/$(TARGET)
export TOPDIR := $(CURDIR)
export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \
$(foreach dir,$(DATA),$(CURDIR)/$(dir))
export DEPSDIR := $(CURDIR)/$(BUILD)
CFILES := $(foreach dir,$(SOURCES),$(filter-out $(notdir $(wildcard $(dir)/*.arch.*.c)) $(notdir $(wildcard $(dir)/*.board.*.c)) $(notdir $(wildcard $(dir)/*.os.*.c)), \
$(notdir $(wildcard $(dir)/*.c))))
CFILES += $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.arch.$(ATMOSPHERE_ARCH_NAME).c)))
CFILES += $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.board.$(ATMOSPHERE_BOARD_NAME).c)))
CFILES += $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.os.$(ATMOSPHERE_OS_NAME).c)))
CPPFILES := $(foreach dir,$(SOURCES),$(filter-out $(notdir $(wildcard $(dir)/*.arch.*.cpp)) $(notdir $(wildcard $(dir)/*.board.*.cpp)) $(notdir $(wildcard $(dir)/*.os.*.cpp)), \
$(notdir $(wildcard $(dir)/*.cpp))))
CPPFILES += $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.arch.$(ATMOSPHERE_ARCH_NAME).cpp)))
CPPFILES += $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.board.$(ATMOSPHERE_BOARD_NAME).cpp)))
CPPFILES += $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.os.$(ATMOSPHERE_OS_NAME).cpp)))
SFILES := $(foreach dir,$(SOURCES),$(filter-out $(notdir $(wildcard $(dir)/*.arch.*.s)) $(notdir $(wildcard $(dir)/*.board.*.s)) $(notdir $(wildcard $(dir)/*.os.*.s)), \
$(notdir $(wildcard $(dir)/*.s))))
SFILES += $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.arch.$(ATMOSPHERE_ARCH_NAME).s)))
SFILES += $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.board.$(ATMOSPHERE_BOARD_NAME).s)))
SFILES += $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.os.$(ATMOSPHERE_OS_NAME).s)))
BINFILES := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.*)))
#---------------------------------------------------------------------------------
# use CXX for linking C++ projects, CC for standard C
#---------------------------------------------------------------------------------
ifeq ($(strip $(CPPFILES)),)
#---------------------------------------------------------------------------------
export LD := $(CC)
#---------------------------------------------------------------------------------
else
#---------------------------------------------------------------------------------
export LD := $(CXX)
#---------------------------------------------------------------------------------
endif
#---------------------------------------------------------------------------------
export OFILES := $(addsuffix .o,$(BINFILES)) \
$(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o)
export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \
$(foreach dir,$(LIBDIRS),-I$(dir)/include) \
-I$(CURDIR)/$(BUILD)
export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib)
export BUILD_EXEFS_SRC := $(TOPDIR)/$(EXEFS_SRC)
ifeq ($(strip $(CONFIG_JSON)),)
jsons := $(wildcard *.json)
ifneq (,$(findstring $(TARGET).json,$(jsons)))
export APP_JSON := $(TOPDIR)/$(TARGET).json
else
ifneq (,$(findstring config.json,$(jsons)))
export APP_JSON := $(TOPDIR)/config.json
endif
endif
else
export APP_JSON := $(TOPDIR)/$(CONFIG_JSON)
endif
.PHONY: $(BUILD) clean all
#---------------------------------------------------------------------------------
all: $(BUILD)
$(BUILD):
@[ -d $@ ] || mkdir -p $@
@$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile
#---------------------------------------------------------------------------------
clean:
@echo clean ...
@rm -fr $(BUILD) $(TARGET).nsp $(TARGET).npdm $(TARGET).nso $(TARGET).elf
#---------------------------------------------------------------------------------
else
.PHONY: all
DEPENDS := $(OFILES:.o=.d)
#---------------------------------------------------------------------------------
# main targets
#---------------------------------------------------------------------------------
all : $(OUTPUT).nsp
ifeq ($(strip $(APP_JSON)),)
$(OUTPUT).nsp : $(OUTPUT).nso
else
$(OUTPUT).nsp : $(OUTPUT).nso $(OUTPUT).npdm
endif
$(OUTPUT).nso : $(OUTPUT).elf
$(OUTPUT).elf : $(OFILES)
#---------------------------------------------------------------------------------
# you need a rule like this for each extension you use as binary data
#---------------------------------------------------------------------------------
%.bin.o : %.bin
#---------------------------------------------------------------------------------
@echo $(notdir $<)
@$(bin2o)
-include $(DEPENDS)
#---------------------------------------------------------------------------------------
endif
#---------------------------------------------------------------------------------------

88
stratosphere/pgl/pgl.json Normal file
View file

@ -0,0 +1,88 @@
{
"name": "pgl",
"title_id": "0x0100000000000042",
"title_id_range_min": "0x0100000000000042",
"title_id_range_max": "0x0100000000000042",
"main_thread_stack_size": "0x00004000",
"main_thread_priority": 49,
"default_cpu_id": 3,
"process_category": 0,
"is_retail": true,
"pool_partition": 2,
"is_64_bit": true,
"address_space_type": 3,
"filesystem_access": {
"permissions": "0xFFFFFFFFFFFFFFFF"
},
"service_access": ["erpt:c", "fatal:u", "fsp-srv", "ldr:shel", "lm", "lr", "pm:shell", "set", "set:sys"],
"service_host": ["pgl"],
"kernel_capabilities": [{
"type": "kernel_flags",
"value": {
"highest_thread_priority": 63,
"lowest_thread_priority": 24,
"lowest_cpu_id": 3,
"highest_cpu_id": 3
}
}, {
"type": "syscalls",
"value": {
"svcSetHeapSize": "0x01",
"svcSetMemoryPermission": "0x02",
"svcSetMemoryAttribute": "0x03",
"svcMapMemory": "0x04",
"svcUnmapMemory": "0x05",
"svcQueryMemory": "0x06",
"svcExitProcess": "0x07",
"svcCreateThread": "0x08",
"svcStartThread": "0x09",
"svcExitThread": "0x0a",
"svcSleepThread": "0x0b",
"svcGetThreadPriority": "0x0c",
"svcSetThreadPriority": "0x0d",
"svcGetThreadCoreMask": "0x0e",
"svcSetThreadCoreMask": "0x0f",
"svcGetCurrentProcessorNumber": "0x10",
"svcSignalEvent": "0x11",
"svcClearEvent": "0x12",
"svcMapSharedMemory": "0x13",
"svcUnmapSharedMemory": "0x14",
"svcCreateTransferMemory": "0x15",
"svcCloseHandle": "0x16",
"svcResetSignal": "0x17",
"svcWaitSynchronization": "0x18",
"svcCancelSynchronization": "0x19",
"svcArbitrateLock": "0x1a",
"svcArbitrateUnlock": "0x1b",
"svcWaitProcessWideKeyAtomic": "0x1c",
"svcSignalProcessWideKey": "0x1d",
"svcGetSystemTick": "0x1e",
"svcConnectToNamedPort": "0x1f",
"svcSendSyncRequestLight": "0x20",
"svcSendSyncRequest": "0x21",
"svcSendSyncRequestWithUserBuffer": "0x22",
"svcSendAsyncRequestWithUserBuffer": "0x23",
"svcGetProcessId": "0x24",
"svcGetThreadId": "0x25",
"svcBreak": "0x26",
"svcOutputDebugString": "0x27",
"svcReturnFromException": "0x28",
"svcGetInfo": "0x29",
"svcWaitForAddress": "0x34",
"svcSignalToAddress": "0x35",
"svcCreateSession": "0x40",
"svcAcceptSession": "0x41",
"svcReplyAndReceiveLight": "0x42",
"svcReplyAndReceive": "0x43",
"svcReplyAndReceiveWithUserBuffer": "0x44",
"svcCreateEvent": "0x45",
"svcCallSecureMonitor": "0x7F"
}
}, {
"type": "min_kernel_version",
"value": "0x0091"
}, {
"type": "handle_table_size",
"value": 256
}]
}

View file

@ -0,0 +1,160 @@
/*
* Copyright (c) 2018-2020 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>
extern "C" {
extern u32 __start__;
u32 __nx_applet_type = AppletType_None;
u32 __nx_fs_num_sessions = 1;
#define INNER_HEAP_SIZE 0x4000
size_t nx_inner_heap_size = INNER_HEAP_SIZE;
char nx_inner_heap[INNER_HEAP_SIZE];
void __libnx_initheap(void);
void __appInit(void);
void __appExit(void);
/* Exception handling. */
alignas(16) u8 __nx_exception_stack[ams::os::MemoryPageSize];
u64 __nx_exception_stack_size = sizeof(__nx_exception_stack);
void __libnx_exception_handler(ThreadExceptionDump *ctx);
}
namespace ams {
ncm::ProgramId CurrentProgramId = ncm::SystemProgramId::Pgl;
namespace result {
bool CallFatalOnResultAssertion = false;
}
}
using namespace ams;
void __libnx_exception_handler(ThreadExceptionDump *ctx) {
ams::CrashHandler(ctx);
}
namespace ams::pgl {
namespace {
/* pgl. */
constexpr size_t NumServers = 1;
ams::sf::hipc::ServerManager<NumServers> g_server_manager;
constexpr sm::ServiceName ShellServiceName = sm::ServiceName::Encode("pgl");
constexpr size_t ShellMaxSessions = 8; /* Official maximum is 8. */
/* TODO: C++20 constinit */ pgl::srv::ShellInterface g_shell_interface;
ALWAYS_INLINE std::shared_ptr<pgl::srv::ShellInterface> GetSharedPointerToShellInterface() {
return ams::sf::ServiceObjectTraits<pgl::srv::ShellInterface>::SharedPointerHelper::GetEmptyDeleteSharedPointer(std::addressof(g_shell_interface));
}
void RegisterServiceSession() {
R_ABORT_UNLESS(g_server_manager.RegisterServer<pgl::srv::ShellInterface>(ShellServiceName, ShellMaxSessions, GetSharedPointerToShellInterface()));
}
void LoopProcess() {
g_server_manager.LoopProcess();
}
/* NOTE: Nintendo reserves only 0x2000 bytes for this heap, which is used "mostly" to allocate shell event observers. */
/* However, we would like very much for homebrew sysmodules to be able to subscribe to events if they so choose */
/* And so we will use a larger heap (32 KB). */
/* We should have a smaller memory footprint than N in the end, regardless. */
u8 g_heap_memory[32_KB];
TYPED_STORAGE(ams::sf::ExpHeapMemoryResource) g_heap_memory_resource;
void *Allocate(size_t size) {
return lmem::AllocateFromExpHeap(GetReference(g_heap_memory_resource).GetHandle(), size);
}
void Deallocate(void *p, size_t size) {
return lmem::FreeToExpHeap(GetReference(g_heap_memory_resource).GetHandle(), p);
}
void InitializeHeap() {
auto heap_handle = lmem::CreateExpHeap(g_heap_memory, sizeof(g_heap_memory), lmem::CreateOption_ThreadSafe);
new (GetPointer(g_heap_memory_resource)) ams::sf::ExpHeapMemoryResource(heap_handle);
}
}
}
void __libnx_initheap(void) {
void* addr = nx_inner_heap;
size_t size = nx_inner_heap_size;
/* Newlib */
extern char* fake_heap_start;
extern char* fake_heap_end;
fake_heap_start = (char*)addr;
fake_heap_end = (char*)addr + size;
ams::pgl::InitializeHeap();
}
void __appInit(void) {
hos::SetVersionForLibnx();
fs::SetAllocator(pgl::Allocate, pgl::Deallocate);
sm::DoWithSession([&]() {
R_ABORT_UNLESS(setInitialize());
R_ABORT_UNLESS(setsysInitialize());
R_ABORT_UNLESS(pmshellInitialize());
R_ABORT_UNLESS(ldrShellInitialize());
R_ABORT_UNLESS(lrInitialize());
R_ABORT_UNLESS(fsInitialize());
});
ams::CheckApiVersion();
}
void __appExit(void) {
fsExit();
lrExit();
ldrShellExit();
pmshellExit();
setsysExit();
setExit();
}
int main(int argc, char **argv)
{
/* Register the pgl service. */
pgl::RegisterServiceSession();
/* Initialize the server library. */
pgl::srv::Initialize(std::addressof(pgl::g_shell_interface), GetPointer(pgl::g_heap_memory_resource));
/* Loop forever, servicing our services. */
pgl::LoopProcess();
/* Cleanup */
return 0;
}

View file

@ -34,26 +34,6 @@ namespace ams::pm::impl {
u32 flags;
};
enum LaunchFlags {
LaunchFlags_None = 0,
LaunchFlags_SignalOnExit = (1 << 0),
LaunchFlags_SignalOnStart = (1 << 1),
LaunchFlags_SignalOnException = (1 << 2),
LaunchFlags_SignalOnDebugEvent = (1 << 3),
LaunchFlags_StartSuspended = (1 << 4),
LaunchFlags_DisableAslr = (1 << 5),
};
enum LaunchFlagsDeprecated {
LaunchFlagsDeprecated_None = 0,
LaunchFlagsDeprecated_SignalOnExit = (1 << 0),
LaunchFlagsDeprecated_StartSuspended = (1 << 1),
LaunchFlagsDeprecated_SignalOnException = (1 << 2),
LaunchFlagsDeprecated_DisableAslr = (1 << 3),
LaunchFlagsDeprecated_SignalOnDebugEvent = (1 << 4),
LaunchFlagsDeprecated_SignalOnStart = (1 << 5),
};
#define GET_FLAG_MASK(flag) (hos_version >= hos::Version_5_0_0 ? static_cast<u32>(LaunchFlags_##flag) : static_cast<u32>(LaunchFlagsDeprecated_##flag))
inline bool ShouldSignalOnExit(u32 launch_flags) {