From e06c8e542feb7624d2e7ca2af0d41bcf5637d887 Mon Sep 17 00:00:00 2001 From: AriA99 Date: Fri, 27 Jan 2017 18:06:28 +0000 Subject: [PATCH] Add nim:s client implementation. --- libctru/include/3ds.h | 1 + libctru/include/3ds/services/nim.h | 151 +++++++++++++++++ libctru/source/services/nim.c | 258 +++++++++++++++++++++++++++++ 3 files changed, 410 insertions(+) create mode 100644 libctru/include/3ds/services/nim.h create mode 100644 libctru/source/services/nim.c diff --git a/libctru/include/3ds.h b/libctru/include/3ds.h index 69b2ddf..ebee3d7 100644 --- a/libctru/include/3ds.h +++ b/libctru/include/3ds.h @@ -46,6 +46,7 @@ extern "C" { #include <3ds/services/httpc.h> #include <3ds/services/uds.h> #include <3ds/services/ndm.h> +#include <3ds/services/nim.h> #include <3ds/services/nwmext.h> #include <3ds/services/ir.h> #include <3ds/services/ns.h> diff --git a/libctru/include/3ds/services/nim.h b/libctru/include/3ds/services/nim.h new file mode 100644 index 0000000..b8b72f1 --- /dev/null +++ b/libctru/include/3ds/services/nim.h @@ -0,0 +1,151 @@ +/** + * @file nim.h + * @brief NIM (network installation management) service. + * + * This service is used to download and install titles from Nintendo's CDN. + * + * We differentiate between two different kinds of downloads: + * + * - "active" downloads, which are downloads started with @ref NIMS_StartDownload or @ref NIMS_StartDownloadSimple. The process must keep polling the current status using @ref NIMS_GetProgress. + * - "tasks", which are downloads started with @ref NIMS_RegisterTask. These are only processed in sleep mode. + */ +#pragma once + +// FS_MediaType +#include <3ds/services/fs.h> + +/// Mode that NIM downloads/installs a title with. +typedef enum +{ + IM_DEFAULT = 0, ///< Initial installation + IM_UNKNOWN1, ///< Unknown + IM_UNKNOWN2, ///< Unknown + IM_REINSTALL, ///< Reinstall currently installed title; use this if the title is already installed (including updates) +} NIM_InstallationMode; + +/// Current state of a NIM download/installation. +typedef enum +{ + DS_NOT_INITIALIZED = 0, ///< Download not yet initialized + DS_INITIALIZED, ///< Download initialized + DS_DOWNLOAD_TMD, ///< Downloading and installing TMD + DS_PREPARE_SAVE_DATA, ///< Initializing save data + DS_DOWNLOAD_CONTENTS, ///< Downloading and installing contents + DS_WAIT_COMMIT, ///< Waiting before calling AM_CommitImportTitles + DS_COMMITTING, ///< Running AM_CommitImportTitles + DS_FINISHED, ///< Title installation finished + DS_VERSION_ERROR, ///< (unknown error regarding title version) + DS_CREATE_CONTEXT, ///< Creating the .ctx file? + DS_CANNOT_RECOVER, ///< Irrecoverable error encountered (e.g. out of space) + DS_INVALID, ///< Invalid state +} NIM_DownloadState; + +/// Input configuration for NIM download/installation tasks. +typedef struct +{ + u64 titleId; ///< Title ID + u32 version; ///< Title version + u32 unknown_0; ///< Always 0 + u8 ratingAge; ///< Age for the HOME Menu parental controls + u8 mediaType; ///< Media type, see @ref FS_MediaType enum + u8 padding[2]; ///< Padding + u32 unknown_1; ///< Unknown input, seems to be always 0 +} NIM_TitleConfig; + +/// Output struct for NIM downloads/installations in progress. +typedef struct +{ + u32 state; ///< State, see NIM_DownloadState enum + Result lastResult; ///< Last result code in NIM + u64 downloadedSize; ///< Amount of bytes that have been downloaded + u64 totalSize; ///< Amount of bytes that need to be downloaded in total +} NIM_TitleProgress; + +/** + * @brief Initializes nim:s. This uses networking and is blocking. + * @param buffer A buffer for internal use. It must be at least 0x20000 bytes long. + * @param buffer_len Length of the passed buffer. + */ +Result nimsInit(void *buffer, size_t buffer_len); + +/** + * @brief Initializes nim:s with the given TIN. This uses networking and is blocking. + * @param buffer A buffer for internal use. It must be at least 0x20000 bytes long. + * @param buffer_len Length of the passed buffer. + * @param TIN The TIN to initialize nim:s with. If you do not know what a TIN is or why you would want to change it, use @ref nimsInit instead. + */ +Result nimsInitWithTIN(void *buffer, size_t buffer_len, const char *TIN); + +/// Exits nim:s. +void nimsExit(void); + +/// Gets the current nim:s session handle. +Handle *nimsGetSessionHandle(void); + +/** + * @brief Sets an attribute. + * @param attr Name of the attribute. + * @param val Value of the attribute. + */ +Result NIMS_SetAttribute(const char *attr, const char *val); + +/** + * @brief Checks if nim wants a system update. + * @param want_update Set to true if a system update is required. Can be NULL. + */ +Result NIMS_WantUpdate(bool *want_update); + +/** + * @brief Makes a TitleConfig struct for use with @ref NIMS_RegisterTask, @ref NIMS_StartDownload or @ref NIMS_StartDownloadSimple. + * @param cfg Struct to initialize. + * @param titleId Title ID to download and install. + * @param version Version of the title to download and install. + * @param ratingAge Age for which the title is aged; used by parental controls in HOME Menu. + * @param mediaType Media type of the title to download and install. + */ +void NIMS_MakeTitleConfig(NIM_TitleConfig *cfg, u64 titleId, u32 version, u8 ratingAge, FS_MediaType mediaType); + +/** + * @brief Registers a background download task with NIM. These are processed in sleep mode only. + * @param cfg Title config to use. See @ref NIMS_MakeTitleConfig. + * @param name Name of the title in UTF-8. Will be displayed on the HOME Menu. Maximum 73 characters. + * @param maker Name of the maker/publisher in UTF-8. Will be displayed on the HOME Menu. Maximum 37 characters. + */ +Result NIMS_RegisterTask(const NIM_TitleConfig *cfg, const char *name, const char *maker); + +/** + * @brief Checks whether a background download task for the given title is registered with NIM. + * @param titleId Title ID to check for. + * @param registered Whether there is a background download task registered. + */ +Result NIMS_IsTaskRegistered(u64 titleId, bool *registered); + +/** + * @brief Unregisters a background download task. + * @param titleId Title ID whose background download task to cancel. + */ +Result NIMS_UnregisterTask(u64 titleId); + +/** + * @brief Starts an active download with NIM. Progress can be checked with @ref NIMS_GetProcess. Do not exit the process while a download is in progress without calling @ref NIMS_CancelDownload. + * @param cfg Title config to use. See @ref NIMS_MakeTitleConfig. + * @param mode The installation mode to use. See @ref NIM_InstallationMode. + */ +Result NIMS_StartDownload(const NIM_TitleConfig *cfg, NIM_InstallationMode mode); + +/** + * @brief Starts an active download with NIM with default installation mode; cannot reinstall titles. Progress can be checked with @ref NIMS_GetProcess. Do not exit the process while a download is in progress without calling @ref NIMS_CancelDownload. + * @param cfg Title config to use. See @ref NIMS_MakeTitleConfig. + */ +Result NIMS_StartDownloadSimple(const NIM_TitleConfig *cfg); + +/** + * @brief Checks the status of the current active download. + * @param tp Title progress struct to write to. See @ref NIM_TitleProgress. + */ +Result NIMS_GetProgress(NIM_TitleProgress *tp); + +/** + * @brief Cancels the current active download with NIM. + */ +Result NIMS_CancelDownload(void); diff --git a/libctru/source/services/nim.c b/libctru/source/services/nim.c new file mode 100644 index 0000000..1e26448 --- /dev/null +++ b/libctru/source/services/nim.c @@ -0,0 +1,258 @@ +#include +#include +#include +#include <3ds/types.h> +#include <3ds/result.h> +#include <3ds/svc.h> +#include <3ds/srv.h> +#include <3ds/synchronization.h> +#include <3ds/services/nim.h> +#include <3ds/ipc.h> +#include <3ds/util/utf.h> + +static Handle nimsHandle; +static int nimsRefCount; + +static Result NIMS_GetInitializeResult(void) +{ + Result ret = 0; + u32 *cmdbuf = getThreadCommandBuffer(); + + cmdbuf[0] = IPC_MakeHeader(0x3f, 0, 0); // 0x003f0000 + + if (R_FAILED(ret = svcSendSyncRequest(nimsHandle))) return ret; + + return (Result)cmdbuf[1]; +} + +static Result NIMS_RegisterSelf(void) +{ + Result ret = 0; + u32 *cmdbuf = getThreadCommandBuffer(); + + cmdbuf[0] = IPC_MakeHeader(0x3c, 0, 2); // 0x003c0002 + cmdbuf[1] = IPC_Desc_CurProcessHandle(); + cmdbuf[2] = IPC_Desc_MoveHandles(1); + + if (R_FAILED(ret = svcSendSyncRequest(nimsHandle))) return ret; + + return (Result)cmdbuf[1]; +} + +static Result NIMS_ConnectNoTicketDownload(void *buffer, size_t buffer_len) +{ + Result ret = 0; + u32 *cmdbuf = getThreadCommandBuffer(); + + cmdbuf[0] = IPC_MakeHeader(0x57, 2, 2); // 0x00570082 + cmdbuf[1] = (u32)buffer; // What the fuck? + cmdbuf[2] = buffer_len; + cmdbuf[3] = IPC_Desc_Buffer(buffer_len, IPC_BUFFER_W); + cmdbuf[4] = (u32)buffer; + + if (R_FAILED(ret = svcSendSyncRequest(nimsHandle))) return ret; + + return (Result)cmdbuf[1]; +} + +Result nimsInit(void *buffer, size_t buffer_len) +{ + // Default TIN obtained from eShop + return nimsInitWithTIN(buffer, buffer_len, "56789"); +} + +Result nimsInitWithTIN(void *buffer, size_t buffer_len, const char *TIN) +{ + Result res; + if (AtomicPostIncrement(&nimsRefCount)) return 0; + res = srvGetServiceHandle(&nimsHandle, "nim:s"); + if (R_FAILED(res)) AtomicDecrement(&nimsRefCount); + + if (R_FAILED(res = NIMS_GetInitializeResult())) { + nimsExit(); + return res; + } + + if (R_FAILED(res = NIMS_RegisterSelf())) { + nimsExit(); + return res; + } + + if (R_FAILED(res = NIMS_SetAttribute("TIN", TIN))) { + nimsExit(); + return res; + } + + if (R_FAILED(res = NIMS_ConnectNoTicketDownload(buffer, buffer_len))) { + nimsExit(); + return res; + } + + return res; +} + +void nimsExit(void) +{ + if (AtomicDecrement(&nimsRefCount)) return; + svcCloseHandle(nimsHandle); +} + +Handle *nimsGetSessionHandle(void) +{ + return &nimsHandle; +} + +Result NIMS_SetAttribute(const char *attr, const char *val) +{ + Result ret = 0; + u32 *cmdbuf = getThreadCommandBuffer(); + + cmdbuf[0] = IPC_MakeHeader(0xb, 2, 4); // 0x000b0084 + cmdbuf[1] = strlen(attr) + 1; + cmdbuf[2] = strlen(val) + 1; + cmdbuf[3] = IPC_Desc_StaticBuffer(cmdbuf[1], 0); + cmdbuf[4] = (u32)attr; + cmdbuf[5] = IPC_Desc_StaticBuffer(cmdbuf[2], 1); + cmdbuf[6] = (u32)val; + + if (R_FAILED(ret = svcSendSyncRequest(nimsHandle))) return ret; + + return (Result)cmdbuf[1]; +} + + +Result NIMS_WantUpdate(bool *want_update) +{ + Result ret = 0; + u32 *cmdbuf = getThreadCommandBuffer(); + + cmdbuf[0] = IPC_MakeHeader(0xa, 0, 0); // 0x000a0000 + + if (R_FAILED(ret = svcSendSyncRequest(nimsHandle))) return ret; + if (want_update) *want_update = cmdbuf[2] & 0xFF; + + return cmdbuf[1]; +} + +void NIMS_MakeTitleConfig(NIM_TitleConfig *cfg, u64 titleId, u32 version, u8 ratingAge, FS_MediaType mediaType) +{ + memset(cfg, 0, sizeof(*cfg)); + + cfg->titleId = titleId; + cfg->version = version; + cfg->ratingAge = ratingAge; + cfg->mediaType = mediaType; +} + +Result NIMS_RegisterTask(const NIM_TitleConfig *cfg, const char *name, const char *maker) +{ + Result ret = 0; + u32 *cmdbuf = getThreadCommandBuffer(); + uint16_t name16[0x92] = {0}, maker16[0x4a] = {0}; + ssize_t units; + + units = utf8_to_utf16(name16, (const uint8_t *)name, sizeof(name16)/sizeof(*name16)); + if (units < 0 || units > sizeof(name16)/sizeof(*name16)) return -2; + units = utf8_to_utf16(maker16, (const uint8_t *)maker, sizeof(maker16)/sizeof(*maker16)); + if (units < 0 || units > sizeof(maker16)/sizeof(*maker16)) return -2; + + cmdbuf[0] = IPC_MakeHeader(0x55, 9, 6); // 0x00550246 + memcpy(&cmdbuf[1], cfg, sizeof(*cfg)); + // unused: cmdbuf[7], cmdbuf[8] + cmdbuf[9] = 0; // always 0? At least it is for eShop + cmdbuf[10] = IPC_Desc_CurProcessHandle(); + // unused: cmdbuf[11] + cmdbuf[12] = IPC_Desc_StaticBuffer(sizeof(name16), 1); + cmdbuf[13] = (unsigned int)name16; + cmdbuf[14] = IPC_Desc_StaticBuffer(sizeof(maker16), 5); + cmdbuf[15] = (unsigned int)maker16; + + if (R_FAILED(ret = svcSendSyncRequest(nimsHandle))) return ret; + + return (Result)cmdbuf[1]; +} + +Result NIMS_IsTaskRegistered(u64 titleId, bool *registered) +{ + Result ret = 0; + u32 *cmdbuf = getThreadCommandBuffer(); + + cmdbuf[0] = IPC_MakeHeader(0x6, 2, 0); // 0x00060080 + cmdbuf[1] = titleId & 0xFFFFFFFF; + cmdbuf[2] = (u32) ((titleId >> 32) & 0xFFFFFFFF); + + if (R_FAILED(ret = svcSendSyncRequest(nimsHandle))) return ret; + if (registered) *registered = cmdbuf[2] & 0xFF; + + return (Result)cmdbuf[1]; +} + +Result NIMS_UnregisterTask(u64 titleId) +{ + Result ret = 0; + u32 *cmdbuf = getThreadCommandBuffer(); + + cmdbuf[0] = IPC_MakeHeader(0x5, 2, 2); // 0x00050082 + cmdbuf[1] = titleId & 0xFFFFFFFF; + cmdbuf[2] = (u32) ((titleId >> 32) & 0xFFFFFFFF); + cmdbuf[3] = IPC_Desc_CurProcessHandle(); + + if (R_FAILED(ret = svcSendSyncRequest(nimsHandle))) return ret; + + return (Result)cmdbuf[1]; +} + +Result NIMS_StartDownload(const NIM_TitleConfig *cfg, NIM_InstallationMode mode) +{ + Result ret = 0; + u32 *cmdbuf = getThreadCommandBuffer(); + + cmdbuf[0] = IPC_MakeHeader(0x42, 9, 0); // 0x00420240 + memcpy(&cmdbuf[1], cfg, sizeof(*cfg)); + // unused: cmdbuf[7], cmdbuf[8] + cmdbuf[9] = mode; + + if (R_FAILED(ret = svcSendSyncRequest(nimsHandle))) return ret; + + return (Result)cmdbuf[1]; +} + +Result NIMS_StartDownloadSimple(const NIM_TitleConfig *cfg) +{ + Result ret = 0; + u32 *cmdbuf = getThreadCommandBuffer(); + + cmdbuf[0] = IPC_MakeHeader(0x1, 8, 0); // 0x00010200 + memcpy(&cmdbuf[1], cfg, sizeof(*cfg)); + + if (R_FAILED(ret = svcSendSyncRequest(nimsHandle))) return ret; + + return (Result)cmdbuf[1]; +} + +Result NIMS_GetProgress(NIM_TitleProgress *tp) +{ + Result ret = 0; + u32 *cmdbuf = getThreadCommandBuffer(); + + cmdbuf[0] = IPC_MakeHeader(0x3, 0, 0); // 0x00030000 + + if (R_FAILED(ret = svcSendSyncRequest(nimsHandle))) + return ret; + + memcpy(tp, &cmdbuf[2], sizeof(*tp)); + + return (Result)cmdbuf[1]; +} + +Result NIMS_CancelDownload(void) +{ + Result ret = 0; + u32 *cmdbuf = getThreadCommandBuffer(); + + cmdbuf[0] = IPC_MakeHeader(0x2, 0, 0); // 0x00020000 + + if (R_FAILED(ret = svcSendSyncRequest(nimsHandle))) return ret; + + return (Result)cmdbuf[1]; +}