├── source ├── sync.s ├── start.s ├── thread_start.s ├── am │ ├── util.c │ ├── cia.c │ ├── demodb.c │ ├── twlexport.c │ └── pipe.c ├── 3ds │ ├── err.c │ ├── svc.s │ ├── srv.c │ ├── synchronization.c │ └── fs.c ├── allocator.c ├── sha256.c └── main.c ├── .gitignore ├── include ├── am │ ├── globals.h │ ├── asserts.h │ ├── ipc.h │ ├── cia.h │ ├── pipe.h │ ├── demodb.h │ ├── format.h │ ├── util.h │ └── twlexport.h ├── allocator.h ├── 3ds │ ├── srv.h │ ├── types.h │ ├── synchronization.h │ ├── err.h │ ├── svc.h │ ├── ipc.h │ ├── fs.h │ ├── result.h │ └── am9.h ├── memops.h ├── sha256.h └── errors.h ├── LICENSE.libctru.md ├── LICENSE ├── LICENSE.sha256.md ├── am11.rsf ├── README.md ├── am11_debug.rsf ├── Makefile └── dsiware_export_stuff.py /source/sync.s: -------------------------------------------------------------------------------- 1 | .syntax unified 2 | .arch armv6k 3 | .arm 4 | .global __sync_synchronize_dmb 5 | .global __dmb 6 | 7 | __dmb: 8 | __sync_synchronize_dmb: 9 | mov r0, #0 10 | mcr p15, 0, r0, c7, c10, 5 11 | bx lr -------------------------------------------------------------------------------- /source/start.s: -------------------------------------------------------------------------------- 1 | .arch armv6k 2 | .section .init, "ax", %progbits 3 | .arm 4 | .align 2 5 | .global _start 6 | .syntax unified 7 | .type _start, %function 8 | _start: 9 | bl AM_Main 10 | svc 0x03 11 | .size _start, .-_start -------------------------------------------------------------------------------- /source/thread_start.s: -------------------------------------------------------------------------------- 1 | .section .text._thread_start, "ax", %progbits 2 | .align 2 3 | .global _thread_start 4 | .type _thread_start, %function 5 | _thread_start: 6 | ldmdb sp, {r0,r1} @ pop function and argument 7 | blx r1 @ call function with argument set 8 | svc 0x09 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode/ 2 | .cache/ 3 | 4 | compile_flags.txt 5 | 6 | privatesav-dsiware-exports/ 7 | dsiware-exports/ 8 | build/* 9 | testcia/* 10 | 11 | 0004* 12 | 13 | *.cxi 14 | *.lst 15 | *.map 16 | *.idb 17 | *.id* 18 | *.nam 19 | *.til 20 | *.cia.* 21 | *.bin 22 | *.elf 23 | *.zip 24 | *.cia 25 | *.sh 26 | *.d 27 | compile_commands.json 28 | -------------------------------------------------------------------------------- /include/am/globals.h: -------------------------------------------------------------------------------- 1 | #ifndef _AM_GLOBALS_H 2 | #define _AM_GLOBALS_H 3 | 4 | #include <3ds/synchronization.h> 5 | #include 6 | #include 7 | 8 | extern RecursiveLock g_TMDReader_Lock; 9 | extern Database g_DemoDatabase; 10 | extern AM_Pipe g_PipeManager; 11 | extern Handle g_AddressArbiter; 12 | extern Handle g_SystemUpdaterMutex; 13 | 14 | #endif -------------------------------------------------------------------------------- /include/allocator.h: -------------------------------------------------------------------------------- 1 | #ifndef _AM_ALLOCATOR_H 2 | #define _AM_ALLOCATOR_H 3 | 4 | #define NULL ((void *)0) 5 | 6 | typedef unsigned int MEMTYPE; 7 | 8 | extern MEMTYPE *mem; 9 | extern MEMTYPE memSize; 10 | extern MEMTYPE avail; // 1st index of the 1st free range 11 | 12 | void meminit(void *ptr, unsigned size); 13 | void *malloc(unsigned size); 14 | void free(void *ptr); 15 | 16 | #endif -------------------------------------------------------------------------------- /source/am/util.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | void int_convert_to_hex(char* out, uint32_t n, bool newline) 4 | { 5 | *out++ = '0'; 6 | *out++ = 'x'; 7 | 8 | static const char hextable[16] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; 9 | for (int i = sizeof(uint32_t) - 1; i >= 0; --i) 10 | { 11 | unsigned char _i = n >> (i * 8); 12 | out[0] = hextable[_i >> 4]; 13 | out[1] = hextable[_i & 0xF]; 14 | out += 2; 15 | } 16 | 17 | if (newline) 18 | *out++ = '\n'; 19 | 20 | *out = 0; 21 | } -------------------------------------------------------------------------------- /LICENSE.libctru.md: -------------------------------------------------------------------------------- 1 | This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. 2 | 3 | Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 4 | 5 | 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 6 | 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 7 | 3. This notice may not be removed or altered from any source distribution. 8 | -------------------------------------------------------------------------------- /include/am/asserts.h: -------------------------------------------------------------------------------- 1 | #ifndef _AM_ASSERTS_H 2 | #define _AM_ASSERTS_H 3 | 4 | #include <3ds/result.h> 5 | #include <3ds/types.h> 6 | #include <3ds/err.h> 7 | #include <3ds/fs.h> 8 | 9 | static inline void assertNotAmOrFsWithMedia(Result res, MediaType media_type) 10 | { 11 | if (R_FAILED(res)) 12 | { 13 | u16 mod = R_MODULE(res); 14 | if ((mod == RM_FS && media_type == MediaType_NAND) || (mod != RM_FS && mod != RM_AM)) 15 | Err_Throw(res) 16 | } 17 | } 18 | 19 | static inline void assertNotAmOrFs(Result res) 20 | { 21 | if (R_FAILED(res)) 22 | { 23 | u16 mod = R_MODULE(res); 24 | if (mod != RM_FS && mod != RM_AM) 25 | Err_Throw(res) 26 | } 27 | } 28 | 29 | static inline void assertNotAm(Result res) 30 | { 31 | if (R_FAILED(res) && R_MODULE(res) != RM_AM) 32 | Err_Throw(res) 33 | } 34 | 35 | #endif 36 | -------------------------------------------------------------------------------- /include/am/ipc.h: -------------------------------------------------------------------------------- 1 | #ifndef _AM_IPC_H 2 | #define _AM_IPC_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include <3ds/types.h> 10 | #include <3ds/ipc.h> 11 | #include <3ds/am9.h> 12 | #include 13 | #include 14 | #include 15 | 16 | typedef struct AM_SessionData 17 | { 18 | Handle thread; 19 | Handle session; // needs to be freed in thread itself! 20 | void (* handle_ipc)(struct AM_SessionData *data); 21 | bool importing_title; 22 | u64 cia_deplist_buf[0x60]; 23 | MediaType media; 24 | } AM_SessionData; 25 | 26 | void AMNET_HandleIPC(AM_SessionData *session); 27 | void AMU_HandleIPC(AM_SessionData *session); 28 | void AMSYS_HandleIPC(AM_SessionData *session); 29 | void AMAPP_HandleIPC(AM_SessionData *session); 30 | 31 | extern void (* AM_IPCHandlers[4])(AM_SessionData *); 32 | 33 | #endif -------------------------------------------------------------------------------- /include/am/cia.h: -------------------------------------------------------------------------------- 1 | #ifndef _AM_CIA_H 2 | #define _AM_CIA_H 3 | 4 | #include <3ds/synchronization.h> 5 | #include <3ds/types.h> 6 | #include 7 | #include <3ds/am9.h> 8 | #include 9 | #include <3ds/fs.h> 10 | 11 | typedef struct CIAReader 12 | { 13 | CIAHeader header; 14 | Handle cia; 15 | } CIAReader; 16 | 17 | Result CIAReader_Init(CIAReader *rd, Handle cia, bool init_tmd); 18 | 19 | Result CIAReader_ReadMinTMD(CIAReader *rd); 20 | Result CIAReader_CalculateTitleSize(CIAReader *rd, MediaType media_type, u64 *size, u16 *indices_count, u32 *align_size, bool skip_tmd_read); 21 | Result CIAReader_CalculateRequiredSize(CIAReader *rd, MediaType media_type, u64 *size); 22 | Result CIAReader_ExtractMetaSMDH(CIAReader *rd, void *smdh); 23 | Result CIAReader_ExtractDependencyList(CIAReader *rd, void *list); 24 | Result CIAReader_ExtractMetaCoreVersion(CIAReader *rd, u32 *version); 25 | Result CIAReader_ExtractMeta(CIAReader *rd, u32 size, void *meta, u32 *read); 26 | Result CIAReader_GetTitleInfo(CIAReader *rd, MediaType media_type, TitleInfo *info); 27 | 28 | void CIAReader_Close(CIAReader *rd); 29 | 30 | #endif -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | 26 | -------------------------------------------------------------------------------- /source/3ds/err.c: -------------------------------------------------------------------------------- 1 | #include <3ds/err.h> 2 | 3 | Handle errf_session; 4 | u32 errf_refcount; 5 | 6 | Result errfInit() 7 | { 8 | Result res; 9 | 10 | if (errf_refcount++) return 0; 11 | if (R_FAILED(res = svcConnectToPort(&errf_session, "err:f"))) 12 | errfExit(); 13 | return res; 14 | } 15 | 16 | void errfExit() 17 | { 18 | if (--errf_refcount) 19 | return; 20 | if (errf_session) 21 | { 22 | svcCloseHandle(errf_session); 23 | errf_session = 0; 24 | } 25 | } 26 | 27 | void ERRF_ThrowResultNoRet(Result failure) 28 | { 29 | while (R_FAILED(errfInit())) 30 | svcSleepThread(100000000LLU); 31 | 32 | // manually inlined ERRF_Throw and adjusted to make smaller code output 33 | uint32_t *ipc_command = getThreadLocalStorage()->ipc_command; 34 | 35 | ipc_command[0] = IPC_MakeHeader(1, 32, 0); 36 | _memset32_aligned(&ipc_command[1], 0, sizeof(FatalErrorInfo)); 37 | 38 | FatalErrorInfo *error = (FatalErrorInfo *)&ipc_command[1]; 39 | 40 | error->type = ERRF_ERRTYPE_GENERIC; 41 | error->pcAddr = (u32)__builtin_extract_return_addr(__builtin_return_address(0)); 42 | error->resCode = failure; 43 | 44 | svcGetProcessId(&error->procId, CUR_PROCESS_HANDLE); 45 | 46 | svcSendSyncRequest(errf_session); 47 | errfExit(); 48 | 49 | while (true) 50 | svcSleepThread(10000000000LLU); // lighter loop 51 | } 52 | -------------------------------------------------------------------------------- /include/3ds/srv.h: -------------------------------------------------------------------------------- 1 | #ifndef _AM_3DS_SRV_H 2 | #define _AM_3DS_SRV_H 3 | 4 | #include <3ds/result.h> 5 | #include <3ds/err.h> 6 | #include <3ds/svc.h> 7 | #include <3ds/ipc.h> 8 | 9 | extern u32 srv_refcount; 10 | extern Handle srv_session; 11 | 12 | Result srvInit(); 13 | void srvExit(); 14 | 15 | // ipc cmd ids 16 | enum 17 | { 18 | ID_SRV_RegisterClient = 0x0001, 19 | ID_SRV_EnableNotification = 0x0002, 20 | ID_SRV_RegisterService = 0x0003, 21 | ID_SRV_UnregisterService = 0x0004, 22 | ID_SRV_GetServiceHandle = 0x0005, 23 | ID_SRV_RegisterPort = 0x0006, 24 | ID_SRV_UnregisterPort = 0x0007, 25 | ID_SRV_ReceiveNotification = 0x000B, 26 | // don't need any more 27 | }; 28 | 29 | Result SRV_RegisterClient(); 30 | Result SRV_EnableNotification(Handle *sempahore); 31 | Result SRV_RegisterService(Handle *service, const char *service_name, u32 service_name_len, u32 max_sessions); 32 | Result SRV_UnregisterService(const char *service_name, u32 service_name_length); 33 | Result SRV_GetServiceHandle(Handle *service, const char *service_name, u32 service_name_length, u32 flags); 34 | Result SRV_RegisterPort(const char *port_name, u32 port_name_length, Handle client_port); 35 | Result SRV_UnregisterPort(const char *port_name, u32 port_name_length); 36 | Result SRV_ReceiveNotification(u32 *notification_id); 37 | 38 | #endif -------------------------------------------------------------------------------- /include/memops.h: -------------------------------------------------------------------------------- 1 | #ifndef _AM_MEMOPS_H 2 | #define _AM_MEMOPS_H 3 | 4 | #include 5 | #include 6 | 7 | /* 8 | credit goes to luigoalma for this code 9 | */ 10 | 11 | inline static void _memset32_aligned(void* dest, uint32_t c, size_t size) { 12 | uint32_t *_dest = (uint32_t*)dest; 13 | for (; size >= 4; size -= 4) { 14 | *_dest = c; 15 | _dest++; 16 | } 17 | uint8_t *_dest8 = (uint8_t*)_dest; 18 | for (; size > 0; size--) { 19 | *_dest8 = c; 20 | _dest8++; 21 | } 22 | } 23 | 24 | inline static void _memset(void* dest, uint32_t c, size_t size) { 25 | if (((uintptr_t)dest & 0x3) == 0) { 26 | _memset32_aligned(dest, c, size); 27 | return; 28 | } 29 | uint8_t *_dest8 = (uint8_t*)dest; 30 | for (; size > 0; size--) { 31 | *_dest8 = c; 32 | _dest8++; 33 | } 34 | } 35 | 36 | inline static void _memcpy32_aligned(void* dest, const void* src, size_t size) { 37 | uint32_t *_dest = (uint32_t*)dest; 38 | const uint32_t *_src = (const uint32_t*)src; 39 | for (; size >= 4; size -= 4) { 40 | *_dest = *_src; 41 | _dest++; 42 | _src++; 43 | } 44 | uint8_t *_dest8 = (uint8_t*)_dest; 45 | const uint8_t *_src8 = (const uint8_t*)_src; 46 | for (; size > 0; size--) { 47 | *_dest8 = *_src8; 48 | _dest8++; 49 | _src8++; 50 | } 51 | } 52 | 53 | inline static void _memcpy(void* dest, const void* src, size_t size) { 54 | if (((uintptr_t)dest & 0x3) == 0 && ((uintptr_t)src & 0x3) == 0) { 55 | _memcpy32_aligned(dest, src, size); 56 | return; 57 | } 58 | uint8_t *_dest8 = (uint8_t*)dest; 59 | const uint8_t *_src8 = (const uint8_t*)src; 60 | for (; size > 0; size--) { 61 | *_dest8 = *_src8; 62 | _dest8++; 63 | _src8++; 64 | } 65 | } 66 | 67 | #endif -------------------------------------------------------------------------------- /include/3ds/types.h: -------------------------------------------------------------------------------- 1 | #ifndef _AM_3DS_TYPES_H 2 | #define _AM_3DS_TYPES_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #define U64_MAX UINT64_MAX 9 | #define U32_MAX UINT32_MAX 10 | #define U16_MAX UINT16_MAX 11 | #define U8_MAX UINT8_MAX 12 | 13 | typedef uint8_t u8; ///< 8-bit unsigned integer 14 | typedef uint16_t u16; ///< 16-bit unsigned integer 15 | typedef uint32_t u32; ///< 32-bit unsigned integer 16 | typedef uint64_t u64; ///< 64-bit unsigned integer 17 | 18 | typedef int8_t s8; ///< 8-bit signed integer 19 | typedef int16_t s16; ///< 16-bit signed integer 20 | typedef int32_t s32; ///< 32-bit signed integer 21 | typedef int64_t s64; ///< 64-bit signed integer 22 | 23 | #define LODWORD(x) ((u32)(x & 0xFFFFFFFF)) 24 | #define HIDWORD(x) ((u32)((x >> 32) & 0xFFFFFFFF)) 25 | #define BIT(n) (1 << (n)) 26 | #define MIN(x, y) (x < y ? x : y) 27 | #define MAX(x, y) (x > y ? x : y) 28 | #define ALIGN(x,y) ((x + y - 1) & ~(y - 1)) 29 | 30 | typedef s32 Handle; 31 | typedef s32 Result; 32 | 33 | // syscalls 34 | 35 | #define OS_HEAP_AREA_BEGIN ((void *)0x08000000) ///< Start of the heap area in the virtual address space 36 | #define OS_HEAP_AREA_END ((void *)0x0E000000) ///< End of the heap area in the virtual address space 37 | #define OS_MAP_AREA_BEGIN ((void *)0x10000000) ///< Start of the mappable area in the virtual address space 38 | #define OS_MAP_AREA_END ((void *)0x14000000) ///< End of the mappable area in the virtual address space 39 | 40 | #define CFG_FIRM_VERSIONREVISION ((u8 *) 0x1FF80061) 41 | #define CFG_FIRM_VERSIONMINOR ((u8 *) 0x1FF80062) 42 | #define CFG_FIRM_SYSCOREVER ((u32 *)0x1FF80064) 43 | 44 | #define CUR_PROCESS_HANDLE 0xFFFF8001 // Handle to current process 45 | 46 | // IPC 47 | 48 | typedef struct __attribute__((aligned(4))) ThreadLocalStorage 49 | { 50 | u8 any_purpose[128]; 51 | u32 ipc_command[64]; 52 | u32 ipc_static_buffers[32]; 53 | } ThreadLocalStorage; 54 | 55 | #endif -------------------------------------------------------------------------------- /include/3ds/synchronization.h: -------------------------------------------------------------------------------- 1 | #ifndef _AM_SYNCHRONIZATION_H 2 | #define _AM_SYNCHRONIZATION_H 3 | 4 | #include <3ds/types.h> 5 | #include <3ds/ipc.h> 6 | #include <3ds/svc.h> 7 | 8 | typedef s32 LightLock; 9 | 10 | typedef struct __attribute__((aligned(4))) RecursiveLock 11 | { 12 | LightLock lock; 13 | ThreadLocalStorage *thread_tag; 14 | u32 counter; 15 | } RecursiveLock; 16 | 17 | typedef struct __attribute__((aligned(4))) LightEvent 18 | { 19 | s32 state; ///< State of the event: -2=cleared sticky, -1=cleared oneshot, 0=signaled oneshot, 1=signaled sticky 20 | LightLock lock; ///< Lock used for sticky timer operation 21 | } LightEvent; 22 | 23 | enum 24 | { 25 | CLEARED_STICKY = -2, 26 | CLEARED_ONESHOT = -1, 27 | SIGNALED_ONESHOT = 0, 28 | SIGNALED_STICKY = 1 29 | }; 30 | 31 | Result syncInit(); 32 | void syncExit(); 33 | 34 | extern void __dmb(void); 35 | 36 | static inline void __clrex(void) 37 | { 38 | __asm__ __volatile__("clrex" ::: "memory"); 39 | } 40 | 41 | 42 | static inline s32 __ldrex(s32 *addr) 43 | { 44 | s32 val; 45 | __asm__ __volatile__("ldrex %[val], %[addr]" : [val] "=r" (val) : [addr] "Q" (*addr)); 46 | return val; 47 | } 48 | 49 | 50 | static inline bool __strex(s32 *addr, s32 val) 51 | { 52 | bool res; 53 | __asm__ __volatile__("strex %[res], %[val], %[addr]" : [res] "=&r" (res) : [val] "r" (val), [addr] "Q" (*addr)); 54 | return res; 55 | } 56 | 57 | static inline u8 __ldrexb(u8 *addr) 58 | { 59 | u8 val; 60 | __asm__ __volatile__("ldrexb %[val], %[addr]" : [val] "=r" (val) : [addr] "Q" (*addr)); 61 | return val; 62 | } 63 | 64 | static inline bool __strexb(u8 *addr, u8 val) 65 | { 66 | bool res; 67 | __asm__ __volatile__("strexb %[res], %[val], %[addr]" : [res] "=&r" (res) : [val] "r" (val), [addr] "Q" (*addr)); 68 | return res; 69 | } 70 | 71 | void LightLock_Init(LightLock *lock); 72 | void LightLock_Lock(LightLock *lock); 73 | void LightLock_Unlock(LightLock *lock); 74 | 75 | void RecursiveLock_Init(RecursiveLock *lock); 76 | void RecursiveLock_Lock(RecursiveLock *lock); 77 | void RecursiveLock_Unlock(RecursiveLock *lock); 78 | 79 | void LightEvent_Signal(LightEvent* event); 80 | void LightEvent_Wait(LightEvent* event); 81 | 82 | #endif -------------------------------------------------------------------------------- /LICENSE.sha256.md: -------------------------------------------------------------------------------- 1 | # Licensing Information 2 | 3 | Except as otherwise noted (below and/or in individual files), this project is 4 | licensed under the [Unlicense](#the-unlicense) 5 | (https://opensource.org/licenses/unlicense) or the [Zero Clause BSD 6 | license](#zero-clause-bsd-license) (https://opensource.org/licenses/0bsd), at 7 | your option. 8 | 9 | ## The Unlicense 10 | 11 | This is free and unencumbered software released into the public domain. 12 | 13 | Anyone is free to copy, modify, publish, use, compile, sell, or distribute this 14 | software, either in source code form or as a compiled binary, for any purpose, 15 | commercial or non-commercial, and by any means. 16 | 17 | In jurisdictions that recognize copyright laws, the author or authors of this 18 | software dedicate any and all copyright interest in the software to the public 19 | domain. We make this dedication for the benefit of the public at large and to 20 | the detriment of our heirs and successors. We intend this dedication to be an 21 | overt act of relinquishment in perpetuity of all present and future rights to 22 | this software under copyright law. 23 | 24 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 25 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 26 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 27 | AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 28 | ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 29 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 30 | 31 | For more information, please refer to 32 | 33 | ## Zero Clause BSD License 34 | 35 | © 2021 Alain Mosnier 36 | 37 | Permission to use, copy, modify, and/or distribute this software for any 38 | purpose with or without fee is hereby granted. 39 | 40 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 41 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND 42 | FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 43 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 44 | LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 45 | OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 46 | PERFORMANCE OF THIS SOFTWARE. 47 | -------------------------------------------------------------------------------- /am11.rsf: -------------------------------------------------------------------------------- 1 | BasicInfo: 2 | Title : am 3 | CompanyCode : "00" 4 | ProductCode : 5 | ContentType : Application 6 | Logo : None 7 | 8 | TitleInfo: 9 | UniqueId : 0x15 10 | Category : Base 11 | Version : 2 12 | 13 | Option: 14 | UseOnSD : false 15 | FreeProductCode : true # Removes limitations on ProductCode 16 | MediaFootPadding : false # If true CCI files are created with padding 17 | EnableCrypt : true # Enables encryption for NCCH and CIA 18 | EnableCompress : true # Compresses exefs code 19 | 20 | AccessControlInfo: 21 | IdealProcessor : 1 22 | AffinityMask : 2 23 | 24 | Priority : 28 25 | 26 | DisableDebug : true 27 | EnableForceDebug : false 28 | CanWriteSharedPage : false 29 | CanUsePrivilegedPriority : false 30 | CanUseNonAlphabetAndNumber : false 31 | PermitMainFunctionArgument : false 32 | CanShareDeviceMemory : false 33 | RunnableOnSleep : true 34 | SpecialMemoryArrange : false 35 | ResourceLimitCategory : Other 36 | 37 | CoreVersion : 2 38 | DescVersion : 2 39 | 40 | MemoryType : Base # Application / System / Base 41 | HandleTableSize: 0x200 42 | 43 | SystemSaveDataId1: 0x00010015 44 | 45 | SystemCallAccess: 46 | ControlMemory: 0x01 47 | ExitProcess: 0x03 48 | CreateThread: 0x08 49 | ExitThread: 0x09 50 | SleepThread: 0x0A 51 | CreateMutex: 0x13 52 | CreateEvent: 0x17 53 | SignalEvent: 0x18 54 | CreateAddressArbiter: 0x21 55 | ArbitrateAddressNoTimeout: 0x22 56 | CloseHandle: 0x23 57 | WaitSynchronization: 0x24 58 | WaitSynchronizationN: 0x25 59 | ConnectToPort: 0x2D 60 | SendSyncRequest: 0x32 61 | GetProcessId: 0x35 62 | GetResourceLimit: 0x38 63 | GetResourceLimitLimitValues: 0x39 64 | GetResourceLimitCurrentValues: 0x3A 65 | Break: 0x3C 66 | CreatePort: 0x47 67 | CreateSessionToPort: 0x48 68 | AcceptSession: 0x4A 69 | ReplyAndReceive: 0x4F 70 | 71 | InterruptNumbers: 72 | ServiceAccessControl: 73 | - pxi:am9 74 | - fs:USER 75 | 76 | SystemControlInfo: 77 | RemasterVersion: 0 78 | StackSize: 0x1000 79 | Dependency: 80 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # About 2 | 3 | An open-source reimplementation of the ARM11-side AM (**A**pplication **M**anager) system module for the Nintendo 3DS. 4 | This is my first extensive reverse-engineer-and-reimplement project, it's been a while since I started and now it's finally done. 5 | 6 | All of the AM-specific code has been written based on the stock implementation with some differences: 7 | - Thread stacks are using memory in the `.data` section instead of being dynamically allocated (along with some other data being in `.data` instead of being heap-allocated) 8 | - The CIA installation code supports misaligned writes and installs contents in batches of 64 or less depending on the amount of contents. 9 | - Content import write sizes are now limited exactly to what AM9 supports. 10 | 11 | I have decided not to give specific names to IPC commands because it would cause a massive amount of differences between what is known on sites like 3DBrew and the actual functionality of each command. 12 | Instead, I decided to give each command a small description that actually matches the behavior of the command. 13 | 14 | # Compiling 15 | 16 | Requirements: 17 | - Latest version of devkitARM 18 | - makerom on PATH 19 | 20 | The following environment variables can be set to alter the output: 21 | - `DEBUG`: if this is set, all optimization is disabled and debug symbols are included in the output ELF. 22 | - `REPLACE_AM`: if this is set, the output CXI will use the stock title ID, which allows you to install it in place of the stock version. 23 | - `DEBUG_PRINTS`: if this is set, you will see debug messages when attaching to the process in GDB. 24 | 25 | To compile: 26 | - `[optional settings] make` 27 | 28 | For example: 29 | `DEBUG=1 REPLACE_AM=1 make` would create a build that would replace the stock version and would also include debug symbols. 30 | 31 | # Licensing 32 | 33 | The project itself is using The Unlicense. 34 | 35 | Parts of [libctru](https://github.com/devkitPro/libctru) were taken and modified (function argument order, names, etc.). The license for libctru can be seen here: [LICENSE.libctru.md](/LICENSE.libctru.md) 36 | 37 | The licensing terms for the allocator can be found in the source file for it: [allocator.c](/source/allocator.c). 38 | 39 | The licensing terms for the SHA-256 implementation can be found here: [LICENSE.sha256.md](/LICENSE.sha256.md). 40 | 41 | # Special thanks 42 | [@luigoalma](https://github.com/luigoalma): quite literally teaching me how to reverse engineer 43 | -------------------------------------------------------------------------------- /am11_debug.rsf: -------------------------------------------------------------------------------- 1 | BasicInfo: 2 | Title : am_f 3 | CompanyCode : "00" 4 | ProductCode : 5 | ContentType : Application 6 | Logo : None 7 | 8 | TitleInfo: 9 | UniqueId : 0x54 10 | Category : Base 11 | Version : 2 12 | 13 | Option: 14 | UseOnSD : false 15 | FreeProductCode : true # Removes limitations on ProductCode 16 | MediaFootPadding : false # If true CCI files are created with padding 17 | EnableCrypt : false # Enables encryption for NCCH and CIA 18 | EnableCompress : false # Compresses exefs code 19 | 20 | AccessControlInfo: 21 | IdealProcessor : 1 22 | AffinityMask : 2 23 | 24 | Priority : 28 25 | 26 | DisableDebug : false 27 | EnableForceDebug : false 28 | CanWriteSharedPage : false 29 | CanUsePrivilegedPriority : false 30 | CanUseNonAlphabetAndNumber : false 31 | PermitMainFunctionArgument : false 32 | CanShareDeviceMemory : false 33 | RunnableOnSleep : true 34 | SpecialMemoryArrange : false 35 | ResourceLimitCategory : Other 36 | 37 | CoreVersion : 2 38 | DescVersion : 2 39 | 40 | MemoryType : Base # Application / System / Base 41 | HandleTableSize: 0x200 42 | 43 | SystemSaveDataId1: 0x00010054 44 | 45 | SystemCallAccess: 46 | ControlMemory: 0x01 47 | ExitProcess: 0x03 48 | CreateThread: 0x08 49 | ExitThread: 0x09 50 | SleepThread: 0x0A 51 | CreateMutex: 0x13 52 | CreateEvent: 0x17 53 | SignalEvent: 0x18 54 | CreateAddressArbiter: 0x21 55 | ArbitrateAddressNoTimeout: 0x22 56 | CloseHandle: 0x23 57 | WaitSynchronization: 0x24 58 | WaitSynchronizationN: 0x25 59 | ConnectToPort: 0x2D 60 | SendSyncRequest: 0x32 61 | GetProcessId: 0x35 62 | GetResourceLimit: 0x38 63 | GetResourceLimitLimitValues: 0x39 64 | GetResourceLimitCurrentValues: 0x3A 65 | Break: 0x3C 66 | OutputDebugString: 0x3D 67 | CreatePort: 0x47 68 | CreateSessionToPort: 0x48 69 | AcceptSession: 0x4A 70 | ReplyAndReceive: 0x4F 71 | 72 | InterruptNumbers: 73 | ServiceAccessControl: 74 | - pxi:am9 75 | - fs:USER 76 | 77 | SystemControlInfo: 78 | RemasterVersion: 0 79 | StackSize: 0x1000 80 | Dependency: 81 | -------------------------------------------------------------------------------- /include/am/pipe.h: -------------------------------------------------------------------------------- 1 | #ifndef _AM_PIPE_H 2 | #define _AM_PIPE_H 3 | 4 | #include <3ds/synchronization.h> 5 | #include 6 | #include 7 | #include <3ds/types.h> 8 | #include 9 | #include <3ds/am9.h> 10 | #include <3ds/svc.h> 11 | #include 12 | #include <3ds/fs.h> 13 | #include 14 | 15 | typedef Result (* AM_PipeWriteImpl)(void *, u64 offset, u32 size, u32 flags, void *data, u32 *written); 16 | 17 | typedef struct AM_Pipe 18 | { 19 | Handle port_client; 20 | Handle current_session; 21 | Handle thread; 22 | Handle thread_close_event; 23 | AM_PipeWriteImpl write; 24 | void *data; 25 | RecursiveLock lock; 26 | LightEvent event; 27 | u8 init; 28 | } AM_Pipe; 29 | 30 | enum 31 | { 32 | InstallState_Header = 0x0, 33 | InstallState_ContentIndex = 0x1, 34 | InstallState_CertChainCopy = 0x2, 35 | InstallState_CertChainInstall = 0x3, 36 | InstallState_TicketHeader = 0x4, 37 | InstallState_Ticket = 0x5, 38 | InstallState_TMDHeader = 0x6, 39 | InstallState_TMD = 0x7, 40 | InstallState_Content = 0x8, 41 | InstallState_Finalize = 0x9 42 | }; 43 | 44 | typedef struct CIAInstallData 45 | { 46 | CIAHeader header; 47 | u64 ticket_tid; 48 | u64 tmd_tid; 49 | MediaType media; 50 | u8 db_type; 51 | u8 state; 52 | u8 num_contents_imported_batch; // dlc only 53 | u8 batch_size; // dlc only 54 | u8 misalign_bufsize; 55 | u8 misalign_buf[16]; 56 | void *buf; 57 | u32 offset; 58 | u32 cur_content_start_offs; 59 | u32 cur_content_end_offs; 60 | u16 num_contents_imported; 61 | u16 cindex; 62 | bool is_dlc: 1; 63 | bool importing_title: 1; 64 | bool importing_content: 1; 65 | bool importing_tmd: 1; 66 | bool importing_tik: 1; 67 | bool tik_header_imported: 1; 68 | bool tmd_header_imported: 1; 69 | bool invalidated: 1; 70 | bool overwrite: 1; 71 | bool system: 1; 72 | } CIAInstallData; 73 | 74 | bool atomicUpdateState(u8 *src, u8 val, u8 wanted); 75 | 76 | Result AM_Pipe_CreateImportHandle(AM_Pipe *pipe, AM_PipeWriteImpl impl, void *data, Handle *import); 77 | Result AM_Pipe_CreateCIAImportHandle(AM_Pipe *pipe, MediaType media_type, u8 title_db_type, bool overwrite, bool is_system, Handle *import); 78 | Result AM_Pipe_CreateTicketImportHandle(AM_Pipe *pipe, Handle *import); 79 | Result AM_Pipe_CreateTMDImportHandle(AM_Pipe *pipe, Handle *import); 80 | Result AM_Pipe_CreateContentImportHandle(AM_Pipe *pipe, Handle *import); 81 | 82 | void AM_Pipe_CloseImportHandle(AM_Pipe *pipe, Handle import); 83 | void AM_Pipe_EnsureThreadExit(AM_Pipe *pipe); 84 | void AM_Pipe_HandleIPC(); 85 | 86 | #endif -------------------------------------------------------------------------------- /include/am/demodb.h: -------------------------------------------------------------------------------- 1 | #ifndef _AM_DEMODB_H 2 | #define _AM_DEMODB_H 3 | 4 | #include <3ds/synchronization.h> 5 | #include <3ds/types.h> 6 | #include <3ds/am9.h> 7 | #include <3ds/fs.h> 8 | 9 | #define SAVE_ARCHIVE_SIZE 0x40000 10 | #define SAVE_FILE_SIZE 0x10008 11 | #define SAVE_FILE_PATH L"/database.bin" 12 | #define SAVE_MAX_ENTRIES 0x1000 // 4096 13 | 14 | /* 15 | stock am refers to the AM save archive as "amsa:", but since we don't use SDK archives, 16 | this archive naming is omitted entirely. the file is called database.bin, the format is as follows: 17 | 18 | everything is little endian 19 | 20 | ----------------------------------------------------------| 21 | | offset | size | description | 22 | |---------------------------------------------------------| 23 | | 0x0 | 0x8 | header | 24 | |---------------------------------------------------------| 25 | | 0x8 | 0x8000 | title ids, without console type 0004 | 26 | |---------------------------------------------------------| 27 | | 0x8008 | 0x8000 | demo play count data | 28 | |---------------------------------------------------------| 29 | 30 | "without console type 0004", for example: 0004000000123400 -> 0000000000123400 31 | 32 | an indexing system is used. first, the title id is resolved using the title id 33 | data in the file. the file is read in chunks. once the title id has been found, 34 | it uses the index to get/set the demo play count in the demo play data. 35 | 36 | each 8-byte entry in the play count data corresponds to a title id in the title 37 | id data. only the first byte is used, which is the play count (u8). 38 | */ 39 | 40 | typedef struct __attribute__((aligned(4))) DatabaseHeader 41 | { 42 | u8 pad[4]; 43 | u16 next_entry_index; 44 | u16 entry_count; 45 | } DatabaseHeader; 46 | 47 | typedef struct __attribute__((aligned(4))) DatabaseEntry 48 | { 49 | u8 play_count; 50 | u8 unused[7]; 51 | } DatabaseEntry; 52 | 53 | typedef struct __attribute__((aligned(4))) Database 54 | { 55 | FS_Archive save_archive; 56 | Handle db_file; 57 | u64 tid_buf[128]; 58 | DatabaseHeader info; 59 | RecursiveLock lock; 60 | } Database; 61 | 62 | typedef struct __attribute__((aligned(4))) DemoDatabaseFile 63 | { 64 | DatabaseHeader header; 65 | u64 title_ids[SAVE_MAX_ENTRIES]; 66 | DatabaseEntry entries[SAVE_MAX_ENTRIES]; 67 | } DatabaseFile; 68 | 69 | typedef TicketLimitInfo DemoLaunchInfo; 70 | 71 | void AM_DemoDatabase_Initialize(Database *db); 72 | void AM_DemoDatabase_WriteHeader(Database *db); 73 | void AM_DemoDatabase_CommitSaveData(Database *db); 74 | void AM_DemoDatabase_InitializeHeader(Database *db); 75 | void AM_DemoDatabase_Close(Database *db); 76 | 77 | void AM_DemoDatabase_GetLaunchInfos(Database *db, u64 *title_ids, u32 count, DemoLaunchInfo *infos); 78 | bool AM_DemoDatabase_HasDemoLaunchRight(Database *db, u64 title_id); 79 | 80 | #endif -------------------------------------------------------------------------------- /include/3ds/err.h: -------------------------------------------------------------------------------- 1 | #ifndef _AM_3DS_ERR_H 2 | #define _AM_3DS_ERR_H 3 | 4 | #include <3ds/result.h> 5 | #include <3ds/types.h> 6 | #include <3ds/svc.h> 7 | #include <3ds/ipc.h> 8 | #include 9 | 10 | extern Handle errf_session; 11 | extern u32 errf_refcount; 12 | 13 | typedef struct 14 | { 15 | u32 r[13]; ///< r0-r12. 16 | u32 sp; ///< sp. 17 | u32 lr; ///< lr. 18 | u32 pc; ///< pc. May need to be adjusted. 19 | u32 cpsr; ///< cpsr. 20 | } CpuRegisters; 21 | 22 | typedef enum 23 | { 24 | ERRF_ERRTYPE_GENERIC = 0, ///< For generic errors. Shows miscellaneous info. 25 | ERRF_ERRTYPE_MEM_CORRUPT = 1, ///< Same output as generic, but informs the user that "the System Memory has been damaged". 26 | ERRF_ERRTYPE_CARD_REMOVED = 2, ///< Displays the "The Game Card was removed." message. 27 | ERRF_ERRTYPE_EXCEPTION = 3, ///< For exceptions, or more specifically 'crashes'. union data should be exception_data. 28 | ERRF_ERRTYPE_FAILURE = 4, ///< For general failure. Shows a message. union data should have a string set in failure_mesg 29 | ERRF_ERRTYPE_LOGGED = 5, ///< Outputs logs to NAND in some cases. 30 | } FatalErrorType; 31 | 32 | typedef enum 33 | { 34 | ERRF_EXCEPTION_PREFETCH_ABORT = 0, ///< Prefetch Abort 35 | ERRF_EXCEPTION_DATA_ABORT = 1, ///< Data abort 36 | ERRF_EXCEPTION_UNDEFINED = 2, ///< Undefined instruction 37 | ERRF_EXCEPTION_VFP = 3, ///< VFP (floating point) exception. 38 | } ExceptionType; 39 | 40 | typedef struct 41 | { 42 | ExceptionType type; ///< Type of the exception. One of the ERRF_EXCEPTION_* values. 43 | u8 reserved[3]; 44 | u32 fsr; ///< ifsr (prefetch abort) / dfsr (data abort) 45 | u32 far; ///< pc = ifar (prefetch abort) / dfar (data abort) 46 | u32 fpexc; 47 | u32 fpinst; 48 | u32 fpinst2; 49 | } ExceptionInfo; 50 | 51 | typedef struct 52 | { 53 | ExceptionInfo excep; ///< Exception info struct 54 | CpuRegisters regs; ///< CPU register dump. 55 | } ExceptionData; 56 | 57 | typedef struct 58 | { 59 | FatalErrorType type; ///< Type, one of the ERRF_ERRTYPE_* enum 60 | u8 revHigh; ///< High revison ID 61 | u16 revLow; ///< Low revision ID 62 | u32 resCode; ///< Result code 63 | u32 pcAddr; ///< PC address at exception 64 | u32 procId; ///< Process ID. 65 | u64 titleId; ///< Title ID. 66 | u64 appTitleId; ///< Application Title ID. 67 | union 68 | { 69 | ExceptionData exception_data; ///< Data for when type is ERRF_ERRTYPE_EXCEPTION 70 | char failure_mesg[0x60]; ///< String for when type is ERRF_ERRTYPE_FAILURE 71 | } data; ///< The different types of data for errors. 72 | } FatalErrorInfo; 73 | 74 | Result errfInit(); 75 | void errfExit(); 76 | 77 | void ERRF_ThrowResultNoRet(Result failure) __attribute__((noreturn, noinline)); 78 | 79 | #define Err_Throw(failure) ERRF_ThrowResultNoRet(failure); 80 | #define Err_FailedThrow(failure) do {Result __tmp = failure; if (R_FAILED(__tmp)) Err_Throw(__tmp);} while(0); 81 | #define Err_Panic(failure) do { Result __tmp = failure; if (!R_FAILED(__tmp)) {break;} while(1) { svcBreak(USERBREAK_PANIC); } } while(0); 82 | 83 | #endif -------------------------------------------------------------------------------- /include/am/format.h: -------------------------------------------------------------------------------- 1 | #ifndef _AM_FORMAT_H 2 | #define _AM_FORMAT_H 3 | 4 | #include <3ds/types.h> 5 | #include 6 | 7 | #define HDR_START 0x0 8 | #define HDR_END 0x20 9 | #define IDX_START HDR_END 10 | #define IDX_END (IDX_START + 0x2000) 11 | #define CRT_START ALIGN(IDX_END, 0x40) 12 | #define CRT_END(x) (CRT_START + (x)->header.CertificateChainSize) 13 | #define TIK_START(x) ALIGN(CRT_END((x)), 0x40) 14 | #define TIK_END(x) (TIK_START((x)) + (x)->header.TicketSize) 15 | #define TMD_START(x) ALIGN(TIK_END((x)), 0x40) 16 | #define TMD_END(x) (TMD_START((x)) + (x)->header.TMDSize) 17 | #define CON_START(x) ALIGN(TMD_END((x)), 0x40) 18 | #define CON_END(x) (CON_START((x)) + (x)->header.ContentSize) 19 | #define META_START(x) ALIGN(CON_END(x), 0x40) 20 | #define META_END(x) (META_START((x)) + (x)->header.MetaSize) 21 | 22 | #define TMD_INFO_RECORDS_COUNT 64 23 | 24 | typedef struct __attribute__((packed)) TicketHeader 25 | { 26 | char Signature[0x140]; 27 | char Issuer[0x40]; 28 | u8 ECCPublicKey[0x3C]; 29 | u8 Version; 30 | u8 CACRLVersion; 31 | u8 SignerCRLVersion; 32 | u8 Titlekey[0x10]; 33 | u8 reserved_0; 34 | u64 TicketID; 35 | u32 ConsoleID; 36 | u64 TitleID; 37 | u8 reserved_1[2]; 38 | u16 TitleVersion; 39 | u8 reserved_2[0x8]; 40 | u8 LicenseType; 41 | u8 CommonKeyYIndex; 42 | u8 reserved_3[0x2A]; 43 | u32 AccountID; 44 | u8 reserved_4; 45 | u8 Audit; 46 | u8 reserved_5[0x42]; 47 | u8 Limits[0x40]; 48 | } TicketHeader; 49 | 50 | typedef struct __attribute__((packed)) TMDSaveInfo 51 | { 52 | union 53 | { 54 | u32 CTRSaveSize; 55 | u32 SRLPublicSaveDataSize; 56 | } Size; 57 | u32 SRLPrivateSaveDataSize; 58 | u32 Reserved0; 59 | u8 SRLFlag; 60 | u8 Pad[3]; 61 | u32 Reserved1[4]; 62 | } TMDSaveInfo; 63 | 64 | typedef struct __attribute__((packed)) TMDHeader 65 | { 66 | u8 Signature[0x140]; 67 | char Issuer[0x40]; 68 | u8 Version; 69 | u8 CACRLVersion; 70 | u8 SignerCRLVersion; 71 | u8 reserved_0; 72 | u64 SystemVersion; 73 | u64 TitleID; 74 | u32 TitleType; 75 | u16 GroupID; 76 | TMDSaveInfo SaveInfo; 77 | u8 reserved_2[0x1E]; 78 | u32 AccessRights; 79 | u16 TitleVersion; 80 | u16 ContentCount; 81 | u16 BootContent; 82 | u8 padding[2]; 83 | u8 ContentInfoRecordsHash[SIZE_OF_SHA_256_HASH]; 84 | } TMDHeader; 85 | 86 | typedef struct __attribute__((packed)) ContentChunkRecord 87 | { 88 | u32 ID; 89 | u16 Index; 90 | u16 Type; 91 | u64 Size; 92 | u8 Hash[SIZE_OF_SHA_256_HASH]; 93 | } ContentChunkRecord; 94 | 95 | typedef struct __attribute__((packed)) ContentInfoRecord 96 | { 97 | u16 IndexOffset; 98 | u16 Count; 99 | u8 hash[SIZE_OF_SHA_256_HASH]; 100 | } ContentInfoRecord; 101 | 102 | typedef struct __attribute__((packed)) MinimumTMD 103 | { 104 | TMDHeader Header; 105 | ContentInfoRecord InfoRecords[TMD_INFO_RECORDS_COUNT]; 106 | } MinimumTMD; 107 | 108 | typedef struct __attribute__((packed)) CIAHeader 109 | { 110 | s32 Size; 111 | u16 Type; 112 | u16 Version; 113 | u32 CertificateChainSize; 114 | u32 TicketSize; 115 | u32 TMDSize; 116 | u32 MetaSize; 117 | u64 ContentSize; 118 | } CIAHeader; 119 | 120 | #endif -------------------------------------------------------------------------------- /include/3ds/svc.h: -------------------------------------------------------------------------------- 1 | #ifndef _AM_3DS_SVC_H 2 | #define _AM_3DS_SVC_H 3 | 4 | #include <3ds/types.h> 5 | 6 | typedef enum 7 | { 8 | ARBITRATION_SIGNAL = 0, ///< Signal #value threads for wake-up. 9 | ARBITRATION_WAIT_IF_LESS_THAN = 1, ///< If the memory at the address is strictly lower than #value, then wait for signal. 10 | ARBITRATION_DECREMENT_AND_WAIT_IF_LESS_THAN = 2, ///< If the memory at the address is strictly lower than #value, then decrement it and wait for signal. 11 | ARBITRATION_WAIT_IF_LESS_THAN_TIMEOUT = 3, ///< If the memory at the address is strictly lower than #value, then wait for signal or timeout. 12 | ARBITRATION_DECREMENT_AND_WAIT_IF_LESS_THAN_TIMEOUT = 4, ///< If the memory at the address is strictly lower than #value, then decrement it and wait for signal or timeout. 13 | } ArbitrationType; 14 | 15 | typedef enum 16 | { 17 | RESLIMIT_COMMIT = 1, ///< Quantity of allocatable memory 18 | } ResourceLimitType; 19 | 20 | typedef enum 21 | { 22 | MEMPERM_READ = 1, ///< Readable 23 | MEMPERM_WRITE = 2, ///< Writable 24 | } MemPerm; 25 | 26 | typedef enum 27 | { 28 | USERBREAK_PANIC = 0, ///< Panic. 29 | } UserBreakType; 30 | 31 | typedef enum 32 | { 33 | MEMOP_FREE = 1, ///< Memory un-mapping 34 | MEMOP_ALLOC = 3, ///< Memory mapping 35 | } MemOp; 36 | 37 | typedef enum { 38 | RESET_ONESHOT = 0, 39 | RESET_STICKY = 1, 40 | RESET_PULSE = 2, 41 | } ResetType; 42 | 43 | Result svcCreateThread(Handle *thread, void (* entrypoint)(void *), void *arg, void *stack_top, s32 thread_priority, s32 processor_id); 44 | Result svcControlMemory(void **addr_out, void *addr0, void *addr1, u32 size, MemOp op, MemPerm perm); 45 | Result svcGetResourceLimitCurrentValues(s64 *values, Handle resourceLimit, ResourceLimitType *names, s32 nameCount); 46 | Result svcGetResourceLimitLimitValues(s64 *values, Handle resourceLimit, ResourceLimitType *names, s32 nameCount); 47 | Result svcGetResourceLimit(Handle *resourceLimit, Handle process); 48 | Result svcConnectToPort(volatile Handle *out, const char *portName); 49 | Result svcCloseHandle(Handle handle); 50 | Result svcWaitSynchronizationN(s32 *out, const Handle *handles, s32 handles_num, bool wait_all, s64 nanoseconds); 51 | Result svcReplyAndReceive(s32 *index, const Handle *handles, s32 handleCount, Handle replyTarget); 52 | Result svcAcceptSession(Handle *session, Handle port); 53 | Result svcWaitSynchronization(Handle handle, s64 nanoseconds); 54 | Result svcSendSyncRequest(Handle session); 55 | Result svcGetProcessId(u32 *id, Handle process); 56 | void svcBreak(UserBreakType breakReason); 57 | void svcSleepThread(u64 nanoseconds); 58 | Result svcCreateAddressArbiter(Handle *arbiter); 59 | Result svcArbitrateAddressNoTimeout(Handle arbiter, u32 addr, ArbitrationType type, s32 value); 60 | Result svcCreateSessionToPort(Handle *client_session, Handle client_port); 61 | Result svcCreatePort(Handle *port_server, Handle *port_client, const char* name, s32 max_sessions); 62 | Result svcCreateMutex(Handle* mutex, bool initially_locked); 63 | Result svcCreateEvent(Handle* event, ResetType reset_type); 64 | Result svcSignalEvent(Handle handle); 65 | #ifndef RELEASE 66 | Result svcOutputDebugString(char *str, s32 length); 67 | #endif 68 | 69 | #endif -------------------------------------------------------------------------------- /include/am/util.h: -------------------------------------------------------------------------------- 1 | #ifndef AM_UTIL_H 2 | #define AM_UTIL_H 3 | 4 | #include <3ds/types.h> 5 | 6 | static inline u16 TitleID_Category(u64 title_id) 7 | { 8 | return (u16)((title_id >> 32) & 0xFFFF); 9 | } 10 | 11 | static inline u16 TitleID_ConsoleType(u64 title_id) 12 | { 13 | return (u16)((title_id >> 48) & 0xFFFF); 14 | } 15 | 16 | static inline bool TitleID_IsTWL(u64 title_id) 17 | { 18 | return (TitleID_Category(title_id) & 0x8000) == 0x8000; 19 | } 20 | 21 | static inline bool TitleID_IsSystemCTR(u64 title_id) 22 | { 23 | return (TitleID_Category(title_id) & 0xC010) == 0x0010; 24 | } 25 | 26 | static inline bool TitleID_IsSystemTWL(u64 title_id) 27 | { 28 | return (TitleID_Category(title_id) & 0xC001) == 0x8001; 29 | } 30 | 31 | static inline bool TitleID_IsAnySystem(u64 title_id) 32 | { 33 | return TitleID_IsSystemCTR(title_id) || TitleID_IsSystemTWL(title_id); 34 | } 35 | 36 | static inline bool TitleID_IsDLPChild(u64 title_id) 37 | { 38 | return TitleID_ConsoleType(title_id) == 0x0004 && TitleID_Category(title_id) == 0x0001; 39 | } 40 | 41 | static inline bool TitleID_IsPatch(u64 title_id) 42 | { 43 | return TitleID_ConsoleType(title_id) == 0x0004 && TitleID_Category(title_id) == 0x000E; 44 | } 45 | 46 | static inline bool TitleID_IsDLC(u64 title_id) 47 | { 48 | return TitleID_ConsoleType(title_id) == 0x0004 && TitleID_Category(title_id) == 0x008C; 49 | } 50 | 51 | static inline bool TitleID_IsLicense(u64 title_id) 52 | { 53 | return TitleID_ConsoleType(title_id) == 0x0004 && TitleID_Category(title_id) == 0x000D; 54 | } 55 | 56 | static inline bool TitleID_IsDLCOrLicense(u64 title_id) 57 | { 58 | return TitleID_IsDLC(title_id) || TitleID_IsLicense(title_id); 59 | } 60 | 61 | // i am aware this is quality:tm: code but yes 62 | 63 | #ifdef DEBUG_PRINTS 64 | void int_convert_to_hex(char* out, uint32_t n, bool newline); 65 | #define DEBUG_PRINT(str) svcOutputDebugString(str, sizeof(str) - 1) 66 | #define DEBUG_PRINTF(str, num) \ 67 | { \ 68 | char __ibuf[12]; \ 69 | \ 70 | svcOutputDebugString(str, sizeof(str) - 1); \ 71 | int_convert_to_hex(__ibuf, num, true); \ 72 | svcOutputDebugString(__ibuf, 11); \ 73 | } 74 | #define DEBUG_PRINTF2(str, num, str2, num2) \ 75 | { \ 76 | char __ibuf[12]; \ 77 | \ 78 | svcOutputDebugString(str, sizeof(str) - 1); \ 79 | int_convert_to_hex(__ibuf, num, false); \ 80 | svcOutputDebugString(__ibuf, 10); \ 81 | \ 82 | svcOutputDebugString(str2, sizeof(str2) - 1); \ 83 | int_convert_to_hex(__ibuf, num2, true); \ 84 | svcOutputDebugString(__ibuf, 11); \ 85 | } 86 | #define DEBUG_PRINTF3(str, num, str2, num2, str3, num3) \ 87 | { \ 88 | char __ibuf[12]; \ 89 | \ 90 | svcOutputDebugString(str, sizeof(str) - 1); \ 91 | int_convert_to_hex(__ibuf, num, false); \ 92 | svcOutputDebugString(__ibuf, 10); \ 93 | \ 94 | svcOutputDebugString(str2, sizeof(str2) - 1); \ 95 | int_convert_to_hex(__ibuf, num2, false); \ 96 | svcOutputDebugString(__ibuf, 10); \ 97 | \ 98 | svcOutputDebugString(str3, sizeof(str3) - 1); \ 99 | int_convert_to_hex(__ibuf, num3, true); \ 100 | svcOutputDebugString(__ibuf, 11); \ 101 | } 102 | 103 | #else 104 | #define DEBUG_PRINT(str) {} 105 | #define DEBUG_PRINTF(str, num) {} 106 | #define DEBUG_PRINTF2(str, num, str2, num2) {} 107 | #define DEBUG_PRINTF3(str, num, str2, num2, str3, num3) {} 108 | #endif 109 | 110 | #endif -------------------------------------------------------------------------------- /include/3ds/ipc.h: -------------------------------------------------------------------------------- 1 | #ifndef _AM_3DS_IPC_H 2 | #define _AM_3DS_IPC_H 3 | 4 | #include <3ds/types.h> 5 | #include 6 | 7 | #define CHECK_RET(x) \ 8 | Result res = svcSendSyncRequest(x); \ 9 | if (R_SUCCEEDED(res)) \ 10 | res = (Result)ipc_command[1]; \ 11 | if (R_FAILED(res)) \ 12 | return res; 13 | 14 | #define BASIC_RET(x) \ 15 | CHECK_RET(x) \ 16 | return res; 17 | 18 | #define RET_OS_INVALID_IPCARG \ 19 | { \ 20 | ipc_command[0] = IPC_MakeHeader(0, 1, 0); \ 21 | ipc_command[1] = OS_INVALID_IPC_ARGUMENT; \ 22 | return; \ 23 | } 24 | 25 | #define CHECK_WRONGARG(x) \ 26 | if ((x)) \ 27 | RET_OS_INVALID_IPCARG 28 | 29 | #define CHECK_HEADER(...) \ 30 | if (cmd_header != IPC_MakeHeader(__VA_ARGS__)) \ 31 | { \ 32 | ipc_command[0] = IPC_MakeHeader(0, 1, 0); \ 33 | ipc_command[1] = OS_INVALID_IPC_HEADER; \ 34 | break; \ 35 | } 36 | 37 | typedef enum 38 | { 39 | IPC_BUFFER_R = BIT(1), ///< Readable 40 | IPC_BUFFER_W = BIT(2), ///< Writable 41 | IPC_BUFFER_RW = IPC_BUFFER_R | IPC_BUFFER_W ///< Readable and Writable 42 | } IPC_BufferRights; 43 | 44 | static inline ThreadLocalStorage *getThreadLocalStorage(void) 45 | { 46 | ThreadLocalStorage *tls; 47 | __asm__ ("mrc p15, 0, %[data], c13, c0, 3" : [data] "=r" (tls)); 48 | return tls; 49 | } 50 | 51 | static inline u32 IPC_MakeHeader(u16 command_id, unsigned normal_params, unsigned translate_params) 52 | { 53 | return ((u32) command_id << 16) | (((u32) normal_params & 0x3F) << 6) | (((u32) translate_params & 0x3F) << 0); 54 | } 55 | 56 | static inline u32 IPC_Desc_SharedHandles(unsigned number) 57 | { 58 | return ((u32)(number - 1) << 26); 59 | } 60 | 61 | static inline u32 IPC_Desc_MoveHandles(unsigned number) 62 | { 63 | return ((u32)(number - 1) << 26) | 0x10; 64 | } 65 | 66 | static inline bool IPC_VerifyMoveHandles(u32 desc, unsigned number) 67 | { 68 | return desc == IPC_Desc_MoveHandles(number); 69 | } 70 | 71 | static inline bool IPC_VerifySharedHandles(u32 desc, unsigned number) 72 | { 73 | return desc == IPC_Desc_SharedHandles(number); 74 | } 75 | 76 | static inline u32 IPC_Desc_CurProcessId(void) 77 | { 78 | return 0x20; 79 | } 80 | 81 | static inline bool IPC_CompareHeader(u32 header, u16 command_id, unsigned normal_params, unsigned translate_params) 82 | { 83 | return header == IPC_MakeHeader(command_id, normal_params, translate_params); 84 | } 85 | 86 | // static buffers 87 | 88 | static inline u32 IPC_Desc_StaticBuffer(size_t size, unsigned buffer_id) 89 | { 90 | return (size << 14) | ((buffer_id & 0xF) << 10) | 0x2; 91 | } 92 | 93 | static inline bool IPC_VerifyStaticBuffer(u32 desc, unsigned buffer_id) 94 | { 95 | return (desc & (0xf << 10 | 0xf)) == (((buffer_id & 0xF) << 10) | 0x2); 96 | } 97 | 98 | static inline size_t IPC_GetStaticBufferSize(u32 desc) 99 | { 100 | return (size_t)((desc >> 14) & 0x3FFFF); 101 | } 102 | 103 | // normal non-pxi buffers 104 | 105 | static inline u32 IPC_Desc_Buffer(size_t size, IPC_BufferRights rights) 106 | { 107 | return (size << 4) | 0x8 | rights; 108 | } 109 | 110 | static inline bool IPC_VerifyBuffer(u32 desc, IPC_BufferRights rights) 111 | { 112 | return (desc & 0xF) == (0x8 | rights); 113 | } 114 | 115 | static inline size_t IPC_GetBufferSize(u32 desc) 116 | { 117 | return (size_t)(desc >> 4); 118 | } 119 | 120 | // pxi buffers 121 | 122 | static inline u32 IPC_Desc_PXIBuffer(size_t size, unsigned buffer_id, bool is_read_only) 123 | { 124 | u8 type = 0x4; 125 | if(is_read_only)type = 0x6; 126 | return (size << 8) | ((buffer_id & 0xF) << 4) | type; 127 | } 128 | 129 | #endif -------------------------------------------------------------------------------- /source/3ds/svc.s: -------------------------------------------------------------------------------- 1 | .arm 2 | .align 4 3 | 4 | .macro BEGIN_ASM_FUNC name, linkage=global, section=text 5 | .section .\section\().\name, "ax", %progbits 6 | .align 2 7 | .\linkage \name 8 | .type \name, %function 9 | .func \name 10 | .cfi_sections .debug_frame 11 | .cfi_startproc 12 | \name: 13 | .endm 14 | 15 | .macro END_ASM_FUNC 16 | .cfi_endproc 17 | .endfunc 18 | .endm 19 | 20 | BEGIN_ASM_FUNC svcControlMemory 21 | push {r0, r4} 22 | ldr r0, [sp, #0x8] 23 | ldr r4, [sp, #0x8+0x4] 24 | svc 0x01 25 | ldr r2, [sp], #4 26 | str r1, [r2] 27 | ldr r4, [sp], #4 28 | bx lr 29 | END_ASM_FUNC 30 | 31 | BEGIN_ASM_FUNC svcGetResourceLimitCurrentValues 32 | svc 0x3A 33 | bx lr 34 | END_ASM_FUNC 35 | 36 | BEGIN_ASM_FUNC svcGetResourceLimitLimitValues 37 | svc 0x39 38 | bx lr 39 | END_ASM_FUNC 40 | 41 | BEGIN_ASM_FUNC svcGetResourceLimit 42 | str r0, [sp, #-0x4]! 43 | svc 0x38 44 | ldr r3, [sp], #4 45 | str r1, [r3] 46 | bx lr 47 | END_ASM_FUNC 48 | 49 | BEGIN_ASM_FUNC svcCloseHandle 50 | svc 0x23 51 | bx lr 52 | END_ASM_FUNC 53 | 54 | BEGIN_ASM_FUNC svcWaitSynchronization 55 | svc 0x24 56 | bx lr 57 | END_ASM_FUNC 58 | 59 | BEGIN_ASM_FUNC svcWaitSynchronizationN 60 | str r5, [sp, #-4]! 61 | str r4, [sp, #-4]! 62 | mov r5, r0 63 | ldr r0, [sp, #0x8] 64 | ldr r4, [sp, #0x8+0x4] 65 | svc 0x25 66 | str r1, [r5] 67 | ldr r4, [sp], #4 68 | ldr r5, [sp], #4 69 | bx lr 70 | END_ASM_FUNC 71 | 72 | BEGIN_ASM_FUNC svcBreak 73 | svc 0x3C 74 | bx lr 75 | END_ASM_FUNC 76 | 77 | BEGIN_ASM_FUNC svcConnectToPort 78 | str r0, [sp, #-0x4]! 79 | svc 0x2D 80 | ldr r3, [sp], #4 81 | str r1, [r3] 82 | bx lr 83 | END_ASM_FUNC 84 | 85 | BEGIN_ASM_FUNC svcSendSyncRequest 86 | svc 0x32 87 | bx lr 88 | END_ASM_FUNC 89 | 90 | BEGIN_ASM_FUNC svcSleepThread 91 | svc 0x0A 92 | bx lr 93 | END_ASM_FUNC 94 | 95 | BEGIN_ASM_FUNC svcGetProcessId 96 | str r0, [sp, #-0x4]! 97 | svc 0x35 98 | ldr r3, [sp], #4 99 | str r1, [r3] 100 | bx lr 101 | END_ASM_FUNC 102 | 103 | BEGIN_ASM_FUNC svcReplyAndReceive 104 | str r0, [sp, #-4]! 105 | svc 0x4F 106 | ldr r2, [sp] 107 | str r1, [r2] 108 | add sp, sp, #4 109 | bx lr 110 | END_ASM_FUNC 111 | 112 | BEGIN_ASM_FUNC svcAcceptSession 113 | str r0, [sp, #-4]! 114 | svc 0x4A 115 | ldr r2, [sp] 116 | str r1, [r2] 117 | add sp, sp, #4 118 | bx lr 119 | END_ASM_FUNC 120 | 121 | BEGIN_ASM_FUNC svcCreateThread 122 | push {r0, r4} 123 | ldr r0, [sp, #0x8] 124 | ldr r4, [sp, #0x8+0x4] 125 | svc 0x08 126 | ldr r2, [sp], #4 127 | str r1, [r2] 128 | ldr r4, [sp], #4 129 | bx lr 130 | END_ASM_FUNC 131 | 132 | BEGIN_ASM_FUNC svcCreateAddressArbiter 133 | push {r0} 134 | svc 0x21 135 | pop {r2} 136 | str r1, [r2] 137 | bx lr 138 | END_ASM_FUNC 139 | 140 | BEGIN_ASM_FUNC svcArbitrateAddressNoTimeout 141 | svc 0x22 142 | bx lr 143 | END_ASM_FUNC 144 | 145 | BEGIN_ASM_FUNC svcCreatePort 146 | push {r0, r1} 147 | svc 0x47 148 | ldr r3, [sp, #0] 149 | str r1, [r3] 150 | ldr r3, [sp, #4] 151 | str r2, [r3] 152 | add sp, sp, #8 153 | bx lr 154 | END_ASM_FUNC 155 | 156 | BEGIN_ASM_FUNC svcCreateSessionToPort 157 | push {r0} 158 | svc 0x48 159 | pop {r2} 160 | str r1, [r2] 161 | bx lr 162 | END_ASM_FUNC 163 | 164 | BEGIN_ASM_FUNC svcOutputDebugString 165 | svc 0x3D 166 | bx lr 167 | END_ASM_FUNC 168 | 169 | BEGIN_ASM_FUNC svcCreateMutex 170 | str r0, [sp, #-4]! 171 | svc 0x13 172 | ldr r3, [sp], #4 173 | str r1, [r3] 174 | bx lr 175 | END_ASM_FUNC 176 | 177 | BEGIN_ASM_FUNC svcCreateEvent 178 | str r0, [sp, #-4]! 179 | svc 0x17 180 | ldr r2, [sp], #4 181 | str r1, [r2] 182 | bx lr 183 | END_ASM_FUNC 184 | 185 | BEGIN_ASM_FUNC svcSignalEvent 186 | svc 0x18 187 | bx lr 188 | END_ASM_FUNC -------------------------------------------------------------------------------- /source/3ds/srv.c: -------------------------------------------------------------------------------- 1 | #include <3ds/srv.h> 2 | 3 | u32 srv_refcount; 4 | Handle srv_session; 5 | 6 | Result srvInit() 7 | { 8 | if (srv_refcount++) 9 | return 0; 10 | 11 | Result res; 12 | 13 | if (!(R_SUCCEEDED(res = svcConnectToPort(&srv_session, "srv:")) && 14 | R_SUCCEEDED(res = SRV_RegisterClient()))) 15 | srvExit(); 16 | 17 | return res; 18 | } 19 | 20 | void srvExit() 21 | { 22 | if (--srv_refcount) 23 | return; 24 | 25 | if (srv_session != 0) 26 | { 27 | Err_FailedThrow(svcCloseHandle(srv_session)); 28 | srv_session = 0; 29 | } 30 | } 31 | 32 | Result SRV_RegisterClient() 33 | { 34 | u32 *ipc_command = getThreadLocalStorage()->ipc_command; 35 | 36 | ipc_command[0] = IPC_MakeHeader(ID_SRV_RegisterClient, 0, 2); 37 | ipc_command[1] = IPC_Desc_CurProcessId(); 38 | 39 | BASIC_RET(srv_session) 40 | } 41 | 42 | Result SRV_EnableNotification(Handle *sempahore) 43 | { 44 | u32 *ipc_command = getThreadLocalStorage()->ipc_command; 45 | 46 | ipc_command[0] = IPC_MakeHeader(ID_SRV_EnableNotification, 0, 0); 47 | 48 | CHECK_RET(srv_session) 49 | 50 | *sempahore = ipc_command[3]; 51 | return res; 52 | } 53 | 54 | Result SRV_RegisterService(Handle *service, const char *service_name, u32 service_name_len, u32 max_sessions) 55 | { 56 | u32 *ipc_command = getThreadLocalStorage()->ipc_command; 57 | 58 | ipc_command[0] = IPC_MakeHeader(ID_SRV_RegisterService, 4, 0); 59 | ipc_command[1] = *((u32 *)service_name); 60 | ipc_command[2] = *((u32 *)(service_name + 4)); 61 | ipc_command[3] = service_name_len; 62 | ipc_command[4] = max_sessions; 63 | 64 | CHECK_RET(srv_session) 65 | 66 | *service = ipc_command[3]; 67 | return res; 68 | } 69 | 70 | Result SRV_UnregisterService(const char *service_name, u32 service_name_length) 71 | { 72 | u32 *ipc_command = getThreadLocalStorage()->ipc_command; 73 | 74 | ipc_command[0] = IPC_MakeHeader(ID_SRV_UnregisterService, 3, 0); 75 | ipc_command[1] = *((u32 *)service_name); 76 | ipc_command[2] = *((u32 *)(service_name + 4)); 77 | ipc_command[3] = service_name_length; 78 | 79 | BASIC_RET(srv_session) 80 | } 81 | 82 | Result SRV_GetServiceHandle(Handle *service, const char *service_name, u32 service_name_length, u32 flags) 83 | { 84 | u32 *ipc_command = getThreadLocalStorage()->ipc_command; 85 | 86 | ipc_command[0] = IPC_MakeHeader(ID_SRV_GetServiceHandle, 4, 0); 87 | ipc_command[1] = *((u32 *)service_name); 88 | ipc_command[2] = *((u32 *)(service_name + 4)); 89 | ipc_command[3] = service_name_length; 90 | ipc_command[4] = flags; 91 | 92 | CHECK_RET(srv_session) 93 | 94 | *service = ipc_command[3]; 95 | return res; 96 | } 97 | 98 | Result SRV_RegisterPort(const char *port_name, u32 port_name_length, Handle client_port) 99 | { 100 | u32 *ipc_command = getThreadLocalStorage()->ipc_command; 101 | 102 | ipc_command[0] = IPC_MakeHeader(ID_SRV_RegisterPort, 3, 2); 103 | ipc_command[1] = *((u32 *)port_name); 104 | ipc_command[2] = *((u32 *)(port_name + 4)); 105 | ipc_command[3] = port_name_length; 106 | ipc_command[4] = IPC_Desc_SharedHandles(1); 107 | ipc_command[5] = client_port; 108 | 109 | BASIC_RET(srv_session) 110 | } 111 | 112 | Result SRV_UnregisterPort(const char *port_name, u32 port_name_length) 113 | { 114 | u32 *ipc_command = getThreadLocalStorage()->ipc_command; 115 | 116 | ipc_command[0] = IPC_MakeHeader(ID_SRV_UnregisterPort, 3, 0); 117 | ipc_command[1] = *((u32 *)port_name); 118 | ipc_command[2] = *((u32 *)(port_name + 4)); 119 | ipc_command[3] = port_name_length; 120 | 121 | BASIC_RET(srv_session) 122 | } 123 | 124 | Result SRV_ReceiveNotification(u32 *notification_id) 125 | { 126 | u32 *ipc_command = getThreadLocalStorage()->ipc_command; 127 | 128 | ipc_command[0] = IPC_MakeHeader(ID_SRV_ReceiveNotification, 0, 0); 129 | 130 | CHECK_RET(srv_session) 131 | 132 | *notification_id = ipc_command[2]; 133 | return res; 134 | } -------------------------------------------------------------------------------- /include/sha256.h: -------------------------------------------------------------------------------- 1 | #ifndef SHA_256_H 2 | #define SHA_256_H 3 | 4 | #include <3ds/types.h> 5 | #include 6 | 7 | /* 8 | * @brief Size of the SHA-256 sum. This times eight is 256 bits. 9 | */ 10 | #define SIZE_OF_SHA_256_HASH 32 11 | 12 | /* 13 | * @brief Size of the chunks used for the calculations. 14 | * 15 | * @note This should mostly be ignored by the user, although when using the streaming API, it has an impact for 16 | * performance. Add chunks whose size is a multiple of this, and you will avoid a lot of superfluous copying in RAM! 17 | */ 18 | #define SIZE_OF_SHA_256_CHUNK 64 19 | 20 | /* 21 | * @brief The opaque SHA-256 type, that should be instantiated when using the streaming API. 22 | * 23 | * @note Although the details are exposed here, in order to make instantiation easy, you should refrain from directly 24 | * accessing the fields, as they may change in the future. 25 | */ 26 | struct SHA256 27 | { 28 | uint8_t *hash; 29 | uint8_t chunk[SIZE_OF_SHA_256_CHUNK]; 30 | uint8_t *chunk_pos; 31 | size_t space_left; 32 | size_t total_len; 33 | uint32_t h[8]; 34 | }; 35 | 36 | /* 37 | * @brief The simple SHA-256 calculation function. 38 | * @param hash Hash array, where the result is delivered. 39 | * @param input Pointer to the data the hash shall be calculated on. 40 | * @param len Length of the input data, in byte. 41 | * 42 | * @note If all of the data you are calculating the hash value on is available in a contiguous buffer in memory, this is 43 | * the function you should use. 44 | * 45 | * @note If either of the passed pointers is NULL, the results are unpredictable. 46 | */ 47 | void calc_sha_256(uint8_t hash[SIZE_OF_SHA_256_HASH], const void *input, size_t len); 48 | 49 | /* 50 | * @brief Initialize a SHA-256 streaming calculation. 51 | * @param sha_256 A pointer to a SHA-256 structure. 52 | * @param hash Hash array, where the result will be delivered. 53 | * 54 | * @note If all of the data you are calculating the hash value on is not available in a contiguous buffer in memory, this is 55 | * where you should start. Instantiate a SHA-256 structure, for instance by simply declaring it locally, make your hash 56 | * buffer available, and invoke this function. Once a SHA-256 hash has been calculated (see further below) a SHA-256 57 | * structure can be initialized again for the next calculation. 58 | * 59 | * @note If either of the passed pointers is NULL, the results are unpredictable. 60 | */ 61 | void sha_256_init(struct SHA256 *sha_256, uint8_t hash[SIZE_OF_SHA_256_HASH]); 62 | 63 | /* 64 | * @brief Stream more input data for an on-going SHA-256 calculation. 65 | * @param sha_256 A pointer to a previously initialized SHA-256 structure. 66 | * @param data Pointer to the data to be added to the calculation. 67 | * @param len Length of the data to add, in byte. 68 | * 69 | * @note This function may be invoked an arbitrary number of times between initialization and closing, but the maximum 70 | * data length is limited by the SHA-256 algorithm: the total number of bits (i.e. the total number of bytes times 71 | * eight) must be representable by a 64-bit unsigned integer. While that is not a practical limitation, the results are 72 | * unpredictable if that limit is exceeded. 73 | * 74 | * @note This function may be invoked on empty data (zero length), although that obviously will not add any data. 75 | * 76 | * @note If either of the passed pointers is NULL, the results are unpredictable. 77 | */ 78 | void sha_256_write(struct SHA256 *sha_256, const void *data, size_t len); 79 | 80 | /* 81 | * @brief Conclude a SHA-256 streaming calculation, making the hash value available. 82 | * @param sha_256 A pointer to a previously initialized SHA-256 structure. 83 | * @return Pointer to the hash array, where the result is delivered. 84 | * 85 | * @note After this function has been invoked, the result is available in the hash buffer that initially was provided. A 86 | * pointer to the hash value is returned for convenience, but you should feel free to ignore it: it is simply a pointer 87 | * to the first byte of your initially provided hash array. 88 | * 89 | * @note If the passed pointer is NULL, the results are unpredictable. 90 | * 91 | * @note Invoking this function for a calculation with no data (the writing function has never been invoked, or it only 92 | * has been invoked with empty data) is legal. It will calculate the SHA-256 value of the empty string. 93 | */ 94 | uint8_t *sha_256_close(struct SHA256 *sha_256); 95 | 96 | int cmp_hash(void *hash1, void *hash2); 97 | 98 | #endif -------------------------------------------------------------------------------- /source/3ds/synchronization.c: -------------------------------------------------------------------------------- 1 | #include <3ds/synchronization.h> 2 | 3 | Handle g_AddressArbiter; 4 | 5 | inline Result syncInit() 6 | { 7 | return svcCreateAddressArbiter(&g_AddressArbiter); 8 | } 9 | 10 | inline void syncExit() 11 | { 12 | svcCloseHandle(g_AddressArbiter); 13 | } 14 | 15 | Result syncArbitrateAddress(s32 *addr, ArbitrationType type, s32 value) 16 | { 17 | return svcArbitrateAddressNoTimeout(g_AddressArbiter, (u32)addr, type, value); 18 | } 19 | 20 | void LightLock_Init(LightLock *lock) 21 | { 22 | do 23 | __ldrex(lock); 24 | while (__strex(lock, 1)); 25 | } 26 | 27 | void LightLock_Lock(LightLock *lock) 28 | { 29 | s32 val; 30 | bool bAlreadyLocked; 31 | 32 | // Try to lock, or if that's not possible, increment the number of waiting threads 33 | do 34 | { 35 | // Read the current lock state 36 | val = __ldrex(lock); 37 | if (val == 0) val = 1; // 0 is an invalid state - treat it as 1 (unlocked) 38 | bAlreadyLocked = val < 0; 39 | 40 | // Calculate the desired next state of the lock 41 | if (!bAlreadyLocked) 42 | val = -val; // transition into locked state 43 | else 44 | --val; // increment the number of waiting threads (which has the sign reversed during locked state) 45 | } while (__strex(lock, val)); 46 | 47 | // While the lock is held by a different thread: 48 | while (bAlreadyLocked) 49 | { 50 | // Wait for the lock holder thread to wake us up 51 | syncArbitrateAddress(lock, ARBITRATION_WAIT_IF_LESS_THAN, 0); 52 | 53 | // Try to lock again 54 | do 55 | { 56 | // Read the current lock state 57 | val = __ldrex(lock); 58 | bAlreadyLocked = val < 0; 59 | 60 | // Calculate the desired next state of the lock 61 | if (!bAlreadyLocked) 62 | val = -(val-1); // decrement the number of waiting threads *and *transition into locked state 63 | else 64 | { 65 | // Since the lock is still held, we need to cancel the atomic update and wait again 66 | __clrex(); 67 | break; 68 | } 69 | } while (__strex(lock, val)); 70 | } 71 | 72 | __dmb(); 73 | } 74 | 75 | void LightLock_Unlock(LightLock *lock) 76 | { 77 | __dmb(); 78 | 79 | s32 val; 80 | do 81 | val = -__ldrex(lock); 82 | while (__strex(lock, val)); 83 | 84 | if (val > 1) 85 | // Wake up exactly one thread 86 | syncArbitrateAddress(lock, ARBITRATION_SIGNAL, 1); 87 | } 88 | 89 | void RecursiveLock_Init(RecursiveLock *lock) 90 | { 91 | LightLock_Init(&lock->lock); 92 | lock->thread_tag = 0; 93 | lock->counter = 0; 94 | } 95 | 96 | void RecursiveLock_Lock(RecursiveLock *lock) 97 | { 98 | ThreadLocalStorage *tag = getThreadLocalStorage(); 99 | if (lock->thread_tag != tag) 100 | { 101 | LightLock_Lock(&lock->lock); 102 | lock->thread_tag = tag; 103 | } 104 | lock->counter ++; 105 | } 106 | 107 | void RecursiveLock_Unlock(RecursiveLock *lock) 108 | { 109 | if (!--lock->counter) 110 | { 111 | lock->thread_tag = 0; 112 | LightLock_Unlock(&lock->lock); 113 | } 114 | } 115 | 116 | static inline int LightEvent_TryReset(LightEvent* event) 117 | { 118 | __dmb(); 119 | do 120 | { 121 | if (__ldrex(&event->state)) 122 | { 123 | __clrex(); 124 | return 0; 125 | } 126 | } while (__strex(&event->state, CLEARED_ONESHOT)); 127 | __dmb(); 128 | return 1; 129 | } 130 | 131 | void LightEvent_Wait(LightEvent* event) 132 | { 133 | while (true) 134 | { 135 | if (event->state == CLEARED_STICKY) 136 | { 137 | syncArbitrateAddress(&event->state, ARBITRATION_WAIT_IF_LESS_THAN, SIGNALED_ONESHOT); 138 | return; 139 | } 140 | 141 | if (event->state != CLEARED_ONESHOT) 142 | { 143 | if (event->state == SIGNALED_STICKY) 144 | return; 145 | if (event->state == SIGNALED_ONESHOT && LightEvent_TryReset(event)) 146 | return; 147 | } 148 | syncArbitrateAddress(&event->state, ARBITRATION_WAIT_IF_LESS_THAN, SIGNALED_ONESHOT); 149 | } 150 | } 151 | 152 | static inline void LightEvent_SetState(LightEvent* event, int state) 153 | { 154 | do 155 | __ldrex(&event->state); 156 | while (__strex(&event->state, state)); 157 | } 158 | 159 | void LightEvent_Signal(LightEvent* event) 160 | { 161 | if (event->state == CLEARED_ONESHOT) 162 | { 163 | __dmb(); 164 | LightEvent_SetState(event, SIGNALED_ONESHOT); 165 | syncArbitrateAddress(&event->state, ARBITRATION_SIGNAL, 1); 166 | } 167 | else if (event->state == CLEARED_STICKY) 168 | { 169 | LightLock_Lock(&event->lock); 170 | LightEvent_SetState(event, SIGNALED_STICKY); 171 | syncArbitrateAddress(&event->state, ARBITRATION_SIGNAL, -1); 172 | LightLock_Unlock(&event->lock); 173 | } 174 | } -------------------------------------------------------------------------------- /include/3ds/fs.h: -------------------------------------------------------------------------------- 1 | #ifndef _AM_3DS_FS_H 2 | #define _AM_3DS_FS_H 3 | 4 | #include <3ds/result.h> 5 | #include <3ds/types.h> 6 | #include <3ds/err.h> 7 | #include <3ds/svc.h> 8 | #include <3ds/srv.h> 9 | 10 | typedef enum MediaType 11 | { 12 | MediaType_NAND = 0, 13 | MediaType_SD = 1, 14 | MediaType_Gamecard = 2 15 | } MediaType; 16 | 17 | typedef struct __attribute__((packed)) FS_SystemSaveDataInfo 18 | { 19 | u8 media_type; 20 | u8 unk_reserved[3]; 21 | u32 save_id; 22 | } FS_SystemSaveDataInfo; 23 | 24 | typedef enum FS_PathType 25 | { 26 | PATH_INVALID = 0, 27 | PATH_EMPTY = 1, 28 | PATH_BINARY = 2, 29 | PATH_ASCII = 3, 30 | PATH_UTF16 = 4, 31 | } FS_PathType; 32 | 33 | typedef enum FS_ArchiveID 34 | { 35 | ARCHIVE_ROMFS = 0x00000003, 36 | ARCHIVE_SAVEDATA = 0x00000004, 37 | ARCHIVE_EXTDATA = 0x00000006, 38 | ARCHIVE_SHARED_EXTDATA = 0x00000007, 39 | ARCHIVE_SYSTEM_SAVEDATA = 0x00000008, 40 | ARCHIVE_SDMC = 0x00000009, 41 | ARCHIVE_SDMC_WRITE_ONLY = 0x0000000A, 42 | ARCHIVE_BOSS_EXTDATA = 0x12345678, 43 | ARCHIVE_CARD_SPIFS = 0x12345679, 44 | ARCHIVE_EXTDATA_AND_BOSS_EXTDATA = 0x1234567B, 45 | ARCHIVE_SYSTEM_SAVEDATA2 = 0x1234567C, 46 | ARCHIVE_NAND_RW = 0x1234567D, 47 | ARCHIVE_NAND_RO = 0x1234567E, 48 | ARCHIVE_NAND_RO_WRITE_ACCESS = 0x1234567F, 49 | ARCHIVE_SAVEDATA_AND_CONTENT = 0x2345678A, 50 | ARCHIVE_SAVEDATA_AND_CONTENT2 = 0x2345678E, 51 | ARCHIVE_NAND_CTR_FS = 0x567890AB, 52 | ARCHIVE_TWL_PHOTO = 0x567890AC, 53 | ARCHIVE_TWL_SOUND = 0x567890AD, 54 | ARCHIVE_NAND_TWL_FS = 0x567890AE, 55 | ARCHIVE_NAND_W_FS = 0x567890AF, 56 | ARCHIVE_GAMECARD_SAVEDATA = 0x567890B1, 57 | ARCHIVE_USER_SAVEDATA = 0x567890B2, 58 | ARCHIVE_DEMO_SAVEDATA = 0x567890B4, 59 | } FS_ArchiveID; 60 | 61 | typedef enum FS_ArchiveAction 62 | { 63 | ARCHIVE_ACTION_COMMIT_SAVE_DATA = 0, 64 | ARCHIVE_ACTION_GET_TIMESTAMP = 1, 65 | ARCHIVE_ACTION_UNKNOWN = 0x789D, 66 | } FS_ArchiveAction; 67 | 68 | 69 | enum 70 | { 71 | FS_OPEN_READ = BIT(0), 72 | FS_OPEN_WRITE = BIT(1), 73 | FS_OPEN_CREATE = BIT(2), 74 | }; 75 | 76 | /// Write flags. 77 | enum 78 | { 79 | FS_WRITE_FLUSH = BIT(0), 80 | FS_WRITE_UPDATE_TIME = BIT(8), 81 | }; 82 | 83 | /// Attribute flags. 84 | enum 85 | { 86 | FS_ATTRIBUTE_DIRECTORY = BIT(0), 87 | FS_ATTRIBUTE_HIDDEN = BIT(8), 88 | FS_ATTRIBUTE_ARCHIVE = BIT(16), 89 | FS_ATTRIBUTE_READ_ONLY = BIT(24), 90 | }; 91 | 92 | enum 93 | { 94 | ID_FSFile_OpenSubFile = 0x0801, 95 | ID_FSFile_Read = 0x0802, 96 | ID_FSFile_Write = 0x0803, 97 | ID_FSFile_GetSize = 0x0804, 98 | ID_FSFile_Close = 0x0808, 99 | }; 100 | 101 | enum 102 | { 103 | ID_FSUser_OpenFile = 0x0802, 104 | ID_FSUser_DeleteFile = 0x0804, 105 | ID_FSUser_CreateFile = 0x0808, 106 | ID_FSUser_OpenArchive = 0x080C, 107 | ID_FSUser_ControlArchive = 0x080D, 108 | ID_FSUser_CloseArchive = 0x080E, 109 | ID_FSUser_CreateSystemSaveData = 0x0856, 110 | ID_FSUser_DeleteSystemSaveData = 0x0857, 111 | ID_FSUser_InitializeWithSDKVersion = 0x0861, 112 | }; 113 | 114 | typedef u64 FS_Archive; 115 | 116 | #ifdef REPLACE_AM 117 | #define SAVE_ID 0x00010015 118 | #else 119 | #define SAVE_ID 0x00010054 120 | #endif 121 | 122 | extern Handle fsuser_session; 123 | extern bool fsuser_init; 124 | 125 | Result fsUserInit(); 126 | void fsUserExit(); 127 | 128 | u32 getBucketCount(u32 count); 129 | 130 | Result FSUser_OpenFile(u32 transaction, FS_Archive archive, FS_PathType path_type, u32 path_size, u32 open_flags, u32 attributes, void *path_data, Handle *file); 131 | Result FSUser_DeleteFile(u32 transaction, FS_Archive archive, FS_Archive path_type, u32 path_size, void *path_data); 132 | Result FSUser_CreateFile(u32 transaction, FS_Archive archive, FS_PathType path_type, u32 path_size, u32 attributes, u64 zero_bytes_count, void *path_data); 133 | Result FSUser_OpenArchive(FS_ArchiveID archive_id, FS_PathType path_type, void *path_data, u32 path_size, FS_Archive *archive); 134 | Result FSUser_ControlArchive(FS_Archive archive, FS_ArchiveAction action, void *input, u32 input_size, void *output, u32 output_size); 135 | Result FSUser_CloseArchive(FS_Archive archive); 136 | Result FSUser_CreateSystemSaveData(FS_SystemSaveDataInfo *save_info, u32 total_size, u32 block_size, u32 dircount, u32 filecount, u32 dirbucketcount, u32 filebucketcount, bool duplicate_data); 137 | Result FSUser_DeleteSystemSaveData(FS_SystemSaveDataInfo *save_info); 138 | Result FSUser_InitializeWithSDKVersion(u32 version); 139 | 140 | Result FSFile_OpenSubFile(Handle file, Handle *sub_file, u64 offset, u64 size); 141 | Result FSFile_Read(u32 *read, Handle file, u64 offset, u32 data_size, void *data); 142 | Result FSFile_Write(u32 *written, Handle file, u64 offset, u32 size, u32 write_option, void *data); 143 | Result FSFile_GetSize(u64 *size, Handle file); 144 | Result FSFile_Close(Handle file); 145 | 146 | #endif -------------------------------------------------------------------------------- /include/am/twlexport.h: -------------------------------------------------------------------------------- 1 | #ifndef _AM_TWLEXPORT_H 2 | #define _AM_TWLEXPORT_H 3 | 4 | #include <3ds/result.h> 5 | #include <3ds/types.h> 6 | #include <3ds/am9.h> 7 | #include 8 | #include <3ds/fs.h> 9 | #include 10 | #include 11 | 12 | typedef enum AM_TWLExportType 13 | { 14 | // 11 content sections 15 | V2_11ContentSectionsDefault0 = 0x0, 16 | V2_11ContentSections2 = 0x2, 17 | V2_11ContentSections4 = 0x4, 18 | V2_11ContentSections5 = 0x5, 19 | // 12 content sections 20 | V2_12ContentSections7 = 0x7, 21 | V2_12ContentSections8 = 0x8, 22 | V2_12ContentSections9 = 0x9, 23 | V2_12ContentSectionsA = 0xA, 24 | V2_12ContentSectionsB = 0xB, 25 | // 4 content sections 26 | V1_4ContentSectionsC = 0xC, // this type is bugged, am9 doesn't write save data size into the header so it's impossible to import 27 | V1_4ContentSectionsD = 0xD, // the only working v1_4 export type 28 | } AM_TWLExportType; 29 | 30 | typedef enum AM_TWLExportSectionIndex 31 | { 32 | TWLExport_Banner = 0x0, 33 | TWLExport_Header = 0x1, 34 | TWLExport_Footer = 0x2, 35 | TWLExport_TMD = 0x3, 36 | TWLExport_Content = 0x4, 37 | TWLExport_PublicSaveData = 0x5, 38 | TWLExport_BannerSaveData = 0x6, 39 | TWLExport_PrivateSaveData = 0x7, 40 | } AM_TWLExportSectionIndex; 41 | 42 | typedef struct AM_TWLExportTypeInfo 43 | { 44 | u32 header_size; 45 | u32 footer_size; 46 | u32 section_count; 47 | u32 footer_data_verifysize; 48 | } AM_TWLExportTypeInfo; 49 | 50 | typedef struct __attribute__((packed)) AM_TWLExportBlockMetadata 51 | { 52 | u8 cmac[0x10]; 53 | u8 iv[0x10]; 54 | } AM_TWLExportBlockMetadata; 55 | 56 | typedef struct __attribute__((packed)) AM_TWLExportHeaderBase // size: 0x48 57 | { 58 | u32 magic; // 0x0 : 0x4 59 | u16 group_id; // 0x4 : 0x6 60 | u16 title_version; // 0x6 : 0x8 61 | u8 movable_hash[0x20]; // 0x8 : 0x28 62 | u8 empty_cbc[0x10]; // 0x28 : 0x38 63 | u64 twl_title_id; // 0x38 : 0x40 64 | u64 required_size; // 0x40 : 0x48 65 | } AM_TWLExportHeaderBase; 66 | 67 | typedef struct __attribute__((packed)) AM_TWLExportHeaderExtraData 68 | { 69 | u32 public_save_size; 70 | u32 private_save_size; 71 | u8 unk[54]; 72 | } AM_TWLExportHeaderExtraData; 73 | 74 | typedef struct __attribute__((packed)) AM_TWLExportHeaderV1_4 // size: 0xA0 (160) 75 | { 76 | AM_TWLExportHeaderBase base; // 0x0 : 0x48 77 | u32 payload_sizes[4]; // 0x48 : 0x58 78 | u32 unknown_2; // 0x58 : 0x5C 79 | AM_TWLExportHeaderExtraData extra_data; // 0x5C : 0x9A 80 | u16 content_index; // 0x9A : 0x9C 81 | u8 unknown_4[4]; // 0x9C : 0xA0 82 | } AM_TWLExportHeaderV1_4; 83 | 84 | typedef struct __attribute__((packed)) AM_TWLExportHeaderV2_11 // size: 0xF0 (240) 85 | { 86 | AM_TWLExportHeaderBase base; // 0x0 : 0x48 87 | u32 payload_sizes[11]; // 0x48 : 0x74 88 | u8 unknown[32]; // 0x74 : 0x94 89 | u16 content_indices[8]; // 0x94 : 0xA4 90 | AM_TWLExportHeaderExtraData extra_data; // 0xA4 : 0xE2 91 | u8 pad[0xE]; // 0xE2 : 0xF0 92 | } AM_TWLExportHeaderV2_11; 93 | 94 | typedef struct __attribute__((packed)) AM_TWLExportHeaderV2_12 // size: 0x100 (256) 95 | { 96 | AM_TWLExportHeaderBase base; // 0x0 : 0x48 97 | u32 payload_sizes[12]; // 0x48 : 0x78 98 | u8 unknown_2[28]; // 0x78 : 0x94 99 | u16 content_indices[8]; // 0x94 : 0xA4 100 | AM_TWLExportHeaderExtraData extra_data; // 0xA4 : 0xE2 101 | u8 pad[0xE]; // 0xE2 : 0xF0 102 | u32 private_save_size; // 0xF0 : 0xF4 103 | u8 pad2[0xC]; // 0xF4 : 0x100 104 | } AM_TWLExportHeaderV2_12; 105 | 106 | typedef struct __attribute__((packed)) AM_TWLExportFooterBase 107 | { 108 | u8 banner_hash[0x20]; 109 | u8 header_hash[0x20]; 110 | } AM_TWLExportFooterBase; 111 | 112 | typedef struct __attribute__((packed)) AM_TWLExportFooterV1_4 113 | { 114 | AM_TWLExportFooterBase base; 115 | u8 content_section_hashes[4][0x20]; 116 | u8 ecdsa_sig[60]; 117 | u8 ecdsa_apcert[384]; 118 | u8 ecdsa_ctcert[384]; 119 | u32 pad; 120 | } AM_TWLExportFooterV1_4; 121 | 122 | typedef struct __attribute__((packed)) AM_TWLExportFooterV2_11 123 | { 124 | AM_TWLExportFooterBase base; 125 | u8 content_section_hashes[11][0x20]; 126 | u8 ecdsa_sig[60]; 127 | u8 ecdsa_apcert[384]; 128 | u8 ecdsa_ctcert[384]; 129 | u32 pad; 130 | } AM_TWLExportFooterV2_11; 131 | 132 | typedef struct __attribute__((packed)) AM_TWLExportFooterV2_12 133 | { 134 | AM_TWLExportFooterBase base; 135 | u8 content_section_hashes[12][0x20]; 136 | u8 ecdsa_sig[60]; 137 | u8 ecdsa_apcert[384]; 138 | u8 ecdsa_ctcert[384]; 139 | u32 pad; 140 | } AM_TWLExportFooterV2_12; 141 | 142 | typedef struct __attribute__((packed)) AM_TWLExportInfo 143 | { 144 | u64 twl_title_id; 145 | u16 group_id; 146 | u16 title_version; 147 | u32 public_save_size; 148 | u32 private_save_size; 149 | u32 reserved_pad; 150 | u64 required_size; 151 | } AM_TWLExportInfo; 152 | 153 | Result AM_ImportTWLBackup(u32 buffer_size, AM_TWLExportType export_type, Handle file, void *buffer); 154 | Result AM_ReadTWLBackupInfo(u32 buffer_size, u32 export_info_size, u32 banner_size, AM_TWLExportType type, Handle file, void *buffer, void *out_export_info, void *banner); 155 | 156 | #endif -------------------------------------------------------------------------------- /include/errors.h: -------------------------------------------------------------------------------- 1 | #ifndef _AM_ERRORS_H 2 | #define _AM_ERRORS_H 3 | 4 | #include <3ds/result.h> 5 | 6 | // module specific am 7 | 8 | #define AM_GENERAL_IO_FAILURE MAKERESULT(RL_STATUS , RS_INVALIDARG , RM_AM, 1) // C8E08001 9 | #define AM_INVALIDATED_INSTALLATTION_STATE MAKERESULT(RL_PERMANENT, RS_INVALIDSTATE, RM_AM, 2) // D8A08002 10 | #define AM_INVALID_CONTENT_IMPORT_STATE MAKERESULT(RL_PERMANENT, RS_INVALIDSTATE, RM_AM, 4) // D8A08004 11 | #define AM_PIPE_UNSUPPORTED_ACTION MAKERESULT(RL_PERMANENT, RS_NOTSUPPORTED, RM_AM, 8) // D8C08008 12 | #define AM_PIPE_ALREADY_INITIALIZED MAKERESULT(RL_PERMANENT, RS_INVALIDSTATE, RM_AM, 9) // D8A08009 13 | #define AM_META_REGION_NOT_FOUND MAKERESULT(RL_PERMANENT, RS_NOTFOUND , RM_AM, 19) // D8808013 14 | #define AM_TRYING_TO_INSTALL_OUTDATED_TICKET MAKERESULT(RL_PERMANENT, RS_INVALIDARG , RM_AM, 25) // D8E08019 15 | #define AM_GENERAL_INVALIDARG MAKERESULT(RL_USAGE , RS_INVALIDARG , RM_AM, 31) // E0E0801F 16 | #define AM_TWL_EXPORT_INVALID_ENUM_VALUE MAKERESULT(RL_USAGE , RS_NOTSUPPORTED, RM_AM, 40) // E0C08028 17 | #define AM_GENERAL_INTERNAL_ERROR MAKERESULT(RL_PERMANENT, RS_INVALIDSTATE, RM_AM, 41) // D8E08029 (Default Internal Result?) 18 | #define AM_TRYING_TO_UNINSTALL_SYSAPP MAKERESULT(RL_USAGE , RS_INVALIDARG , RM_AM, 44) // C8A0802C 19 | #define AM_MOVABLE_VERIFY_SUCCESS MAKERESULT(RL_STATUS , RS_INVALIDSTATE, RM_AM, 46) // C8A0802E 20 | #define AM_FOOTER_SECTION_HASH_MISMATCH MAKERESULT(RL_STATUS , RS_INVALIDSTATE, RM_AM, 48) // C8A08030 21 | #define AM_SECTION_CMAC_VERIFY_FAIL MAKERESULT(RL_STATUS , RS_INVALIDSTATE, RM_AM, 49) // C8A08031 22 | #define AM_FAILED_READING_BLOCKMETA MAKERESULT(RL_STATUS , RS_INVALIDSTATE, RM_AM, 51) // C8A08033 23 | #define AM_INVALID_EXPORT MAKERESULT(RL_STATUS , RS_INVALIDSTATE, RM_AM, 52) // C8A08034 24 | #define AM_INVALID_TITLE_ID_HIGH MAKERESULT(RL_USAGE , RS_INVALIDARG , RM_AM, 60) // E0E0803C 25 | #define AM_CIA_META_READ_SIZE_TOO_SMALL MAKERESULT(RL_PERMANENT, RS_WRONGARG , RM_AM, 63) // D900803F 26 | #define AM_TICKET_UNEXPECTED_TITLE_ID MAKERESULT(RL_PERMANENT, RS_INVALIDSTATE, RM_AM, 68) // D8A08044 27 | #define AM_TMD_UNEXPECTED_TITLE_ID MAKERESULT(RL_PERMANENT, RS_INVALIDSTATE, RM_AM, 69) // D8A08045 28 | 29 | /* these are shared (also used in AM9) */ 30 | 31 | #define AM_INVALID_CONTENT_INDEX_SIZE MAKERESULT(RL_PERMANENT, RS_INVALIDARG , RM_AM, 101) // D8E08065 (Internal Result -1) 32 | #define AM_GENERAL_INTERNAL_INVALIDARG MAKERESULT(RL_PERMANENT, RS_INVALIDARG , RM_AM, 102) // D8E08066 (Internal Result -2) 33 | #define AM_PREVIOUS_RESULT_FAILED MAKERESULT(RL_PERMANENT, RS_INVALIDARG , RM_AM, 103) // D8E08067 (Internal Result -3) 34 | #define AM_IO_SIZE_MISMATCH MAKERESULT(RL_PERMANENT, RS_INVALIDARG , RM_AM, 104) // D8E08068 (Internal Result -4) 35 | #define AM_CERT_SIG_HASH_CHECK_FAILED MAKERESULT(RL_PERMANENT, RS_INVALIDARG , RM_AM, 106) // D8E0806A (Internal Result -6) 36 | #define AM_INVALID_SIGNATURE_TYPE MAKERESULT(RL_PERMANENT, RS_INVALIDARG , RM_AM, 109) // D8E0806D (Internal Result -9) 37 | #define AM_INVALID_TICKET_VERSION MAKERESULT(RL_PERMANENT, RS_INVALIDARG , RM_AM, 111) // D8E0806F (Internal Result -11) 38 | 39 | #define AM_INTERNAL_RESULT(x) (AM_INVALID_CONTENT_INDEX_SIZE - (x + 1)) 40 | 41 | /* 42 | no idea what these are, not used in AM11 43 | they can probably be named if i take the time to look in AM9 44 | 45 | -5 -> 0xD8E08069 MAKERESULT(RL_PERMANENT, RS_INVALIDARG , RM_AM, 105) Unknown 46 | -7 -> 0xD8E0806B MAKERESULT(RL_PERMANENT, RS_INVALIDARG , RM_AM, 107) Unknown 47 | -8 -> 0xD8E0806C MAKERESULT(RL_PERMANENT, RS_INVALIDARG , RM_AM, 108) Unknown 48 | -10 -> 0xD8E0806E MAKERESULT(RL_PERMANENT, RS_INVALIDARG , RM_AM, 110) Unknown 49 | -12 -> 0xD8E08070 MAKERESULT(RL_PERMANENT, RS_INVALIDARG , RM_AM, 112) Unknown 50 | -13 -> 0xD8E08071 MAKERESULT(RL_PERMANENT, RS_INVALIDARG , RM_AM, 113) Unknown 51 | -14 -> 0xD8E08072 MAKERESULT(RL_PERMANENT, RS_INVALIDARG , RM_AM, 114) Unknown 52 | */ 53 | 54 | // module specific os 55 | 56 | #define OS_REMOTE_SESSION_CLOSED MAKERESULT(RL_STATUS , RS_CANCELED, RM_OS, 26 ) // C920181A 57 | #define OS_INVALID_IPC_ARGUMENT MAKERESULT(RL_PERMANENT, RS_WRONGARG, RM_OS, 48 ) // D9001830 58 | #define OS_INVALID_IPC_HEADER MAKERESULT(RL_PERMANENT, RS_WRONGARG, RM_OS, 47 ) // D900182F 59 | #define OS_EXCEEDED_HANDLES_INDEX MAKERESULT(RL_PERMANENT, RS_WRONGARG, RM_OS, 49 ) // D9001831 60 | 61 | // module specific fs 62 | 63 | #define FS_UNSUPPORTED_NOT_IMPLEMENTED MAKERESULT(RL_USAGE, RS_NOTSUPPORTED, RM_FS, 760) // E0C046F8 64 | 65 | // general am 66 | 67 | #define AM_OUT_OF_MEMORY MAKERESULT(RL_PERMANENT, RS_OUTOFRESOURCE, RM_AM, RD_OUT_OF_MEMORY ) 68 | #define AM_INTERNAL_RANGE MAKERESULT(RL_FATAL , RS_INTERNAL , RM_AM, RD_OUT_OF_RANGE ) 69 | #define AM_CANCELED_RANGE MAKERESULT(RL_FATAL , RS_CANCELED , RM_AM, RD_OUT_OF_RANGE ) 70 | #define AM_INVALID_SIZE MAKERESULT(RL_USAGE , RS_INVALIDARG , RM_AM, RD_INVALID_SIZE ) 71 | #define AM_NOT_FOUND MAKERESULT(RL_PERMANENT, RS_INVALIDSTATE , RM_AM, RD_NOT_FOUND ) 72 | #define AM_INVALID_ENUM_VALUE MAKERESULT(RL_PERMANENT, RS_NOTSUPPORTED , RM_AM, RD_INVALID_ENUM_VALUE) 73 | #define AM_NOT_IMPLEMENTED MAKERESULT(RL_FATAL , RS_NOTSUPPORTED , RM_AM, RD_NOT_IMPLEMENTED ) 74 | 75 | // general os 76 | 77 | #define OS_MISALIGNED_ADDRESS MAKERESULT(RL_USAGE, RS_INVALIDARG, RM_OS, RD_MISALIGNED_ADDRESS) 78 | 79 | #endif -------------------------------------------------------------------------------- /include/3ds/result.h: -------------------------------------------------------------------------------- 1 | #ifndef _AM_3DS_RESULT_H 2 | #define _AM_3DS_RESULT_H 3 | 4 | #include <3ds/types.h> 5 | 6 | #define R_SUCCEEDED(res) ((res) >= 0 ) 7 | #define R_FAILED(res) ((res) < 0 ) 8 | 9 | #define MAKERESULT(level,summary,module,description) \ 10 | ((((level)&0x1F)<<27) | (((summary)&0x3F)<<21) | (((module)&0xFF)<<10) | ((description)&0x3FF)) 11 | #define R_LEVEL(res) (((res)>>27)&0x1F) 12 | #define R_SUMMARY(res) (((res)>>21)&0x3F) 13 | #define R_MODULE(res) (((res)>>10)&0xFF) 14 | #define R_DESCRIPTION(res) ((res)&0x3FF) 15 | #define R_DESCRANGE(res,lower,higher) (R_DESCRIPTION(res) >= lower && R_DESCRIPTION(res) <= higher) 16 | #define R_MODULEDESCRANGE(res,module,lower,higher) (R_MODULE(res) == module && R_DESCRANGE(res,lower,higher)) 17 | 18 | /// Result code level values. 19 | enum 20 | { 21 | // >= 0 22 | RL_SUCCESS = 0, 23 | RL_INFO = 1, 24 | 25 | // < 0 26 | RL_FATAL = 0x1F, 27 | RL_RESET = RL_FATAL - 1, 28 | RL_REINITIALIZE = RL_FATAL - 2, 29 | RL_USAGE = RL_FATAL - 3, 30 | RL_PERMANENT = RL_FATAL - 4, 31 | RL_TEMPORARY = RL_FATAL - 5, 32 | RL_STATUS = RL_FATAL - 6, 33 | }; 34 | 35 | /// Result code summary values. 36 | enum 37 | { 38 | RS_SUCCESS = 0, 39 | RS_NOP = 1, 40 | RS_WOULDBLOCK = 2, 41 | RS_OUTOFRESOURCE = 3, 42 | RS_NOTFOUND = 4, 43 | RS_INVALIDSTATE = 5, 44 | RS_NOTSUPPORTED = 6, 45 | RS_INVALIDARG = 7, 46 | RS_WRONGARG = 8, 47 | RS_CANCELED = 9, 48 | RS_STATUSCHANGED = 10, 49 | RS_INTERNAL = 11, 50 | RS_INVALIDRESVAL = 63, 51 | }; 52 | 53 | /// Result code module values. 54 | enum 55 | { 56 | RM_COMMON = 0, 57 | RM_KERNEL = 1, 58 | RM_UTIL = 2, 59 | RM_FILE_SERVER = 3, 60 | RM_LOADER_SERVER = 4, 61 | RM_TCB = 5, 62 | RM_OS = 6, 63 | RM_DBG = 7, 64 | RM_DMNT = 8, 65 | RM_PDN = 9, 66 | RM_GSP = 10, 67 | RM_I2C = 11, 68 | RM_GPIO = 12, 69 | RM_DD = 13, 70 | RM_CODEC = 14, 71 | RM_SPI = 15, 72 | RM_PXI = 16, 73 | RM_FS = 17, 74 | RM_DI = 18, 75 | RM_HID = 19, 76 | RM_CAM = 20, 77 | RM_PI = 21, 78 | RM_PM = 22, 79 | RM_PM_LOW = 23, 80 | RM_FSI = 24, 81 | RM_SRV = 25, 82 | RM_NDM = 26, 83 | RM_NWM = 27, 84 | RM_SOC = 28, 85 | RM_LDR = 29, 86 | RM_ACC = 30, 87 | RM_ROMFS = 31, 88 | RM_AM = 32, 89 | RM_HIO = 33, 90 | RM_UPDATER = 34, 91 | RM_MIC = 35, 92 | RM_FND = 36, 93 | RM_MP = 37, 94 | RM_MPWL = 38, 95 | RM_AC = 39, 96 | RM_HTTP = 40, 97 | RM_DSP = 41, 98 | RM_SND = 42, 99 | RM_DLP = 43, 100 | RM_HIO_LOW = 44, 101 | RM_CSND = 45, 102 | RM_SSL = 46, 103 | RM_AM_LOW = 47, 104 | RM_NEX = 48, 105 | RM_FRIENDS = 49, 106 | RM_RDT = 50, 107 | RM_APPLET = 51, 108 | RM_NIM = 52, 109 | RM_PTM = 53, 110 | RM_MIDI = 54, 111 | RM_MC = 55, 112 | RM_SWC = 56, 113 | RM_FATFS = 57, 114 | RM_NGC = 58, 115 | RM_CARD = 59, 116 | RM_CARDNOR = 60, 117 | RM_SDMC = 61, 118 | RM_BOSS = 62, 119 | RM_DBM = 63, 120 | RM_CONFIG = 64, 121 | RM_PS = 65, 122 | RM_CEC = 66, 123 | RM_IR = 67, 124 | RM_UDS = 68, 125 | RM_PL = 69, 126 | RM_CUP = 70, 127 | RM_GYROSCOPE = 71, 128 | RM_MCU = 72, 129 | RM_NS = 73, 130 | RM_NEWS = 74, 131 | RM_RO = 75, 132 | RM_GD = 76, 133 | RM_CARD_SPI = 77, 134 | RM_EC = 78, 135 | RM_WEB_BROWSER = 79, 136 | RM_TEST = 80, 137 | RM_ENC = 81, 138 | RM_PIA = 82, 139 | RM_ACT = 83, 140 | RM_VCTL = 84, 141 | RM_OLV = 85, 142 | RM_NEIA = 86, 143 | RM_NPNS = 87, 144 | RM_AVD = 90, 145 | RM_L2B = 91, 146 | RM_MVD = 92, 147 | RM_NFC = 93, 148 | RM_UART = 94, 149 | RM_SPM = 95, 150 | RM_QTM = 96, 151 | RM_NFP = 97, 152 | RM_APPLICATION = 254, 153 | RM_INVALIDRESVAL = 255, 154 | }; 155 | 156 | /// Result code generic description values. 157 | enum 158 | { 159 | RD_SUCCESS = 0, 160 | RD_INVALID_RESULT_VALUE = 0x3FF, 161 | RD_TIMEOUT = RD_INVALID_RESULT_VALUE - 1, 162 | RD_OUT_OF_RANGE = RD_INVALID_RESULT_VALUE - 2, 163 | RD_ALREADY_EXISTS = RD_INVALID_RESULT_VALUE - 3, 164 | RD_CANCEL_REQUESTED = RD_INVALID_RESULT_VALUE - 4, 165 | RD_NOT_FOUND = RD_INVALID_RESULT_VALUE - 5, 166 | RD_ALREADY_INITIALIZED = RD_INVALID_RESULT_VALUE - 6, 167 | RD_NOT_INITIALIZED = RD_INVALID_RESULT_VALUE - 7, 168 | RD_INVALID_HANDLE = RD_INVALID_RESULT_VALUE - 8, 169 | RD_INVALID_POINTER = RD_INVALID_RESULT_VALUE - 9, 170 | RD_INVALID_ADDRESS = RD_INVALID_RESULT_VALUE - 10, 171 | RD_NOT_IMPLEMENTED = RD_INVALID_RESULT_VALUE - 11, 172 | RD_OUT_OF_MEMORY = RD_INVALID_RESULT_VALUE - 12, 173 | RD_MISALIGNED_SIZE = RD_INVALID_RESULT_VALUE - 13, 174 | RD_MISALIGNED_ADDRESS = RD_INVALID_RESULT_VALUE - 14, 175 | RD_BUSY = RD_INVALID_RESULT_VALUE - 15, 176 | RD_NO_DATA = RD_INVALID_RESULT_VALUE - 16, 177 | RD_INVALID_COMBINATION = RD_INVALID_RESULT_VALUE - 17, 178 | RD_INVALID_ENUM_VALUE = RD_INVALID_RESULT_VALUE - 18, 179 | RD_INVALID_SIZE = RD_INVALID_RESULT_VALUE - 19, 180 | RD_ALREADY_DONE = RD_INVALID_RESULT_VALUE - 20, 181 | RD_NOT_AUTHORIZED = RD_INVALID_RESULT_VALUE - 21, 182 | RD_TOO_LARGE = RD_INVALID_RESULT_VALUE - 22, 183 | RD_INVALID_SELECTION = RD_INVALID_RESULT_VALUE - 23, 184 | }; 185 | 186 | #endif -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | #--------------------------------------------------------------------------------- 2 | .SUFFIXES: 3 | #--------------------------------------------------------------------------------- 4 | 5 | ifeq ($(strip $(DEVKITARM)),) 6 | $(error "Please set DEVKITARM in your environment. export DEVKITARM=devkitARM") 7 | endif 8 | 9 | TOPDIR ?= $(CURDIR) 10 | include $(DEVKITARM)/3ds_rules 11 | 12 | #--------------------------------------------------------------------------------- 13 | # TARGET is the name of the output 14 | # BUILD is the directory where object files & intermediate files will be placed 15 | # SOURCES is a list of directories containing source code 16 | # DATA is a list of directories containing data files 17 | # INCLUDES is a list of directories containing header files 18 | #--------------------------------------------------------------------------------- 19 | TARGET := am11 20 | BUILD := build 21 | SOURCES := source source/3ds source/am 22 | INCLUDES := include include/3ds source/am 23 | 24 | #--------------------------------------------------------------------------------- 25 | # options for code generation 26 | #--------------------------------------------------------------------------------- 27 | ARCH := -march=armv6k -mtune=mpcore -mfloat-abi=hard -mtp=soft 28 | DEFINES := 29 | 30 | CFLAGS := -std=gnu11 -Wall -Wextra -Werror -Wno-unused-value -flto -mword-relocations \ 31 | -fomit-frame-pointer -ffunction-sections -fdata-sections \ 32 | -fno-exceptions -fno-ident -fno-unwind-tables -fno-asynchronous-unwind-tables \ 33 | -fno-tree-loop-distribute-patterns -fshort-wchar \ 34 | $(ARCH) $(DEFINES) $(INCLUDE) 35 | 36 | ASFLAGS := $(ARCH) 37 | LDFLAGS = -specs=3dsx.specs -nostartfiles -nostdlib \ 38 | $(ARCH) -Wl,-Map,$(notdir $*.map) 39 | 40 | RSF = $(OUTPUT)_debug.rsf 41 | 42 | ifneq ($(DEBUG),) 43 | CFLAGS += -g -O0 44 | else 45 | CFLAGS += -Os 46 | endif 47 | 48 | ifneq ($(REPLACE_AM),) 49 | CFLAGS += -DREPLACE_AM 50 | RSF = $(OUTPUT).rsf 51 | endif 52 | 53 | ifneq ($(DEBUG_PRINTS),) 54 | CFLAGS += -DDEBUG_PRINTS 55 | endif 56 | 57 | LIBS := 58 | 59 | #--------------------------------------------------------------------------------- 60 | # list of directories containing libraries, this must be the top level containing 61 | # include and lib 62 | #--------------------------------------------------------------------------------- 63 | LIBDIRS := $(TOPDIR) 64 | 65 | 66 | #--------------------------------------------------------------------------------- 67 | # no real need to edit anything past this point unless you need to add additional 68 | # rules for different file extensions 69 | #--------------------------------------------------------------------------------- 70 | ifneq ($(BUILD),$(notdir $(CURDIR))) 71 | #--------------------------------------------------------------------------------- 72 | 73 | export OUTPUT := $(CURDIR)/$(TARGET) 74 | export TOPDIR := $(CURDIR) 75 | 76 | export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \ 77 | $(foreach dir,$(DATA),$(CURDIR)/$(dir)) 78 | 79 | export DEPSDIR := $(CURDIR)/$(BUILD) 80 | 81 | CFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.c))) 82 | SFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.s))) 83 | BINFILES := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.*))) 84 | 85 | # we don't use C++ here 86 | export LD := $(CC) 87 | 88 | export OFILES_BIN := $(addsuffix .o,$(BINFILES)) 89 | export OFILES_SRC := $(CFILES:.c=.o) $(SFILES:.s=.o) 90 | export OFILES := $(OFILES_BIN) $(OFILES_SRC) 91 | export HFILES_BIN := $(addsuffix .h,$(subst .,_,$(BINFILES))) 92 | 93 | export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \ 94 | $(foreach dir,$(LIBDIRS),-I$(dir)/include) \ 95 | -I$(CURDIR)/$(BUILD) 96 | 97 | export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib) 98 | 99 | .PHONY: $(BUILD) clean all 100 | 101 | #--------------------------------------------------------------------------------- 102 | all: $(BUILD) 103 | 104 | $(BUILD): 105 | @[ -d $@ ] || mkdir -p $@ 106 | @$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile 107 | 108 | #--------------------------------------------------------------------------------- 109 | clean: 110 | @echo clean ... 111 | @rm -fr $(BUILD) $(TARGET).cxi $(TARGET).elf 112 | 113 | 114 | #--------------------------------------------------------------------------------- 115 | else 116 | .PHONY: all 117 | 118 | DEPENDS := $(OFILES:.o=.d) 119 | 120 | #--------------------------------------------------------------------------------- 121 | # main targets 122 | #--------------------------------------------------------------------------------- 123 | 124 | all : $(OUTPUT).cia 125 | 126 | $(OUTPUT).cia : $(OUTPUT).cxi 127 | @makerom -f cia -o $(OUTPUT).cia -ver 10245 -i $(OUTPUT).cxi:0:0 -ignoresign -v 128 | @echo built ... $(notdir $@) 129 | 130 | $(OUTPUT).cxi : $(OUTPUT).elf $(RSF) 131 | ifeq ($(DEBUG),) 132 | arm-none-eabi-strip $(OUTPUT).elf 133 | endif 134 | @makerom -f ncch -rsf $(word 2,$^) -o $@ -elf $(OUTPUT).elf -target t -ignoresign 135 | @echo built ... $(notdir $@) 136 | 137 | $(OUTPUT).elf : $(OFILES) 138 | 139 | $(OFILES_SRC) : $(HFILES_BIN) 140 | 141 | #--------------------------------------------------------------------------------- 142 | # you need a rule like this for each extension you use as binary data 143 | #--------------------------------------------------------------------------------- 144 | %.bin.o %_bin.h: %.bin 145 | #--------------------------------------------------------------------------------- 146 | @echo $(notdir $<) 147 | @$(bin2o) 148 | #--------------------------------------------------------------------------------- 149 | %.xml.o %_xml.h: %.xml 150 | #--------------------------------------------------------------------------------- 151 | @echo $(notdir $<) 152 | @$(bin2o) 153 | 154 | -include $(DEPENDS) 155 | 156 | #--------------------------------------------------------------------------------------- 157 | endif 158 | #--------------------------------------------------------------------------------------- 159 | -------------------------------------------------------------------------------- /source/allocator.c: -------------------------------------------------------------------------------- 1 | /* 2 | * The 1st project for Data Structures and Algorithms course of 2010 3 | * 4 | * The Faculty of Informatics and Information Technologies at 5 | * The Slovak University of Technology, Bratislava, Slovakia 6 | * 7 | * 8 | * Own implementation of stdlib's malloc() and free() functions 9 | * 10 | * Author: mnicky 11 | * 12 | * 13 | * License: modified MIT License - see the section b) below 14 | * 15 | * Copyright (C) 2010 by mnicky 16 | * 17 | * Permission is hereby granted, free of charge, to any person obtaining a copy 18 | * of this software and associated documentation files (the "Software"), to deal 19 | * in the Software without restriction, including without limitation the rights 20 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 21 | * copies of the Software, and to permit persons to whom the Software is 22 | * furnished to do so, subject to the following conditions: 23 | * 24 | * a) The above copyright notice and this permission notice - including the 25 | * section b) - shall be included in all copies or substantial portions 26 | * of the Software. 27 | * 28 | * b) the Software WILL NOT BE USED IN ANY WORK DIRECTLY OR INDIRECTLY 29 | * CONNECTED WITH The Faculty of Informatics and Information Technologies at 30 | * The Slovak University of Technology, Bratislava, Slovakia 31 | * 32 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 33 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 34 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 35 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 36 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 37 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 38 | * THE SOFTWARE. 39 | * 40 | */ 41 | 42 | #include 43 | 44 | MEMTYPE *mem; 45 | MEMTYPE memSize; 46 | MEMTYPE avail; // 1st index of the 1st free range 47 | 48 | // return size of block 49 | MEMTYPE blockSize(MEMTYPE x) 50 | { 51 | return mem[x]; 52 | } 53 | 54 | // return next free block 55 | MEMTYPE next(MEMTYPE x) 56 | { 57 | return mem[x + mem[x]]; 58 | } 59 | 60 | // return index of pointer to next free block 61 | MEMTYPE linkToNext(MEMTYPE x) 62 | { 63 | return x + mem[x]; 64 | } 65 | 66 | // initialize memory 67 | void meminit(void *ptr, unsigned size) 68 | { 69 | 70 | mem = (MEMTYPE *)ptr; 71 | memSize = size / sizeof(MEMTYPE); 72 | mem[0] = memSize - 1; 73 | mem[memSize - 1] = memSize; 74 | avail = 0; 75 | } 76 | 77 | // allocate memory 78 | void *malloc(unsigned size) 79 | { 80 | // return NULL pointer after attempt to allocate 0-length memory 81 | if (size == 0) 82 | return NULL; 83 | 84 | MEMTYPE num = size / sizeof(MEMTYPE); 85 | if (size % sizeof(MEMTYPE) > 0) 86 | num++; 87 | MEMTYPE cur, prev; // pointer to (actually index of) current block, previous block 88 | MEMTYPE isFirstFreeBeingAllocated = 1; // whether the first free block is being allocated 89 | 90 | prev = cur = avail; 91 | 92 | // testing, whether we have enough free space for allocation 93 | test: 94 | // if we are on the end of the memory 95 | if (avail == memSize) 96 | return NULL; 97 | 98 | // if the size of free block is lower than requested 99 | if (blockSize(cur) < num) 100 | { 101 | isFirstFreeBeingAllocated = 0; 102 | prev = cur; 103 | 104 | if (next(cur) == memSize) // if not enough memory 105 | return NULL; 106 | else 107 | cur = next(cur); 108 | goto test; 109 | } 110 | 111 | // if the size of free block is equal to requested 112 | if (blockSize(cur) == num) 113 | { 114 | 115 | if (isFirstFreeBeingAllocated) 116 | avail = next(cur); 117 | else 118 | mem[linkToNext(prev)] = next(cur); 119 | } 120 | else // if the size of free block is greater than requested 121 | { 122 | if (isFirstFreeBeingAllocated) 123 | { 124 | if ((blockSize(cur) - num) == 1) // if there is only 1 free item left from this (previously) free block 125 | avail = next(cur); 126 | else 127 | avail = cur + num + 1; 128 | } 129 | else 130 | { 131 | if ((blockSize(cur) - num) == 1) // if there is only 1 free item left from this (previously) free block 132 | mem[linkToNext(prev)] = next(cur); 133 | else 134 | mem[linkToNext(prev)] = cur + num + 1; 135 | } 136 | 137 | if ((blockSize(cur) - num) == 1) // if there is only 1 free item left from this (previously) free block 138 | mem[cur] = num + 1; 139 | else 140 | { 141 | mem[cur + num + 1] = blockSize(cur) - num - 1; 142 | mem[cur] = num; 143 | } 144 | } 145 | 146 | return (void *)&(mem[cur + 1]); 147 | } 148 | 149 | // free memory 150 | void free(void *ptr) 151 | { 152 | 153 | MEMTYPE toFree; // pointer to block (to free) 154 | MEMTYPE cur, prev; 155 | 156 | toFree = ((MEMTYPE *)ptr - (mem + 1)); 157 | 158 | if (toFree < avail) 159 | { 160 | // if block, that is being freed is before the first free block 161 | if (((linkToNext(toFree) + 1) == avail) && avail < memSize) // if next free block is immediately after block that is being freed 162 | mem[toFree] += (mem[avail] + 1); // defragmentation of free space 163 | else 164 | mem[linkToNext(toFree)] = avail; 165 | 166 | avail = toFree; 167 | } 168 | 169 | else 170 | { 171 | // if block, that is being freed isn't before the first free block 172 | prev = cur = avail; 173 | 174 | while (cur < toFree) 175 | { 176 | prev = cur; 177 | cur = next(cur); 178 | } 179 | 180 | if ((linkToNext(prev) + 1) == toFree) 181 | { 182 | // if previous free block is immediately before block that is being freed 183 | 184 | mem[prev] += (mem[toFree] + 1); // defragmentation of free space 185 | 186 | if (((linkToNext(toFree) + 1) == cur) && cur < memSize) // if next free block is immediately after block that is being freed 187 | mem[prev] += (mem[cur] + 1); // defragmentation of free space 188 | else 189 | mem[linkToNext(toFree)] = cur; 190 | } 191 | else 192 | { 193 | mem[linkToNext(prev)] = toFree; 194 | mem[linkToNext(toFree)] = cur; 195 | } 196 | } 197 | } -------------------------------------------------------------------------------- /source/am/cia.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | static TMDHeader __attribute__((section(".data.min_tmd"))) min_tmd; 4 | RecursiveLock g_TMDReader_Lock; 5 | 6 | static Result CIAReader_ReadEnabledIndices(CIAReader *rd, u16 amount, u16 *indices, u16 *read_indices) 7 | { 8 | u8 batch[0x400]; 9 | Result res; 10 | 11 | u32 read; 12 | u16 enabled = 0, processed = 0; 13 | 14 | for (u16 i = 0; i < 0x2000; i += sizeof(batch)) 15 | { 16 | if (R_FAILED(res = FSFile_Read(&read, rd->cia, 0x20 + i, sizeof(batch), batch))) 17 | return res; 18 | 19 | if (read != sizeof(batch)) 20 | return AM_GENERAL_IO_FAILURE; 21 | 22 | for (u16 j = 0; j < sizeof(batch); j++) 23 | { 24 | for (u8 k = 0; k < 8; k++, processed++) 25 | { 26 | if (batch[j] & (0x80 >> k)) 27 | { 28 | indices[enabled] = processed; 29 | enabled++; 30 | 31 | if (enabled == amount) 32 | goto exit; 33 | } 34 | } 35 | } 36 | } 37 | exit: 38 | *read_indices = enabled; 39 | return 0; 40 | } 41 | 42 | Result CIAReader_Init(CIAReader *rd, Handle cia, bool init_tmd) 43 | { 44 | rd->cia = cia; 45 | (void)init_tmd; 46 | 47 | Result res; 48 | u32 read; 49 | 50 | if (R_FAILED(res = FSFile_Read(&read, rd->cia, 0, sizeof(CIAHeader), &rd->header))) 51 | return res; 52 | 53 | return 0; 54 | } 55 | 56 | Result CIAReader_ReadMinTMD(CIAReader *rd) 57 | { 58 | u32 read; 59 | 60 | Result res = FSFile_Read(&read, rd->cia, TMD_START(rd), sizeof(TMDHeader), &min_tmd); 61 | 62 | if (R_FAILED(res)) 63 | return AM_INTERNAL_RESULT(-3); // result failed 64 | 65 | if (read != sizeof(TMDHeader)) 66 | return AM_INTERNAL_RESULT(-4); // size mismatch 67 | 68 | return res; 69 | } 70 | 71 | void CIAReader_Close(CIAReader *rd) 72 | { 73 | svcCloseHandle(rd->cia); 74 | } 75 | 76 | Result CIAReader_CalculateTitleSize(CIAReader *rd, MediaType media_type, u64 *size, u16 *indices_count, u32 *align_size, bool skip_tmd_read) 77 | { 78 | if (media_type != MediaType_SD && media_type != MediaType_NAND) 79 | return AM_INVALID_ENUM_VALUE; 80 | 81 | u16 indices[256]; 82 | u16 read_indices = 0; 83 | Result res; 84 | 85 | if (R_FAILED(res = CIAReader_ReadEnabledIndices(rd, 256, indices, &read_indices))) 86 | return res; 87 | 88 | u64 _size = 0; 89 | u32 align = media_type == MediaType_SD ? 0x8000 : 0x4000; 90 | 91 | // step 1: contents 92 | 93 | RecursiveLock_Lock(&g_TMDReader_Lock); 94 | 95 | if (!skip_tmd_read && R_FAILED(res = CIAReader_ReadMinTMD(rd))) 96 | return res; 97 | 98 | ContentChunkRecord ccr; 99 | u16 tmd_content_count = __builtin_bswap16(min_tmd.ContentCount); 100 | u32 read; 101 | 102 | for (u16 i = 0; i < tmd_content_count; i++) 103 | { 104 | if (R_FAILED(res = FSFile_Read(&read, rd->cia, TMD_START(rd) + sizeof(MinimumTMD) + sizeof(ContentChunkRecord) * i, sizeof(ContentChunkRecord), &ccr))) 105 | return res; 106 | 107 | for (u16 j = 0; j < read_indices; j++) 108 | if (__builtin_bswap16(ccr.Index) == indices[j]) 109 | _size += ALIGN(__builtin_bswap64(ccr.Size), align); 110 | } 111 | 112 | _size += (tmd_content_count > 254 ? 2 : 1) * align; // not sure why this is done but /shrug 113 | 114 | // step 2: tmd 115 | 116 | u32 tmd_size = sizeof(MinimumTMD) + (tmd_content_count * sizeof(ContentChunkRecord)); 117 | _size += ALIGN(tmd_size, align); 118 | 119 | // step 3: save data 120 | 121 | u64 tmd_tid = __builtin_bswap64(min_tmd.TitleID); 122 | u32 save_size; 123 | 124 | if (TitleID_IsTWL(tmd_tid)) 125 | save_size = 126 | ALIGN(min_tmd.SaveInfo.Size.SRLPublicSaveDataSize, align) + 127 | ALIGN(min_tmd.SaveInfo.SRLPrivateSaveDataSize, align) + 128 | (min_tmd.SaveInfo.SRLFlag & 0x2 ? align : 0); 129 | else 130 | save_size = ALIGN(min_tmd.SaveInfo.Size.CTRSaveSize, align); 131 | 132 | RecursiveLock_Unlock(&g_TMDReader_Lock); 133 | 134 | if (save_size) // extra align block if save data is there 135 | save_size += align; 136 | 137 | _size += save_size; 138 | 139 | // step 4: THREE extra blocks of alignment??? 140 | _size += 3 * align; 141 | 142 | // done 143 | 144 | *size = _size; 145 | 146 | if (indices_count) *indices_count = read_indices; 147 | if (align_size) *align_size = align; 148 | 149 | return 0; 150 | } 151 | 152 | Result CIAReader_GetTitleInfo(CIAReader *rd, MediaType media_type, TitleInfo *info) 153 | { 154 | _memset32_aligned(info, 0x00, sizeof(TitleInfo)); 155 | 156 | RecursiveLock_Lock(&g_TMDReader_Lock); 157 | Result res = CIAReader_ReadMinTMD(rd); 158 | 159 | if (R_SUCCEEDED(res)) 160 | { 161 | info->title_id = __builtin_bswap64(min_tmd.TitleID); 162 | info->type = __builtin_bswap32(min_tmd.TitleType); 163 | info->version = __builtin_bswap16(min_tmd.TitleVersion); 164 | 165 | res = CIAReader_CalculateTitleSize(rd, media_type, &info->size, NULL, NULL, true); 166 | } 167 | 168 | RecursiveLock_Unlock(&g_TMDReader_Lock); 169 | return res; 170 | } 171 | 172 | Result CIAReader_ExtractMetaSMDH(CIAReader *rd, void *smdh) 173 | { 174 | Result res; 175 | u32 read; 176 | 177 | if (R_FAILED(res = FSFile_Read(&read, rd->cia, CON_END(rd) + 0x400, 0x36C0, smdh))) 178 | return res; 179 | 180 | if (read != 0x36C0) // cia has no meta region 181 | return AM_META_REGION_NOT_FOUND; 182 | 183 | return 0; 184 | } 185 | 186 | Result CIAReader_ExtractDependencyList(CIAReader *rd, void *list) 187 | { 188 | Result res; 189 | u32 read; 190 | 191 | if (R_FAILED(res = FSFile_Read(&read, rd->cia, CON_END(rd), 0x300, list))) 192 | return res; 193 | 194 | if (read != 0x300) // cia has no meta region 195 | return AM_META_REGION_NOT_FOUND; 196 | 197 | return 0; 198 | } 199 | 200 | Result CIAReader_ExtractMetaCoreVersion(CIAReader *rd, u32 *version) 201 | { 202 | Result res; 203 | u32 read; 204 | 205 | if (R_FAILED(res = FSFile_Read(&read, rd->cia, CON_END(rd) + 0x300, sizeof(u32), version))) 206 | return res; 207 | 208 | if (read != sizeof(u32)) // cia has no meta region 209 | return AM_META_REGION_NOT_FOUND; 210 | 211 | return 0; 212 | } 213 | 214 | Result CIAReader_CalculateRequiredSize(CIAReader *rd, MediaType media_type, u64 *size) 215 | { 216 | u16 indices_count; 217 | u32 align; 218 | Result res; 219 | 220 | if (R_FAILED(res = CIAReader_CalculateTitleSize(rd, media_type, size, &indices_count, &align, false))) 221 | return res; 222 | 223 | // step 1: add another block of align size 224 | 225 | *size += align; 226 | 227 | // step 2: ??? 228 | 229 | *size += ALIGN((indices_count * 1024) + 0x200, align); 230 | 231 | return 0; 232 | } 233 | 234 | Result CIAReader_ExtractMeta(CIAReader *rd, u32 size, void *meta, u32 *read) 235 | { 236 | Result res; 237 | 238 | if (rd->header.MetaSize > size) 239 | return AM_CIA_META_READ_SIZE_TOO_SMALL; 240 | 241 | if (R_FAILED(res = FSFile_Read(read, rd->cia, CON_END(rd), size, meta))) 242 | return res; 243 | 244 | return 0; 245 | } -------------------------------------------------------------------------------- /dsiware_export_stuff.py: -------------------------------------------------------------------------------- 1 | from Cryptodome.Hash import CMAC, SHA256 2 | from pyctr.crypto import CryptoEngine 3 | from Cryptodome.Cipher.AES import * 4 | from typing import BinaryIO, Tuple 5 | from Cryptodome.Cipher import AES 6 | from enum import IntEnum 7 | from math import ceil 8 | import struct 9 | import sys 10 | import os 11 | 12 | BANNER_SIZE = 0x4000 13 | DECRYPT_KEY = None 14 | CMAC_KEY = None 15 | 16 | class ExportSection: 17 | data: bytes 18 | mac: bytes 19 | iv: bytes 20 | decrypted_sha256: bytes = None 21 | 22 | def __init__(self, section, mac, iv) -> None: 23 | self.data = section 24 | self.mac = mac 25 | self.iv = iv 26 | 27 | class ExportHeader: 28 | raw_data: bytes = None 29 | 30 | magic: str = None 31 | group_id: int = None 32 | title_version: int = None 33 | encrypted_movable_hash: bytes = None 34 | zeroiv_crypted_zeros_cbc_block: bytes = None 35 | title_id: int = None 36 | required_size: int = None 37 | payload_sizes: list[int] = None 38 | private_save_size: int = None 39 | 40 | 41 | def __init__(self, data: bytes, section_count: int): 42 | self.magic = data[0x0:0x4].decode("ascii") 43 | self.group_id = int.from_bytes(data[0x4:0x6], "little") 44 | self.title_version = int.from_bytes(data[0x6:0x8], "little") 45 | self.encrypted_movable_hash = data[0x8:0x28] 46 | self.zeroiv_crypted_zeros_cbc_block = data[0x28:0x38] 47 | self.title_id = int.from_bytes(data[0x38:0x40], "little") 48 | self.required_size = int.from_bytes(data[0x40:0x48], "little") 49 | 50 | payload_and_more = data[0x48:] 51 | unpack_format = "<" + "I" * section_count 52 | self.payload_sizes = list(struct.unpack(unpack_format, payload_and_more[0:section_count * 4])) 53 | if section_count == 12: 54 | self.private_save_size = int.from_bytes(data[0xF0:0xF4], "little") 55 | 56 | def __str__(self) -> str: 57 | return \ 58 | f"Magic : {self.magic}\n" \ 59 | f"Group ID : {self.group_id:04X}\n" \ 60 | f"Title Version : {self.title_version}\n" \ 61 | f"Encrypted movable.sed SHA-256 hash : {self.encrypted_movable_hash.hex().upper()}\n" \ 62 | f"Zero-IV crypted CBC-block : {self.zeroiv_crypted_zeros_cbc_block.hex().upper()}\n" \ 63 | f"Title ID : {self.title_id:016X}\n" \ 64 | f"Required Size: : {self.required_size} ({ceil(self.required_size / 1024 / 128):.0F} blocks)\n" \ 65 | f"Private save size : {self.private_save_size if self.private_save_size else 'N/A'}\n" 66 | 67 | class TWLExportType(IntEnum): 68 | # 12 content sections 69 | V2_12ContentSections7 = 0x7 70 | V2_12ContentSections8 = 0x8 71 | V2_12ContentSections9 = 0x9 72 | V2_12ContentSectionsA = 0xA 73 | V2_12ContentSectionsB = 0xB 74 | # 4 content sections 75 | V2_4ContentSectionsC = 0xC 76 | V1_4ContentSectionsD = 0xD 77 | # 11 content sections 78 | V2_11ContentSectionsE = 0xE 79 | Default = 0xFF # this will be used if the export type is not one of the previous ones, also uses 11 content sections 80 | 81 | def get_export_info(export_type: int) -> Tuple[int, int, int]: 82 | if 7 <= export_type <= 0xB: 83 | return (0x100, 0x500, 12) 84 | elif export_type == 0xC or export_type == 0xD: 85 | return (0xA0, 0x400, 4) 86 | else: 87 | return (0xF0, 0x4E0, 11) 88 | 89 | def align(x: int, y: int) -> int: 90 | return 0 if x == 0 else ((x + y - 1) & ~(y - 1)) 91 | 92 | def read_section(fp: BinaryIO, size: int) -> ExportSection: 93 | sect = fp.read(align(size, 0x10)) 94 | mac = fp.read(0x10) 95 | iv = fp.read(0x10) 96 | return ExportSection(sect, mac, iv) 97 | 98 | def decrypt_and_verify_section(section: ExportSection) -> bytes: 99 | cipher = AES.new(DECRYPT_KEY, MODE_CBC, section.iv) 100 | decrypted = cipher.decrypt(section.data) 101 | 102 | sha = SHA256.new() 103 | sha.update(decrypted) 104 | sha256_digest = sha.digest() 105 | 106 | cmac = CMAC.new(CMAC_KEY, ciphermod=AES) 107 | cmac.update(sha256_digest) 108 | cmac_digest = cmac.digest() 109 | 110 | if cmac_digest != section.mac: 111 | raise Exception(f"CMAC invalid! Expected {section.mac.hex()} but got {cmac_digest.hex()}") 112 | 113 | section.decrypted_sha256 = sha256_digest 114 | 115 | return decrypted 116 | 117 | def decrypt_verify_section_to_file(export_fp: BinaryIO, path: str, size: int, name: str): 118 | print(f"/================== {name} ==================\\") 119 | print(f" Offset: 0x{export_fp.tell():X}") 120 | print(f" Size: 0x{size:X} ({size})") 121 | section = read_section(export_fp, size) 122 | print(f" CMAC: {section.mac.hex().upper()}") 123 | print(f" IV: {section.iv.hex().upper()}") 124 | section_decrypted = decrypt_and_verify_section(section) 125 | print(f"\\================== {name} ==================/\n") 126 | 127 | with open(path, "wb") as f: 128 | f.write(section_decrypted) 129 | 130 | return section_decrypted 131 | 132 | def parse_export(path: str, movable_path: str, export_type_int: int, allow_default_export_fallback: bool = False): 133 | if export_type_int < 0 or export_type_int > 0xFF: 134 | raise Exception("Export types must be within 0x00-0xFF.") 135 | 136 | must_use_default = export_type_int < 7 or export_type_int > 0xD 137 | 138 | if must_use_default and not allow_default_export_fallback: 139 | raise Exception(f"Invalid export type {export_type_int:02X}, cannot fall back to default") 140 | 141 | if must_use_default: 142 | export_type = TWLExportType.Default 143 | else: 144 | for type in TWLExportType: 145 | if type.value == export_type_int: 146 | export_type = type 147 | break 148 | 149 | info = get_export_info(export_type.value) 150 | header_size = info[0] 151 | footer_size = info[1] 152 | sections_count = info[2] 153 | 154 | if not os.path.isfile(path): 155 | raise FileNotFoundError(path) 156 | 157 | if os.path.getsize(path) < header_size + 0x20 + footer_size + 0x20 + BANNER_SIZE: 158 | raise Exception("Input file is too small.") 159 | 160 | with open(movable_path, "rb") as f: 161 | f.seek(0x110) 162 | keyy = f.read(0x10) 163 | 164 | crypt = CryptoEngine() 165 | 166 | crypt.set_keyslot("y", 0x34, keyy) # crypt key 167 | crypt.set_keyslot("y", 0x3A, keyy) # cmac key 168 | 169 | global DECRYPT_KEY 170 | global CMAC_KEY 171 | 172 | DECRYPT_KEY = crypt.key_normal[0x34] 173 | CMAC_KEY = crypt.key_normal[0x3A] 174 | 175 | with open(path, "rb") as export_fp: 176 | decrypt_verify_section_to_file(export_fp, "banner.bin", BANNER_SIZE, "banner") 177 | header = decrypt_verify_section_to_file(export_fp, "header.bin", header_size, "header") 178 | decrypt_verify_section_to_file(export_fp, "footer.bin", footer_size, "footer") 179 | hdr = ExportHeader(header, sections_count) 180 | for idx, payload_size in enumerate(hdr.payload_sizes): 181 | if payload_size == 0: 182 | continue 183 | 184 | decrypt_verify_section_to_file(export_fp, f"contentsection_{idx}.bin", payload_size, f"Content section {idx}") 185 | if hdr.private_save_size != None and hdr.private_save_size != 0: 186 | decrypt_verify_section_to_file(export_fp, f"contentsection_privatesav.bin", hdr.private_save_size, "Private save data") 187 | 188 | print(hdr) 189 | print(f"Resolved export type : {export_type.name} ({export_type.value})") 190 | print(f"Header size : 0x{header_size:X}") 191 | print(f"Footer size : 0x{footer_size:X}") 192 | print(f"Section count : {sections_count}") 193 | try: 194 | rawint = int(sys.argv[3], base=16) 195 | except Exception as e: 196 | sys.exit("Invalid integer") 197 | 198 | parse_export(sys.argv[1], sys.argv[2], rawint, allow_default_export_fallback=True) -------------------------------------------------------------------------------- /source/am/demodb.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | __attribute__((section(".data.demodb"), aligned(4))) Database g_DemoDatabase; 4 | 5 | static bool findIndex(Database *db, u64 title_id, u16 *index) 6 | { 7 | title_id &= 0x0000FFFFFFFFFFFF; // remove console type 8 | 9 | if (!db->info.entry_count) // no entries 10 | return false; 11 | 12 | u32 remain = sizeof(u64) * db->info.entry_count; 13 | u32 offset = sizeof(DatabaseHeader); 14 | bool found = false; 15 | u16 tid_index = 0; 16 | u32 read; 17 | 18 | while (remain) 19 | { 20 | u32 batch = MIN(sizeof(db->tid_buf), remain); 21 | 22 | Err_FailedThrow(FSFile_Read(&read, db->db_file, offset, batch, &db->tid_buf)) 23 | 24 | for (u32 i = 0; i < batch / sizeof(u64); i++, tid_index++) 25 | { 26 | if (db->tid_buf[i] == title_id) 27 | { 28 | *index = tid_index; 29 | found = true; 30 | goto ret; 31 | } 32 | } 33 | 34 | offset += batch; 35 | remain -= batch; 36 | } 37 | 38 | ret: 39 | return found; // stock am returns a Result (AM_NOT_FOUND) if index isn't found but who cares 40 | } 41 | 42 | static u8 readPlayCount(Database *db, u16 index) 43 | { 44 | DatabaseEntry entry; 45 | u32 read; 46 | 47 | Err_FailedThrow(FSFile_Read(&read, db->db_file, sizeof(DatabaseHeader) + (sizeof(u64) * SAVE_MAX_ENTRIES) + (sizeof(DatabaseEntry) * index), sizeof(DatabaseEntry), &entry)) 48 | 49 | return entry.play_count; 50 | } 51 | 52 | static void writePlayCount(Database *db, u16 index, u8 count) 53 | { 54 | DatabaseEntry entry; 55 | u32 written; 56 | 57 | entry.play_count = count; 58 | 59 | Err_FailedThrow(FSFile_Write(&written, db->db_file, sizeof(DatabaseHeader) + (sizeof(u64) * SAVE_MAX_ENTRIES) + (sizeof(DatabaseEntry) * index), sizeof(DatabaseEntry), FS_WRITE_FLUSH, &entry)) 60 | } 61 | 62 | static void writeTitleId(Database *db, u16 index, u64 title_id) 63 | { 64 | title_id &= 0x0000FFFFFFFFFFFF; // remove console type 65 | u32 written; 66 | 67 | Err_FailedThrow(FSFile_Write(&written,db->db_file, sizeof(DatabaseHeader) + (sizeof(u64) * index), sizeof(u64), FS_WRITE_FLUSH, &title_id)) 68 | } 69 | 70 | void AM_DemoDatabase_Initialize(Database *db) 71 | { 72 | _memset32_aligned(db, 0, sizeof(Database)); 73 | 74 | RecursiveLock_Init(&db->lock); 75 | 76 | FS_SystemSaveDataInfo save_info = { MediaType_NAND, { 0, 0, 0 }, SAVE_ID }; 77 | bool save_created_new = false; 78 | Result res; 79 | 80 | res = FSUser_OpenArchive(ARCHIVE_SYSTEM_SAVEDATA, PATH_BINARY, &save_info, sizeof(save_info), &db->save_archive); 81 | 82 | // perhaps archive doesn't exist yet 83 | if (R_FAILED(res)) 84 | { 85 | // if it's not a normal not found error, delete archive 86 | if (!R_MODULEDESCRANGE(res, RM_FS, 100, 179)) 87 | FSUser_DeleteSystemSaveData(&save_info); 88 | 89 | // can now recreate archive 90 | Err_FailedThrow(FSUser_CreateSystemSaveData(&save_info, SAVE_ARCHIVE_SIZE, 0x1000, 10, 10, getBucketCount(10), getBucketCount(10), true)); 91 | 92 | // if still fail, something is seriously fucked 93 | Err_FailedThrow(FSUser_OpenArchive(ARCHIVE_SYSTEM_SAVEDATA, PATH_BINARY, &save_info, sizeof(save_info), &db->save_archive)) 94 | } 95 | 96 | // let's see if the file exists now 97 | res = FSUser_OpenFile(0, db->save_archive, PATH_UTF16, sizeof(SAVE_FILE_PATH), FS_OPEN_READ | FS_OPEN_WRITE, 0, SAVE_FILE_PATH, &db->db_file); 98 | 99 | // could be simply the fact that it doesn't exist 100 | if (R_FAILED(res)) 101 | { 102 | Err_FailedThrow(FSUser_CreateFile(0, db->save_archive, PATH_UTF16, sizeof(SAVE_FILE_PATH), 0, SAVE_FILE_SIZE, SAVE_FILE_PATH)) 103 | Err_FailedThrow(FSUser_OpenFile(0, db->save_archive, PATH_UTF16, sizeof(SAVE_FILE_PATH), FS_OPEN_READ | FS_OPEN_WRITE, 0, SAVE_FILE_PATH, &db->db_file)) 104 | 105 | save_created_new = true; 106 | } 107 | 108 | // need to fill file with zeros else hash errors on next read 109 | if (save_created_new) 110 | { 111 | u32 written; 112 | Err_FailedThrow(FSFile_Write(&written, db->db_file, 0, 8, FS_WRITE_FLUSH, db->tid_buf)) 113 | 114 | for (u32 i = 0; i < (SAVE_FILE_SIZE - 8) / sizeof(db->tid_buf); i += sizeof(db->tid_buf)) 115 | Err_FailedThrow(FSFile_Write(&written, db->db_file, i + sizeof(DatabaseHeader), sizeof(db->tid_buf), FS_WRITE_FLUSH, db->tid_buf)) 116 | 117 | AM_DemoDatabase_CommitSaveData(db); 118 | return; 119 | } 120 | 121 | u32 read; 122 | 123 | // read header, since if code reaches here we have an existing file with the correct size 124 | Err_FailedThrow(FSFile_Read(&read, db->db_file, 0, sizeof(DatabaseHeader), &db->info)) 125 | } 126 | 127 | void AM_DemoDatabase_WriteHeader(Database *db) 128 | { 129 | u32 written; 130 | Err_FailedThrow(FSFile_Write(&written, db->db_file, 0, sizeof(DatabaseHeader), FS_WRITE_FLUSH, &db->info)) 131 | } 132 | 133 | void AM_DemoDatabase_CommitSaveData(Database *db) 134 | { 135 | u8 input, output; 136 | Err_FailedThrow(FSUser_ControlArchive(db->save_archive, ARCHIVE_ACTION_COMMIT_SAVE_DATA, &input, 1, &output, 1)) 137 | } 138 | 139 | void AM_DemoDatabase_InitializeHeader(Database *db) 140 | { 141 | _memset32_aligned(&db->info, 0x00, sizeof(DatabaseHeader)); // clear everything 142 | AM_DemoDatabase_WriteHeader(db); 143 | AM_DemoDatabase_CommitSaveData(db); 144 | } 145 | 146 | void AM_DemoDatabase_Close(Database *db) 147 | { 148 | AM_DemoDatabase_CommitSaveData(db); 149 | FSFile_Close(db->db_file); 150 | FSUser_CloseArchive(db->save_archive); 151 | } 152 | 153 | void AM_DemoDatabase_GetLaunchInfos(Database *db, u64 *title_ids, u32 count, DemoLaunchInfo *infos) 154 | { 155 | RecursiveLock_Lock(&db->lock); 156 | 157 | _memset32_aligned(infos, 0x00, sizeof(DemoLaunchInfo) * count); 158 | 159 | u32 remain = sizeof(u64) * db->info.entry_count; 160 | u32 offset = sizeof(DatabaseHeader); 161 | u32 read; 162 | 163 | u32 processed = 0; 164 | 165 | while (remain) 166 | { 167 | u32 batch = MIN(sizeof(db->tid_buf), remain); 168 | u32 batch_tids_count = batch / sizeof(u64); 169 | 170 | Err_FailedThrow(FSFile_Read(&read, db->db_file, offset, batch, &db->tid_buf)) 171 | 172 | for (u32 i = 0; i < batch_tids_count; i++) 173 | for (u32 j = 0; j < count; j++) 174 | if (db->tid_buf[i] == (title_ids[j] & 0x0000FFFFFFFFFFFF)) 175 | { 176 | infos[j].flags |= 8; 177 | infos[j].playcount = readPlayCount(db, processed + i); 178 | } 179 | 180 | processed += batch_tids_count; 181 | 182 | offset += batch; 183 | remain -= batch; 184 | } 185 | 186 | RecursiveLock_Unlock(&db->lock); 187 | } 188 | 189 | bool AM_DemoDatabase_HasDemoLaunchRight(Database *db, u64 title_id) 190 | { 191 | RecursiveLock_Lock(&db->lock); 192 | 193 | TicketLimitInfo ticket_limit_info; 194 | DemoLaunchInfo demo_launch_info; 195 | 196 | if (R_FAILED(AM9_GetTicketLimitInfos(1, &title_id, &ticket_limit_info))) 197 | { 198 | RecursiveLock_Unlock(&db->lock); 199 | return false; 200 | } 201 | 202 | if (ticket_limit_info.flags) 203 | { 204 | AM_DemoDatabase_GetLaunchInfos(db, &title_id, 1, &demo_launch_info); 205 | 206 | if (demo_launch_info.flags & 0x8 && demo_launch_info.playcount >= ticket_limit_info.playcount) 207 | { 208 | RecursiveLock_Unlock(&db->lock); 209 | return false; 210 | } 211 | } 212 | 213 | // at this point we have the launch right 214 | 215 | u16 index; 216 | 217 | // title id not in db 218 | if (!findIndex(db, title_id, &index)) 219 | { 220 | writeTitleId(db, db->info.next_entry_index, title_id); 221 | writePlayCount(db, db->info.next_entry_index, 1); 222 | 223 | if (db->info.entry_count < 4096) 224 | db->info.entry_count++; 225 | 226 | // if we are at the last index (0xFFF or 4095), we overlap back to the 0th index) 227 | db->info.next_entry_index = db->info.next_entry_index == 4095 ? 0 : db->info.next_entry_index + 1; 228 | 229 | AM_DemoDatabase_WriteHeader(db); 230 | } 231 | else 232 | { 233 | // tid in db, go do stuff 234 | u8 playcount = readPlayCount(db, index); 235 | playcount++; 236 | writePlayCount(db, index, playcount); 237 | } 238 | 239 | AM_DemoDatabase_CommitSaveData(db); 240 | 241 | RecursiveLock_Unlock(&db->lock); 242 | return true; 243 | } -------------------------------------------------------------------------------- /source/sha256.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #define TOTAL_LEN_LEN 8 4 | 5 | /* 6 | * Comments from pseudo-code at https://en.wikipedia.org/wiki/SHA-2 are reproduced here. 7 | * When useful for clarification, portions of the pseudo-code are reproduced here too. 8 | */ 9 | 10 | /* 11 | * @brief Rotate a 32-bit value by a number of bits to the right. 12 | * @param value The value to be rotated. 13 | * @param count The number of bits to rotate by. 14 | * @return The rotated value. 15 | */ 16 | static inline uint32_t right_rot(uint32_t value, unsigned int count) 17 | { 18 | /* 19 | * Defined behaviour in standard C for all count where 0 < count < 32, which is what we need here. 20 | */ 21 | return value >> count | value << (32 - count); 22 | } 23 | 24 | /* 25 | * @brief Update a hash value under calculation with a new chunk of data. 26 | * @param h Pointer to the first hash item, of a total of eight. 27 | * @param p Pointer to the chunk data, which has a standard length. 28 | * 29 | * @note This is the SHA-256 work horse. 30 | */ 31 | static inline void consume_chunk(uint32_t *h, const uint8_t *p) 32 | { 33 | unsigned i, j; 34 | uint32_t ah[8]; 35 | 36 | /* Initialize working variables to current hash value: */ 37 | for (i = 0; i < 8; i++) 38 | ah[i] = h[i]; 39 | 40 | /* 41 | * The w-array is really w[64], but since we only need 16 of them at a time, we save stack by 42 | * calculating 16 at a time. 43 | * 44 | * This optimization was not there initially and the rest of the comments about w[64] are kept in their 45 | * initial state. 46 | */ 47 | 48 | /* 49 | * create a 64-entry message schedule array w[0..63] of 32-bit words (The initial values in w[0..63] 50 | * don't matter, so many implementations zero them here) copy chunk into first 16 words w[0..15] of the 51 | * message schedule array 52 | */ 53 | uint32_t w[16]; 54 | 55 | /* Compression function main loop: */ 56 | for (i = 0; i < 4; i++) { 57 | for (j = 0; j < 16; j++) { 58 | if (i == 0) { 59 | w[j] = 60 | (uint32_t)p[0] << 24 | (uint32_t)p[1] << 16 | (uint32_t)p[2] << 8 | (uint32_t)p[3]; 61 | p += 4; 62 | } else { 63 | /* Extend the first 16 words into the remaining 48 words w[16..63] of the 64 | * message schedule array: */ 65 | const uint32_t s0 = right_rot(w[(j + 1) & 0xf], 7) ^ right_rot(w[(j + 1) & 0xf], 18) ^ 66 | (w[(j + 1) & 0xf] >> 3); 67 | const uint32_t s1 = right_rot(w[(j + 14) & 0xf], 17) ^ 68 | right_rot(w[(j + 14) & 0xf], 19) ^ (w[(j + 14) & 0xf] >> 10); 69 | w[j] = w[j] + s0 + w[(j + 9) & 0xf] + s1; 70 | } 71 | const uint32_t s1 = right_rot(ah[4], 6) ^ right_rot(ah[4], 11) ^ right_rot(ah[4], 25); 72 | const uint32_t ch = (ah[4] & ah[5]) ^ (~ah[4] & ah[6]); 73 | 74 | /* 75 | * Initialize array of round constants: 76 | * (first 32 bits of the fractional parts of the cube roots of the first 64 primes 2..311): 77 | */ 78 | static const uint32_t k[] = { 79 | 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 80 | 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 81 | 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 82 | 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 83 | 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 84 | 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b, 85 | 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, 0x19a4c116, 86 | 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, 87 | 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 88 | 0xc67178f2}; 89 | 90 | const uint32_t temp1 = ah[7] + s1 + ch + k[i << 4 | j] + w[j]; 91 | const uint32_t s0 = right_rot(ah[0], 2) ^ right_rot(ah[0], 13) ^ right_rot(ah[0], 22); 92 | const uint32_t maj = (ah[0] & ah[1]) ^ (ah[0] & ah[2]) ^ (ah[1] & ah[2]); 93 | const uint32_t temp2 = s0 + maj; 94 | 95 | ah[7] = ah[6]; 96 | ah[6] = ah[5]; 97 | ah[5] = ah[4]; 98 | ah[4] = ah[3] + temp1; 99 | ah[3] = ah[2]; 100 | ah[2] = ah[1]; 101 | ah[1] = ah[0]; 102 | ah[0] = temp1 + temp2; 103 | } 104 | } 105 | 106 | /* Add the compressed chunk to the current hash value: */ 107 | for (i = 0; i < 8; i++) 108 | h[i] += ah[i]; 109 | } 110 | 111 | /* 112 | * Public functions. See header file for documentation. 113 | */ 114 | 115 | void sha_256_init(struct SHA256 *sha_256, uint8_t hash[SIZE_OF_SHA_256_HASH]) 116 | { 117 | sha_256->hash = hash; 118 | sha_256->chunk_pos = sha_256->chunk; 119 | sha_256->space_left = SIZE_OF_SHA_256_CHUNK; 120 | sha_256->total_len = 0; 121 | /* 122 | * Initialize hash values (first 32 bits of the fractional parts of the square roots of the first 8 primes 123 | * 2..19): 124 | */ 125 | sha_256->h[0] = 0x6a09e667; 126 | sha_256->h[1] = 0xbb67ae85; 127 | sha_256->h[2] = 0x3c6ef372; 128 | sha_256->h[3] = 0xa54ff53a; 129 | sha_256->h[4] = 0x510e527f; 130 | sha_256->h[5] = 0x9b05688c; 131 | sha_256->h[6] = 0x1f83d9ab; 132 | sha_256->h[7] = 0x5be0cd19; 133 | } 134 | 135 | void sha_256_write(struct SHA256 *sha_256, const void *data, size_t len) 136 | { 137 | sha_256->total_len += len; 138 | 139 | const uint8_t *p = data; 140 | 141 | while (len > 0) { 142 | /* 143 | * If the input chunks have sizes that are multiples of the calculation chunk size, no copies are 144 | * necessary. We operate directly on the input data instead. 145 | */ 146 | if (sha_256->space_left == SIZE_OF_SHA_256_CHUNK && len >= SIZE_OF_SHA_256_CHUNK) { 147 | consume_chunk(sha_256->h, p); 148 | len -= SIZE_OF_SHA_256_CHUNK; 149 | p += SIZE_OF_SHA_256_CHUNK; 150 | continue; 151 | } 152 | /* General case, no particular optimization. */ 153 | const size_t consumed_len = len < sha_256->space_left ? len : sha_256->space_left; 154 | _memcpy(sha_256->chunk_pos, p, consumed_len); 155 | sha_256->space_left -= consumed_len; 156 | len -= consumed_len; 157 | p += consumed_len; 158 | if (sha_256->space_left == 0) { 159 | consume_chunk(sha_256->h, sha_256->chunk); 160 | sha_256->chunk_pos = sha_256->chunk; 161 | sha_256->space_left = SIZE_OF_SHA_256_CHUNK; 162 | } else { 163 | sha_256->chunk_pos += consumed_len; 164 | } 165 | } 166 | } 167 | 168 | uint8_t *sha_256_close(struct SHA256 *sha_256) 169 | { 170 | uint8_t *pos = sha_256->chunk_pos; 171 | size_t space_left = sha_256->space_left; 172 | uint32_t *const h = sha_256->h; 173 | 174 | /* 175 | * The current chunk cannot be full. Otherwise, it would already have be consumed. I.e. there is space left for 176 | * at least one byte. The next step in the calculation is to add a single one-bit to the data. 177 | */ 178 | *pos++ = 0x80; 179 | --space_left; 180 | 181 | /* 182 | * Now, the last step is to add the total data length at the end of the last chunk, and zero padding before 183 | * that. But we do not necessarily have enough space left. If not, we pad the current chunk with zeroes, and add 184 | * an extra chunk at the end. 185 | */ 186 | if (space_left < TOTAL_LEN_LEN) { 187 | _memset(pos, 0x00, space_left); 188 | consume_chunk(h, sha_256->chunk); 189 | pos = sha_256->chunk; 190 | space_left = SIZE_OF_SHA_256_CHUNK; 191 | } 192 | const size_t left = space_left - TOTAL_LEN_LEN; 193 | _memset(pos, 0x00, left); 194 | pos += left; 195 | size_t len = sha_256->total_len; 196 | pos[7] = (uint8_t)(len << 3); 197 | len >>= 5; 198 | int i; 199 | for (i = 6; i >= 0; --i) { 200 | pos[i] = (uint8_t)len; 201 | len >>= 8; 202 | } 203 | consume_chunk(h, sha_256->chunk); 204 | /* Produce the final hash value (big-endian): */ 205 | int j; 206 | uint8_t *const hash = sha_256->hash; 207 | for (i = 0, j = 0; i < 8; i++) { 208 | hash[j++] = (uint8_t)(h[i] >> 24); 209 | hash[j++] = (uint8_t)(h[i] >> 16); 210 | hash[j++] = (uint8_t)(h[i] >> 8); 211 | hash[j++] = (uint8_t)h[i]; 212 | } 213 | return sha_256->hash; 214 | } 215 | 216 | void calc_sha_256(uint8_t hash[SIZE_OF_SHA_256_HASH], const void *input, size_t len) 217 | { 218 | struct SHA256 sha_256; 219 | sha_256_init(&sha_256, hash); 220 | sha_256_write(&sha_256, input, len); 221 | (void)sha_256_close(&sha_256); 222 | } 223 | 224 | inline int cmp_hash(void *hash1, void *hash2) 225 | { 226 | uint32_t *one = (uint32_t *)hash1; 227 | uint32_t *two = (uint32_t *)hash2; 228 | return 229 | one[0] == two[0] && 230 | one[1] == two[1] && 231 | one[2] == two[2] && 232 | one[3] == two[3] && 233 | one[4] == two[4] && 234 | one[5] == two[5] && 235 | one[6] == two[6] && 236 | one[7] == two[7]; 237 | } -------------------------------------------------------------------------------- /source/3ds/fs.c: -------------------------------------------------------------------------------- 1 | #include <3ds/ipc.h> 2 | #include <3ds/fs.h> 3 | 4 | Handle fsuser_session; 5 | bool fsuser_init; 6 | 7 | static const char fs_srvname[8] = "fs:USER"; 8 | 9 | Result fsUserInit() 10 | { 11 | if (!fsuser_init) 12 | { 13 | Result res = SRV_GetServiceHandle(&fsuser_session, fs_srvname, 7, 0); 14 | 15 | if (R_SUCCEEDED(res)) 16 | fsuser_init = true; 17 | 18 | Err_FailedThrow(FSUser_InitializeWithSDKVersion(0xB0502C8)) 19 | 20 | return res; 21 | } 22 | 23 | return 0; 24 | } 25 | 26 | void fsUserExit() 27 | { 28 | if (fsuser_init) 29 | { 30 | Err_FailedThrow(svcCloseHandle(fsuser_session)) 31 | fsuser_session = 0; 32 | fsuser_init = false; 33 | } 34 | } 35 | 36 | 37 | u32 getBucketCount(u32 count) 38 | { 39 | if (count < 20) 40 | return (count < 4) ? 3 : count | 1; 41 | 42 | for (int i = 0; i < 100; ++i) 43 | { 44 | u32 ret = count + i; 45 | 46 | if (ret & 1 && ret % 3 && ret % 5 && ret % 7 && ret % 11 && ret % 13 && ret % 17) 47 | return ret; 48 | } 49 | 50 | return count | 1; 51 | } 52 | 53 | // fs 54 | 55 | Result FSUser_CreateSystemSaveData(FS_SystemSaveDataInfo *save_info, u32 total_size, u32 block_size, 56 | u32 dircount, u32 file_count, u32 dir_bucket_count, 57 | u32 file_bucket_count, bool duplicate_data) 58 | { 59 | u32 *ipc_command = getThreadLocalStorage()->ipc_command; 60 | 61 | ipc_command[0] = IPC_MakeHeader(ID_FSUser_CreateSystemSaveData, 9, 0); 62 | _memcpy32_aligned(&ipc_command[1], save_info, sizeof(FS_SystemSaveDataInfo)); 63 | ipc_command[3] = total_size; 64 | ipc_command[4] = block_size; 65 | ipc_command[5] = dircount; 66 | ipc_command[6] = file_count; 67 | ipc_command[7] = dir_bucket_count; 68 | ipc_command[8] = file_bucket_count; 69 | ipc_command[9] = (u32)duplicate_data; 70 | 71 | BASIC_RET(fsuser_session) 72 | } 73 | 74 | Result FSUser_DeleteSystemSaveData(FS_SystemSaveDataInfo *save_info) 75 | { 76 | u32 *ipc_command = getThreadLocalStorage()->ipc_command; 77 | 78 | ipc_command[0] = IPC_MakeHeader(ID_FSUser_DeleteSystemSaveData, 2, 0); 79 | 80 | ipc_command[1] = LODWORD(*((u64 *)save_info)); 81 | ipc_command[2] = HIDWORD(*((u64 *)save_info)); 82 | 83 | BASIC_RET(fsuser_session) 84 | } 85 | 86 | // fsuser 87 | 88 | Result FSUser_OpenFile(u32 transaction, FS_Archive archive, FS_PathType path_type, u32 path_size, 89 | u32 open_flags, u32 attributes, void *path_data, Handle *file) 90 | { 91 | u32 *ipc_command = getThreadLocalStorage()->ipc_command; 92 | 93 | ipc_command[0] = IPC_MakeHeader(ID_FSUser_OpenFile, 7, 2); 94 | ipc_command[1] = transaction; 95 | ipc_command[2] = LODWORD(archive); 96 | ipc_command[3] = HIDWORD(archive); 97 | ipc_command[4] = path_type; 98 | ipc_command[5] = path_size; 99 | ipc_command[6] = open_flags; 100 | ipc_command[7] = attributes; 101 | ipc_command[8] = IPC_Desc_StaticBuffer(path_size, 0); 102 | ipc_command[9] = (u32)path_data; 103 | 104 | CHECK_RET(fsuser_session) 105 | 106 | // ipc_command[2] = 0x10, move handles 107 | if (file) *file = ipc_command[3]; 108 | 109 | return res; 110 | } 111 | 112 | Result FSUser_DeleteFile(u32 transaction, FS_Archive archive, FS_Archive path_type, u32 path_size, void *path_data) 113 | { 114 | u32 *ipc_command = getThreadLocalStorage()->ipc_command; 115 | 116 | ipc_command[0] = IPC_MakeHeader(ID_FSUser_DeleteFile, 5, 2); 117 | ipc_command[1] = transaction; 118 | ipc_command[2] = LODWORD(archive); 119 | ipc_command[3] = HIDWORD(archive); 120 | ipc_command[4] = path_type; 121 | ipc_command[5] = path_size; 122 | ipc_command[6] = IPC_Desc_Buffer(path_size, IPC_BUFFER_R); 123 | ipc_command[7] = (u32)path_data; 124 | 125 | BASIC_RET(fsuser_session) 126 | } 127 | 128 | Result FSUser_CreateFile(u32 transaction, FS_Archive archive, FS_PathType path_type, u32 path_size, 129 | u32 attributes, u64 zero_bytes_count, void *path_data) 130 | { 131 | u32 *ipc_command = getThreadLocalStorage()->ipc_command; 132 | 133 | ipc_command[0] = IPC_MakeHeader(ID_FSUser_CreateFile, 8, 2); 134 | ipc_command[1] = transaction; 135 | ipc_command[2] = LODWORD(archive); 136 | ipc_command[3] = HIDWORD(archive); 137 | ipc_command[4] = path_type; 138 | ipc_command[5] = path_size; 139 | ipc_command[6] = attributes; 140 | ipc_command[7] = LODWORD(zero_bytes_count); 141 | ipc_command[8] = HIDWORD(zero_bytes_count); 142 | ipc_command[9] = IPC_Desc_StaticBuffer(path_size, 0); 143 | ipc_command[10] = (u32)path_data; 144 | 145 | BASIC_RET(fsuser_session) 146 | } 147 | 148 | Result FSUser_OpenArchive(FS_ArchiveID archive_id, FS_PathType path_type, void *path_data, 149 | u32 path_size, FS_Archive *archive) 150 | { 151 | u32 *ipc_command = getThreadLocalStorage()->ipc_command; 152 | 153 | ipc_command[0] = IPC_MakeHeader(ID_FSUser_OpenArchive, 3, 2); 154 | ipc_command[1] = archive_id; 155 | ipc_command[2] = path_type; 156 | ipc_command[3] = path_size; 157 | ipc_command[4] = IPC_Desc_StaticBuffer(path_size, 0); 158 | ipc_command[5] = (u32)path_data; 159 | 160 | CHECK_RET(fsuser_session) 161 | 162 | if (archive) *archive = *((u64 *)&ipc_command[2]); 163 | 164 | return res; 165 | } 166 | 167 | Result FSUser_ControlArchive(FS_Archive archive, FS_ArchiveAction action, void *input, u32 input_size, 168 | void *output, u32 output_size) 169 | { 170 | u32 *ipc_command = getThreadLocalStorage()->ipc_command; 171 | 172 | ipc_command[0] = IPC_MakeHeader(ID_FSUser_ControlArchive, 5, 4); 173 | ipc_command[1] = LODWORD(archive); 174 | ipc_command[2] = HIDWORD(archive); 175 | ipc_command[3] = (u32)action; 176 | ipc_command[4] = input_size; 177 | ipc_command[5] = output_size; 178 | ipc_command[6] = IPC_Desc_Buffer(input_size, IPC_BUFFER_R); 179 | ipc_command[7] = (u32)input; 180 | ipc_command[8] = IPC_Desc_Buffer(output_size, IPC_BUFFER_W); 181 | ipc_command[9] = (u32)output; 182 | 183 | BASIC_RET(fsuser_session) 184 | } 185 | 186 | Result FSUser_CloseArchive(FS_Archive archive) 187 | { 188 | u32 *ipc_command = getThreadLocalStorage()->ipc_command; 189 | 190 | ipc_command[0] = IPC_MakeHeader(ID_FSUser_CloseArchive, 2, 0); 191 | ipc_command[1] = LODWORD(archive); 192 | ipc_command[2] = HIDWORD(archive); 193 | 194 | BASIC_RET(fsuser_session) 195 | } 196 | 197 | Result FSUser_InitializeWithSDKVersion(u32 version) 198 | { 199 | u32 *ipc_command = getThreadLocalStorage()->ipc_command; 200 | 201 | ipc_command[0] = IPC_MakeHeader(ID_FSUser_InitializeWithSDKVersion, 1, 2); 202 | ipc_command[1] = version; 203 | ipc_command[2] = 0x20; // process ID header 204 | 205 | BASIC_RET(fsuser_session) 206 | } 207 | 208 | // fsfile 209 | 210 | Result FSFile_OpenSubFile(Handle file, Handle *sub_file, u64 offset, u64 size) 211 | { 212 | u32 *ipc_command = getThreadLocalStorage()->ipc_command; 213 | 214 | ipc_command[0] = IPC_MakeHeader(ID_FSFile_OpenSubFile, 4, 0); 215 | ipc_command[1] = LODWORD(offset); 216 | ipc_command[2] = HIDWORD(offset); 217 | ipc_command[3] = LODWORD(size); 218 | ipc_command[4] = HIDWORD(size); 219 | 220 | CHECK_RET(file) 221 | 222 | if (sub_file) *sub_file = ipc_command[3]; 223 | 224 | return res; 225 | } 226 | 227 | Result FSFile_Read(u32 *read, Handle file, u64 offset, u32 data_size, void *data) 228 | { 229 | u32 *ipc_command = getThreadLocalStorage()->ipc_command; 230 | 231 | ipc_command[0] = IPC_MakeHeader(ID_FSFile_Read, 3, 2); 232 | ipc_command[1] = LODWORD(offset); 233 | ipc_command[2] = HIDWORD(offset); 234 | ipc_command[3] = data_size; 235 | ipc_command[4] = IPC_Desc_Buffer(data_size, IPC_BUFFER_W); 236 | ipc_command[5] = (u32)data; 237 | 238 | CHECK_RET(file) 239 | 240 | if (read) *read = ipc_command[2]; 241 | 242 | return res; 243 | } 244 | 245 | 246 | Result FSFile_Write(u32 *written, Handle file, u64 offset, u32 size, u32 write_option, void *data) 247 | { 248 | u32 *ipc_command = getThreadLocalStorage()->ipc_command; 249 | 250 | ipc_command[0] = IPC_MakeHeader(ID_FSFile_Write, 4, 2); 251 | ipc_command[1] = LODWORD(offset); 252 | ipc_command[2] = HIDWORD(offset); 253 | ipc_command[3] = size; 254 | ipc_command[4] = write_option; 255 | ipc_command[5] = IPC_Desc_Buffer(size, IPC_BUFFER_R); 256 | ipc_command[6] = (u32)data; 257 | 258 | CHECK_RET(file) 259 | 260 | if (written) *written = ipc_command[2]; 261 | 262 | return res; 263 | } 264 | 265 | Result FSFile_GetSize(u64 *size, Handle file) 266 | { 267 | u32 *ipc_command = getThreadLocalStorage()->ipc_command; 268 | 269 | ipc_command[0] = IPC_MakeHeader(ID_FSFile_GetSize, 0, 0); 270 | 271 | CHECK_RET(file) 272 | 273 | if (size) *size = *((u64 *)&ipc_command[2]); 274 | 275 | return res; 276 | } 277 | 278 | Result FSFile_Close(Handle file) 279 | { 280 | u32 *ipc_command = getThreadLocalStorage()->ipc_command; 281 | 282 | ipc_command[0] = IPC_MakeHeader(ID_FSFile_Close, 0, 0); 283 | 284 | CHECK_RET(file) 285 | 286 | svcCloseHandle(file); 287 | 288 | return res; 289 | } -------------------------------------------------------------------------------- /source/main.c: -------------------------------------------------------------------------------- 1 | #include <3ds/synchronization.h> 2 | #include 3 | #include <3ds/result.h> 4 | #include 5 | #include 6 | #include <3ds/types.h> 7 | #include <3ds/svc.h> 8 | #include <3ds/srv.h> 9 | #include 10 | #include <3ds/am9.h> 11 | #include 12 | #include 13 | #include <3ds/fs.h> 14 | #include 15 | #include 16 | 17 | #define countof(arr) (sizeof(arr) / sizeof(arr[0])) 18 | 19 | // service constants 20 | 21 | #define AM_SERVICE_COUNT 4 22 | #define AM_MAX_SESSIONS_PER_SERVICE 5 23 | #define AM_MAX_TOTAL_SESSIONS 5 24 | 25 | // names 26 | 27 | #ifdef REPLACE_AM 28 | #define SERVICE_NAME "am" 29 | #else 30 | // can't register `am` if we're not replacing am 31 | #define SERVICE_NAME "amf" 32 | #endif 33 | 34 | static const struct 35 | { 36 | const char *name; 37 | u32 len; 38 | } AM_ServiceNames[AM_SERVICE_COUNT] = 39 | { 40 | { .name = SERVICE_NAME ":net", .len = sizeof(SERVICE_NAME ":net" ) - 1 }, 41 | { .name = SERVICE_NAME ":sys", .len = sizeof(SERVICE_NAME ":sys" ) - 1 }, 42 | { .name = SERVICE_NAME ":u" , .len = sizeof(SERVICE_NAME ":u" ) - 1 }, 43 | { .name = SERVICE_NAME ":app", .len = sizeof(SERVICE_NAME ":app" ) - 1 } 44 | }; 45 | 46 | const u32 heap_size = 0x8000; // stock am allocates 0xC000, stacks in this case are in .data so we can use less 47 | 48 | __attribute__((section(".data.heap"))) 49 | void *heap = NULL; 50 | 51 | __attribute__((section(".data.notif_id"))) 52 | u32 notification_id = 0; 53 | 54 | __attribute__((section(".data.sys_update_mutex"))) 55 | Handle g_SystemUpdaterMutex; 56 | 57 | // thread stacks for non-pipe threads (5), and one pipe thread (1) 58 | __attribute__((section(".data.stacks"),aligned(8))) 59 | static u8 AM_SessionThreadStacks[AM_MAX_TOTAL_SESSIONS + 1][0x1000]; 60 | 61 | // session data + thread for non-pipes 62 | __attribute__((section(".data.sessioninfo"))) 63 | static AM_SessionData AM_SessionsData[AM_MAX_TOTAL_SESSIONS + 1] = 64 | { 65 | { .thread = 0, .session = 0, .handle_ipc = NULL, .importing_title = false, .cia_deplist_buf = { 0 }, .media = 0 }, 66 | { .thread = 0, .session = 0, .handle_ipc = NULL, .importing_title = false, .cia_deplist_buf = { 0 }, .media = 0 }, 67 | { .thread = 0, .session = 0, .handle_ipc = NULL, .importing_title = false, .cia_deplist_buf = { 0 }, .media = 0 }, 68 | { .thread = 0, .session = 0, .handle_ipc = NULL, .importing_title = false, .cia_deplist_buf = { 0 }, .media = 0 }, 69 | { .thread = 0, .session = 0, .handle_ipc = NULL, .importing_title = false, .cia_deplist_buf = { 0 }, .media = 0 }, 70 | }; 71 | 72 | // session storage/thread management 73 | 74 | void _thread_start(); 75 | 76 | Result startThread(Handle *thread, void (* function)(void *), void *arg, void *stack_top, s32 priority, s32 processor_id) 77 | { 78 | if ((u32)(stack_top) & (0x8 - 1)) 79 | return OS_MISALIGNED_ADDRESS; 80 | //_thread_start will pop these out 81 | ((u32 *)stack_top)[-1] = (u32)function; 82 | ((u32 *)stack_top)[-2] = (u32)arg; 83 | 84 | return svcCreateThread(thread, _thread_start, function, stack_top, priority, processor_id); 85 | } 86 | 87 | static inline void freeThread(Handle *thread) 88 | { 89 | if (thread && *thread) 90 | { 91 | Err_FailedThrow(svcWaitSynchronization(*thread, -1)) 92 | Err_FailedThrow(svcCloseHandle(*thread)); 93 | *thread = 0; 94 | } 95 | } 96 | 97 | extern void (* AM_IPCHandlers[4])(AM_SessionData *); 98 | 99 | static inline AM_SessionData *getNewSessionData(Handle session, u8 *index, s32 service_index) 100 | { 101 | for (u8 i = 0; i < AM_MAX_TOTAL_SESSIONS + 1; i++) 102 | if (!AM_SessionsData[i].session) 103 | { 104 | freeThread(&AM_SessionsData[i].thread); 105 | AM_SessionsData[i].session = session; 106 | AM_SessionsData[i].handle_ipc = AM_IPCHandlers[service_index]; 107 | AM_SessionsData[i].importing_title = false; 108 | *index = i; 109 | return &AM_SessionsData[i]; 110 | } 111 | 112 | return NULL; 113 | } 114 | 115 | // regular service thread entrypoint 116 | 117 | void tmain(void *arg) 118 | { 119 | AM_SessionData *data = (AM_SessionData *)arg; 120 | Result res; 121 | s32 index; 122 | 123 | getThreadLocalStorage()->ipc_command[0] = 0xFFFF0000; 124 | 125 | while (true) 126 | { 127 | res = svcReplyAndReceive(&index, &data->session, 1, data->session); 128 | 129 | if (R_FAILED(res)) 130 | { 131 | if (res != OS_REMOTE_SESSION_CLOSED) 132 | Err_Panic(res) 133 | 134 | break; 135 | } 136 | else if (index != 0) 137 | Err_Panic(OS_EXCEEDED_HANDLES_INDEX) 138 | 139 | data->handle_ipc(data); 140 | } 141 | 142 | Err_FailedThrow(svcCloseHandle(data->session)) 143 | data->session = 0; 144 | } 145 | 146 | // pipe thread entry point 147 | 148 | void pipe_tmain() 149 | { 150 | DEBUG_PRINT("[pipe thread] hello, pipe thread has started\n"); 151 | DEBUG_PRINT("[pipe thread] signaling event\n"); 152 | 153 | LightEvent_Signal(&g_PipeManager.event); 154 | 155 | DEBUG_PRINT("[pipe thread] event signaled\n"); 156 | 157 | // zero-out static buffers so they can't be used 158 | 159 | u32 *static_bufs = getThreadLocalStorage()->ipc_static_buffers; 160 | 161 | static_bufs[0] = IPC_Desc_StaticBuffer(0, 0); 162 | static_bufs[1] = (u32)NULL; 163 | static_bufs[2] = IPC_Desc_StaticBuffer(0, 2); 164 | static_bufs[3] = (u32)NULL; 165 | static_bufs[4] = IPC_Desc_StaticBuffer(0, 4); 166 | static_bufs[5] = (u32)NULL; 167 | 168 | Handle handles[2] = { g_PipeManager.current_session, g_PipeManager.thread_close_event }; 169 | Handle target = 0; 170 | 171 | Result res; 172 | s32 index; 173 | 174 | getThreadLocalStorage()->ipc_command[0] = 0xFFFF0000; 175 | 176 | while (true) 177 | { 178 | res = svcReplyAndReceive(&index, handles, 2, target); 179 | 180 | if (R_FAILED(res)) 181 | { 182 | if (res != OS_REMOTE_SESSION_CLOSED) 183 | Err_Panic(res) 184 | 185 | target = 0; 186 | break; 187 | } 188 | 189 | if (index == 0) 190 | { 191 | #ifdef DEBUG_PRINTS 192 | u32 *ipc_command = getThreadLocalStorage()->ipc_command; 193 | 194 | u32 header_og = ipc_command[0]; 195 | 196 | AM_Pipe_HandleIPC(); 197 | 198 | u32 header_ret = ipc_command[0]; 199 | Result r = ipc_command[1]; 200 | 201 | DEBUG_PRINTF3("[am:pipe] src (", header_og, ") -> (", header_ret, ") replying with result ", r); 202 | #else 203 | AM_Pipe_HandleIPC(); 204 | #endif 205 | } 206 | else if (index == 1) 207 | break; 208 | else 209 | Err_Panic(OS_EXCEEDED_HANDLES_INDEX) 210 | 211 | target = handles[index]; 212 | } 213 | 214 | Err_FailedThrow(svcCloseHandle(g_PipeManager.current_session)) 215 | g_PipeManager.current_session = 0; 216 | 217 | if (g_PipeManager.write) 218 | { 219 | if (g_PipeManager.data) free(g_PipeManager.data); 220 | g_PipeManager.data = NULL; 221 | g_PipeManager.write = NULL; 222 | } 223 | 224 | DEBUG_PRINT("[pipe thread] goodbye\n"); 225 | } 226 | 227 | // BSS and heap 228 | 229 | static inline void initializeBSS() 230 | { 231 | extern void *__bss_start__; 232 | extern void *__bss_end__; 233 | 234 | _memset32_aligned(__bss_start__, 0, (size_t)__bss_end__ - (size_t)__bss_end__); 235 | } 236 | 237 | void initializeHeap(void) 238 | { 239 | Handle reslimit; 240 | 241 | Result res = svcGetResourceLimit(&reslimit, CUR_PROCESS_HANDLE); 242 | 243 | if (R_FAILED(res)) 244 | svcBreak(USERBREAK_PANIC); 245 | 246 | s64 maxCommit = 0, currentCommit = 0; 247 | 248 | ResourceLimitType reslimitType = RESLIMIT_COMMIT; 249 | 250 | svcGetResourceLimitLimitValues(&maxCommit, reslimit, &reslimitType, 1); // for APPLICATION this is equal to APPMEMALLOC at all times 251 | svcGetResourceLimitCurrentValues(¤tCommit, reslimit, &reslimitType, 1); 252 | 253 | Err_FailedThrow(svcCloseHandle(reslimit)); 254 | 255 | u32 remaining = (u32)(maxCommit - currentCommit) &~ 0xFFF; 256 | 257 | if (heap_size > remaining) 258 | svcBreak(USERBREAK_PANIC); 259 | 260 | res = svcControlMemory(&heap, OS_HEAP_AREA_BEGIN, 0x0, heap_size, MEMOP_ALLOC, MEMPERM_READ | MEMPERM_WRITE); 261 | 262 | if (R_FAILED(res)) 263 | svcBreak(USERBREAK_PANIC); 264 | 265 | meminit(heap, heap_size); 266 | } 267 | 268 | void deinitializeHeap() 269 | { 270 | void *tmp; 271 | 272 | if (R_FAILED(svcControlMemory(&tmp, heap, 0x0, heap_size, MEMOP_FREE, 0x0))) 273 | svcBreak(USERBREAK_PANIC); 274 | } 275 | 276 | 277 | // better looking index checking 278 | 279 | #define SEMAPHORE_REPLY(idx) (idx == 0) // handles[0] 280 | #define SERVICE_REPLY(idx) (idx > 0 && idx < AM_SERVICE_COUNT + 1) // handles[1] until handles[4] 281 | #define PIPE_REPLY(idx) (idx == AM_SERVICE_COUNT + 1) // handles[5] 282 | 283 | void AM_Main() 284 | { 285 | initializeBSS(); 286 | initializeHeap(); 287 | 288 | Err_FailedThrow(srvInit()) 289 | Err_FailedThrow(am9Init()) 290 | Err_FailedThrow(fsUserInit()) 291 | Err_FailedThrow(syncInit()) 292 | 293 | /* 294 | handles[0] = semaphore 295 | handles[1] = am:net service handle 296 | handles[2] = am:sys service handle 297 | handles[3] = am:u service handle 298 | handles[4] = am:app service handle 299 | handles[5] = am:pipe pipe port server handle 300 | */ 301 | Handle handles[AM_SERVICE_COUNT + 2]; 302 | 303 | // handles[0] - semaphore 304 | Err_FailedThrow(SRV_EnableNotification(&handles[0])); 305 | 306 | // handles[1] through handles[4] - services 307 | for (u8 i = 0, j = 1; i < AM_SERVICE_COUNT; i++, j++) 308 | Err_FailedThrow(SRV_RegisterService(&handles[j], AM_ServiceNames[i].name, AM_ServiceNames[i].len, AM_MAX_SESSIONS_PER_SERVICE)) 309 | 310 | // handles[5] - pipe port server 311 | Err_FailedThrow(svcCreatePort(&handles[5], &g_PipeManager.port_client, NULL, 1)) 312 | 313 | // locks pipe manager to one thread at a time 314 | RecursiveLock_Init(&g_PipeManager.lock); 315 | 316 | // locks tmd reading for cia ipcs to one thread at a time 317 | RecursiveLock_Init(&g_TMDReader_Lock); 318 | 319 | // used to do ??? in home menu and nim(?) 320 | Err_FailedThrow(svcCreateMutex(&g_SystemUpdaterMutex, 0)) 321 | 322 | // used to tell pipe thread to exit when pausing or cancelling imports 323 | Err_FailedThrow(svcCreateEvent(&g_PipeManager.thread_close_event, RESET_ONESHOT)) 324 | 325 | // initialize demo db 326 | AM_DemoDatabase_Initialize(&g_DemoDatabase); 327 | 328 | while (true) 329 | { 330 | s32 index; 331 | 332 | Result res = svcWaitSynchronizationN(&index, handles, countof(handles), false, -1); 333 | 334 | if (R_FAILED(res)) 335 | Err_Throw(res); 336 | 337 | if (SEMAPHORE_REPLY(index)) // SRV semaphore fired for notification 338 | { 339 | Err_FailedThrow(SRV_ReceiveNotification(¬ification_id)) 340 | if (notification_id == 0x100) // terminate 341 | break; 342 | } 343 | else if (SERVICE_REPLY(index)) // service handle received request to create session 344 | { 345 | Handle session, thread; 346 | u8 stack_index; 347 | 348 | Err_FailedThrow(svcAcceptSession(&session, handles[index])); 349 | 350 | AM_SessionData *data = getNewSessionData(session, &stack_index, index - 1); 351 | 352 | Err_FailedThrow(startThread(&thread, &tmain, data, AM_SessionThreadStacks[stack_index] + 0x1000, 61, -2)); 353 | 354 | data->thread = thread; 355 | } 356 | else if (PIPE_REPLY(index)) // pipe server session received request to open pipe session 357 | { 358 | Err_FailedThrow(svcAcceptSession(&g_PipeManager.current_session, handles[5])) 359 | 360 | freeThread(&g_PipeManager.thread); 361 | 362 | Err_FailedThrow(startThread(&g_PipeManager.thread, &pipe_tmain, NULL, AM_SessionThreadStacks[5] + 0x1000, 61, -2)) 363 | } 364 | else // invalid index 365 | Err_Throw(AM_INTERNAL_RANGE) 366 | } 367 | 368 | // save, commit and close demodb 369 | AM_DemoDatabase_Close(&g_DemoDatabase); 370 | 371 | // wait and close thread handles 372 | for (u8 i = 0; i < AM_MAX_TOTAL_SESSIONS + 1; i++) 373 | freeThread(&AM_SessionsData[i].thread); 374 | 375 | // official services to do this, so why not 376 | Err_FailedThrow(svcCloseHandle(handles[0])) 377 | 378 | // unregister services 379 | for (u8 i = 0, j = 1; i < AM_SERVICE_COUNT; i++, j++) 380 | { 381 | Err_FailedThrow(SRV_UnregisterService(AM_ServiceNames[i].name, AM_ServiceNames[i].len)) 382 | Err_FailedThrow(svcCloseHandle(handles[j])); 383 | } 384 | 385 | // pipe stuff 386 | Err_FailedThrow(svcCloseHandle(handles[5])); 387 | Err_FailedThrow(svcCloseHandle(g_PipeManager.thread_close_event)) 388 | 389 | fsUserExit(); 390 | am9Exit(); 391 | srvExit(); 392 | syncExit(); 393 | deinitializeHeap(); 394 | } -------------------------------------------------------------------------------- /include/3ds/am9.h: -------------------------------------------------------------------------------- 1 | #ifndef _AM_3DS_AM9_H 2 | #define _AM_3DS_AM9_H 3 | 4 | #include <3ds/types.h> 5 | #include <3ds/ipc.h> 6 | #include <3ds/err.h> 7 | #include <3ds/svc.h> 8 | #include <3ds/fs.h> 9 | #include 10 | 11 | extern Handle am9_session; 12 | extern bool am9_init; 13 | extern bool importing_content; 14 | 15 | Result am9Init(); 16 | void am9Exit(); 17 | 18 | enum 19 | { 20 | IMPORT_STATE_NONE = 0, 21 | IMPORT_STATE_NOT_STARTED_YET = 1, 22 | IMPORT_STATE_PAUSED = 2, 23 | IMPORT_STATE_WAIT_FOR_COMMIT = 3, 24 | IMPORT_STATE_ALREADY_EXISTS = 4, 25 | IMPORT_STATE_DELETING = 5, 26 | IMPORT_STATE_NEEDS_CLEANUP = 6 27 | }; 28 | 29 | enum 30 | { 31 | TitleDB = 0, 32 | TempDB = 1 33 | }; 34 | 35 | enum 36 | { 37 | ContentTypeFlag_Encrypted = 0x0001, 38 | ContentTypeFlag_Disk = 0x0002, 39 | ContentTypeFlag_CFM = 0x0004, 40 | ContentTypeFlag_Optional = 0x4000, 41 | ContentTypeFlag_Shared = 0x8000, 42 | }; 43 | 44 | enum 45 | { 46 | ID_AM9_GetTitleCount = 0x0001, 47 | ID_AM9_GetTitleList = 0x0002, 48 | ID_AM9_GetTitleInfos = 0x0003, 49 | ID_AM9_DeleteTitle = 0x0004, 50 | ID_AM9_GetTitleProductCode = 0x0005, 51 | ID_AM9_GetTitleExtDataID = 0x0006, 52 | ID_AM9_DeletePendingTitles = 0x0007, 53 | ID_AM9_InstallFIRM = 0x0008, 54 | ID_AM9_InstallTicketBegin = 0x0009, 55 | ID_AM9_InstallTicketWrite = 0x000A, 56 | ID_AM9_InstallTicketCancel = 0x000B, 57 | ID_AM9_InstallTicketFinish = 0x000C, 58 | ID_AM9_DeleteTicket = 0x000D, 59 | ID_AM9_GetTicketCount = 0x000E, 60 | ID_AM9_GetTicketList = 0x000F, 61 | ID_AM9_InstallTitleBegin = 0x0010, 62 | ID_AM9_InstallTitlePause = 0x0011, 63 | ID_AM9_InstallTitleResume = 0x0012, 64 | ID_AM9_InstallTMDBegin = 0x0013, 65 | ID_AM9_InstallTMDWrite = 0x0014, 66 | ID_AM9_InstallTMDCancel = 0x0015, 67 | ID_AM9_InstallTMDFinish = 0x0016, 68 | ID_AM9_InstallContentBegin = 0x0017, 69 | ID_AM9_InstallContentWrite = 0x0018, 70 | ID_AM9_InstallContentPause = 0x0019, 71 | ID_AM9_InstallContentCancel = 0x001A, 72 | ID_AM9_InstallContentResume = 0x001B, 73 | ID_AM9_InstallContentFinish = 0x001C, 74 | ID_AM9_GetPendingTitleCount = 0x001D, 75 | ID_AM9_GetPendingTitleList = 0x001E, 76 | ID_AM9_GetPendingTitleInfo = 0x001F, 77 | ID_AM9_DeletePendingTitle = 0x0020, 78 | ID_AM9_GetImportContentContextsCount = 0x0021, 79 | ID_AM9_GetImportContentContextsList = 0x0022, 80 | ID_AM9_GetImportContentContexts = 0x0023, 81 | ID_AM9_DeleteImportContentContexts = 0x0024, 82 | ID_AM9_GetCurrentImportContentContextsCount = 0x0025, 83 | ID_AM9_GetCurrentImportContentContextsList = 0x0026, 84 | ID_AM9_GetCurrentImportContentContexts = 0x0027, 85 | ID_AM9_InstallTitleCancel = 0x0028, 86 | ID_AM9_InstallTitleFinish = 0x0029, 87 | ID_AM9_InstallTitlesCommit = 0x002A, 88 | ID_AM9_Stubbed_0x2B = 0x002B, 89 | ID_AM9_Stubbed_0x2C = 0x002C, 90 | ID_AM9_Stubbed_0x2D = 0x002D, 91 | ID_AM9_Stubbed_0x2E = 0x002E, 92 | ID_AM9_Stubbed_0x2F = 0x002F, 93 | ID_AM9_Stubbed_0x30 = 0x0030, 94 | ID_AM9_Stubbed_0x31 = 0x0031, 95 | ID_AM9_Stubbed_0x32 = 0x0032, 96 | ID_AM9_Stubbed_0x33 = 0x0033, 97 | ID_AM9_Stubbed_0x34 = 0x0034, 98 | ID_AM9_Stubbed_0x35 = 0x0035, 99 | ID_AM9_Stubbed_0x36 = 0x0036, 100 | ID_AM9_Stubbed_0x37 = 0x0037, 101 | ID_AM9_Stubbed_0x38 = 0x0038, 102 | ID_AM9_Sign = 0x0039, 103 | ID_AM9_Stubbed_0x3A = 0x003A, 104 | ID_AM9_GetDeviceCertificate = 0x003B, 105 | ID_AM9_GetDeviceID = 0x003C, 106 | ID_AM9_ImportCertificates = 0x003D, 107 | ID_AM9_ImportCertificate = 0x003E, 108 | ID_AM9_ImportDatabaseInitialized = 0x003F, 109 | ID_AM9_Cleanup = 0x0040, 110 | ID_AM9_DeleteTemporaryTitles = 0x0041, 111 | ID_AM9_InstallTitlesFinishAndInstallFIRM = 0x0042, 112 | ID_AM9_DSiWareExportVerifyFooter = 0x0043, 113 | ID_AM9_Stubbed_0x44 = 0x0044, 114 | ID_AM9_DSiWareExportDecryptData = 0x0045, 115 | ID_AM9_DSiWareWriteSaveData = 0x0046, 116 | ID_AM9_InitializeTitleDatabase = 0x0047, 117 | ID_AM9_ReloadTitleDatabase = 0x0048, 118 | ID_AM9_GetTicketIDCount = 0x0049, 119 | ID_AM9_GetTicketIDList = 0x004A, 120 | ID_AM9_DeleteTicketID = 0x004B, 121 | ID_AM9_GetPersonalizedTicketInfos = 0x004C, 122 | ID_AM9_DSiWareExportCreate = 0x004D, 123 | ID_AM9_DSiWareExportInstallTitleBegin = 0x004E, 124 | ID_AM9_DSiWareExportGetSize = 0x004F, 125 | ID_AM9_GetTWLTitleListForReboot = 0x0050, 126 | ID_AM9_DeleteUserDSiWareTitles = 0x0051, 127 | ID_AM9_DeleteExpiredUserTitles = 0x0052, 128 | ID_AM9_DSiWareExportVerifyMovableSedHash = 0x0053, 129 | ID_AM9_GetTWLArchiveResourceInfo = 0x0054, 130 | ID_AM9_DSiWareExportValidateSectionMAC = 0x0055, 131 | ID_AM9_CheckContentRight = 0x0056, 132 | ID_AM9_CreateImportContentContexts = 0x0057, 133 | ID_AM9_GetContentInfoCount = 0x0058, 134 | ID_AM9_FindContentInfos = 0x0059, 135 | ID_AM9_ListContentInfos = 0x005A, 136 | ID_AM9_GetCurrentContentInfoCount = 0x005B, 137 | ID_AM9_FindCurrentContentInfos = 0x005C, 138 | ID_AM9_ListCurrentContentInfos = 0x005D, 139 | ID_AM9_DeleteContents = 0x005E, 140 | ID_AM9_GetTitleInstalledTicketsCount = 0x005F, 141 | ID_AM9_ListTicketInfos = 0x0060, 142 | ID_AM9_ExportLicenseTicket = 0x0061, 143 | ID_AM9_GetTicketLimitInfos = 0x0062, 144 | ID_AM9_UpdateImportContentContexts = 0x0063, 145 | ID_AM9_GetInternalTitleLocationInfo = 0x0064, 146 | ID_AM9_MigrateAGBToSAV = 0x0065, 147 | ID_AM9_Stubbed_0x66 = 0x0066, 148 | ID_AM9_DeleteTitles = 0x0067, 149 | ID_AM9_GetItemRights = 0x0068, 150 | ID_AM9_TitleInUse = 0x0069, 151 | ID_AM9_GetInstalledContentInfoCount = 0x006A, 152 | ID_AM9_ListInstalledContentInfos = 0x006B, 153 | ID_AM9_InstallTitleBeginOverwrite = 0x006C, 154 | ID_AM9_ExportTicketWrapped = 0x006D, 155 | }; 156 | 157 | typedef struct __attribute__((aligned(4))) ContentInfo 158 | { 159 | u16 index; 160 | u16 type; 161 | u32 id; 162 | u64 size; 163 | u8 flags; ///< BIT(0): downloaded, BIT(1): owned 164 | u8 pad[0x7]; 165 | } ContentInfo; 166 | 167 | typedef struct __attribute__((aligned(8))) TWLArchiveResourceInfo 168 | { 169 | u64 total_capacity; 170 | u64 total_free_space; 171 | u64 titles_capacity; 172 | u64 titles_free_space; 173 | } TWLArchiveResourceInfo; 174 | 175 | typedef struct __attribute__((aligned(4))) TitleInfo 176 | { 177 | u64 title_id; 178 | u64 size; 179 | u16 version; 180 | u8 pad[2]; 181 | u32 type; 182 | } TitleInfo; 183 | 184 | typedef struct __attribute__((aligned(4))) TicketInfo 185 | { 186 | u64 title_id; 187 | u64 ticket_id; 188 | u16 version; 189 | u16 padding; 190 | u32 size; 191 | } TicketInfo; 192 | 193 | typedef struct __attribute__((aligned(4))) ImportTitleContext 194 | { 195 | u64 title_id; 196 | u16 version; 197 | u16 state; 198 | u32 type; 199 | u64 size; 200 | } ImportTitleContext; 201 | 202 | typedef struct __attribute__((aligned(4))) ImportContentContext 203 | { 204 | u32 content_id; 205 | u16 content_index; 206 | u16 state; 207 | u64 size; 208 | u64 current_install_offset; 209 | } ImportContentContext; 210 | 211 | typedef struct __attribute__((aligned(4))) InternalTitleLocationInfo 212 | { 213 | u8 data[32]; 214 | } InternalTitleLocationInfo; 215 | 216 | typedef struct __attribute__((aligned(4))) TicketLimitInfo 217 | { 218 | u8 flags; 219 | u8 playcount; 220 | u8 unk[14]; 221 | } TicketLimitInfo; 222 | 223 | Result AM9_GetTitleCount(u32 *count, MediaType media_type); 224 | Result AM9_GetTitleList(u32 *count, u32 amount, MediaType media_type, u64 *title_ids); 225 | Result AM9_GetTitleInfos(MediaType media_type, u32 count, u64 *title_ids, TitleInfo *infos); 226 | Result AM9_DeleteTitle(MediaType media_type, u64 title_id); 227 | Result AM9_GetTitleProductCode(char *product_code, MediaType media_type, u64 title_id); 228 | Result AM9_GetTitleExtDataID(u64 *extdata_id, MediaType media_type, u64 title_id); 229 | Result AM9_DeletePendingTitles(MediaType media_type, u8 flags); 230 | Result AM9_InstallFIRM(u64 title_id); 231 | Result AM9_InstallTicketBegin(); 232 | Result AM9_InstallTicketWrite(void *tik_data, u32 tik_data_size); 233 | Result AM9_InstallTicketCancel(); 234 | Result AM9_InstallTicketFinish(); 235 | Result AM9_DeleteTicket(u64 title_id); 236 | Result AM9_GetTicketCount(u32 *count); 237 | Result AM9_GetTicketList(u32 *count, u32 amount, u32 offset, u64 *title_ids); 238 | Result AM9_InstallTitleBegin(MediaType media_type, u64 title_id, u8 db_type); 239 | Result AM9_InstallTitlePause(); 240 | Result AM9_InstallTitleResume(MediaType media_type, u64 title_id); 241 | Result AM9_InstallTMDBegin(); 242 | Result AM9_InstallTMDWrite(void *tmd_data, u32 tmd_data_size); 243 | Result AM9_InstallTMDCancel(); 244 | Result AM9_InstallTMDFinish(bool unused); 245 | Result AM9_InstallContentBegin(u16 content_index); 246 | Result AM9_InstallContentWrite(void *content_data, u32 content_data_size); 247 | Result AM9_InstallContentPause(); 248 | Result AM9_InstallContentCancel(); 249 | Result AM9_InstallContentResume(u16 content_index, u64 *resume_offset); 250 | Result AM9_InstallContentFinish(); 251 | Result AM9_GetPendingTitleCount(u32 *count, MediaType media_type, u8 status_mask); 252 | Result AM9_GetPendingTitleList(u32 *count, u32 amount, MediaType media_type, u8 status_mask, u64 *title_ids); 253 | Result AM9_GetPendingTitleInfo(u32 count, MediaType media_type, u64 *title_ids, TitleInfo *title_infos); 254 | Result AM9_DeletePendingTitle(MediaType media_type, u64 title_id); 255 | Result AM9_GetImportContentContextsCount(u32 *count, MediaType media_type, u64 title_id); 256 | Result AM9_GetImportContentContextsList(u32 *count, u32 amount, MediaType media_type, u64 title_id, u16 *indices); 257 | Result AM9_GetImportContentContexts(u32 count, MediaType media_type, u64 title_id, u16 *indices, ImportContentContext *contexts); 258 | Result AM9_DeleteImportContentContexts(u32 count, MediaType media_type, u64 title_id, u16 *indices); 259 | Result AM9_GetCurrentImportContentContextsCount(u32 *count); 260 | Result AM9_GetCurrentImportContentContextsList(u32 *count, u32 amount, u16 *indices); 261 | Result AM9_GetCurrentImportContentContexts(u32 count, u16 *indices, ImportContentContext *contexts); 262 | Result AM9_InstallTitleCancel(); 263 | Result AM9_InstallTitleFinish(); 264 | Result AM9_InstallTitlesCommit(MediaType media_type, u32 count, u8 db_type, u64 *title_ids); 265 | Result AM9_Sign(u8 *retval, u32 sig_outsize, u32 cert_outsize, u64 title_id, u32 data_size, void *data, void *sig, void *cert); 266 | Result AM9_GetDeviceCertificate(u32 *retval, u32 out_data_size, void *out_data); 267 | Result AM9_GetDeviceID(s32 *out_internal_result, u32 *id); 268 | Result AM9_ImportCertificates(u32 certsize_1, u32 certsize_2, u32 certsize_3, u32 certsize_4, void *cert_1, void *cert_2, void *cert_3, void *cert_4); 269 | Result AM9_ImportCertificate(u32 certsize, void *cert); 270 | Result AM9_ImportDatabaseInitialized(u8 *initialized, MediaType media_type); 271 | Result AM9_Cleanup(MediaType media_type); 272 | Result AM9_DeleteTemporaryTitles(); 273 | Result AM9_InstallTitlesFinishAndInstallFIRM(MediaType media_type, u32 count, u64 firm_title_id, u8 db_type, u64 *title_ids); 274 | Result AM9_DSiWareExportVerifyFooter(u64 twl_title_id, u32 data_size, u32 ecdsa_sigsize, u32 device_cert_size, u32 apcert_size, u8 dsiware_export_section_index, void *data, void *ecdsa_sig, void *device_cert, void *apcert); 275 | Result AM9_DSiWareExportDecryptData(u32 input_size, u32 output_size, u32 iv_size, u8 dsiware_export_section_index, void *input, void *input_iv, void *output, void *output_iv); 276 | Result AM9_DSiWareWriteSaveData(u64 twl_title_id, u32 data_size, u32 nand_file_offset, u8 section_type, u8 operation, void *data); 277 | Result AM9_InitializeTitleDatabase(MediaType media_type, bool overwrite); 278 | Result AM9_ReloadTitleDatabase(u8 *available, MediaType media_type); 279 | Result AM9_GetTicketIDCount(u32 *count, u64 title_id); 280 | Result AM9_GetTicketIDList(u32 *count, u32 amount, u64 title_id, bool verifyTickets, u64 *ticket_ids); 281 | Result AM9_DeleteTicketID(u64 title_id, u64 ticket_id); 282 | Result AM9_GetPersonalizedTicketInfos(u32 *count, u32 amount, TicketInfo *infos); 283 | Result AM9_DSiWareExportCreate(u64 twl_title_id, u32 path_size, u32 buffer_size, u8 export_type, void *path, void *buffer); 284 | Result AM9_DSiWareExportInstallTitleBegin(u64 title_id, u8 export_type); 285 | Result AM9_DSiWareExportGetSize(u32 *size, u64 twl_title_id, u8 export_type); 286 | Result AM9_GetTWLTitleListForReboot(u32 *count, u32 amount, u64 *title_ids, u32 *content_ids); 287 | Result AM9_DeleteUserDSiWareTitles(); 288 | Result AM9_DeleteExpiredUserTitles(MediaType media_type); 289 | Result AM9_DSiWareExportVerifyMovableSedHash(u32 buf0_size, u32 buf1_size, void *buf0, void *buf1); 290 | Result AM9_GetTWLArchiveResourceInfo(TWLArchiveResourceInfo *info); 291 | Result AM9_DSiWareExportValidateSectionMAC(u32 mac_size, u32 hash_size, u8 dsiware_export_section_index, void *mac, void *hash); 292 | Result AM9_CheckContentRight(u8 *has_right, u64 title_id, u16 content_index); 293 | Result AM9_CreateImportContentContexts(u32 count, u16 *indices); 294 | Result AM9_GetContentInfoCount(u32 *count, MediaType media_type, u64 title_id); 295 | Result AM9_FindContentInfos(MediaType media_type, u64 title_id, u32 count, u16 *indices, ContentInfo *infos); 296 | Result AM9_ListContentInfos(u32 *count, u32 amount, MediaType media_type, u64 title_id, u32 offset, ContentInfo *infos); 297 | Result AM9_GetCurrentContentInfoCount(u32 *count); 298 | Result AM9_FindCurrentContentInfos(u32 count, u16 *indices, ContentInfo *infos); 299 | Result AM9_ListCurrentContentInfos(u32 *count, u32 amount, u32 offset, ContentInfo *infos); 300 | Result AM9_DeleteContents(MediaType media_type, u64 title_id, u32 count, u16 *indices); 301 | Result AM9_GetTitleInstalledTicketsCount(u32 *count, u64 title_id); 302 | Result AM9_ListTicketInfos(u32 *count, u32 amount, u64 title_id, u32 offset, TicketInfo *infos); 303 | Result AM9_ExportLicenseTicket(u32 *actual_size, u32 data_size, u64 title_id, u64 ticket_id, void *data); 304 | Result AM9_GetTicketLimitInfos(u32 count, u64 *ticket_ids, TicketLimitInfo *infos); 305 | Result AM9_UpdateImportContentContexts(u32 count, u16 *indices); 306 | Result AM9_GetInternalTitleLocationInfo(InternalTitleLocationInfo *info, MediaType media_type, u64 title_id); 307 | Result AM9_MigrateAGBToSAV(MediaType media_type, u64 title_id); 308 | Result AM9_DeleteTitles(MediaType media_type, u32 count, u64 *title_ids); 309 | Result AM9_GetItemRights(u32 *outval1, u32 *outval2, u32 data_size, u32 unk_enumval, u64 title_id, u64 ticket_id, u32 offset, void *data); 310 | Result AM9_TitleInUse(u8 *in_use, MediaType media_type, u64 title_id); 311 | Result AM9_GetInstalledContentInfoCount(u32 *count, MediaType media_type, u64 title_id); 312 | Result AM9_ListInstalledContentInfos(u32 *count, u32 amount, MediaType media_type, u64 title_id, u32 offset, ContentInfo *infos); 313 | Result AM9_InstallTitleBeginOverwrite(MediaType media_type, u64 title_id); 314 | Result AM9_ExportTicketWrapped(u32 *crypted_ticket_size, u32 *crypted_key_iv_size, u32 crypted_ticket_buffer_size, u32 crypted_key_iv_buffer_size, u64 title_id, u64 ticket_id, void *crypted_ticket, void *crypted_key_iv); 315 | 316 | #endif 317 | -------------------------------------------------------------------------------- /source/am/twlexport.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #define BANNER_SIZE 0x4000 // always the same 4 | #define TWLEXPORT_HDR_MAX 0x100 5 | #define TWLEXPORT_FTR_MAX 0x500 6 | 7 | #define HB(x) ((AM_TWLExportHeaderBase *)((x))) 8 | #define FB(x) ((AM_TWLExportFooterBase *)((x))) 9 | #define HF(x) ((AM_TWLExportHeaderV2_12 *)((x))) 10 | #define FF(x) ((AM_TWLExportFooterV2_12 *)((x))) 11 | 12 | #define FOOTER_ECDSA_SIG(x, info) ((x) + sizeof(AM_TWLExportFooterBase) + (0x20 * info->section_count)) 13 | #define FOOTER_ECDSA_APCERT(x, info) ((x) + sizeof(AM_TWLExportFooterBase) + (0x20 * info->section_count) + 60) 14 | #define FOOTER_ECDSA_CTCERT(x, info) ((x) + sizeof(AM_TWLExportFooterBase) + (0x20 * info->section_count) + 60 + 384) 15 | #define FOOTER_SECTION_HASH(x, info, idx) ((x) + sizeof(AM_TWLExportFooterBase) + (0x20 * info->section_count) + 60 + 384 + (idx * 0x20)) 16 | 17 | #define GET_BLOCKMETA(buf,siz) ((AM_TWLExportBlockMetadata *)(((u8 *)(buf)) + siz)) 18 | 19 | #define HEADER_OFFSET(buf) (buf + alignMeta(BANNER_SIZE)) 20 | #define FOOTER_OFFSET(buf, info) (HEADER_OFFSET(buf) + alignMeta(info->header_size)) 21 | 22 | #define READ_BLKMETA(section_size) \ 23 | if (R_FAILED(res = FSFile_Read(&read, file, *off + section_size, sizeof(AM_TWLExportBlockMetadata), &blockmeta))) \ 24 | CLOSEFILE_RET(res); \ 25 | if (read != sizeof(AM_TWLExportBlockMetadata)) \ 26 | CLOSEFILE_RET(AM_FAILED_READING_BLOCKMETA) 27 | 28 | #define CLOSEFILE_RET(x) {\ 29 | FSFile_Close(file); \ 30 | return x; } 31 | 32 | // can be simplified into structures like this 33 | 34 | static const AM_TWLExportTypeInfo EXPORTINFO_V2_12 = { 0x100, 0x500, 12, 0x1C0 }; 35 | static const AM_TWLExportTypeInfo EXPORTINFO_V2_11 = { 0xF0, 0x4E0, 11, 0x1A0 }; 36 | static const AM_TWLExportTypeInfo EXPORTINFO_V1_4 = { 0xA0, 0x400, 4, 0xC0 }; 37 | 38 | static const AM_TWLExportTypeInfo *getExportTypeInfo(AM_TWLExportType type) 39 | { 40 | switch (type) 41 | { 42 | // 12 content sections 43 | case V2_12ContentSections7: 44 | case V2_12ContentSections8: 45 | case V2_12ContentSections9: 46 | case V2_12ContentSectionsA: 47 | case V2_12ContentSectionsB: 48 | return &EXPORTINFO_V2_12; 49 | // 4 content sections 50 | case V1_4ContentSectionsC: 51 | case V1_4ContentSectionsD: 52 | return &EXPORTINFO_V1_4; 53 | // everything else uses 11 content sections 54 | default: 55 | return &EXPORTINFO_V2_11; 56 | } 57 | } 58 | 59 | static inline u32 alignMeta(u32 size) 60 | { 61 | u32 siz = ALIGN(size, 0x10); 62 | if (siz) siz += sizeof(AM_TWLExportBlockMetadata); 63 | return siz; 64 | } 65 | 66 | static void convertV1HeaderToV2_12Header(void *out, void *in) 67 | { 68 | AM_TWLExportHeaderV2_12 *v2_header = (AM_TWLExportHeaderV2_12 *)out; 69 | AM_TWLExportHeaderV1_4 *v1_header = (AM_TWLExportHeaderV1_4 *)in; 70 | 71 | _memset32_aligned(v2_header, 0x00, sizeof(AM_TWLExportHeaderV2_12)); 72 | 73 | _memcpy32_aligned(&v2_header->base, &v1_header->base, sizeof(AM_TWLExportHeaderBase)); // data structure same, no need to do this manually 74 | 75 | v2_header->payload_sizes[0] = v1_header->payload_sizes[0]; // TMD size 76 | v2_header->payload_sizes[1] = v1_header->payload_sizes[1]; // content size 77 | v2_header->payload_sizes[9] = v1_header->payload_sizes[2]; // public.sav size 78 | v2_header->payload_sizes[10] = v1_header->payload_sizes[3]; // banner.sav size 79 | v2_header->payload_sizes[11] = v1_header->unknown_2; // i have no idea what this is tbh 80 | 81 | v2_header->content_indices[0] = v1_header->content_index; 82 | 83 | _memcpy32_aligned(&v2_header->extra_data, &v1_header->extra_data, sizeof(AM_TWLExportHeaderExtraData)); 84 | } 85 | 86 | static void convertV1FooterToV2_12Footer(void *out, void *in) 87 | { 88 | AM_TWLExportFooterV2_12 *v2_footer = (AM_TWLExportFooterV2_12 *)out; 89 | AM_TWLExportFooterV1_4 *v1_footer = (AM_TWLExportFooterV1_4 *)in; 90 | 91 | _memset32_aligned(out, 0x00, sizeof(AM_TWLExportFooterV2_12)); 92 | 93 | _memcpy32_aligned(out, in, SIZE_OF_SHA_256_HASH * 2); // banner + header hashes can be done directly 94 | _memcpy32_aligned(v2_footer->content_section_hashes[0], v1_footer->content_section_hashes[0], SIZE_OF_SHA_256_HASH); // tmd 95 | _memcpy32_aligned(v2_footer->content_section_hashes[1], v1_footer->content_section_hashes[1], SIZE_OF_SHA_256_HASH); // content 96 | _memcpy32_aligned(v2_footer->content_section_hashes[9], v1_footer->content_section_hashes[2], SIZE_OF_SHA_256_HASH); // ?? 97 | _memcpy32_aligned(v2_footer->content_section_hashes[10], v1_footer->content_section_hashes[3], SIZE_OF_SHA_256_HASH); // ?? 98 | 99 | // copy sigs etc 100 | 101 | _memcpy32_aligned(v2_footer->ecdsa_sig, v1_footer->ecdsa_sig, 60 + (384 * 2) + 4); // real am doesn't do this because N doesn't feel like it 102 | } 103 | 104 | static Result decryptSectionVerify(void *in, void *out, void *out_hash, u32 size, AM_TWLExportSectionIndex index) 105 | { 106 | u8 newiv[0x10]; 107 | 108 | AM_TWLExportBlockMetadata *meta = GET_BLOCKMETA(in, size); 109 | 110 | Result res = AM9_DSiWareExportDecryptData(size, size, 0x10, index, in, meta->iv, out, newiv); 111 | 112 | if (R_FAILED(res)) 113 | return res; 114 | 115 | u8 hash[SIZE_OF_SHA_256_HASH]; 116 | struct SHA256 sha; 117 | 118 | sha_256_init(&sha, hash); 119 | sha_256_write(&sha, out, size); 120 | sha_256_close(&sha); 121 | 122 | res = AM9_DSiWareExportValidateSectionMAC(0x10, SIZE_OF_SHA_256_HASH, index, meta->cmac, hash); 123 | 124 | if (R_FAILED(res)) 125 | return res; 126 | 127 | if (out_hash) 128 | _memcpy32_aligned(out_hash, hash, SIZE_OF_SHA_256_HASH); 129 | 130 | return res; 131 | } 132 | 133 | static u64 getExportSize(AM_TWLExportHeaderV2_12 *header, const AM_TWLExportTypeInfo *info) 134 | { 135 | u64 ret = 0; 136 | 137 | #define I(x) alignMeta(x) 138 | 139 | ret += I(BANNER_SIZE); 140 | ret += I(info->header_size); 141 | ret += I(info->footer_size); 142 | ret += I(header->private_save_size); 143 | 144 | for (u8 i = 0; i < 11; i++) // max supported is 11 145 | ret += I(header->payload_sizes[i]); 146 | 147 | #undef I 148 | 149 | return ret; 150 | } 151 | 152 | static Result verifyExportHeader(void *header, const AM_TWLExportTypeInfo *info, u64 filesize) 153 | { 154 | AM_TWLExportHeaderV2_12 *hdr = (AM_TWLExportHeaderV2_12 *)header; 155 | return 156 | hdr->base.magic != 0x54444633 /* '3FDT' */ || 157 | getExportSize(hdr, info) != filesize || 158 | !hdr->payload_sizes[0] /* tmd */ || 159 | !hdr->payload_sizes[1] /* content 0 */ ? 160 | AM_INVALID_EXPORT : 0; 161 | } 162 | 163 | static Result processBegin(AM_TWLExportSectionIndex index, u16 content_index /* null for non-content */) 164 | { 165 | switch (index) 166 | { 167 | case TWLExport_TMD: 168 | return AM9_InstallTMDBegin(); 169 | case TWLExport_Content: 170 | return AM9_InstallContentBegin(content_index); 171 | case TWLExport_PublicSaveData: 172 | case TWLExport_BannerSaveData: 173 | case TWLExport_PrivateSaveData: 174 | return 0; 175 | default: 176 | return AM_TWL_EXPORT_INVALID_ENUM_VALUE; 177 | } 178 | } 179 | 180 | static Result processWrite(AM_TWLExportSectionIndex index, u64 twl_title_id, AM_TWLExportType type, 181 | void *buffer, u32 data_size, u64 nand_save_offset) 182 | { 183 | switch (index) 184 | { 185 | case TWLExport_TMD: 186 | return AM9_InstallTMDWrite(buffer, data_size); 187 | case TWLExport_Content: 188 | return AM9_InstallContentWrite(buffer, data_size); 189 | case TWLExport_PublicSaveData: 190 | case TWLExport_BannerSaveData: 191 | case TWLExport_PrivateSaveData: 192 | return AM9_DSiWareWriteSaveData(twl_title_id, data_size, nand_save_offset, index, type, buffer); 193 | default: 194 | return AM_TWL_EXPORT_INVALID_ENUM_VALUE; 195 | } 196 | } 197 | 198 | static Result processFinish(AM_TWLExportSectionIndex index) 199 | { 200 | switch (index) 201 | { 202 | case TWLExport_TMD: 203 | return AM9_InstallTMDFinish(true); 204 | case TWLExport_Content: 205 | return AM9_InstallContentFinish(); 206 | case TWLExport_PublicSaveData: 207 | case TWLExport_BannerSaveData: 208 | case TWLExport_PrivateSaveData: 209 | return 0; 210 | default: 211 | return AM_TWL_EXPORT_INVALID_ENUM_VALUE; 212 | } 213 | } 214 | 215 | static Result processCancel(AM_TWLExportSectionIndex index) 216 | { 217 | switch (index) 218 | { 219 | case TWLExport_TMD: 220 | return AM9_InstallTMDCancel(); 221 | case TWLExport_Content: 222 | return AM9_InstallContentCancel(); 223 | case TWLExport_PublicSaveData: 224 | case TWLExport_BannerSaveData: 225 | case TWLExport_PrivateSaveData: 226 | return 0; 227 | default: 228 | return AM_TWL_EXPORT_INVALID_ENUM_VALUE; 229 | } 230 | } 231 | 232 | // it's ninty, it must be a mess 233 | 234 | static Result processSection(u32 size, u8 *inhash, void *buf, u32 bufsize, Handle file, u64 *off, bool onlyverify, AM_TWLExportSectionIndex index, AM_TWLExportType type, u64 twl_title_id, u16 content_index) 235 | { 236 | AM_TWLExportBlockMetadata blockmeta; 237 | u8 hash[SIZE_OF_SHA_256_HASH]; 238 | u64 nand_save_offset = 0; 239 | struct SHA256 sha256; 240 | Result res; 241 | u32 read; 242 | 243 | if (!onlyverify && R_FAILED(res = processBegin(index, content_index))) 244 | return res; 245 | 246 | sha_256_init(&sha256, hash); 247 | 248 | READ_BLKMETA(ALIGN(size, 0x10)) /* don't care, just get it read */ 249 | 250 | u32 remaining = size; 251 | 252 | u32 section_misalign = size % 0x10; 253 | remaining -= section_misalign; /* remaining is now aligned to 0x10 */ 254 | bufsize -= bufsize % 0x10; /* bufsize is now aligned to 0x10 */ 255 | 256 | u32 to_process = MIN(bufsize, remaining); /* we can now forget about aligning things:tm: */ 257 | 258 | while (remaining) 259 | { 260 | if (R_FAILED(res = FSFile_Read(&read, file, *off, to_process, buf)) || 261 | R_FAILED(res = AM9_DSiWareExportDecryptData(to_process, to_process, 0x10, index, buf, blockmeta.iv, buf, blockmeta.iv)) || 262 | (!onlyverify && R_FAILED(res = processWrite(index, twl_title_id, type, buf, to_process, nand_save_offset)))) 263 | { 264 | if (!onlyverify) processCancel(index); 265 | return res; 266 | } 267 | 268 | sha_256_write(&sha256, buf, to_process); 269 | 270 | *off += to_process; 271 | nand_save_offset += to_process; 272 | remaining -= to_process; 273 | 274 | to_process = MIN(bufsize, remaining); 275 | } 276 | 277 | if (section_misalign) 278 | { 279 | if (R_FAILED(res = FSFile_Read(&read, file, *off, 0x10, buf)) || /* read a whole aes block */ 280 | R_FAILED(res = AM9_DSiWareExportDecryptData(0x10, 0x10, 0x10, index, buf, blockmeta.iv, buf, blockmeta.iv)) || 281 | ((!onlyverify) && R_FAILED(res = processWrite(index, twl_title_id, type, buf, section_misalign, nand_save_offset)))) 282 | { 283 | if (!onlyverify) processCancel(index); 284 | return res; 285 | } 286 | 287 | *off += 0x10; 288 | 289 | sha_256_write(&sha256, buf, 0x10); /* it hashes the padding too... */ 290 | } 291 | 292 | sha_256_close(&sha256); 293 | 294 | if (R_FAILED(res = AM9_DSiWareExportValidateSectionMAC(0x10, 0x20, index, blockmeta.cmac, hash)) || 295 | R_FAILED(res = cmp_hash(hash, inhash) ? 0 : AM_FOOTER_SECTION_HASH_MISMATCH) || 296 | ((!onlyverify) && R_FAILED(res = processFinish(index)))) 297 | { 298 | if (!onlyverify) processCancel(index); 299 | return res; 300 | } 301 | 302 | *off += sizeof(AM_TWLExportBlockMetadata); 303 | 304 | return 0; 305 | } 306 | 307 | /* 308 | mset calls this twice when importing a backup 309 | 1 - first it uses export type 4, this type doesn't do any content installation, instead, it just verifies: 310 | - banner (hash, cmac) 311 | - header (hash, cmac) 312 | - footer (cmac, sig, ctcert, apcert) 313 | - dsiware export file size. this is calculated using sizes from the header 314 | 2 - and finally, if the run above succeeds, it proceeds with rerunning the import but with export type 5. 315 | this will install the content sections, including the following: 316 | - TMD (first content section) 317 | - content(s) 1-8 (content section 2-9) 318 | - public.sav data (content section 10) 319 | - banner.sav data (content section 11) 320 | - private.sav data (content section 12) (see note) 321 | 322 | some notes about private.sav: 323 | - it is exclusive to backups exported using V2_12 export types. this means it is only included if 324 | there are 12 content sections. 325 | - the size of it is not a part of the payload sizes in the header. rather, it is at a seemingly odd 326 | offset in the header. 327 | 328 | note about export type 0xC (V1_4): 329 | - this export type seems to be bugged within AM9. it will export tmd, content0 and public.sav 330 | if it exists, but it will not write the size of public.sav to the header. 331 | due to this, it is impossible to import a backup made using 0xC. 332 | (0xD, the only other V1_4 type, is unaffected by this.) 333 | */ 334 | 335 | Result AM_ImportTWLBackup(u32 buffer_size, AM_TWLExportType type, Handle file, void *buffer) 336 | { 337 | Result res = 0; 338 | u64 export_filesize = 0; 339 | bool requires_conversion = false; 340 | 341 | // allowed values: 11sections: (2, 4, 5), 12sections: (9, 10, 11) 342 | if (type != V2_11ContentSections2 && 343 | type != V2_11ContentSections4 && 344 | type != V2_11ContentSections5 && 345 | type != V2_12ContentSections9 && 346 | type != V2_12ContentSectionsA && 347 | type != V2_12ContentSectionsB) 348 | CLOSEFILE_RET(AM_TWL_EXPORT_INVALID_ENUM_VALUE) 349 | 350 | if (buffer_size < 0x20000) 351 | CLOSEFILE_RET(AM_GENERAL_INVALIDARG) 352 | 353 | if (R_FAILED(res = FSFile_GetSize(&export_filesize, file))) 354 | CLOSEFILE_RET(res) 355 | 356 | const AM_TWLExportTypeInfo *info = getExportTypeInfo(type); 357 | 358 | // we have to use stack here, otherwise content install would only have (buffer_size - header_size - footer_size) 359 | // as available working buffer space 360 | u8 header[TWLEXPORT_HDR_MAX]; 361 | u8 footer[TWLEXPORT_FTR_MAX]; 362 | 363 | u8 banner_hash[SIZE_OF_SHA_256_HASH]; 364 | u8 header_hash[SIZE_OF_SHA_256_HASH]; 365 | 366 | u32 read = 0; 367 | u64 offset = 0; 368 | 369 | u32 banner_fullsize = alignMeta(BANNER_SIZE); 370 | u32 header_fullsize = alignMeta(info->header_size); 371 | u32 footer_fullsize = alignMeta(info->footer_size); 372 | 373 | u32 hbf_fullsize = banner_fullsize + header_fullsize + footer_fullsize; 374 | 375 | if (export_filesize < hbf_fullsize) 376 | CLOSEFILE_RET(AM_INVALID_SIZE) 377 | else if (R_FAILED(res = FSFile_Read(&read, file, 0, hbf_fullsize, buffer))) 378 | CLOSEFILE_RET(res) 379 | 380 | // let's do this sequentially, like sane humans 381 | 382 | // 1 - banner (this is not even installed, just verified :thonk:) 383 | 384 | if (R_FAILED(res = decryptSectionVerify(buffer, buffer, &banner_hash, BANNER_SIZE, TWLExport_Banner))) 385 | CLOSEFILE_RET(res) 386 | 387 | offset += banner_fullsize; 388 | 389 | // 2 - header (complete mess) 390 | 391 | void *headerptr = HEADER_OFFSET(buffer); 392 | 393 | if (R_FAILED(res = decryptSectionVerify(headerptr, header, &header_hash, info->header_size, TWLExport_Header))) 394 | { 395 | // we don't need to reread the file at this point, other export types read more than required anyway 396 | 397 | if (info->section_count == 12) CLOSEFILE_RET(res) // normal fail, with 12 sections this should not fail 398 | 399 | // may be an older export, try old format (while avoiding more stack usage) 400 | 401 | info = &EXPORTINFO_V1_4; 402 | type = V1_4ContentSectionsC; 403 | 404 | if (R_FAILED(res = decryptSectionVerify(headerptr, headerptr, &header_hash, info->header_size, TWLExport_Header))) 405 | CLOSEFILE_RET(res) 406 | 407 | // detected an older header, gotta convert to a v2_12 header 408 | 409 | requires_conversion = true; // so we can immediately convert the v1 footer to a v2 one 410 | header_fullsize = alignMeta(info->header_size); 411 | 412 | convertV1HeaderToV2_12Header(header, headerptr); 413 | } 414 | 415 | if (R_FAILED(res = verifyExportHeader(header, info, export_filesize))) 416 | CLOSEFILE_RET(res) 417 | 418 | res = AM9_DSiWareExportVerifyMovableSedHash(0x10, 0x20, HB(header)->empty_cbc, HB(header)->movable_hash); 419 | 420 | if (res != AM_MOVABLE_VERIFY_SUCCESS) 421 | CLOSEFILE_RET(res) 422 | 423 | offset += header_fullsize; 424 | 425 | // 3 - footer (complete mess #2, why the hell do hashes have to be here if already verified using a CMAC?) 426 | 427 | void *footerptr = FOOTER_OFFSET(buffer, info); 428 | 429 | if (R_FAILED(res = decryptSectionVerify(footerptr, footerptr, NULL, info->footer_size, TWLExport_Footer))) 430 | CLOSEFILE_RET(res) 431 | 432 | if (R_FAILED(res = AM9_DSiWareExportVerifyFooter( 433 | HB(header)->twl_title_id, 434 | info->footer_data_verifysize, 435 | 0x3C, // constant 436 | 0x180, // constant 437 | 0x180, // constant 438 | TWLExport_Footer, 439 | footerptr, 440 | FOOTER_ECDSA_SIG(footerptr, info), 441 | FOOTER_ECDSA_CTCERT(footerptr, info), 442 | FOOTER_ECDSA_APCERT(footerptr, info)))) 443 | CLOSEFILE_RET(res) 444 | 445 | if (requires_conversion) 446 | convertV1FooterToV2_12Footer(footer, footerptr); 447 | else 448 | _memcpy32_aligned(footer, footerptr, info->footer_size); 449 | 450 | if (!cmp_hash(FB(footer)->banner_hash, banner_hash) || !cmp_hash(FB(footer)->header_hash, header_hash)) 451 | CLOSEFILE_RET(AM_FOOTER_SECTION_HASH_MISMATCH) 452 | 453 | // mset uses type 4 (although 10 has the same effect) to validate the export file size, the banner, 454 | // header and footer sections. 455 | // content installation will be interrupted and a success result will be returned 456 | if (type == 4 || type == 10) 457 | CLOSEFILE_RET(0) 458 | 459 | offset += footer_fullsize; 460 | 461 | // types 2, 9 and 5, 11 can pass through here 462 | // types 2, 9 will have no tmd, content or save installation 463 | // only types 5 and 11 will have tmd, content and save installation (5 is the one used by mset) 464 | 465 | bool only_verify = type != 5 && type != 11; 466 | 467 | AM_TWLExportHeaderV2_12 *hdr = HF(header); 468 | AM_TWLExportFooterV2_12 *ftr = FF(footer); 469 | 470 | u64 title_id = hdr->base.twl_title_id; 471 | 472 | if (!only_verify && R_FAILED(res = AM9_DSiWareExportInstallTitleBegin(title_id, type))) 473 | CLOSEFILE_RET(res) 474 | 475 | // 4 - tmd 476 | 477 | if (R_FAILED(res = processSection(hdr->payload_sizes[0], ftr->content_section_hashes[0], buffer, buffer_size, file, &offset, only_verify, TWLExport_TMD, type, 0, 0))) 478 | { 479 | if (!only_verify) 480 | AM9_InstallTitleCancel(); 481 | CLOSEFILE_RET(res) 482 | } 483 | 484 | // 5 - contents 1-8 (payload index 2-9) 485 | 486 | for (u8 i = 0; i < 8; i++) 487 | { 488 | if (!hdr->payload_sizes[i + 1]) 489 | continue; 490 | 491 | if (R_FAILED(res = processSection(hdr->payload_sizes[i + 1], ftr->content_section_hashes[i + 1], buffer, buffer_size, file, &offset, only_verify, TWLExport_Content, type, 0, hdr->content_indices[i]))) 492 | { 493 | if (!only_verify) 494 | AM9_InstallTitleCancel(); 495 | 496 | CLOSEFILE_RET(res) 497 | } 498 | } 499 | 500 | // 6 - save data (public.sav, banner.sav, private.sav) 501 | // such a mess, can't find a better way 502 | 503 | if ((hdr->payload_sizes[9] && R_FAILED(res = processSection(hdr->payload_sizes[9], ftr->content_section_hashes[9], buffer, buffer_size, file, &offset, only_verify, TWLExport_PublicSaveData, type, hdr->base.twl_title_id, 0))) || 504 | (hdr->payload_sizes[10] && R_FAILED(res = processSection(hdr->payload_sizes[10], ftr->content_section_hashes[10], buffer, buffer_size, file, &offset, only_verify, TWLExport_BannerSaveData, type, hdr->base.twl_title_id, 0))) || 505 | (hdr->private_save_size && R_FAILED(res = processSection(hdr->private_save_size, ftr->content_section_hashes[11], buffer, buffer_size, file, &offset, only_verify, TWLExport_PrivateSaveData, type, hdr->base.twl_title_id, 0)))) 506 | { 507 | if (!only_verify) 508 | AM9_InstallTitleCancel(); 509 | 510 | CLOSEFILE_RET(res) 511 | } 512 | 513 | if (!only_verify && (R_FAILED(res = AM9_InstallTitleFinish()) || 514 | R_FAILED(res = AM9_InstallTitlesCommit(MediaType_NAND, 1, 1 /* temp db? */, &title_id)))) 515 | { 516 | if (!only_verify) 517 | AM9_InstallTitleCancel(); 518 | 519 | CLOSEFILE_RET(res) 520 | } 521 | 522 | CLOSEFILE_RET(0) 523 | } 524 | 525 | Result AM_ReadTWLBackupInfo(u32 buffer_size, u32 export_info_size, u32 banner_size, AM_TWLExportType type, 526 | Handle file, void *buffer, void *out_export_info, void *banner) 527 | { 528 | const AM_TWLExportTypeInfo *info = getExportTypeInfo(type); 529 | Result res = 0; 530 | u32 read; 531 | 532 | u32 banner_fullsize = alignMeta(BANNER_SIZE); 533 | u32 header_fullsize = alignMeta(info->header_size); 534 | u32 banner_header_fullsize = banner_fullsize + header_fullsize; 535 | 536 | if (buffer_size < banner_header_fullsize) // stock am doesn't do this because ??? 537 | CLOSEFILE_RET(AM_GENERAL_INVALIDARG) // saves us some checks afterwards 538 | 539 | u64 export_filesize; 540 | 541 | if (R_FAILED(res = FSFile_GetSize(&export_filesize, file))) 542 | CLOSEFILE_RET(res) 543 | 544 | if (export_filesize < banner_header_fullsize) // we only need banner + header anyway 545 | CLOSEFILE_RET(AM_GENERAL_INVALIDARG); 546 | 547 | if (R_FAILED(res = FSFile_Read(&read, file, 0, banner_header_fullsize, buffer))) 548 | CLOSEFILE_RET(res) 549 | 550 | if (banner_size) // sane code??? how??? 551 | { 552 | if R_FAILED(res = decryptSectionVerify(buffer, buffer, NULL, BANNER_SIZE, TWLExport_Banner)) 553 | CLOSEFILE_RET(res) 554 | 555 | banner_size = banner_size >= BANNER_SIZE ? BANNER_SIZE : banner_size; 556 | 557 | _memcpy(banner, buffer, banner_size); 558 | } 559 | 560 | if (export_info_size) // no need for processing anything really if nothing was requested 561 | { 562 | // back to header handling, ugh 563 | 564 | AM_TWLExportHeaderV2_12 *hdr = HEADER_OFFSET(buffer); 565 | 566 | // since we already did (or didn't) copy over the banner, the space the banner is in right now is free 567 | // for us to use now, avoids any additional stack usage 568 | if (R_FAILED(res = decryptSectionVerify(hdr, buffer, NULL, info->header_size, 569 | TWLExport_Header))) 570 | { 571 | if (info->section_count == 12) 572 | CLOSEFILE_RET(res) // again here, with 12 content sections should not fail 573 | 574 | // try reading old header, same as import 575 | 576 | info = &EXPORTINFO_V1_4; 577 | type = V1_4ContentSectionsC; 578 | 579 | if (R_FAILED(res = decryptSectionVerify(hdr, buffer, NULL, info->header_size, 580 | TWLExport_Header))) 581 | CLOSEFILE_RET(res) 582 | 583 | convertV1HeaderToV2_12Header(hdr, buffer); // using same buffer, efficiency:tm: 584 | } 585 | else 586 | _memcpy32_aligned(hdr, buffer, info->header_size); 587 | 588 | // now we have a valid v2 header @ hdr 589 | // fun 590 | 591 | AM_TWLExportInfo export_info; 592 | 593 | export_info.twl_title_id = hdr->base.twl_title_id; 594 | export_info.group_id = hdr->base.group_id; 595 | export_info.title_version = hdr->base.title_version; 596 | export_info.public_save_size = hdr->extra_data.public_save_size; 597 | export_info.private_save_size = hdr->extra_data.private_save_size; 598 | export_info.reserved_pad = 0; 599 | export_info.required_size = hdr->base.required_size; 600 | 601 | // fun fact: stock am doesn't even check the copy size here :) 602 | _memcpy(out_export_info, &export_info, MIN(export_info_size, sizeof(AM_TWLExportInfo))); 603 | } 604 | 605 | CLOSEFILE_RET(0) 606 | } -------------------------------------------------------------------------------- /source/am/pipe.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | static u8 __attribute__((section(".data.cia_cindex"))) cia_content_index[0x2000]; 4 | 5 | AM_Pipe g_PipeManager; 6 | 7 | static Result AM_Pipe_CIA_Write(void *data, u64 offset, u32 size, u32 flags, void *indata, u32 *written) 8 | { 9 | (void)flags; 10 | (void)offset; // who cares, no stupid restrictions here 11 | 12 | CIAInstallData *c = (CIAInstallData *)data; 13 | Result res = 0; 14 | u32 cur_written = 0; 15 | u8 *chunk = (u8 *)indata; 16 | 17 | DEBUG_PRINTF("start size: ", size); 18 | 19 | if (c->invalidated) 20 | return AM_INVALIDATED_INSTALLATTION_STATE; 21 | 22 | if (!size) 23 | { 24 | DEBUG_PRINT("exiting, no more data available\n"); 25 | *written = 0; 26 | return 0; 27 | } 28 | 29 | #define TMD_HDR(x) ((TMDHeader *)(x)) 30 | #define TIK_HDR(x) ((TicketHeader *)(x)) 31 | 32 | #define CON_ENABLED(x) (cia_content_index[x >> 3] & (0x80 >> (x & 7))) 33 | 34 | #define BASIC_COPY(dst, dsize, off_base, next_state) \ 35 | if (c->offset == (off_base + dsize)) \ 36 | { \ 37 | c->state = next_state; \ 38 | continue; \ 39 | } \ 40 | \ 41 | u32 write = MIN((off_base + dsize) - c->offset, size); \ 42 | DEBUG_PRINTF("copying size: ", write); \ 43 | DEBUG_PRINTF(" to: ", (u32)&((u8 *)(dst))[c->offset - off_base]); \ 44 | _memcpy(&((u8 *)(dst))[c->offset - off_base], chunk, write); \ 45 | c->offset += write; \ 46 | cur_written += write; \ 47 | size -= write; \ 48 | chunk += write; 49 | 50 | #define SKIP_PADDING(newoff) \ 51 | DEBUG_PRINTF("trying to skip padding from: ", c->offset); \ 52 | DEBUG_PRINTF(" to: ", newoff); \ 53 | if (c->offset < newoff) \ 54 | { \ 55 | u32 diff = newoff - c->offset; \ 56 | \ 57 | if (size < diff) \ 58 | { \ 59 | c->offset += size; \ 60 | cur_written += size; \ 61 | size -= size; \ 62 | continue; \ 63 | } \ 64 | else \ 65 | { \ 66 | c->offset += diff; \ 67 | cur_written += diff; \ 68 | size -= diff; \ 69 | chunk += diff; \ 70 | } \ 71 | } 72 | 73 | while (true) 74 | { 75 | if (!size) 76 | { 77 | DEBUG_PRINTF("no more size, wrote total: ", cur_written); 78 | *written = cur_written; 79 | return 0; 80 | } 81 | 82 | switch (c->state) 83 | { 84 | case InstallState_Header: 85 | { 86 | DEBUG_PRINT("reached install state: header\n"); 87 | BASIC_COPY(&c->header, sizeof(CIAHeader), 0, InstallState_ContentIndex) 88 | } 89 | break; 90 | case InstallState_ContentIndex: 91 | { 92 | DEBUG_PRINT("reached install state: content index\n"); 93 | BASIC_COPY(cia_content_index, sizeof(cia_content_index), 0x20, InstallState_CertChainCopy) 94 | } 95 | break; 96 | case InstallState_CertChainCopy: 97 | { 98 | DEBUG_PRINT("reached install state: certificate chain copy\n"); 99 | if (!c->header.CertificateChainSize) 100 | { 101 | DEBUG_PRINT("no cert chain, skipping to ticket header...\n"); 102 | c->state = InstallState_TicketHeader; 103 | continue; 104 | } 105 | 106 | SKIP_PADDING(CRT_START) // skip to 0x2040 107 | 108 | if (!c->buf && !(c->buf = malloc(c->header.CertificateChainSize))) 109 | { 110 | res = AM_OUT_OF_MEMORY; 111 | DEBUG_PRINT("could not allocate buffer for cert chain!\n"); 112 | goto err_exit; 113 | } 114 | 115 | BASIC_COPY(c->buf, c->header.CertificateChainSize, 0x2040, InstallState_CertChainInstall) 116 | } 117 | break; 118 | case InstallState_CertChainInstall: 119 | { 120 | DEBUG_PRINT("reached install state: certificate chain install\n"); 121 | if (R_FAILED(res = AM9_ImportCertificate(c->header.CertificateChainSize, c->buf))) 122 | goto err_exit; 123 | 124 | DEBUG_PRINT("!! freeing cert chain buffer !!\n"); 125 | free(c->buf); 126 | c->buf = NULL; 127 | c->state = InstallState_TicketHeader; 128 | DEBUG_PRINT("certificate chain imported\n"); 129 | } 130 | break; 131 | case InstallState_TicketHeader: 132 | { 133 | DEBUG_PRINT("reached install state: ticket header\n"); 134 | if (!c->header.TicketSize) 135 | { 136 | DEBUG_PRINT("cia has no ticket, skipping...\n"); 137 | c->state = InstallState_TMD; 138 | continue; 139 | } 140 | 141 | SKIP_PADDING(TIK_START(c)) // skip padding leading to ticket 142 | 143 | if (!c->buf && !(c->buf = malloc(sizeof(TicketHeader)))) 144 | { 145 | res = AM_OUT_OF_MEMORY; 146 | DEBUG_PRINT("failed allocating ticket header import buffer!\n"); 147 | goto err_exit; 148 | } 149 | 150 | BASIC_COPY(c->buf, sizeof(TicketHeader), TIK_START(c), InstallState_Ticket) 151 | } 152 | break; 153 | case InstallState_Ticket: 154 | { 155 | DEBUG_PRINT("reached install state: ticket\n"); 156 | if (!c->tik_header_imported) 157 | { 158 | DEBUG_PRINT("ticket header not imported yet, importing...\n"); 159 | c->ticket_tid = __builtin_bswap64(TIK_HDR(c->buf)->TitleID); 160 | 161 | if ((c->system && !TitleID_IsSystemCTR(c->ticket_tid)) || (c->db_type == TempDB && !TitleID_IsDLPChild(c->ticket_tid))) 162 | { 163 | res = AM_TICKET_UNEXPECTED_TITLE_ID; 164 | DEBUG_PRINT("unexpected ticket title id!\n"); 165 | goto err_exit; 166 | } 167 | 168 | if (R_FAILED(res = AM9_InstallTicketBegin())) 169 | { 170 | DEBUG_PRINTF("install ticket begin failed: ", res); 171 | goto err_exit; 172 | } 173 | 174 | c->importing_tik = true; 175 | 176 | DEBUG_PRINTF("install ticket write size: ", sizeof(TicketHeader)); 177 | 178 | if (R_FAILED(res = AM9_InstallTicketWrite(c->buf, sizeof(TicketHeader)))) 179 | { 180 | DEBUG_PRINTF("(header) install ticket write failed: ", res); 181 | goto err_exit; 182 | } 183 | 184 | free(c->buf); 185 | c->buf = NULL; 186 | 187 | // ticket wants a 0x3000 buffer max it seems 188 | if (!(c->buf = malloc(0x3000))) 189 | { 190 | res = AM_OUT_OF_MEMORY; 191 | DEBUG_PRINT("could not allocate buffer for ticket import!\n"); 192 | goto err_exit; 193 | } 194 | 195 | c->tik_header_imported = true; 196 | DEBUG_PRINT("finished ticket header import\n"); 197 | } 198 | 199 | if (c->offset == TIK_END(c)) 200 | { 201 | DEBUG_PRINT("reached ticket end offset, finishing install\n"); 202 | // if a newer ticket is already installed, it'll error 203 | // but AM allows it so we must do the same 204 | if (R_FAILED(res = AM9_InstallTicketFinish()) && res != AM_TRYING_TO_INSTALL_OUTDATED_TICKET) 205 | { 206 | DEBUG_PRINTF("install ticket finish failed: ", res); 207 | goto err_exit; 208 | } 209 | 210 | DEBUG_PRINT("finished ticket install completely\n"); 211 | 212 | free(c->buf); 213 | c->buf = NULL; 214 | c->state = InstallState_TMDHeader; 215 | c->importing_tik = false; 216 | continue; 217 | } 218 | 219 | // ticket isn't written crypted, so we don't care about alignments 220 | 221 | u32 write = MIN(MIN(TIK_END(c) - c->offset, size), 0x3000); // stock am never exceeds 0x3000 222 | 223 | DEBUG_PRINTF("install ticket write size: ", write); 224 | 225 | if (R_FAILED(res = AM9_InstallTicketWrite(chunk, write))) 226 | { 227 | DEBUG_PRINTF("install ticket write failed: ", res); 228 | goto err_exit; 229 | } 230 | 231 | c->offset += write; 232 | cur_written += write; 233 | chunk += write; 234 | size -= write; 235 | } 236 | break; 237 | case InstallState_TMDHeader: 238 | { 239 | // tmd must not be skipped 240 | 241 | SKIP_PADDING(TMD_START(c)) // skip padding leading to ticket 242 | 243 | if (!c->buf && !(c->buf = malloc(sizeof(TMDHeader) + 0xC))) // + 0xC for align 244 | { 245 | res = AM_OUT_OF_MEMORY; 246 | DEBUG_PRINT("could not allocate tmd header import buffer!\n"); 247 | goto err_exit; 248 | } 249 | 250 | BASIC_COPY(c->buf, sizeof(TMDHeader) + 0xC, TMD_START(c), InstallState_TMD) 251 | } 252 | break; 253 | case InstallState_TMD: 254 | { 255 | if (!c->tmd_header_imported) 256 | { 257 | DEBUG_PRINT("tmd header not imported, importing...\n"); 258 | c->tmd_tid = __builtin_bswap64(TMD_HDR(c->buf)->TitleID); 259 | c->is_dlc = TitleID_IsDLC(c->tmd_tid); 260 | 261 | if ((c->system && !TitleID_IsSystemCTR(c->tmd_tid)) || (c->db_type == TempDB && !TitleID_IsDLPChild(c->tmd_tid))) 262 | { 263 | res = AM_TMD_UNEXPECTED_TITLE_ID; 264 | DEBUG_PRINT("unexpected tmd title id!\n"); 265 | goto err_exit; 266 | } 267 | 268 | res = c->overwrite ? 269 | AM9_InstallTitleBeginOverwrite(c->media, c->tmd_tid) : 270 | AM9_InstallTitleBegin(c->media, c->tmd_tid, c->db_type); 271 | 272 | if (R_FAILED(res)) 273 | { 274 | if (c->overwrite) 275 | { 276 | DEBUG_PRINTF("install title begin overwrite failed: ", res); 277 | } 278 | else 279 | { 280 | DEBUG_PRINTF("install title begin failed: ", res); 281 | } 282 | 283 | goto err_exit; 284 | } 285 | 286 | c->importing_title = true; 287 | 288 | if (R_FAILED(res = AM9_InstallTMDBegin())) 289 | { 290 | DEBUG_PRINTF("install tmd begin failed: ", res); 291 | goto err_exit; 292 | } 293 | 294 | c->importing_tmd = true; 295 | 296 | DEBUG_PRINTF("install tmd write size: ", sizeof(TMDHeader) + 0xC); 297 | 298 | if (R_FAILED(res = AM9_InstallTMDWrite(c->buf, sizeof(TMDHeader) + 0xC))) 299 | { 300 | DEBUG_PRINTF("(header) install tmd write failed: ", res); 301 | goto err_exit; 302 | } 303 | 304 | free(c->buf); 305 | c->buf = NULL; 306 | 307 | if (!(c->buf = malloc(0x3000))) 308 | { 309 | res = AM_OUT_OF_MEMORY; 310 | DEBUG_PRINT("could not allocate tmd import buffer!\n"); 311 | goto err_exit; 312 | } 313 | 314 | c->tmd_header_imported = true; 315 | DEBUG_PRINT("tmd header imported.\n"); 316 | } 317 | 318 | if (c->offset == TMD_END(c)) 319 | { 320 | DEBUG_PRINT("reached tmd end offset, finalizing import...\n"); 321 | 322 | if (R_FAILED(res = AM9_InstallTMDFinish(true))) // no clue wtf the bool is 323 | { 324 | DEBUG_PRINTF("install tmd finish failed: ", res); 325 | goto err_exit; 326 | } 327 | 328 | free(c->buf); 329 | c->buf = NULL; 330 | c->state = InstallState_Content; 331 | c->cur_content_start_offs = CON_START(c); 332 | c->importing_tmd = false; 333 | DEBUG_PRINT("tmd completely imported.\n"); 334 | continue; 335 | } 336 | 337 | // try depleting the misalign buffer, if we even have one 338 | if (c->misalign_bufsize) 339 | { 340 | DEBUG_PRINTF("[TMD] have a misalign buffer of size: ", (u32)c->misalign_bufsize); 341 | // enough for a full aes block 342 | if (c->misalign_bufsize + size >= sizeof(c->misalign_buf)) 343 | { 344 | DEBUG_PRINT("[TMD] have enough data for full aes block\n"); 345 | u8 diff = 16 - c->misalign_bufsize; 346 | _memcpy(c->misalign_buf + c->misalign_bufsize, chunk, diff); 347 | 348 | DEBUG_PRINTF("install tmd write (misalign) size: ", 16); 349 | 350 | if (R_FAILED(res = AM9_InstallTMDWrite(c->misalign_buf, 16))) 351 | { 352 | DEBUG_PRINTF("install tmd write failed (misalignbuf): ", res); 353 | goto err_exit; 354 | } 355 | 356 | c->misalign_bufsize = 0; 357 | c->offset += diff; 358 | cur_written += diff; 359 | size -= diff; 360 | chunk += diff; 361 | } 362 | else // not enough for a full aes block, same ordeal as setting the buf up initially 363 | { 364 | _memcpy(c->misalign_buf + c->misalign_bufsize, chunk, size); 365 | c->misalign_bufsize += size; // increment here, as the buf is not empty here 366 | c->offset += size; 367 | cur_written += size; 368 | size -= size; 369 | DEBUG_PRINTF("[TMD] not enough data to fill misalign buf, size after push: ", (u32)c->misalign_bufsize); 370 | } 371 | } 372 | 373 | u32 remaining_size = TMD_END(c) - c->offset; 374 | bool last_write = size >= remaining_size; 375 | u32 write = last_write ? remaining_size : MIN(MIN(remaining_size, size), 0x3000); // stock am never exceeds 0x3000 376 | 377 | if (!last_write) 378 | { 379 | u8 diff = write % 16; // TMD must align, since it's written to sd crypted 380 | write -= diff; 381 | 382 | if (diff) 383 | DEBUG_PRINTF("detected tmd misalign: ", diff); 384 | 385 | // misaligned write, size is not at least 0x10 386 | if (!write) 387 | { 388 | DEBUG_PRINTF("tmd misalign causes misalign buf to be filled with: ", diff); 389 | _memcpy(c->misalign_buf, chunk, diff); 390 | c->misalign_bufsize = diff; // set here, misalign buf is empty here 391 | c->offset += diff; 392 | cur_written += diff; 393 | size -= diff; 394 | // no need for chunk incrementing 395 | continue; 396 | } 397 | } 398 | 399 | DEBUG_PRINTF("install tmd write size: ", write); 400 | 401 | if (R_FAILED(res = AM9_InstallTMDWrite(chunk, write))) 402 | { 403 | DEBUG_PRINTF("install tmd write failed: ", res); 404 | goto err_exit; 405 | } 406 | 407 | c->offset += write; 408 | cur_written += write; 409 | chunk += write; 410 | size -= write; 411 | } 412 | break; 413 | case InstallState_Content: 414 | { 415 | SKIP_PADDING(c->cur_content_start_offs) 416 | 417 | // for DLC, after first content, commit required 418 | // install title stop, commit, restart title install 419 | if (c->is_dlc && 420 | ((!c->batch_size && c->num_contents_imported == 1) || // first content, DLC needs a commit here to continue 421 | (c->batch_size && c->num_contents_imported_batch == c->batch_size))) // batch finished 422 | { 423 | DEBUG_PRINTF("commit required, current imported: ", c->num_contents_imported); 424 | DEBUG_PRINTF(" imported batch: ", c->num_contents_imported_batch); 425 | if (R_FAILED(res = AM9_InstallTitleFinish())) 426 | goto err_exit; 427 | 428 | c->importing_title = false; 429 | 430 | if (R_FAILED(res = AM9_InstallTitlesCommit(c->media, 1, c->db_type, &c->tmd_tid))) 431 | { 432 | DEBUG_PRINTF("install titles commit failed: ", res); 433 | goto err_exit; 434 | } 435 | 436 | if (R_FAILED(res = AM9_InstallTitleBegin(c->media, c->tmd_tid, c->db_type))) 437 | { 438 | DEBUG_PRINTF("install title begin failed: ", res); 439 | goto err_exit; 440 | } 441 | 442 | c->importing_title = true; 443 | c->num_contents_imported_batch = 0; // reset batch count 444 | c->batch_size = 0; // reset batch size 445 | } 446 | 447 | // we aren't importing anything at the moment, try initializing an import 448 | if (!c->importing_content) 449 | { 450 | DEBUG_PRINT("not importing content, trying to set up an import\n"); 451 | DEBUG_PRINTF("current index: ", c->cindex); 452 | DEBUG_PRINTF("is dlc: ", (u32)c->is_dlc); 453 | DEBUG_PRINTF("imported contents: ", c->num_contents_imported); 454 | DEBUG_PRINTF("batch size: ", c->batch_size); 455 | 456 | // curse you ninty for having this restriction for DLC 457 | if (c->is_dlc && c->num_contents_imported >= 1 && !c->batch_size) 458 | { 459 | u16 batch_indices[64]; // installing 64 contents in a batch is way faster 460 | // ! do not update the cindex here, we need the old value later 461 | for (u16 i = c->cindex; i < 0xFFFF && c->batch_size < 64; i++) 462 | { 463 | if (CON_ENABLED(i)) 464 | { 465 | batch_indices[c->batch_size] = i; // can use size as index here 466 | DEBUG_PRINTF("added to batch buffer: ", i); 467 | c->batch_size++; 468 | } 469 | } 470 | 471 | // essentially once the batch has been set up, content imports can resume 472 | // normally until all content indices that are used to create the import 473 | // contexts below have been imported 474 | // at which point we have to repeat this process... 475 | 476 | if (c->batch_size && 477 | R_FAILED(res = AM9_CreateImportContentContexts(c->batch_size, batch_indices))) 478 | { 479 | DEBUG_PRINTF("create import content contexts failed: ", res); 480 | goto err_exit; 481 | } 482 | } 483 | 484 | bool content_found = false; 485 | 486 | DEBUG_PRINT("trying to find an enabled content to install\n"); 487 | DEBUG_PRINTF("starting with content index: ", c->cindex); 488 | 489 | // find enabled content 490 | for (; c->cindex < 0xFFFF; c->cindex++) 491 | { 492 | if (CON_ENABLED(c->cindex)) 493 | { 494 | if (R_FAILED(res = AM9_InstallContentBegin(c->cindex))) 495 | { 496 | DEBUG_PRINTF("install content begin failed: ", res); 497 | goto err_exit; 498 | } 499 | 500 | c->importing_content = true; 501 | 502 | ImportContentContext ctx; 503 | 504 | if (R_FAILED(res = AM9_GetCurrentImportContentContexts(1, &c->cindex, &ctx))) 505 | { 506 | DEBUG_PRINTF("get current import content contexts failed: ", res); 507 | goto err_exit; 508 | } 509 | 510 | c->cur_content_start_offs = ALIGN(c->offset, 0x40); 511 | c->cur_content_end_offs = c->cur_content_start_offs + (u32)ctx.size; 512 | content_found = true; 513 | DEBUG_PRINT("content import initialized.\n"); 514 | DEBUG_PRINTF("current offset: ", c->offset); 515 | DEBUG_PRINTF("content start offset: ", c->cur_content_start_offs); 516 | DEBUG_PRINTF("content end offset: ", c->cur_content_end_offs); 517 | break; 518 | } 519 | } 520 | 521 | if (content_found) 522 | { 523 | DEBUG_PRINT("content was found, relooping...\n"); 524 | continue; 525 | } 526 | 527 | DEBUG_PRINT("content was NOT found, jumping to finalize...\n"); 528 | // if code reaches here, no more content to import 529 | c->state = InstallState_Finalize; 530 | continue; 531 | } 532 | 533 | // try depleting the misalign buffer, if we even have one 534 | if (c->misalign_bufsize) 535 | { 536 | DEBUG_PRINTF("[content] have a misalign buffer of size: ", c->misalign_bufsize); 537 | // enough for a full aes block 538 | if (c->misalign_bufsize + size >= sizeof(c->misalign_buf)) 539 | { 540 | DEBUG_PRINT("[content] have enough data for depleting misalign buf\n"); 541 | u8 diff = 16 - c->misalign_bufsize; 542 | _memcpy(c->misalign_buf + c->misalign_bufsize, chunk, diff); 543 | 544 | DEBUG_PRINTF("install content write (misalign_buf) size: ", 16); 545 | if (R_FAILED(res = AM9_InstallContentWrite(c->misalign_buf, 16))) 546 | { 547 | DEBUG_PRINTF("install content write failed (misalign_buf): ", res); 548 | goto err_exit; 549 | } 550 | 551 | c->misalign_bufsize = 0; 552 | c->offset += diff; 553 | cur_written += diff; 554 | size -= diff; 555 | chunk += diff; 556 | } 557 | else // not enough for a full aes block, same ordeal as setting the buf up initially 558 | { 559 | DEBUG_PRINTF("[content] don't have enough data for misalign buffer of size: ", c->misalign_bufsize); 560 | _memcpy(c->misalign_buf + c->misalign_bufsize, chunk, size); 561 | c->misalign_bufsize += size; // increment here, as the buf is not empty here 562 | c->offset += size; 563 | cur_written += size; 564 | size -= size; 565 | // no need for chunk incrementing 566 | } 567 | } 568 | 569 | if (size) 570 | { 571 | u32 remaining_size = c->cur_content_end_offs - c->offset; 572 | bool last_write = size >= remaining_size; 573 | u32 write = last_write ? remaining_size : MIN(remaining_size, size); // stock am never exceeds 0x3000 574 | 575 | if (!last_write) 576 | { 577 | u8 diff = write % 16; // content must align, since it's written to sd crypted 578 | write -= diff; 579 | 580 | if (diff) 581 | DEBUG_PRINTF("detected content misalign: ", diff); 582 | 583 | // misaligned write, size is not at least 0x10 584 | if (!write) 585 | { 586 | DEBUG_PRINTF("content misalign causes misalign buf to be filled with: ", diff); 587 | _memcpy(c->misalign_buf, chunk, diff); 588 | c->misalign_bufsize = diff; // set here, misalign buf is empty here 589 | c->offset += diff; 590 | cur_written += diff; 591 | size -= diff; 592 | // no need for chunk incrementing 593 | goto check_finish_content; 594 | } 595 | } 596 | 597 | if (!write) 598 | goto check_finish_content; 599 | 600 | DEBUG_PRINTF("content write size: ", write); 601 | if (R_FAILED(res = AM9_InstallContentWrite(chunk, write))) 602 | { 603 | DEBUG_PRINTF("install content write failed: ", res); 604 | goto err_exit; 605 | } 606 | 607 | c->offset += write; 608 | cur_written += write; 609 | chunk += write; 610 | size -= write; 611 | } 612 | 613 | check_finish_content: 614 | // finish off installing content, if needed 615 | if (c->offset == c->cur_content_end_offs) 616 | { 617 | DEBUG_PRINTF("finalizing installation for content: ", c->cindex); 618 | DEBUG_PRINTF(" offset: ", c->offset); 619 | 620 | if (R_FAILED(res = AM9_InstallContentFinish())) 621 | { 622 | DEBUG_PRINTF("install content finish failed: ", res); 623 | goto err_exit; 624 | } 625 | 626 | // content 0 is NOT part of a batch 627 | if (c->is_dlc && c->num_contents_imported != 0) 628 | { 629 | DEBUG_PRINTF("is dlc, incrementing, before: ", c->num_contents_imported_batch); 630 | c->num_contents_imported_batch++; 631 | } 632 | 633 | c->num_contents_imported++; 634 | c->cindex++; 635 | c->importing_content = false; 636 | } 637 | } 638 | break; 639 | case InstallState_Finalize: 640 | { 641 | if (c->header.MetaSize) 642 | { 643 | SKIP_PADDING(META_END(c)) 644 | } 645 | 646 | return 0; 647 | } 648 | break; 649 | } 650 | } 651 | 652 | err_exit: 653 | assertNotAmOrFs(res); 654 | 655 | if (c->buf) 656 | { 657 | free(c->buf); 658 | c->buf = NULL; 659 | } 660 | 661 | c->invalidated = true; 662 | 663 | return res; 664 | 665 | #undef TMD_HDR 666 | #undef TIK_HDR 667 | #undef BASIC_COPY 668 | #undef SKIP_PADDING 669 | } 670 | 671 | static Result AM_Pipe_Ticket_Write(void *data, u64 offset, u32 size, u32 flags, void *indata, u32 *written) 672 | { 673 | (void)flags; 674 | (void)offset; 675 | (void)data; 676 | 677 | Result res = AM9_InstallTicketWrite(indata, size); 678 | assertNotAmOrFs(res); 679 | 680 | if (R_SUCCEEDED(res)) 681 | { 682 | DEBUG_PRINTF("successfully wrote ticket bytes, size: ", size); 683 | *written = size; 684 | } 685 | 686 | return res; 687 | } 688 | 689 | static Result AM_Pipe_TMD_Write(void *data, u64 offset, u32 size, u32 flags, void *indata, u32 *written) 690 | { 691 | (void)flags; 692 | (void)offset; 693 | (void)data; 694 | 695 | Result res = AM9_InstallTMDWrite(indata, size); 696 | assertNotAmOrFs(res); 697 | 698 | if (R_SUCCEEDED(res)) 699 | { 700 | DEBUG_PRINTF("successfully wrote tmd bytes, size: ", size); 701 | *written = size; 702 | } 703 | 704 | return res; 705 | } 706 | 707 | static Result AM_Pipe_Content_Write(void *data, u64 offset, u32 size, u32 flags, void *indata, u32 *written) 708 | { 709 | (void)flags; 710 | (void)offset; 711 | (void)data; 712 | 713 | Result res = AM9_InstallContentWrite(indata, size); 714 | assertNotAmOrFs(res); 715 | 716 | if (R_SUCCEEDED(res)) 717 | { 718 | DEBUG_PRINTF("successfully wrote content bytes, size: ", size); 719 | *written = size; 720 | } 721 | 722 | return res; 723 | } 724 | 725 | bool atomicUpdateState(u8 *src, u8 val, u8 wanted) 726 | { 727 | u8 cur; 728 | 729 | while (true) 730 | { 731 | cur = __ldrexb(src); 732 | 733 | if (cur != wanted) 734 | { 735 | __clrex(); 736 | return false; 737 | } 738 | 739 | cur = val; 740 | 741 | if (__strexb(src, cur) == 0) 742 | return true; 743 | } 744 | } 745 | 746 | Result AM_Pipe_CreateImportHandle(AM_Pipe *pipe, AM_PipeWriteImpl impl, void *data, Handle *import) 747 | { 748 | RecursiveLock_Lock(&pipe->lock); // lock so we have exclusive access 749 | 750 | // check if a pipe is already initialized, if so then we return an error 751 | // expected value: false, value to update to: true 752 | if (!atomicUpdateState(&pipe->init, true, false)) 753 | { 754 | DEBUG_PRINT("pipe already initialized!\n"); 755 | RecursiveLock_Unlock(&pipe->lock); 756 | return AM_PIPE_ALREADY_INITIALIZED; 757 | } 758 | 759 | Handle session; 760 | 761 | // create out session, this is what the user will utilize to access am:pipe ipcs 762 | Result res = svcCreateSessionToPort(&session, pipe->port_client); 763 | 764 | if (R_FAILED(res)) 765 | { 766 | DEBUG_PRINTF("svcCreateSessionToPort failed: ", res); 767 | RecursiveLock_Unlock(&pipe->lock); 768 | return res; 769 | } 770 | 771 | pipe->write = impl; 772 | pipe->data = data; 773 | 774 | // must wait for the pipe thread to start, else the user could try am:pipe ipcs while we're not 775 | // ready yet 776 | 777 | DEBUG_PRINT("waiting for thread to start...\n"); 778 | 779 | LightEvent_Wait(&pipe->event); 780 | 781 | // safe to send handle now 782 | *import = session; 783 | 784 | DEBUG_PRINT("handle sent.\n"); 785 | 786 | RecursiveLock_Unlock(&pipe->lock); 787 | 788 | DEBUG_PRINT("successfully created import handle.\n"); 789 | 790 | return res; 791 | } 792 | 793 | void AM_Pipe_CloseImportHandle(AM_Pipe *pipe, Handle import) 794 | { 795 | svcCloseHandle(import); 796 | atomicUpdateState(&pipe->init, false, true); 797 | } 798 | 799 | void AM_Pipe_EnsureThreadExit(AM_Pipe *pipe) 800 | { 801 | RecursiveLock_Lock(&pipe->lock); 802 | 803 | if (g_PipeManager.init) // just ensuring we don't have a pipe thread running 804 | { 805 | Err_FailedThrow(svcSignalEvent(pipe->thread_close_event)); 806 | svcWaitSynchronization(pipe->thread, -1); 807 | atomicUpdateState(&pipe->init, false, true); 808 | } 809 | 810 | RecursiveLock_Unlock(&pipe->lock); 811 | } 812 | 813 | Result AM_Pipe_CreateCIAImportHandle(AM_Pipe *pipe, MediaType media_type, u8 title_db_type, bool overwrite, bool is_system, Handle *import) 814 | { 815 | CIAInstallData *data = malloc(sizeof(CIAInstallData)); 816 | 817 | if (!data) 818 | return AM_OUT_OF_MEMORY; 819 | 820 | _memset(data, 0x00, sizeof(CIAInstallData)); 821 | 822 | data->media = media_type; 823 | data->db_type = title_db_type; 824 | data->overwrite = overwrite; 825 | data->system = is_system; 826 | data->state = InstallState_Header; 827 | 828 | Result res = AM_Pipe_CreateImportHandle(pipe, &AM_Pipe_CIA_Write, data, import); 829 | 830 | if (R_SUCCEEDED(res)) 831 | { 832 | DEBUG_PRINT("created cia pipe successfully\n"); 833 | return res; 834 | } 835 | 836 | free(data); 837 | 838 | return res; 839 | } 840 | 841 | Result AM_Pipe_CreateTicketImportHandle(AM_Pipe *pipe, Handle *import) 842 | { 843 | Result res = AM_Pipe_CreateImportHandle(pipe, &AM_Pipe_Ticket_Write, NULL, import); 844 | 845 | if (R_SUCCEEDED(res)) 846 | DEBUG_PRINT("created ticket pipe successully\n"); 847 | 848 | return res; 849 | } 850 | 851 | Result AM_Pipe_CreateTMDImportHandle(AM_Pipe *pipe, Handle *import) 852 | { 853 | Result res = AM_Pipe_CreateImportHandle(pipe, &AM_Pipe_TMD_Write, NULL, import); 854 | 855 | if (R_SUCCEEDED(res)) 856 | DEBUG_PRINT("created tmd pipe successfully\n"); 857 | 858 | return res; 859 | } 860 | 861 | Result AM_Pipe_CreateContentImportHandle(AM_Pipe *pipe, Handle *import) 862 | { 863 | Result res = AM_Pipe_CreateImportHandle(pipe, &AM_Pipe_Content_Write, NULL, import); 864 | 865 | if (R_SUCCEEDED(res)) 866 | DEBUG_PRINT("created content pipe successfully\n"); 867 | 868 | return res; 869 | } 870 | 871 | void AM_Pipe_HandleIPC() 872 | { 873 | u32 *ipc_command = getThreadLocalStorage()->ipc_command; 874 | u32 cmd_header = ipc_command[0]; 875 | u16 cmd_id = (cmd_header >> 16) & 0xFFFF; 876 | 877 | switch(cmd_id) 878 | { 879 | case 0x0001: // dummy 880 | case 0x0401: // control 881 | case 0x0801: // open sub file 882 | case 0x0802: // read 883 | case 0x0804: // get size 884 | case 0x0805: // set size 885 | case 0x0806: // get attributes 886 | case 0x0807: // set attributes 887 | case 0x080A: // set priority 888 | case 0x080B: // get priority 889 | case 0x080C: // open link file 890 | { 891 | CHECK_HEADER(cmd_id, 0, 0) 892 | 893 | ipc_command[0] = IPC_MakeHeader(cmd_id, 1, 0); 894 | ipc_command[1] = AM_PIPE_UNSUPPORTED_ACTION; 895 | } 896 | break; 897 | case 0x0808: // close 898 | case 0x0809: // flush 899 | { 900 | ipc_command[0] = IPC_MakeHeader(cmd_id, 1, 0); 901 | ipc_command[1] = 0; 902 | } 903 | break; 904 | case 0x0803: // write 905 | { 906 | CHECK_HEADER(0x0803, 4, 2) 907 | 908 | u32 written; 909 | u64 offset = *((u64 *)&ipc_command[1]); 910 | u32 size = ipc_command[3]; 911 | u32 flags = ipc_command[4]; 912 | 913 | CHECK_WRONGARG 914 | ( 915 | !IPC_VerifyBuffer(ipc_command[5], IPC_BUFFER_R) || 916 | IPC_GetBufferSize(ipc_command[5]) != size 917 | ) 918 | 919 | void *buf = (void *)ipc_command[6]; 920 | 921 | Result res = g_PipeManager.write(g_PipeManager.data, offset, size, flags, buf, &written); 922 | 923 | ipc_command[0] = IPC_MakeHeader(0x0803, 2, 2); 924 | ipc_command[1] = res; 925 | ipc_command[2] = written; 926 | ipc_command[3] = IPC_Desc_Buffer(size, IPC_BUFFER_R); 927 | ipc_command[4] = (u32)buf; 928 | } 929 | break; 930 | } 931 | } --------------------------------------------------------------------------------