diff --git a/nx/include/switch/services/applet.h b/nx/include/switch/services/applet.h index d46f6faa..aec6a3b5 100644 --- a/nx/include/switch/services/applet.h +++ b/nx/include/switch/services/applet.h @@ -189,6 +189,13 @@ typedef enum { AppletCaptureSharedBuffer_CallerApplet = 2, ///< CallerApplet } AppletCaptureSharedBuffer; +/// ProgramSpecifyKind for the ExecuteProgram cmd. Controls the type of the u64 passed to the ExecuteProgram cmd. +typedef enum { + AppletProgramSpecifyKind_ExecuteProgram = 0, ///< u8 ProgramIndex. + AppletProgramSpecifyKind_JumpToSubApplicationProgramForDevelopment = 1, ///< u64 titleID. Only available when DebugMode is enabled. + AppletProgramSpecifyKind_RestartProgram = 2, ///< u64 = value 0. +} AppletProgramSpecifyKind; + /// applet hook function. typedef void (*AppletHookFn)(AppletHookType hook, void* param); @@ -1345,6 +1352,42 @@ Result appletQueryApplicationPlayStatistics(PdmApplicationPlayStatistics *stats, */ Result appletQueryApplicationPlayStatisticsByUid(u128 userID, PdmApplicationPlayStatistics *stats, const u64 *titleIDs, s32 count, s32 *total_out); +/** + * @brief Launches Application title {current_titleID}+programIndex. This will enter an infinite-sleep-loop on success. + * @note Only available with AppletType_*Application on [5.0.0+]. + * @note Creates the storage if needed. Uses cmd ClearUserChannel. Uses cmd UnpopToUserChannel when the storage was created. Lastly cmd ExecuteProgramCmd is used. + * @param[in] programIndex ProgramIndex, must be 0x0-0xFF. 0 is the same as the current titleID. ProgramIndex values where the title is not installed should not be used. + * @param[in] buffer Optional buffer containing the storage data which will be used for ::AppletLaunchParameterKind_UserChannel with the launched Application, can be NULL. + * @param[in] size Size of the above buffer, 0 to not use the storage. Must be <=0x1000. + */ +Result appletExecuteProgram(s32 programIndex, const void* buffer, size_t size); + +/** + * @brief Launches the specified Application titleID. + * @note Only available with AppletType_*Application on [5.0.0+], with DebugMode enabled. + * @note Creates the storage if needed. Uses cmd ClearUserChannel. Uses cmd UnpopToUserChannel when the storage was created. Lastly cmd ExecuteProgramCmd is used. + * @param[in] titleID Application titleID. + * @param[in] buffer Optional buffer containing the storage data which will be used for ::AppletLaunchParameterKind_UserChannel with the launched Application, can be NULL. + * @param[in] size Size of the above buffer, 0 to not use the storage. Must be <=0x1000. + */ +Result appletJumpToSubApplicationProgramForDevelopment(u64 titleID, const void* buffer, size_t size); + +/** + * @brief Relaunches the current Application. + * @note Only available with AppletType_*Application on [5.0.0+]. + * @note Creates the storage if needed. Uses cmd ClearUserChannel. Uses cmd UnpopToUserChannel when the storage was created. Lastly cmd ExecuteProgramCmd is used. + * @param[in] buffer Optional buffer containing the storage data which will be used for ::AppletLaunchParameterKind_UserChannel with the launched Application, can be NULL. + * @param[in] size Size of the above buffer, 0 to not use the storage. Must be <=0x1000. + */ +Result appletRestartProgram(const void* buffer, size_t size); + +/** + * @brief Gets the ProgramIndex of the Application which launched this title. + * @note Only available with AppletType_*Application on [5.0.0+]. + * @param[out] programIndex ProgramIndex, -1 when there was no previous title. + */ +Result appletGetPreviousProgramIndex(s32 *programIndex); + /** * @brief Gets an Event which is signaled for GpuErrorDetected. * @note Only available with AppletType_*Application on [8.0.0+]. diff --git a/nx/source/services/applet.c b/nx/source/services/applet.c index 8e1c07a4..850186c5 100644 --- a/nx/source/services/applet.c +++ b/nx/source/services/applet.c @@ -4277,6 +4277,112 @@ Result appletQueryApplicationPlayStatisticsByUid(u128 userID, PdmApplicationPlay return rc; } +static Result _appletExecuteProgramCmd(AppletProgramSpecifyKind kind, u64 inval) { + if (!serviceIsActive(&g_appletSrv) || !_appletIsApplication()) + return MAKERESULT(Module_Libnx, LibnxError_NotInitialized); + if (hosversionBefore(5,0,0)) + return MAKERESULT(Module_Libnx, LibnxError_IncompatSysVer); + + IpcCommand c; + ipcInitialize(&c); + + struct { + u64 magic; + u64 cmd_id; + u32 kind; + u64 inval; + } *raw; + + raw = serviceIpcPrepareHeader(&g_appletIFunctions, &c, sizeof(*raw)); + + raw->magic = SFCI_MAGIC; + raw->cmd_id = 120; + raw->kind = kind; + raw->inval = inval; + + Result rc = serviceIpcDispatch(&g_appletIFunctions); + + if (R_SUCCEEDED(rc)) { + IpcParsedCommand r; + struct { + u64 magic; + u64 result; + } *resp; + + serviceIpcParse(&g_appletIFunctions, &r, sizeof(*resp)); + resp = r.Raw; + + rc = resp->result; + } + + return rc; +} + +static Result _appletClearUserChannel(void) { + if (!serviceIsActive(&g_appletSrv) || !_appletIsApplication()) + return MAKERESULT(Module_Libnx, LibnxError_NotInitialized); + if (hosversionBefore(5,0,0)) + return MAKERESULT(Module_Libnx, LibnxError_IncompatSysVer); + + return _appletCmdNoIO(&g_appletIFunctions, 121); +} + +static Result _appletUnpopToUserChannel(AppletStorage *s) { + if (!serviceIsActive(&g_appletSrv) || !_appletIsApplication()) + return MAKERESULT(Module_Libnx, LibnxError_NotInitialized); + if (hosversionBefore(5,0,0)) + return MAKERESULT(Module_Libnx, LibnxError_IncompatSysVer); + + return _appletCmdInStorage(&g_appletIFunctions, s, 122); +} + +static Result _appletExecuteProgram(AppletProgramSpecifyKind kind, u64 inval, const void* buffer, size_t size) { + Result rc=0; + AppletStorage storage={0}; + + if (size > 0x1000) + return MAKERESULT(Module_Libnx, LibnxError_BadInput); + + if (buffer!=NULL && size!=0) { + rc = appletCreateStorage(&storage, size); + if (R_SUCCEEDED(rc)) rc = appletStorageWrite(&storage, 0, buffer, size); + } + + if (R_SUCCEEDED(rc)) rc = _appletClearUserChannel(); + if (R_SUCCEEDED(rc) && buffer!=0 && size!=0) rc = _appletUnpopToUserChannel(&storage); + if (R_SUCCEEDED(rc)) rc = _appletExecuteProgramCmd(kind, inval); + + appletStorageClose(&storage); + + return rc; +} + +Result appletExecuteProgram(s32 programIndex, const void* buffer, size_t size) { + Result rc=0; + + if (programIndex<0 || programIndex>0xff) rc = MAKERESULT(Module_Libnx, LibnxError_BadInput); + if (R_SUCCEEDED(rc)) rc = _appletExecuteProgram(AppletProgramSpecifyKind_ExecuteProgram, (u64)programIndex, buffer, size); + if (R_SUCCEEDED(rc)) _appletInfiniteSleepLoop(); + return rc; +} + +Result appletJumpToSubApplicationProgramForDevelopment(u64 titleID, const void* buffer, size_t size) { + return _appletExecuteProgram(AppletProgramSpecifyKind_JumpToSubApplicationProgramForDevelopment, titleID, buffer, size); +} + +Result appletRestartProgram(const void* buffer, size_t size) { + return _appletExecuteProgram(AppletProgramSpecifyKind_RestartProgram, 0, buffer, size); +} + +Result appletGetPreviousProgramIndex(s32 *programIndex) { + if (!serviceIsActive(&g_appletSrv) || !_appletIsApplication()) + return MAKERESULT(Module_Libnx, LibnxError_NotInitialized); + if (hosversionBefore(5,0,0)) + return MAKERESULT(Module_Libnx, LibnxError_IncompatSysVer); + + return _appletCmdNoInOut32(&g_appletIFunctions, (u32*)programIndex, 123); +} + Result appletGetGpuErrorDetectedSystemEvent(Event *out_event) { if (!serviceIsActive(&g_appletSrv) || !_appletIsApplication()) return MAKERESULT(Module_Libnx, LibnxError_NotInitialized);