From 14263ddd78905d2ffc8981b0649adeb7dc6390ae Mon Sep 17 00:00:00 2001 From: yellows8 Date: Tue, 20 Feb 2018 18:14:21 -0500 Subject: [PATCH 01/24] Initial time support, timezones are not handled yet. --- nx/include/switch.h | 1 + nx/include/switch/result.h | 1 + nx/include/switch/services/time.h | 24 +++++ nx/source/runtime/init.c | 6 ++ nx/source/runtime/newlib.c | 29 ++++++ nx/source/services/time.c | 143 ++++++++++++++++++++++++++++++ 6 files changed, 204 insertions(+) create mode 100644 nx/include/switch/services/time.h create mode 100644 nx/source/services/time.c diff --git a/nx/include/switch.h b/nx/include/switch.h index 006fa1f0..2745edc7 100644 --- a/nx/include/switch.h +++ b/nx/include/switch.h @@ -35,6 +35,7 @@ extern "C" { #include "switch/services/audout.h" #include "switch/services/bsd.h" #include "switch/services/fatal.h" +#include "switch/services/time.h" #include "switch/services/usb.h" #include "switch/services/hid.h" #include "switch/services/irs.h" diff --git a/nx/include/switch/result.h b/nx/include/switch/result.h index 3827152d..3299aa6e 100644 --- a/nx/include/switch/result.h +++ b/nx/include/switch/result.h @@ -64,6 +64,7 @@ enum { LibnxError_JitUnavailable, LibnxError_WeirdKernel, LibnxError_IncompatSysVer, + LibnxError_InitFail_Time, }; /// libnx nvidia error codes diff --git a/nx/include/switch/services/time.h b/nx/include/switch/services/time.h new file mode 100644 index 00000000..eb3ed396 --- /dev/null +++ b/nx/include/switch/services/time.h @@ -0,0 +1,24 @@ +/** + * @file time.h + * @brief Time services IPC wrapper. + * @author yellows8 + * @copyright libnx Authors + */ +#pragma once + +#include "../types.h" +#include "../services/sm.h" + +typedef enum { + TimeType_UserSystemClock, + TimeType_NetworkSystemClock, + TimeType_LocalSystemClock, + TimeType_Default = TimeType_NetworkSystemClock, +} TimeType; + +Result timeInitialize(void); +void timeExit(void); + +Service* timeGetSessionService(void); + +Result timeGetCurrentTime(TimeType type, u64 *timestamp); diff --git a/nx/source/runtime/init.c b/nx/source/runtime/init.c index d3224465..d034c67b 100644 --- a/nx/source/runtime/init.c +++ b/nx/source/runtime/init.c @@ -4,6 +4,7 @@ #include "services/fatal.h" #include "services/fs.h" #include "services/hid.h" +#include "services/time.h" #include "services/applet.h" #include "runtime/devices/fs_dev.h" @@ -101,6 +102,10 @@ void __attribute__((weak)) __appInit(void) fatalSimple(MAKERESULT(Module_Libnx, LibnxError_InitFail_HID)); } + rc = timeInitialize(); + if (R_FAILED(rc)) + fatalSimple(MAKERESULT(Module_Libnx, LibnxError_InitFail_Time)); + rc = fsInitialize(); if (R_FAILED(rc)) fatalSimple(MAKERESULT(Module_Libnx, LibnxError_InitFail_FS)); @@ -113,6 +118,7 @@ void __attribute__((weak)) __appExit(void) // Cleanup default services. fsdevExit(); fsExit(); + timeExit(); hidExit(); appletExit(); smExit(); diff --git a/nx/source/runtime/newlib.c b/nx/source/runtime/newlib.c index dccc888e..561ef34e 100644 --- a/nx/source/runtime/newlib.c +++ b/nx/source/runtime/newlib.c @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -8,6 +9,7 @@ #include "runtime/env.h" #include "kernel/mutex.h" #include "services/fatal.h" +#include "services/time.h" #include "result.h" void __attribute__((weak)) NORETURN __libnx_exit(int rc); @@ -16,6 +18,9 @@ extern const u8 __tdata_lma[]; extern const u8 __tdata_lma_end[]; extern u8 __tls_start[]; +/// TimeType passed to timeGetCurrentTime() by __libnx_gtod(). +__attribute__((weak)) TimeType __nx_time_type = TimeType_Default; + static struct _reent* __libnx_get_reent(void) { ThreadVars* tv = getThreadVars(); if (tv->magic != THREADVARS_MAGIC) @@ -23,9 +28,33 @@ static struct _reent* __libnx_get_reent(void) { return tv->reent; } +int __libnx_gtod(struct _reent *ptr, struct timeval *tp, struct timezone *tz) { + if (tp != NULL) { + u64 now=0; + Result rc=0; + + rc = timeGetCurrentTime(__nx_time_type, &now); + if (R_FAILED(rc)) { + ptr->_errno = EINVAL; + return -1; + } + + tp->tv_sec = now; + tp->tv_usec = 0;//timeGetCurrentTime() only returns seconds. + } + + if (tz != NULL) {//TODO: This needs handled properly, timeGetCurrentTime() returns UTC time. + tz->tz_minuteswest = 0; + tz->tz_dsttime = 0; + } + + return 0; +} + void newlibSetup(void) { // Register newlib syscalls __syscalls.exit = __libnx_exit; + __syscalls.gettod_r = __libnx_gtod; __syscalls.getreent = __libnx_get_reent; // Register locking syscalls diff --git a/nx/source/services/time.c b/nx/source/services/time.c new file mode 100644 index 00000000..30e502e1 --- /dev/null +++ b/nx/source/services/time.c @@ -0,0 +1,143 @@ +#include +#include "types.h" +#include "result.h" +#include "ipc.h" +#include "services/time.h" +#include "services/sm.h" + +static Service g_timeSrv; +static Service g_timeUserSystemClock; +static Service g_timeNetworkSystemClock; +static Service g_timeTimeZoneService; +static Service g_timeLocalSystemClock; + +static Result _timeGetSession(Service* srv_out, u64 cmd_id); + +Result timeInitialize(void) +{ + if (serviceIsActive(&g_timeSrv)) + return MAKERESULT(Module_Libnx, LibnxError_AlreadyInitialized); + + Result rc; + + rc = smGetService(&g_timeSrv, "time:u"); + if (R_FAILED(rc)) + return rc; + + rc = _timeGetSession(&g_timeUserSystemClock, 0); + + if (R_SUCCEEDED(rc)) + rc = _timeGetSession(&g_timeNetworkSystemClock, 1); + + if (R_SUCCEEDED(rc)) + rc = _timeGetSession(&g_timeTimeZoneService, 3); + + if (R_SUCCEEDED(rc)) + rc = _timeGetSession(&g_timeLocalSystemClock, 4); + + if (R_FAILED(rc)) + timeExit(); + + return rc; +} + +void timeExit(void) +{ + if (!serviceIsActive(&g_timeSrv)) + return; + + serviceClose(&g_timeLocalSystemClock); + serviceClose(&g_timeTimeZoneService); + serviceClose(&g_timeNetworkSystemClock); + serviceClose(&g_timeUserSystemClock); + serviceClose(&g_timeSrv); +} + +Service* timeGetSessionService(void) { + return &g_timeSrv; +} + +static Result _timeGetSession(Service* srv_out, u64 cmd_id) { + IpcCommand c; + ipcInitialize(&c); + + struct { + u64 magic; + u64 cmd_id; + } *raw; + + raw = ipcPrepareHeader(&c, sizeof(*raw)); + + raw->magic = SFCI_MAGIC; + raw->cmd_id = cmd_id; + + Result rc = serviceIpcDispatch(&g_timeSrv); + + if (R_SUCCEEDED(rc)) { + IpcParsedCommand r; + ipcParse(&r); + + struct { + u64 magic; + u64 result; + } *resp = r.Raw; + + rc = resp->result; + + if (R_SUCCEEDED(rc)) { + serviceCreate(srv_out, r.Handles[0]); + } + } + + return rc; +} + +Result timeGetCurrentTime(TimeType type, u64 *timestamp) { + Service *srv = NULL; + + if (type==TimeType_UserSystemClock) { + srv = &g_timeUserSystemClock; + } + else if (type==TimeType_NetworkSystemClock) { + srv = &g_timeNetworkSystemClock; + } + else if (type==TimeType_LocalSystemClock) { + srv = &g_timeLocalSystemClock; + } + else { + return MAKERESULT(Module_Libnx, LibnxError_BadInput); + } + + IpcCommand c; + ipcInitialize(&c); + + struct { + u64 magic; + u64 cmd_id; + } *raw; + + raw = ipcPrepareHeader(&c, sizeof(*raw)); + + raw->magic = SFCI_MAGIC; + raw->cmd_id = 0; + + Result rc = serviceIpcDispatch(srv); + + if (R_SUCCEEDED(rc)) { + IpcParsedCommand r; + ipcParse(&r); + + struct { + u64 magic; + u64 result; + u64 timestamp; + } *resp = r.Raw; + + rc = resp->result; + + if (R_SUCCEEDED(rc) && timestamp) *timestamp = resp->timestamp; + } + + return rc; +} + From 842ce50c2ffec9201315d7feb8452f29b882f095 Mon Sep 17 00:00:00 2001 From: plutoo Date: Wed, 21 Feb 2018 03:05:15 +0100 Subject: [PATCH 02/24] Condvar fix --- nx/source/kernel/condvar.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nx/source/kernel/condvar.c b/nx/source/kernel/condvar.c index 6e474b18..d3be0ed2 100644 --- a/nx/source/kernel/condvar.c +++ b/nx/source/kernel/condvar.c @@ -13,7 +13,7 @@ void condvarInit(CondVar* c, Mutex* m) { Result condvarWaitTimeout(CondVar* c, u64 timeout) { Result rc; - rc = svcWaitProcessWideKeyAtomic(&c->tag, (u32*) c->mutex, getThreadVars()->handle, timeout); + rc = svcWaitProcessWideKeyAtomic((u32*) c->mutex, &c->tag, getThreadVars()->handle, timeout); // On timeout, we need to acquire it manually. if (rc == 0xEA01) From 0dcaeec9592434780b0975a76f92ef6f58b5116c Mon Sep 17 00:00:00 2001 From: yellows8 Date: Tue, 20 Feb 2018 22:24:35 -0500 Subject: [PATCH 03/24] In __libnx_gtod() set tv_usec, and moved+updated the timezones comment. --- nx/source/runtime/newlib.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/nx/source/runtime/newlib.c b/nx/source/runtime/newlib.c index 561ef34e..43463d4b 100644 --- a/nx/source/runtime/newlib.c +++ b/nx/source/runtime/newlib.c @@ -28,6 +28,8 @@ static struct _reent* __libnx_get_reent(void) { return tv->reent; } +//TODO: timeGetCurrentTime() returns UTC time. How to handle timezones? + int __libnx_gtod(struct _reent *ptr, struct timeval *tp, struct timezone *tz) { if (tp != NULL) { u64 now=0; @@ -40,10 +42,10 @@ int __libnx_gtod(struct _reent *ptr, struct timeval *tp, struct timezone *tz) { } tp->tv_sec = now; - tp->tv_usec = 0;//timeGetCurrentTime() only returns seconds. + tp->tv_usec = now*1000000;//timeGetCurrentTime() only returns seconds. } - if (tz != NULL) {//TODO: This needs handled properly, timeGetCurrentTime() returns UTC time. + if (tz != NULL) { tz->tz_minuteswest = 0; tz->tz_dsttime = 0; } From d61154f0e5be9e34faf7889e4f782e954d73fa1b Mon Sep 17 00:00:00 2001 From: yellows8 Date: Wed, 21 Feb 2018 00:26:21 -0500 Subject: [PATCH 04/24] Added timeSetCurrentTime() and attempt to initialize time-serv with 'time:s' first. --- nx/include/switch/services/time.h | 9 +++++ nx/source/services/time.c | 62 +++++++++++++++++++++++++++---- 2 files changed, 63 insertions(+), 8 deletions(-) diff --git a/nx/include/switch/services/time.h b/nx/include/switch/services/time.h index eb3ed396..0ef21a09 100644 --- a/nx/include/switch/services/time.h +++ b/nx/include/switch/services/time.h @@ -9,6 +9,7 @@ #include "../types.h" #include "../services/sm.h" +/// Time clock type. typedef enum { TimeType_UserSystemClock, TimeType_NetworkSystemClock, @@ -22,3 +23,11 @@ void timeExit(void); Service* timeGetSessionService(void); Result timeGetCurrentTime(TimeType type, u64 *timestamp); + +/** + * @brief Sets the time for the specified clock. + * @param[in] type Clock to use. + * @param[in] timestamp POSIX UTC timestamp. + * @return Result code. + */ +Result timeSetCurrentTime(TimeType type, u64 timestamp); diff --git a/nx/source/services/time.c b/nx/source/services/time.c index 30e502e1..67cc89c0 100644 --- a/nx/source/services/time.c +++ b/nx/source/services/time.c @@ -20,7 +20,10 @@ Result timeInitialize(void) Result rc; - rc = smGetService(&g_timeSrv, "time:u"); + rc = smGetService(&g_timeSrv, "time:s"); + if (R_FAILED(rc)) + rc = smGetService(&g_timeSrv, "time:u"); + if (R_FAILED(rc)) return rc; @@ -92,21 +95,26 @@ static Result _timeGetSession(Service* srv_out, u64 cmd_id) { return rc; } -Result timeGetCurrentTime(TimeType type, u64 *timestamp) { - Service *srv = NULL; - +static Service* _timeGetClockSession(TimeType type) { if (type==TimeType_UserSystemClock) { - srv = &g_timeUserSystemClock; + return &g_timeUserSystemClock; } else if (type==TimeType_NetworkSystemClock) { - srv = &g_timeNetworkSystemClock; + return &g_timeNetworkSystemClock; } else if (type==TimeType_LocalSystemClock) { - srv = &g_timeLocalSystemClock; + return &g_timeLocalSystemClock; } else { - return MAKERESULT(Module_Libnx, LibnxError_BadInput); + return NULL; } +} + +Result timeGetCurrentTime(TimeType type, u64 *timestamp) { + Service *srv = _timeGetClockSession(type); + + if (srv==NULL) + return MAKERESULT(Module_Libnx, LibnxError_BadInput); IpcCommand c; ipcInitialize(&c); @@ -141,3 +149,41 @@ Result timeGetCurrentTime(TimeType type, u64 *timestamp) { return rc; } +Result timeSetCurrentTime(TimeType type, u64 timestamp) { + Service *srv = _timeGetClockSession(type); + + if (srv==NULL) + return MAKERESULT(Module_Libnx, LibnxError_BadInput); + + IpcCommand c; + ipcInitialize(&c); + + struct { + u64 magic; + u64 cmd_id; + u64 timestamp; + } *raw; + + raw = ipcPrepareHeader(&c, sizeof(*raw)); + + raw->magic = SFCI_MAGIC; + raw->cmd_id = 1; + raw->timestamp = timestamp; + + Result rc = serviceIpcDispatch(srv); + + if (R_SUCCEEDED(rc)) { + IpcParsedCommand r; + ipcParse(&r); + + struct { + u64 magic; + u64 result; + } *resp = r.Raw; + + rc = resp->result; + } + + return rc; +} + From e25aae5a974a6fc300b217f32bcca2f1f812af85 Mon Sep 17 00:00:00 2001 From: plutoo Date: Wed, 21 Feb 2018 08:38:22 +0100 Subject: [PATCH 05/24] Workaround for burn in issue --- nx/include/switch/runtime/devices/console.h | 1 + nx/source/runtime/devices/console.c | 23 +++++++++++++++++---- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/nx/include/switch/runtime/devices/console.h b/nx/include/switch/runtime/devices/console.h index 7c7f3726..9ed7b748 100644 --- a/nx/include/switch/runtime/devices/console.h +++ b/nx/include/switch/runtime/devices/console.h @@ -75,6 +75,7 @@ typedef struct PrintConsole ConsoleFont font; ///< Font of the console u32 *frameBuffer; ///< Framebuffer address + u32 *frameBuffer2; ///< Framebuffer address int cursorX; ///< Current X location of the cursor (as a tile offset by default) int cursorY; ///< Current Y location of the cursor (as a tile offset by default) diff --git a/nx/source/runtime/devices/console.c b/nx/source/runtime/devices/console.c index 8ab6b144..eba57fa6 100644 --- a/nx/source/runtime/devices/console.c +++ b/nx/source/runtime/devices/console.c @@ -47,6 +47,7 @@ PrintConsole defaultConsole = 256 //number of characters in the font set }, (u32*)NULL, + (u32*)NULL, 0,0, //cursorX cursorY 0,0, //prevcursorX prevcursorY 160, //console width @@ -123,6 +124,8 @@ static void consoleCls(char mode) { } } gfxFlushBuffers(); + gfxSwapBuffers(); + gfxWaitForVsync(); } //--------------------------------------------------------------------------------- static void consoleClearLine(char mode) { @@ -176,6 +179,8 @@ static void consoleClearLine(char mode) { } } gfxFlushBuffers(); + gfxSwapBuffers(); + gfxWaitForVsync(); } @@ -547,13 +552,16 @@ PrintConsole* consoleInit(PrintConsole* console) { console->consoleInitialised = 1; - gfxSetMode(GfxMode_TiledSingle); + gfxSetMode(GfxMode_TiledDouble); + + console->frameBuffer = (u32*)gfxGetFramebuffer(NULL, NULL); + gfxSwapBuffers(); + console->frameBuffer2 = (u32*)gfxGetFramebuffer(NULL, NULL); + gfxFlushBuffers(); + gfxSwapBuffers(); gfxWaitForVsync(); - console->frameBuffer = (u32*)gfxGetFramebuffer(NULL, NULL); - - consoleCls('2'); return currentConsole; @@ -625,6 +633,9 @@ static void newRow(void) { to = ¤tConsole->frameBuffer[gfxGetFramebufferDisplayOffset(x + i, y + j)]; from = ¤tConsole->frameBuffer[gfxGetFramebufferDisplayOffset(x + i, y + 8 + j)]; *to = *from; + to = ¤tConsole->frameBuffer2[gfxGetFramebufferDisplayOffset(x + i, y + j)]; + from = ¤tConsole->frameBuffer2[gfxGetFramebufferDisplayOffset(x + i, y + 8 + j)]; + *to = *from; } } @@ -676,6 +687,8 @@ void consoleDrawChar(int c) { for (j=0;j<8;j++) { screen = ¤tConsole->frameBuffer[gfxGetFramebufferDisplayOffset(x + i, y + j)]; if (bval >> (8*j) & mask) { *screen = fg; }else{ *screen = bg; } + screen = ¤tConsole->frameBuffer2[gfxGetFramebufferDisplayOffset(x + i, y + j)]; + if (bval >> (8*j) & mask) { *screen = fg; }else{ *screen = bg; } } mask >>= 1; } @@ -730,6 +743,8 @@ void consolePrintChar(int c) { case 13: currentConsole->cursorX = 0; gfxFlushBuffers(); + gfxSwapBuffers(); + gfxWaitForVsync(); break; default: consoleDrawChar(c); From 1b255bfe15ecf3965720b9d39a1fd65d318e95f1 Mon Sep 17 00:00:00 2001 From: shinyquagsire23 Date: Tue, 20 Feb 2018 21:17:11 -0700 Subject: [PATCH 06/24] Add usleep --- nx/source/runtime/newlib.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/nx/source/runtime/newlib.c b/nx/source/runtime/newlib.c index 43463d4b..08238cff 100644 --- a/nx/source/runtime/newlib.c +++ b/nx/source/runtime/newlib.c @@ -53,6 +53,12 @@ int __libnx_gtod(struct _reent *ptr, struct timeval *tp, struct timezone *tz) { return 0; } +int usleep(useconds_t useconds) +{ + svcSleepThread(useconds * 1000ull); + return 0; +} + void newlibSetup(void) { // Register newlib syscalls __syscalls.exit = __libnx_exit; From 954a48b8fef7b65ff69e74c8b9c0551e8f7147ea Mon Sep 17 00:00:00 2001 From: shinyquagsire23 Date: Wed, 21 Feb 2018 06:18:46 -0700 Subject: [PATCH 07/24] Add hidMouseRead, allows mouse position/velocity/scrollwheel velocity to be fetched --- nx/include/switch/services/hid.h | 18 ++++++++++++------ nx/source/services/hid.c | 6 ++++++ 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/nx/include/switch/services/hid.h b/nx/include/switch/services/hid.h index d29a58ad..2c0edde7 100644 --- a/nx/include/switch/services/hid.h +++ b/nx/include/switch/services/hid.h @@ -328,6 +328,16 @@ typedef struct JoystickPosition s32 dy; } JoystickPosition; +typedef struct MousePosition +{ + u32 x; + u32 y; + u32 velocityX; + u32 velocityY; + u32 scrollVelocityX; + u32 scrollVelocityY; +} MousePosition; + #define JOYSTICK_MAX (0x8000) #define JOYSTICK_MIN (-0x8000) @@ -399,12 +409,7 @@ typedef struct HidMouseEntry { u64 timestamp; u64 timestamp_2; - u32 x; - u32 y; - u32 velocityX; - u32 velocityY; - u32 scrollVelocityX; - u32 scrollVelocityY; + MousePosition position; u64 buttons; } HidMouseEntry; static_assert(sizeof(HidMouseEntry) == 0x30, "Hid mouse entry structure has incorrect size"); @@ -552,6 +557,7 @@ u64 hidKeysUp(HidControllerID id); u64 hidMouseButtonsHeld(void); u64 hidMouseButtonsDown(void); u64 hidMouseButtonsUp(void); +void hidMouseRead(MousePosition *pos); bool hidKeyboardModifierHeld(HidKeyboardModifier modifier); bool hidKeyboardModifierDown(HidKeyboardModifier modifier); diff --git a/nx/source/services/hid.c b/nx/source/services/hid.c index 2d60daca..cad32029 100644 --- a/nx/source/services/hid.c +++ b/nx/source/services/hid.c @@ -267,6 +267,12 @@ u64 hidMouseButtonsUp(void) { return tmp; } +void hidMouseRead(MousePosition *pos) { + rwlockReadLock(&g_hidLock); + *pos = g_mouseEntry.position; + rwlockReadUnlock(&g_hidLock); +} + bool hidKeyboardModifierHeld(HidKeyboardModifier modifier) { rwlockReadLock(&g_hidLock); bool tmp = g_keyboardModHeld & modifier; From 60876ef3f38dfa387e79237180d4214aef103ea0 Mon Sep 17 00:00:00 2001 From: plutoo Date: Thu, 22 Feb 2018 08:43:56 +0100 Subject: [PATCH 08/24] Eyeballin set:sys --- nx/include/switch.h | 1 + nx/include/switch/services/set.h | 13 ++++++++ nx/source/services/set.c | 56 ++++++++++++++++++++++++++++++++ 3 files changed, 70 insertions(+) create mode 100644 nx/include/switch/services/set.h create mode 100644 nx/source/services/set.c diff --git a/nx/include/switch.h b/nx/include/switch.h index 2745edc7..b687f14c 100644 --- a/nx/include/switch.h +++ b/nx/include/switch.h @@ -42,6 +42,7 @@ extern "C" { #include "switch/services/vi.h" #include "switch/services/nv.h" #include "switch/services/pm.h" +#include "switch/services/set.h" #include "switch/gfx/gfx.h" #include "switch/gfx/binder.h" diff --git a/nx/include/switch/services/set.h b/nx/include/switch/services/set.h new file mode 100644 index 00000000..c21ae4ee --- /dev/null +++ b/nx/include/switch/services/set.h @@ -0,0 +1,13 @@ +// Copyright 2018 plutoo +#include "result.h" + +typedef enum { + ColorSetId_Light=0, + ColorSetId_Dark=1 +} ColorSetId; + +Result setsysInitialize(void); +void setsysExit(void); + +/// Gets the current system theme. +Result setsysGetColorSetId(ColorSetId* out); diff --git a/nx/source/services/set.c b/nx/source/services/set.c new file mode 100644 index 00000000..3953b722 --- /dev/null +++ b/nx/source/services/set.c @@ -0,0 +1,56 @@ +// Copyright 2018 plutoo +#include "types.h" +#include "result.h" +#include "ipc.h" +#include "services/set.h" +#include "services/sm.h" + +static Service g_setsysSrv; + +Result setsysInitialize(void) +{ + if (serviceIsActive(&g_setsysSrv)) + return MAKERESULT(Module_Libnx, LibnxError_AlreadyInitialized); + + return smGetService(&g_setsysSrv, "set:sys"); +} + +void setsysExit(void) +{ + serviceClose(&g_setsysSrv); +} + +Result setsysGetColorSetId(ColorSetId* out) +{ + IpcCommand c; + ipcInitialize(&c); + + struct { + u64 magic; + u64 cmd_id; + } *raw; + + raw = ipcPrepareHeader(&c, sizeof(*raw)); + + raw->magic = SFCI_MAGIC; + raw->cmd_id = 23; + + Result rc = serviceIpcDispatch(&g_setsysSrv); + + if (R_SUCCEEDED(rc)) { + IpcParsedCommand r; + ipcParse(&r); + + struct { + u64 magic; + u64 result; + u32 color_set; + } *resp = r.Raw; + + *out = resp->color_set; + rc = resp->result; + } + + return rc; + +} From ff8a95b47d2685b2f9742145b11a8bc5459a2d6a Mon Sep 17 00:00:00 2001 From: plutoo Date: Thu, 22 Feb 2018 08:48:05 +0100 Subject: [PATCH 09/24] Headerfail --- nx/include/switch/services/set.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nx/include/switch/services/set.h b/nx/include/switch/services/set.h index c21ae4ee..97c10697 100644 --- a/nx/include/switch/services/set.h +++ b/nx/include/switch/services/set.h @@ -1,5 +1,5 @@ // Copyright 2018 plutoo -#include "result.h" +#include "../result.h" typedef enum { ColorSetId_Light=0, From 3c4c35e6e5cf23310212bfd4719139175fb54784 Mon Sep 17 00:00:00 2001 From: yellows8 Date: Thu, 22 Feb 2018 20:31:51 -0500 Subject: [PATCH 10/24] Added gfxSetDrawFlip() and gfxConfigureTransform(). --- nx/include/switch/gfx/gfx.h | 9 ++++++++- nx/source/gfx/gfx.c | 14 +++++++++++++- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/nx/include/switch/gfx/gfx.h b/nx/include/switch/gfx/gfx.h index f1c93e64..8a39503c 100644 --- a/nx/include/switch/gfx/gfx.h +++ b/nx/include/switch/gfx/gfx.h @@ -83,6 +83,12 @@ size_t gfxGetFramebufferSize(void); /// Sets the \ref GfxMode. void gfxSetMode(GfxMode mode); +/// Controls whether a vertical-flip is done when determining the pixel-offset within the actual framebuffer. By default this is enabled. +void gfxSetDrawFlip(bool flip); + +/// Configures transform. See the NATIVE_WINDOW_TRANSFORM_* enums in buffer_producer.h. The default is NATIVE_WINDOW_TRANSFORM_FLIP_V. +void gfxConfigureTransform(u32 transform); + /// Flushes the framebuffer in the data cache. When \ref GfxMode is GfxMode_LinearDouble, this also transfers the linear-framebuffer to the actual framebuffer. void gfxFlushBuffers(void); @@ -94,10 +100,11 @@ static inline u32 gfxGetFramebufferDisplayOffset(u32 x, u32 y) { extern size_t g_gfx_framebuf_aligned_width; extern size_t g_gfx_framebuf_display_height; + extern bool g_gfx_drawflip; //if (x >= g_gfx_framebuf_width || y >= g_gfx_framebuf_display_height) return (gfxGetFramebufferSize()-4)/4;//Return the last pixel-offset in the buffer, the data located here is not displayed due to alignment. (Disabled for perf) - y = g_gfx_framebuf_display_height-1-y; + if (g_gfx_drawflip) y = g_gfx_framebuf_display_height-1-y; tmp_pos = ((y & 127) / 16) + (x/16*8) + ((y/16/8)*(g_gfx_framebuf_aligned_width/16*8)); tmp_pos *= 16*16 * 4; diff --git a/nx/source/gfx/gfx.c b/nx/source/gfx/gfx.c index a70ae5fe..8bfa2208 100644 --- a/nx/source/gfx/gfx.c +++ b/nx/source/gfx/gfx.c @@ -40,6 +40,8 @@ size_t g_gfx_framebuf_display_width=0, g_gfx_framebuf_display_height=0; size_t g_gfx_singleframebuf_size=0; size_t g_gfx_singleframebuf_linear_size=0; +bool g_gfx_drawflip = true; + static AppletHookCookie g_gfx_autoresolution_applethookcookie; static bool g_gfx_autoresolution_enabled; @@ -55,7 +57,6 @@ extern nvioctl_fence g_nvgfx_nvhostgpu_gpfifo_fence; //static Result _gfxGetDisplayResolution(u64 *width, u64 *height); -//TODO: Let the user configure some of this? static bufferProducerQueueBufferInput g_gfxQueueBufferData = { .timestamp = 0x0, .isAutoTimestamp = 0x1, @@ -178,6 +179,9 @@ static Result _gfxInit(ViServiceType servicetype, const char *DisplayName, u32 L g_gfxFramebufSize = 0; g_gfxMode = GfxMode_LinearDouble; + g_gfx_drawflip = true; + g_gfxQueueBufferData.transform = NATIVE_WINDOW_TRANSFORM_FLIP_V; + memset(g_gfx_ProducerSlotsRequested, 0, sizeof(g_gfx_ProducerSlotsRequested)); memset(&g_gfx_DequeueBuffer_fence, 0, sizeof(g_gfx_DequeueBuffer_fence)); @@ -539,6 +543,14 @@ void gfxSetMode(GfxMode mode) { g_gfxMode = mode; } +void gfxSetDrawFlip(bool flip) { + g_gfx_drawflip = flip; +} + +void gfxConfigureTransform(u32 transform) { + g_gfxQueueBufferData.transform = transform; +} + void gfxFlushBuffers(void) { u32 *actual_framebuf = (u32*)&g_gfxFramebuf[g_gfxCurrentBuffer*g_gfx_singleframebuf_size]; From 2022d545dc7e742360bffee4c209ac70dcf26d1c Mon Sep 17 00:00:00 2001 From: yellows8 Date: Thu, 22 Feb 2018 21:49:55 -0500 Subject: [PATCH 11/24] Store pixel-format in a global instead of hard-coding it and added a disabled func for setting it. Added a pixel-format comment to gfx.h. --- nx/include/switch/gfx/gfx.h | 2 ++ nx/source/gfx/gfx.c | 9 ++++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/nx/include/switch/gfx/gfx.h b/nx/include/switch/gfx/gfx.h index 8a39503c..a7555f03 100644 --- a/nx/include/switch/gfx/gfx.h +++ b/nx/include/switch/gfx/gfx.h @@ -22,6 +22,8 @@ typedef enum GfxMode_LinearDouble ///< Double-buffering with linear framebuffer, which is transferred to the actual framebuffer by \ref gfxFlushBuffers(). } GfxMode; +/// Framebuffer pixel-format is RGBA8888, there's no known way to change this. + /** * @brief Initializes the graphics subsystem. * @warning Do not use \ref viInitialize when using this function. diff --git a/nx/source/gfx/gfx.c b/nx/source/gfx/gfx.c index 8bfa2208..c93e2b53 100644 --- a/nx/source/gfx/gfx.c +++ b/nx/source/gfx/gfx.c @@ -34,6 +34,8 @@ static GfxMode g_gfxMode = GfxMode_LinearDouble; static u8 *g_gfxFramebufLinear; +static s32 g_gfxPixelFormat = 0; + size_t g_gfx_framebuf_width=0, g_gfx_framebuf_aligned_width=0; size_t g_gfx_framebuf_height=0, g_gfx_framebuf_aligned_height=0; size_t g_gfx_framebuf_display_width=0, g_gfx_framebuf_display_height=0; @@ -136,7 +138,7 @@ static Result _gfxDequeueBuffer(void) { memcpy(&tmp_fence, fence, sizeof(bufferProducerFence));//Offical sw waits on the fence from the previous DequeueBuffer call. Using the fence from the current DequeueBuffer call results in nvgfxEventWait() failing. - rc = bufferProducerDequeueBuffer(async, g_gfx_framebuf_width, g_gfx_framebuf_height, 0, 0x300, &g_gfxCurrentProducerBuffer, fence); + rc = bufferProducerDequeueBuffer(async, g_gfx_framebuf_width, g_gfx_framebuf_height, g_gfxPixelFormat, 0x300, &g_gfxCurrentProducerBuffer, fence); //Only run nvgfxEventWait when the fence is valid and the id is not NO_FENCE. if (R_SUCCEEDED(rc) && tmp_fence.is_valid && tmp_fence.nv_fences[0].id!=0xffffffff) rc = nvgfxEventWait(tmp_fence.nv_fences[0].id, tmp_fence.nv_fences[0].value, -1); @@ -181,6 +183,7 @@ static Result _gfxInit(ViServiceType servicetype, const char *DisplayName, u32 L g_gfx_drawflip = true; g_gfxQueueBufferData.transform = NATIVE_WINDOW_TRANSFORM_FLIP_V; + g_gfxPixelFormat = 0; memset(g_gfx_ProducerSlotsRequested, 0, sizeof(g_gfx_ProducerSlotsRequested)); memset(&g_gfx_DequeueBuffer_fence, 0, sizeof(g_gfx_DequeueBuffer_fence)); @@ -551,6 +554,10 @@ void gfxConfigureTransform(u32 transform) { g_gfxQueueBufferData.transform = transform; } +/*void gfxSetPixelFormat(s32 format) { + g_gfxPixelFormat = format; +}*/ + void gfxFlushBuffers(void) { u32 *actual_framebuf = (u32*)&g_gfxFramebuf[g_gfxCurrentBuffer*g_gfx_singleframebuf_size]; From 3d0ae50a8987125e367c348835d89a2a74214bd2 Mon Sep 17 00:00:00 2001 From: yellows8 Date: Fri, 23 Feb 2018 12:59:51 -0500 Subject: [PATCH 12/24] Updated comment for the GfxMode used by console. --- nx/include/switch/gfx/gfx.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nx/include/switch/gfx/gfx.h b/nx/include/switch/gfx/gfx.h index a7555f03..590f909e 100644 --- a/nx/include/switch/gfx/gfx.h +++ b/nx/include/switch/gfx/gfx.h @@ -14,7 +14,7 @@ /// Same as \ref RGBA8 except with alpha=0xff. #define RGBA8_MAXALPHA(r,g,b) RGBA8(r,g,b,0xff) -/// GfxMode set by \ref gfxSetMode. The default is GfxMode_LinearDouble. Note that the text-console (see console.h) sets this to GfxMode_TiledSingle. +/// GfxMode set by \ref gfxSetMode. The default is GfxMode_LinearDouble. Note that the text-console (see console.h) sets this to GfxMode_TiledDouble. typedef enum { GfxMode_TiledSingle, ///< Single-buffering with raw tiled (block-linear) framebuffer. From 9e9663968157a1cbf9c7175b342ac469ab281934 Mon Sep 17 00:00:00 2001 From: james Date: Fri, 23 Feb 2018 21:58:05 +0000 Subject: [PATCH 13/24] update default app icon --- README.md | 4 ---- nx/default_icon.jpg | Bin 7985 -> 30757 bytes 2 files changed, 4 deletions(-) diff --git a/README.md b/README.md index e7f8cad4..5bf08dbe 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,3 @@ Based on libctru. # Install instructions See [Switchbrew](http://switchbrew.org/index.php?title=Setting_up_Development_Environment). - -# Icon - -`nx/default_icon.jpg` is based on the icon by [Sweet Farm from the Noun Project](https://thenounproject.com/term/nintendo-switch/694750/). diff --git a/nx/default_icon.jpg b/nx/default_icon.jpg index e93c1d7e4c74cd4b64b50d19d09aebd527b07745..ba6c338471acca8520568df6cb9572b12b698432 100644 GIT binary patch literal 30757 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 MIDD1@% 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 From 4c1b09d6b974a94237665350419da3c504164591 Mon Sep 17 00:00:00 2001 From: yellows8 Date: Fri, 23 Feb 2018 22:29:21 -0500 Subject: [PATCH 14/24] Check for invalid handle in fs*Close(). Added fsOpenDataStorageByCurrentProcess(), fsStorageRead(), and fsStorageClose(). Added fsdevGetDefaultFileSystem(). Imported nro.h from nx-hbmenu. Implemented proper RomFS support. --- nx/include/switch.h | 3 + nx/include/switch/nro.h | 54 ++++++ nx/include/switch/runtime/devices/fs_dev.h | 3 + nx/include/switch/runtime/devices/romfs_dev.h | 14 +- nx/include/switch/services/fs.h | 5 +- nx/source/runtime/devices/fs_dev.c | 7 + nx/source/runtime/devices/romfs_dev.c | 183 +++++++++--------- nx/source/services/fs.c | 82 +++++++- 8 files changed, 251 insertions(+), 100 deletions(-) create mode 100644 nx/include/switch/nro.h diff --git a/nx/include/switch.h b/nx/include/switch.h index b687f14c..9bb1ad23 100644 --- a/nx/include/switch.h +++ b/nx/include/switch.h @@ -12,6 +12,8 @@ extern "C" { #include "switch/types.h" #include "switch/result.h" +#include "switch/nro.h" + #include "switch/kernel/svc.h" #include "switch/kernel/tmem.h" #include "switch/kernel/shmem.h" @@ -59,6 +61,7 @@ extern "C" { #include "switch/runtime/devices/console.h" #include "switch/runtime/devices/usb_comms.h" #include "switch/runtime/devices/fs_dev.h" +#include "switch/runtime/devices/romfs_dev.h" #ifdef __cplusplus } diff --git a/nx/include/switch/nro.h b/nx/include/switch/nro.h new file mode 100644 index 00000000..75876f0e --- /dev/null +++ b/nx/include/switch/nro.h @@ -0,0 +1,54 @@ +/** + * @file nro.h + * @brief NRO headers. + * @copyright libnx Authors + */ + +#pragma once + +#define NROHEADER_MAGICNUM 0x304f524e + +#define ASSETHEADER_MAGICNUM 0x54455341 +#define ASSETHEADER_VERSION 0 + +/// Entry for each segment in the codebin. +typedef struct { + u32 FileOff; + u32 Size; +} NsoSegment; + +/// Offset 0x0 in the NRO. +typedef struct { + u32 unused; + u32 modOffset; + u8 Padding[8]; +} NroStart; + +/// This follows NroStart, the actual nro-header. +typedef struct { + u32 Magic; + u32 Unk1; + u32 size; + u32 Unk2; + NsoSegment Segments[3]; + u32 bssSize; + u32 Unk3; + u8 BuildId[0x20]; + u8 Padding[0x20]; +} NroHeader; + +/// Custom asset section. +typedef struct { + u64 offset; + u64 size; +} AssetSection; + +/// Custom asset header. +typedef struct { + u32 magic; + u32 version; + AssetSection icon; + AssetSection nacp; + AssetSection romfs; +} AssetHeader; + diff --git a/nx/include/switch/runtime/devices/fs_dev.h b/nx/include/switch/runtime/devices/fs_dev.h index 76164d40..de3e05c3 100644 --- a/nx/include/switch/runtime/devices/fs_dev.h +++ b/nx/include/switch/runtime/devices/fs_dev.h @@ -38,3 +38,6 @@ int fsdevUnmountDevice(const char *name); /// Uses fsFsCommit() with the specified device. This must be used after any savedata-write operations(not just file-write). /// This is not used automatically at device unmount. Result fsdevCommitDevice(const char *name); + +/// Returns the FsFileSystem for the default device (SD card), if mounted. Used internally by romfs_dev. +FsFileSystem* fsdevGetDefaultFileSystem(void); diff --git a/nx/include/switch/runtime/devices/romfs_dev.h b/nx/include/switch/runtime/devices/romfs_dev.h index d6dee80c..f23ab5ab 100644 --- a/nx/include/switch/runtime/devices/romfs_dev.h +++ b/nx/include/switch/runtime/devices/romfs_dev.h @@ -66,7 +66,7 @@ static inline Result romfsInit(void) /** * @brief Mounts RomFS from an open file. - * @param file Handle of the RomFS file. + * @param file FsFile of the RomFS image. * @param offset Offset of the RomFS within the file. * @param mount Output mount handle */ @@ -76,6 +76,18 @@ static inline Result romfsInitFromFile(FsFile file, u64 offset) return romfsMountFromFile(file, offset, NULL); } +/** + * @brief Mounts RomFS from an open storage. + * @param storage FsStorage of the RomFS image. + * @param offset Offset of the RomFS within the storage. + * @param mount Output mount handle + */ +Result romfsMountFromStorage(FsStorage storage, u64 offset, struct romfs_mount **mount); +static inline Result romfsInitFromStorage(FsStorage storage, u64 offset) +{ + return romfsMountFromStorage(storage, offset, NULL); +} + /// Bind the RomFS mount Result romfsBind(struct romfs_mount *mount); diff --git a/nx/include/switch/services/fs.h b/nx/include/switch/services/fs.h index 2cd41499..14546685 100644 --- a/nx/include/switch/services/fs.h +++ b/nx/include/switch/services/fs.h @@ -93,6 +93,7 @@ Service* fsGetServiceSession(void); Result fsMountSdcard(FsFileSystem* out); Result fsMountSaveData(FsFileSystem* out, u8 inval, FsSave *save); +Result fsOpenDataStorageByCurrentProcess(FsStorage* out); // todo: Rest of commands here /// FsFileSystem can be mounted with fs_dev for use with stdio, see fs_dev.h. @@ -130,4 +131,6 @@ Result fsDirRead(FsDir* d, u64 inval, size_t* total_entries, size_t max_entries, Result fsDirGetEntryCount(FsDir* d, u64* count); void fsDirClose(FsDir* d); -// todo: IStorage +// IStorage +Result fsStorageRead(FsStorage* s, u64 off, void* buf, size_t len); +void fsStorageClose(FsStorage* s); diff --git a/nx/source/runtime/devices/fs_dev.c b/nx/source/runtime/devices/fs_dev.c index f5e99590..e4d787ef 100644 --- a/nx/source/runtime/devices/fs_dev.c +++ b/nx/source/runtime/devices/fs_dev.c @@ -469,6 +469,13 @@ Result fsdevExit(void) return 0; } +FsFileSystem* fsdevGetDefaultFileSystem(void) +{ + if(fsdev_fsdevice_default==-1) return NULL; + + return &fsdev_fsdevices[fsdev_fsdevice_default].fs; +} + /*! Open a file * * @param[in,out] r newlib reentrancy struct diff --git a/nx/source/runtime/devices/romfs_dev.c b/nx/source/runtime/devices/romfs_dev.c index fd2a415d..5bfd3497 100644 --- a/nx/source/runtime/devices/romfs_dev.c +++ b/nx/source/runtime/devices/romfs_dev.c @@ -9,14 +9,17 @@ #include #include "runtime/devices/romfs_dev.h" +#include "runtime/devices/fs_dev.h" #include "runtime/util/utf.h" #include "services/fs.h" - -/// WARNING: This is not ready to be used. +#include "runtime/env.h" +#include "nro.h" typedef struct romfs_mount { + bool fd_type; FsFile fd; + FsStorage fd_storage; time_t mtime; u64 offset; romfs_header header; @@ -30,7 +33,6 @@ extern int __system_argc; extern char** __system_argv; static char __component[PATH_MAX+1]; -//static uint16_t __utf16path[PATH_MAX+1]; #define romFS_root(m) ((romfs_dir*)(m)->dirTable) #define romFS_dir(m,x) ((romfs_dir*) ((u8*)(m)->dirTable + (x))) @@ -39,16 +41,25 @@ static char __component[PATH_MAX+1]; #define romFS_dir_mode (S_IFDIR | S_IRUSR | S_IRGRP | S_IROTH) #define romFS_file_mode (S_IFREG | S_IRUSR | S_IRGRP | S_IROTH) -static ssize_t _romfs_read(romfs_mount *mount, u64 offset, void* buffer, u32 size) +static ssize_t _romfs_read(romfs_mount *mount, u64 offset, void* buffer, u64 size) { u64 pos = mount->offset + offset; size_t read = 0; - Result rc = fsFileRead(&mount->fd, pos, buffer, size, &read); + Result rc = 0; + if(!mount->fd_type) + { + rc = fsFileRead(&mount->fd, pos, buffer, size, &read); + } + else + { + rc = fsStorageRead(&mount->fd_storage, pos, buffer, size); + read = size; + } if (R_FAILED(rc)) return -1; return read; } -static bool _romfs_read_chk(romfs_mount *mount, u64 offset, void* buffer, u32 size) +static bool _romfs_read_chk(romfs_mount *mount, u64 offset, void* buffer, u64 size) { return _romfs_read(mount, offset, buffer, size) == size; } @@ -104,26 +115,8 @@ static devoptab_t romFS_devoptab = //----------------------------------------------------------------------------- -// File header -/*#define _3DSX_MAGIC 0x58534433 // '3DSX' -typedef struct -{ - u32 magic; - u16 headerSize, relocHdrSize; - u32 formatVer; - u32 flags; - - // Sizes of the code, rodata and data segments + - // size of the BSS section (uninitialized latter half of the data segment) - u32 codeSegSize, rodataSegSize, dataSegSize, bssSize; - // offset and size of smdh - u32 smdhOffset, smdhSize; - // offset to filesystem - u32 fsOffset; -} _3DSX_Header;*/ - static Result romfsMountCommon(romfs_mount *mount); -//static void romfsInitMtime(romfs_mount *mount, FS_ArchiveID archId, FS_Path archPath, FS_Path filePath); +static void romfsInitMtime(romfs_mount *mount); __attribute__((weak)) const char* __romfs_path = NULL; @@ -173,9 +166,19 @@ Result romfsMount(struct romfs_mount **p) if(mount == NULL) return 99; - if (/*envIsHomebrew()*/1)//TODO: How to handle? + if (!envIsNso()) { - // RomFS appended to a 3DSX file + // RomFS embedded in a NRO + + mount->fd_type = 0; + + FsFileSystem *sdfs = fsdevGetDefaultFileSystem(); + if(sdfs==NULL) + { + romfs_free(mount); + return 1; + } + const char* filename = __romfs_path; if (__system_argc > 0 && __system_argv[0]) filename = __system_argv[0]; @@ -187,69 +190,57 @@ Result romfsMount(struct romfs_mount **p) if (strncmp(filename, "sdmc:/", 6) == 0) filename += 5; - /*else if (strncmp(filename, "3dslink:/", 9) == 0) + else if (strncmp(filename, "nxlink:/", 8) == 0) { - strncpy(__component, "/3ds", PATH_MAX); - strncat(__component, filename+8, PATH_MAX); + strncpy(__component, "/switch", PATH_MAX); + strncat(__component, filename+7, PATH_MAX); __component[PATH_MAX] = 0; filename = __component; - }*/ + } else { romfs_free(mount); return 2; } - //TODO - /*ssize_t units = utf8_to_utf16(__utf16path, (const uint8_t*)filename, PATH_MAX); - if (units < 0) - { - romfs_free(mount); - return 3; - } - if (units >= PATH_MAX) - { - romfs_free(mount); - return 4; - } - __utf16path[units] = 0; - - FS_Path archPath = { PATH_EMPTY, 1, (u8*)"" }; - FS_Path filePath = { PATH_UTF16, (units+1)*2, (u8*)__utf16path }; - - Result rc = FSUSER_OpenFileDirectly(&mount->fd, ARCHIVE_SDMC, archPath, filePath, FS_OPEN_READ, 0); + Result rc = fsFsOpenFile(sdfs, filename, FS_OPEN_READ, &mount->fd); if (R_FAILED(rc)) { romfs_free(mount); return rc; } - //romfsInitMtime(mount, ARCHIVE_SDMC, archPath, filePath); + romfsInitMtime(mount); - _3DSX_Header hdr; - if (!_romfs_read_chk(mount, 0, &hdr, sizeof(hdr))) goto _fail0; - if (hdr.magic != _3DSX_MAGIC) goto _fail0; - if (hdr.headerSize < sizeof(hdr)) goto _fail0; - mount->offset = hdr.fsOffset; - if (!mount->offset) goto _fail0;*/ + NroHeader hdr; + AssetHeader asset_header; + + if (!_romfs_read_chk(mount, sizeof(NroStart), &hdr, sizeof(hdr))) goto _fail0; + if (hdr.Magic != NROHEADER_MAGICNUM) goto _fail0; + if (!_romfs_read_chk(mount, hdr.size, &asset_header, sizeof(asset_header))) goto _fail0; + + if (asset_header.magic != ASSETHEADER_MAGICNUM + || asset_header.version > ASSETHEADER_VERSION + || asset_header.romfs.offset == 0 + || asset_header.romfs.size == 0) + goto _fail0; + + mount->offset = hdr.size + asset_header.romfs.offset; } - else//TODO + else { // Regular RomFS - /*u8 zeros[0xC]; - memset(zeros, 0, sizeof(zeros)); - FS_Path archPath = { PATH_EMPTY, 1, (u8*)"" }; - FS_Path filePath = { PATH_BINARY, sizeof(zeros), zeros }; + mount->fd_type = 1; - Result rc = FSUSER_OpenFileDirectly(&mount->fd, ARCHIVE_ROMFS, archPath, filePath, FS_OPEN_READ, 0); + Result rc = fsOpenDataStorageByCurrentProcess(&mount->fd_storage); if (R_FAILED(rc)) { romfs_free(mount); return rc; } - //romfsInitMtime(mount, ARCHIVE_ROMFS, archPath, filePath);*/ + romfsInitMtime(mount); } Result ret = romfsMountCommon(mount); @@ -258,8 +249,9 @@ Result romfsMount(struct romfs_mount **p) return ret; -//_fail0: - fsFileClose(&mount->fd); +_fail0: + if(!mount->fd_type)fsFileClose(&mount->fd); + if(mount->fd_type)fsStorageClose(&mount->fd_storage); romfs_free(mount); return 10; } @@ -270,6 +262,7 @@ Result romfsMountFromFile(FsFile file, u64 offset, struct romfs_mount **p) if(mount == NULL) return 99; + mount->fd_type = 0; mount->fd = file; mount->offset = offset; @@ -280,6 +273,23 @@ Result romfsMountFromFile(FsFile file, u64 offset, struct romfs_mount **p) return ret; } +Result romfsMountFromStorage(FsStorage storage, u64 offset, struct romfs_mount **p) +{ + romfs_mount *mount = romfs_alloc(); + if(mount == NULL) + return 99; + + mount->fd_type = 1; + mount->fd_storage = storage; + mount->offset = offset; + + Result ret = romfsMountCommon(mount); + if(R_SUCCEEDED(ret) && p) + *p = mount; + + return ret; +} + Result romfsMountCommon(romfs_mount *mount) { if (_romfs_read(mount, 0, &mount->header, sizeof(mount->header)) != sizeof(mount->header)) @@ -318,35 +328,16 @@ Result romfsMountCommon(romfs_mount *mount) return 0; fail: - fsFileClose(&mount->fd); + if(!mount->fd_type)fsFileClose(&mount->fd); + if(mount->fd_type)fsStorageClose(&mount->fd_storage); romfs_free(mount); return 10; } -/*static void romfsInitMtime(romfs_mount *mount, FS_ArchiveID archId, FS_Path archPath, FS_Path filePath) +static void romfsInitMtime(romfs_mount *mount) { - u64 mtime; - FS_Archive arch; - Result rc; - mount->mtime = time(NULL); - rc = FSUSER_OpenArchive(&arch, archId, archPath); - if (R_FAILED(rc)) - return; - - rc = FSUSER_ControlArchive(arch, ARCHIVE_ACTION_GET_TIMESTAMP, - (void*)filePath.data, filePath.size, - &mtime, sizeof(mtime)); - FSUSER_CloseArchive(arch); - if (R_FAILED(rc)) - return;*/ - - /* convert from milliseconds to seconds */ - //mtime /= 1000; - /* convert from 2000-based timestamp to UNIX timestamp */ - /*mtime += 946684800; - mount->mtime = mtime; -}*/ +} Result romfsBind(struct romfs_mount *mount) { @@ -368,7 +359,8 @@ Result romfsUnmount(struct romfs_mount *mount) if(mount) { // unmount specific - fsFileClose(&mount->fd); + if(!mount->fd_type)fsFileClose(&mount->fd); + if(mount->fd_type)fsStorageClose(&mount->fd_storage); romfs_free(mount); } else @@ -376,7 +368,8 @@ Result romfsUnmount(struct romfs_mount *mount) // unmount everything while(romfs_mount_list) { - fsFileClose(&romfs_mount_list->fd); + if(!romfs_mount_list->fd_type)fsFileClose(&romfs_mount_list->fd); + if(romfs_mount_list->fd_type)fsStorageClose(&romfs_mount_list->fd_storage); romfs_free(romfs_mount_list); } } @@ -483,7 +476,7 @@ static int navigateToDir(romfs_mount *mount, romfs_dir** ppDir, const char** pPa } } - *ppDir = searchForDir(mount, *ppDir, (uint8_t*)component, strlen(component)+1); + *ppDir = searchForDir(mount, *ppDir, (uint8_t*)component, strlen(component)); if (!*ppDir) return EEXIST; } @@ -551,7 +544,7 @@ int romfs_open(struct _reent *r, void *fileStruct, const char *path, int flags, if (r->_errno != 0) return -1; - romfs_file* file = searchForFile(fileobj->mount, curDir, (uint8_t*)path, strlen(path)+1); + romfs_file* file = searchForFile(fileobj->mount, curDir, (uint8_t*)path, strlen(path)); if (!file) { if(flags & O_CREAT) @@ -567,7 +560,7 @@ int romfs_open(struct _reent *r, void *fileStruct, const char *path, int flags, } fileobj->file = file; - fileobj->offset = (u64)fileobj->mount->header.fileDataOff + file->dataOff; + fileobj->offset = fileobj->mount->header.fileDataOff + file->dataOff; fileobj->pos = 0; return 0; @@ -669,7 +662,7 @@ int romfs_stat(struct _reent *r, const char *path, struct stat *st) if(r->_errno != 0) return -1; - romfs_dir* dir = searchForDir(mount, curDir, (uint8_t*)path, strlen(path)+1); + romfs_dir* dir = searchForDir(mount, curDir, (uint8_t*)path, strlen(path)); if(dir) { memset(st, 0, sizeof(*st)); @@ -684,7 +677,7 @@ int romfs_stat(struct _reent *r, const char *path, struct stat *st) return 0; } - romfs_file* file = searchForFile(mount, curDir, (uint8_t*)path, strlen(path)+1); + romfs_file* file = searchForFile(mount, curDir, (uint8_t*)path, strlen(path)); if(file) { memset(st, 0, sizeof(*st)); diff --git a/nx/source/services/fs.c b/nx/source/services/fs.c index f23ec654..013a5779 100644 --- a/nx/source/services/fs.c +++ b/nx/source/services/fs.c @@ -131,6 +131,41 @@ Result fsMountSaveData(FsFileSystem* out, u8 inval, FsSave *save) { return rc; } +Result fsOpenDataStorageByCurrentProcess(FsStorage* out) { + IpcCommand c; + ipcInitialize(&c); + + struct { + u64 magic; + u64 cmd_id; + } *raw; + + raw = ipcPrepareHeader(&c, sizeof(*raw)); + + raw->magic = SFCI_MAGIC; + raw->cmd_id = 200; + + Result rc = serviceIpcDispatch(&g_fsSrv); + + if (R_SUCCEEDED(rc)) { + IpcParsedCommand r; + ipcParse(&r); + + struct { + u64 magic; + u64 result; + } *resp = r.Raw; + + rc = resp->result; + + if (R_SUCCEEDED(rc)) { + out->h = r.Handles[0]; + } + } + + return rc; +} + // Wrapper(s) for fsMountSaveData. Result fsMount_SaveData(FsFileSystem* out, u64 titleID, u128 userID) { FsSave save; @@ -595,7 +630,7 @@ Result fsFsGetTotalSpace(FsFileSystem* fs, const char* path, u64* out) { } void fsFsClose(FsFileSystem* fs) { - svcCloseHandle(fs->h); + if(fs->h != INVALID_HANDLE) svcCloseHandle(fs->h); } // IFile implementation @@ -778,12 +813,12 @@ Result fsFileGetSize(FsFile* f, u64* out) { } void fsFileClose(FsFile* f) { - svcCloseHandle(f->h); + if(f->h != INVALID_HANDLE) svcCloseHandle(f->h); } // IDirectory implementation void fsDirClose(FsDir* d) { - svcCloseHandle(d->h); + if(d->h != INVALID_HANDLE) svcCloseHandle(d->h); } Result fsDirRead(FsDir* d, u64 inval, size_t* total_entries, size_t max_entries, FsDirectoryEntry *buf) { @@ -858,3 +893,44 @@ Result fsDirGetEntryCount(FsDir* d, u64* count) { return rc; } +// IStorage implementation +Result fsStorageRead(FsStorage* s, u64 off, void* buf, size_t len) { + IpcCommand c; + ipcInitialize(&c); + ipcAddRecvBuffer(&c, buf, len, 1); + + struct { + u64 magic; + u64 cmd_id; + u64 offset; + u64 read_size; + } *raw; + + raw = ipcPrepareHeader(&c, sizeof(*raw)); + + raw->magic = SFCI_MAGIC; + raw->cmd_id = 0; + raw->offset = off; + raw->read_size = len; + + Result rc = ipcDispatch(s->h); + + if (R_SUCCEEDED(rc)) { + IpcParsedCommand r; + ipcParse(&r); + + struct { + u64 magic; + u64 result; + } *resp = r.Raw; + + rc = resp->result; + } + + return rc; +} + +void fsStorageClose(FsStorage* s) { + if(s->h != INVALID_HANDLE) svcCloseHandle(s->h); +} + From e24c20f9e713983faddd168079f0b1a178198e60 Mon Sep 17 00:00:00 2001 From: yellows8 Date: Sat, 24 Feb 2018 15:39:30 -0500 Subject: [PATCH 15/24] Added Nro prefix to Asset in nro.h + updated romfs_dev for this. --- nx/include/switch/nro.h | 14 +++++++------- nx/source/runtime/devices/romfs_dev.c | 6 +++--- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/nx/include/switch/nro.h b/nx/include/switch/nro.h index 75876f0e..f2c9dfb6 100644 --- a/nx/include/switch/nro.h +++ b/nx/include/switch/nro.h @@ -8,8 +8,8 @@ #define NROHEADER_MAGICNUM 0x304f524e -#define ASSETHEADER_MAGICNUM 0x54455341 -#define ASSETHEADER_VERSION 0 +#define NROASSETHEADER_MAGICNUM 0x54455341 +#define NROASSETHEADER_VERSION 0 /// Entry for each segment in the codebin. typedef struct { @@ -41,14 +41,14 @@ typedef struct { typedef struct { u64 offset; u64 size; -} AssetSection; +} NroAssetSection; /// Custom asset header. typedef struct { u32 magic; u32 version; - AssetSection icon; - AssetSection nacp; - AssetSection romfs; -} AssetHeader; + NroAssetSection icon; + NroAssetSection nacp; + NroAssetSection romfs; +} NroAssetHeader; diff --git a/nx/source/runtime/devices/romfs_dev.c b/nx/source/runtime/devices/romfs_dev.c index 5bfd3497..3ea64416 100644 --- a/nx/source/runtime/devices/romfs_dev.c +++ b/nx/source/runtime/devices/romfs_dev.c @@ -213,14 +213,14 @@ Result romfsMount(struct romfs_mount **p) romfsInitMtime(mount); NroHeader hdr; - AssetHeader asset_header; + NroAssetHeader asset_header; if (!_romfs_read_chk(mount, sizeof(NroStart), &hdr, sizeof(hdr))) goto _fail0; if (hdr.Magic != NROHEADER_MAGICNUM) goto _fail0; if (!_romfs_read_chk(mount, hdr.size, &asset_header, sizeof(asset_header))) goto _fail0; - if (asset_header.magic != ASSETHEADER_MAGICNUM - || asset_header.version > ASSETHEADER_VERSION + if (asset_header.magic != NROASSETHEADER_MAGICNUM + || asset_header.version > NROASSETHEADER_VERSION || asset_header.romfs.offset == 0 || asset_header.romfs.size == 0) goto _fail0; From b964c69da838b2cc1c32106c9775d5b8dc119a30 Mon Sep 17 00:00:00 2001 From: yellows8 Date: Sat, 24 Feb 2018 21:38:53 -0500 Subject: [PATCH 16/24] Added comments regarding direct FS usage in fs.h. --- nx/include/switch/services/fs.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/nx/include/switch/services/fs.h b/nx/include/switch/services/fs.h index 14546685..4ea337eb 100644 --- a/nx/include/switch/services/fs.h +++ b/nx/include/switch/services/fs.h @@ -1,6 +1,7 @@ /** * @file fs.h * @brief Filesystem (fsp-srv) service IPC wrapper. + * Normally applications should just use standard stdio not FS-serv directly. However this can be used if obtaining a FsFileSystem, FsFile, or FsStorage, for mounting with fs_dev/romfs_dev. * @author plutoo * @author yellows8 * @copyright libnx Authors @@ -91,7 +92,9 @@ void fsExit(void); Service* fsGetServiceSession(void); +/// Do not call this directly, see fs_dev.h. Result fsMountSdcard(FsFileSystem* out); + Result fsMountSaveData(FsFileSystem* out, u8 inval, FsSave *save); Result fsOpenDataStorageByCurrentProcess(FsStorage* out); // todo: Rest of commands here From 0bbfbadc3938948771a98608c5bc6633eaa7bbc5 Mon Sep 17 00:00:00 2001 From: Dave Murphy Date: Sun, 25 Feb 2018 13:51:35 +0000 Subject: [PATCH 17/24] switch to 16x16 console font --- nx/data/default_font.bin | Bin 2048 -> 8192 bytes nx/include/switch/runtime/devices/console.h | 12 ++--- nx/source/runtime/devices/console.c | 56 ++++++++++++-------- 3 files changed, 39 insertions(+), 29 deletions(-) diff --git a/nx/data/default_font.bin b/nx/data/default_font.bin index e3ae20b324d5a53e08bdf62063e67cd2744475e1..758460e4575c67ffe8c8782da2fa479f2df287f0 100644 GIT binary patch literal 8192 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 diff --git a/nx/include/switch/runtime/devices/console.h b/nx/include/switch/runtime/devices/console.h index 9ed7b748..0d16a6aa 100644 --- a/nx/include/switch/runtime/devices/console.h +++ b/nx/include/switch/runtime/devices/console.h @@ -37,7 +37,7 @@ typedef bool(*ConsolePrint)(void* con, int c); /// A font struct for the console. typedef struct ConsoleFont { - u8* gfx; ///< A pointer to the font graphics + u16* gfx; ///< A pointer to the font graphics u16 asciiOffset; ///< Offset to the first valid character in the font table u16 numChars; ///< Number of characters in the font graphics }ConsoleFont; @@ -51,18 +51,18 @@ typedef struct ConsoleFont * { * //Font: * { - * (u8*)default_font_bin, //font gfx + * (u16*)default_font_bin, //font gfx * 0, //first ascii character in the set * 128, //number of characters in the font set * }, * 0,0, //cursorX cursorY * 0,0, //prevcursorX prevcursorY - * 160, //console width - * 90, //console height + * 80, //console width + * 45, //console height * 0, //window x * 0, //window y - * 160, //window width - * 90, //window height + * 80, //window width + * 45, //window height * 3, //tab size * 0, //font character offset * 0, //print callback diff --git a/nx/source/runtime/devices/console.c b/nx/source/runtime/devices/console.c index eba57fa6..bb9c03f3 100644 --- a/nx/source/runtime/devices/console.c +++ b/nx/source/runtime/devices/console.c @@ -42,7 +42,7 @@ PrintConsole defaultConsole = { //Font: { - (u8*)default_font_bin, //font gfx + (u16*)default_font_bin, //font gfx 0, //first ascii character in the set 256 //number of characters in the font set }, @@ -50,12 +50,12 @@ PrintConsole defaultConsole = (u32*)NULL, 0,0, //cursorX cursorY 0,0, //prevcursorX prevcursorY - 160, //console width - 90, //console height + 80, //console width + 45, //console height 0, //window x 0, //window y - 160, //window width - 90, //window height + 80, //window width + 45, //window height 3, //tab size 7, // foreground color 0, // background color @@ -623,18 +623,18 @@ static void newRow(void) { int i,j; u32 x, y; - x = currentConsole->windowX * 8; - y = currentConsole->windowY * 8; + x = currentConsole->windowX * 16; + y = currentConsole->windowY * 16; - for (i=0; iwindowWidth*8; i++) { + for (i=0; iwindowWidth*16; i++) { u32 *from; u32 *to; - for (j=0;j<(currentConsole->windowHeight-1)*8;j++) { + for (j=0;j<(currentConsole->windowHeight-1)*16;j++) { to = ¤tConsole->frameBuffer[gfxGetFramebufferDisplayOffset(x + i, y + j)]; - from = ¤tConsole->frameBuffer[gfxGetFramebufferDisplayOffset(x + i, y + 8 + j)]; + from = ¤tConsole->frameBuffer[gfxGetFramebufferDisplayOffset(x + i, y + 16 + j)]; *to = *from; to = ¤tConsole->frameBuffer2[gfxGetFramebufferDisplayOffset(x + i, y + j)]; - from = ¤tConsole->frameBuffer2[gfxGetFramebufferDisplayOffset(x + i, y + 8 + j)]; + from = ¤tConsole->frameBuffer2[gfxGetFramebufferDisplayOffset(x + i, y + 16 + j)]; *to = *from; } } @@ -648,7 +648,7 @@ void consoleDrawChar(int c) { c -= currentConsole->font.asciiOffset; if ( c < 0 || c > currentConsole->font.numChars ) return; - u8 *fontdata = currentConsole->font.gfx + (8 * c); + u16 *fontdata = currentConsole->font.gfx + (16 * c); int writingColor = currentConsole->fg; int screenColor = currentConsole->bg; @@ -668,27 +668,37 @@ void consoleDrawChar(int c) { u32 bg = colorTable[screenColor]; u32 fg = colorTable[writingColor]; - u64 bval = *((u64*)fontdata); + u128 *tmp = (u128*)fontdata; - if (currentConsole->flags & CONSOLE_UNDERLINE) bval |= 0xffUL << 7*8; + u128 bvaltop = tmp[0]; + u128 bvalbtm = tmp[1]; - if (currentConsole->flags & CONSOLE_CROSSED_OUT) bval |= 0xff << 3*8; + if (currentConsole->flags & CONSOLE_UNDERLINE) bvalbtm |= (u128)0xffffULL << 7*16; - u8 mask = 0x80; + if (currentConsole->flags & CONSOLE_CROSSED_OUT) bvaltop |= (u128)0xffffULL << 7*16; + + u16 mask = 0x8000; int i, j; - int x = (currentConsole->cursorX + currentConsole->windowX) * 8; - int y = ((currentConsole->cursorY + currentConsole->windowY) *8 ); + int x = (currentConsole->cursorX + currentConsole->windowX) * 16; + int y = ((currentConsole->cursorY + currentConsole->windowY) *16 ); u32 *screen; - for (i=0;i<8;i++) { + for (i=0;i<16;i++) { for (j=0;j<8;j++) { - screen = ¤tConsole->frameBuffer[gfxGetFramebufferDisplayOffset(x + i, y + j)]; - if (bval >> (8*j) & mask) { *screen = fg; }else{ *screen = bg; } - screen = ¤tConsole->frameBuffer2[gfxGetFramebufferDisplayOffset(x + i, y + j)]; - if (bval >> (8*j) & mask) { *screen = fg; }else{ *screen = bg; } + uint32_t screenOffset = gfxGetFramebufferDisplayOffset(x + i, y + j); + screen = ¤tConsole->frameBuffer[screenOffset]; + if (bvaltop >> (16*j) & mask) { *screen = fg; }else{ *screen = bg; } + screen = ¤tConsole->frameBuffer2[screenOffset]; + if (bvaltop >> (16*j) & mask) { *screen = fg; }else{ *screen = bg; } + + screenOffset = gfxGetFramebufferDisplayOffset(x + i, y + j + 8); + screen = ¤tConsole->frameBuffer[screenOffset]; + if (bvalbtm >> (16*j) & mask) { *screen = fg; }else{ *screen = bg; } + screen = ¤tConsole->frameBuffer2[screenOffset]; + if (bvalbtm >> (16*j) & mask) { *screen = fg; }else{ *screen = bg; } } mask >>= 1; } From bc29d34cfc204489c8921eeccc2a888ddf2cd87a Mon Sep 17 00:00:00 2001 From: Dave Murphy Date: Sun, 25 Feb 2018 02:43:57 +0000 Subject: [PATCH 18/24] use only one portlibs directory --- nx/switch_rules | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nx/switch_rules b/nx/switch_rules index cb1bfe7d..c5a570fa 100644 --- a/nx/switch_rules +++ b/nx/switch_rules @@ -4,7 +4,7 @@ endif include $(DEVKITPRO)/devkitA64/base_rules -PORTLIBS := $(PORTLIBS_PATH)/switch $(PORTLIBS_PATH)/armv8-a +PORTLIBS := $(PORTLIBS_PATH)/switch LIBNX ?= $(DEVKITPRO)/libnx From 7b0e4194de7eae294ebcfa4eac841013c5438bec Mon Sep 17 00:00:00 2001 From: Adubbz Date: Sun, 25 Feb 2018 10:36:03 +1100 Subject: [PATCH 19/24] Changed the default icon (again) --- nx/default_icon.jpg | Bin 30757 -> 6285 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/nx/default_icon.jpg b/nx/default_icon.jpg index ba6c338471acca8520568df6cb9572b12b698432..859caf4af3f533cb3a7dd1db0ca04c4479693ce5 100644 GIT binary patch literal 6285 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 MIDD1@% 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 N`2+ua#P)0I{{Y9V_^ Date: Mon, 26 Feb 2018 02:10:49 +0100 Subject: [PATCH 20/24] Stylefix for nro --- nx/include/switch/nro.h | 30 +++++++++++++-------------- nx/source/runtime/devices/romfs_dev.c | 4 ++-- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/nx/include/switch/nro.h b/nx/include/switch/nro.h index f2c9dfb6..53e7c270 100644 --- a/nx/include/switch/nro.h +++ b/nx/include/switch/nro.h @@ -6,35 +6,35 @@ #pragma once -#define NROHEADER_MAGICNUM 0x304f524e +#define NROHEADER_MAGIC 0x304f524e -#define NROASSETHEADER_MAGICNUM 0x54455341 +#define NROASSETHEADER_MAGIC 0x54455341 #define NROASSETHEADER_VERSION 0 /// Entry for each segment in the codebin. typedef struct { - u32 FileOff; - u32 Size; -} NsoSegment; + u32 file_off; + u32 size; +} NroSegment; /// Offset 0x0 in the NRO. typedef struct { u32 unused; - u32 modOffset; - u8 Padding[8]; + u32 mod_offset; + u8 padding[8]; } NroStart; /// This follows NroStart, the actual nro-header. typedef struct { - u32 Magic; - u32 Unk1; + u32 magic; + u32 unk1; u32 size; - u32 Unk2; - NsoSegment Segments[3]; - u32 bssSize; - u32 Unk3; - u8 BuildId[0x20]; - u8 Padding[0x20]; + u32 unk2; + NroSegment segments[3]; + u32 bss_size; + u32 unk3; + u8 build_id[0x20]; + u8 padding[0x20]; } NroHeader; /// Custom asset section. diff --git a/nx/source/runtime/devices/romfs_dev.c b/nx/source/runtime/devices/romfs_dev.c index 3ea64416..5e7a7899 100644 --- a/nx/source/runtime/devices/romfs_dev.c +++ b/nx/source/runtime/devices/romfs_dev.c @@ -216,10 +216,10 @@ Result romfsMount(struct romfs_mount **p) NroAssetHeader asset_header; if (!_romfs_read_chk(mount, sizeof(NroStart), &hdr, sizeof(hdr))) goto _fail0; - if (hdr.Magic != NROHEADER_MAGICNUM) goto _fail0; + if (hdr.magic != NROHEADER_MAGIC) goto _fail0; if (!_romfs_read_chk(mount, hdr.size, &asset_header, sizeof(asset_header))) goto _fail0; - if (asset_header.magic != NROASSETHEADER_MAGICNUM + if (asset_header.magic != NROASSETHEADER_MAGIC || asset_header.version > NROASSETHEADER_VERSION || asset_header.romfs.offset == 0 || asset_header.romfs.size == 0) From 47fa88d838c8884826a4862fd125d72ba1a30ca3 Mon Sep 17 00:00:00 2001 From: plutoo Date: Mon, 26 Feb 2018 03:16:54 +0100 Subject: [PATCH 21/24] Amend codestyle --- CODESTYLE.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CODESTYLE.md b/CODESTYLE.md index 393e0a03..7f836bf8 100644 --- a/CODESTYLE.md +++ b/CODESTYLE.md @@ -22,5 +22,10 @@ ## Functions `modulenameFunctionName` +### Submodule function naming +Singletons use names like smInitialize/smExit + +Objects use names like tmemCreate/tmemClose + ## Macros `LIKE_THIS` From f07d09aeb046ca7312e6c44bb4b443fd12ea23e2 Mon Sep 17 00:00:00 2001 From: yellows8 Date: Mon, 26 Feb 2018 12:32:55 -0500 Subject: [PATCH 22/24] Added appletGetDesiredLanguage(). In set-serv('set'), added support for all language/region commands, etc. --- nx/include/switch/services/applet.h | 2 + nx/include/switch/services/set.h | 56 +++++- nx/source/services/applet.c | 39 ++++ nx/source/services/set.c | 283 +++++++++++++++++++++++++++- 4 files changed, 378 insertions(+), 2 deletions(-) diff --git a/nx/include/switch/services/applet.h b/nx/include/switch/services/applet.h index ef217b76..74d32f85 100644 --- a/nx/include/switch/services/applet.h +++ b/nx/include/switch/services/applet.h @@ -51,6 +51,8 @@ Result appletGetAppletResourceUserId(u64 *out); void appletNotifyRunning(u8 *out); Result appletCreateManagedDisplayLayer(u64 *out); +Result appletGetDesiredLanguage(u64 *LanguageCode); + /** * @brief Controls whether screenshot-capture is allowed. * @param val 0 = disable, 1 = enable. diff --git a/nx/include/switch/services/set.h b/nx/include/switch/services/set.h index 97c10697..b33b9196 100644 --- a/nx/include/switch/services/set.h +++ b/nx/include/switch/services/set.h @@ -1,4 +1,10 @@ -// Copyright 2018 plutoo +/** + * @file set.h + * @brief Settings services IPC wrapper. + * @author plutoo + * @author yellows8 + * @copyright libnx Authors + */ #include "../result.h" typedef enum { @@ -6,6 +12,54 @@ typedef enum { ColorSetId_Dark=1 } ColorSetId; +/// IDs for Language. +typedef enum +{ + SetLanguage_JA = 0, ///< Japanese + SetLanguage_ENUS = 1, ///< US English + SetLanguage_FR = 2, ///< French + SetLanguage_DE = 3, ///< German + SetLanguage_IT = 4, ///< Italian + SetLanguage_ES = 5, ///< Spanish + SetLanguage_ZHCN = 6, ///< Simplified Chinese + SetLanguage_KO = 7, ///< Korean + SetLanguage_NL = 8, ///< Dutch + SetLanguage_PT = 9, ///< Portugese + SetLanguage_RU = 10, ///< Russian + SetLanguage_ZHTW = 11, ///< Traditional Chinese + SetLanguage_ENGB = 12, ///< GB English + SetLanguage_FRCA = 13, ///< CA French + SetLanguage_ES419 = 14, ///< {?} Spanish +} SetLanguage; + +Result setInitialize(void); +void setExit(void); + +/// Converts LanguageCode to Language. +Result setMakeLanguage(u64 LanguageCode, s32 *Language); + +/// Converts Language to LanguageCode. +Result setMakeLanguageCode(s32 Language, u64 *LanguageCode); + +/// Gets the current system LanguageCode. +/// Normally this should be used instead of \ref setGetLanguageCode. +/// LanguageCode is a string, see here: http://switchbrew.org/index.php?title=Settings_services#LanguageCode +Result setGetSystemLanguage(u64 *LanguageCode); + +/// Gets the current LanguageCode, \ref setGetSystemLanguage should be used instead normally. +Result setGetLanguageCode(u64 *LanguageCode); + +/// Gets available LanguageCodes. +/// On system-version <4.0.0, max_entries is set to the output from \ref setGetAvailableLanguageCodeCount if max_entries is larger than that. +Result setGetAvailableLanguageCodes(s32 *total_entries, u64 *LanguageCodes, size_t max_entries); + +/// Gets total available LanguageCodes. +/// Output total is overridden with value 0 if the total is <0. +Result setGetAvailableLanguageCodeCount(s32 *total); + +/// Gets the RegionCode. +Result setGetRegionCode(s32 *RegionCode); + Result setsysInitialize(void); void setsysExit(void); diff --git a/nx/source/services/applet.c b/nx/source/services/applet.c index de8f972f..56d4fae3 100644 --- a/nx/source/services/applet.c +++ b/nx/source/services/applet.c @@ -503,6 +503,45 @@ Result appletGetAppletResourceUserId(u64 *out) { return 0; } +Result appletGetDesiredLanguage(u64 *LanguageCode) { + IpcCommand c; + ipcInitialize(&c); + + if (!serviceIsActive(&g_appletSrv) || __nx_applet_type!=AppletType_Application) + return MAKERESULT(Module_Libnx, LibnxError_NotInitialized); + + struct { + u64 magic; + u64 cmd_id; + } *raw; + + raw = ipcPrepareHeader(&c, sizeof(*raw)); + + raw->magic = SFCI_MAGIC; + raw->cmd_id = 21; + + Result rc = serviceIpcDispatch(&g_appletIFunctions); + + if (R_SUCCEEDED(rc)) { + IpcParsedCommand r; + ipcParse(&r); + + struct { + u64 magic; + u64 result; + u64 LanguageCode; + } *resp = r.Raw; + + rc = resp->result; + + if (R_SUCCEEDED(rc) && LanguageCode) { + *LanguageCode = resp->LanguageCode; + } + } + + return rc; +} + void appletNotifyRunning(u8 *out) { IpcCommand c; ipcInitialize(&c); diff --git a/nx/source/services/set.c b/nx/source/services/set.c index 3953b722..ff207aed 100644 --- a/nx/source/services/set.c +++ b/nx/source/services/set.c @@ -1,12 +1,42 @@ -// Copyright 2018 plutoo +/** + * @file set.h + * @brief Settings services IPC wrapper. + * @author plutoo + * @author yellows8 + * @copyright libnx Authors + */ #include "types.h" #include "result.h" #include "ipc.h" +#include "kernel/detect.h" #include "services/set.h" #include "services/sm.h" +#include "services/applet.h" +static Service g_setSrv; static Service g_setsysSrv; +static bool g_setLanguageCodesInitialized; +static u64 g_setLanguageCodes[0x40]; +static s32 g_setLanguageCodesTotal; + +static Result _setMakeLanguageCode(s32 Language, u64 *LanguageCode); + +Result setInitialize(void) +{ + if (serviceIsActive(&g_setSrv)) + return MAKERESULT(Module_Libnx, LibnxError_AlreadyInitialized); + + g_setLanguageCodesInitialized = 0; + + return smGetService(&g_setSrv, "set"); +} + +void setExit(void) +{ + serviceClose(&g_setSrv); +} + Result setsysInitialize(void) { if (serviceIsActive(&g_setsysSrv)) @@ -20,6 +50,257 @@ void setsysExit(void) serviceClose(&g_setsysSrv); } +static Result setInitializeLanguageCodesCache(void) { + if (g_setLanguageCodesInitialized) return 0; + Result rc = 0; + + rc = setGetAvailableLanguageCodes(&g_setLanguageCodesTotal, g_setLanguageCodes, sizeof(g_setLanguageCodes)/sizeof(u64)); + if (R_FAILED(rc)) return rc; + + if (g_setLanguageCodesTotal < 0) g_setLanguageCodesTotal = 0; + + g_setLanguageCodesInitialized = 1; + + return rc; +} + +Result setMakeLanguage(u64 LanguageCode, s32 *Language) { + Result rc = setInitializeLanguageCodesCache(); + if (R_FAILED(rc)) return rc; + + s32 i; + rc = MAKERESULT(Module_Libnx, LibnxError_BadInput); + for (i=0; i= g_setLanguageCodesTotal) { + if (!kernelAbove400()) return MAKERESULT(Module_Libnx, LibnxError_BadInput); + return _setMakeLanguageCode(Language, LanguageCode); + } + + *LanguageCode = g_setLanguageCodes[Language]; + + return rc; +} + +Result setGetSystemLanguage(u64 *LanguageCode) { + //This is disabled because the returned LanguageCode can differ from the system language, for example ja instead of {English}. + /*Result rc = appletGetDesiredLanguage(LanguageCode); + if (R_SUCCEEDED(rc)) return rc;*/ + + return setGetLanguageCode(LanguageCode); +} + +Result setGetLanguageCode(u64 *LanguageCode) { + IpcCommand c; + ipcInitialize(&c); + + struct { + u64 magic; + u64 cmd_id; + } *raw; + + raw = ipcPrepareHeader(&c, sizeof(*raw)); + + raw->magic = SFCI_MAGIC; + raw->cmd_id = 0; + + Result rc = serviceIpcDispatch(&g_setSrv); + + if (R_SUCCEEDED(rc)) { + IpcParsedCommand r; + ipcParse(&r); + + struct { + u64 magic; + u64 result; + u64 LanguageCode; + } *resp = r.Raw; + + rc = resp->result; + + if (R_SUCCEEDED(rc) && LanguageCode) *LanguageCode = resp->LanguageCode; + } + + return rc; +} + +Result setGetAvailableLanguageCodes(s32 *total_entries, u64 *LanguageCodes, size_t max_entries) { + IpcCommand c; + ipcInitialize(&c); + + Result rc=0; + bool new_cmd = kernelAbove400(); + + if (!new_cmd) {//On system-version <4.0.0 the sysmodule will close the session if max_entries is too large. + s32 tmptotal = 0; + + rc = setGetAvailableLanguageCodeCount(&tmptotal); + if (R_FAILED(rc)) return rc; + + if (max_entries > (size_t)tmptotal) max_entries = (size_t)tmptotal; + } + + size_t bufsize = max_entries*sizeof(u64); + + if (!new_cmd) { + ipcAddRecvStatic(&c, LanguageCodes, bufsize, 0); + } + else { + ipcAddRecvBuffer(&c, LanguageCodes, bufsize, 0); + } + + struct { + u64 magic; + u64 cmd_id; + } *raw; + + raw = ipcPrepareHeader(&c, sizeof(*raw)); + + raw->magic = SFCI_MAGIC; + raw->cmd_id = new_cmd ? 5 : 1; + + rc = serviceIpcDispatch(&g_setSrv); + + if (R_SUCCEEDED(rc)) { + IpcParsedCommand r; + ipcParse(&r); + + struct { + u64 magic; + u64 result; + s32 total_entries; + } *resp = r.Raw; + + rc = resp->result; + + if (R_SUCCEEDED(rc) && total_entries) *total_entries = resp->total_entries; + } + + return rc; +} + +static Result _setMakeLanguageCode(s32 Language, u64 *LanguageCode) { + IpcCommand c; + ipcInitialize(&c); + + struct { + u64 magic; + u64 cmd_id; + s32 Language; + } *raw; + + raw = ipcPrepareHeader(&c, sizeof(*raw)); + + raw->magic = SFCI_MAGIC; + raw->cmd_id = 2; + raw->Language = Language; + + Result rc = serviceIpcDispatch(&g_setSrv); + + if (R_SUCCEEDED(rc)) { + IpcParsedCommand r; + ipcParse(&r); + + struct { + u64 magic; + u64 result; + u64 LanguageCode; + } *resp = r.Raw; + + rc = resp->result; + + if (R_SUCCEEDED(rc) && LanguageCode) *LanguageCode = resp->LanguageCode; + } + + return rc; +} + +Result setGetAvailableLanguageCodeCount(s32 *total) { + IpcCommand c; + ipcInitialize(&c); + + struct { + u64 magic; + u64 cmd_id; + } *raw; + + raw = ipcPrepareHeader(&c, sizeof(*raw)); + + raw->magic = SFCI_MAGIC; + raw->cmd_id = kernelAbove400() ? 6 : 3; + + Result rc = serviceIpcDispatch(&g_setSrv); + + if (R_SUCCEEDED(rc)) { + IpcParsedCommand r; + ipcParse(&r); + + struct { + u64 magic; + u64 result; + s32 total; + } *resp = r.Raw; + + rc = resp->result; + + if (R_SUCCEEDED(rc) && total) { + *total = resp->total; + if (*total < 0) *total = 0; + } + } + + return rc; +} + +Result setGetRegionCode(s32 *RegionCode) { + IpcCommand c; + ipcInitialize(&c); + + struct { + u64 magic; + u64 cmd_id; + } *raw; + + raw = ipcPrepareHeader(&c, sizeof(*raw)); + + raw->magic = SFCI_MAGIC; + raw->cmd_id = 4; + + Result rc = serviceIpcDispatch(&g_setSrv); + + if (R_SUCCEEDED(rc)) { + IpcParsedCommand r; + ipcParse(&r); + + struct { + u64 magic; + u64 result; + s32 RegionCode; + } *resp = r.Raw; + + rc = resp->result; + + if (R_SUCCEEDED(rc) && RegionCode) *RegionCode = resp->RegionCode; + } + + return rc; +} + Result setsysGetColorSetId(ColorSetId* out) { IpcCommand c; From f1fc00c516e84028a535e5f3b2d5d142325c51a0 Mon Sep 17 00:00:00 2001 From: yellows8 Date: Mon, 26 Feb 2018 15:17:21 -0500 Subject: [PATCH 23/24] Updated comments for SetLanguage. --- nx/include/switch/services/set.h | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/nx/include/switch/services/set.h b/nx/include/switch/services/set.h index b33b9196..9d314ffb 100644 --- a/nx/include/switch/services/set.h +++ b/nx/include/switch/services/set.h @@ -16,20 +16,20 @@ typedef enum { typedef enum { SetLanguage_JA = 0, ///< Japanese - SetLanguage_ENUS = 1, ///< US English + SetLanguage_ENUS = 1, ///< US English ("AmericanEnglish") SetLanguage_FR = 2, ///< French SetLanguage_DE = 3, ///< German SetLanguage_IT = 4, ///< Italian SetLanguage_ES = 5, ///< Spanish - SetLanguage_ZHCN = 6, ///< Simplified Chinese + SetLanguage_ZHCN = 6, ///< Simplified Chinese ("Chinese") SetLanguage_KO = 7, ///< Korean SetLanguage_NL = 8, ///< Dutch - SetLanguage_PT = 9, ///< Portugese + SetLanguage_PT = 9, ///< Portuguese SetLanguage_RU = 10, ///< Russian - SetLanguage_ZHTW = 11, ///< Traditional Chinese - SetLanguage_ENGB = 12, ///< GB English - SetLanguage_FRCA = 13, ///< CA French - SetLanguage_ES419 = 14, ///< {?} Spanish + SetLanguage_ZHTW = 11, ///< Traditional Chinese ("Taiwanese") + SetLanguage_ENGB = 12, ///< GB English ("BritishEnglish") + SetLanguage_FRCA = 13, ///< CA French ("CanadianFrench") + SetLanguage_ES419 = 14, ///< "LatinAmericanSpanish" } SetLanguage; Result setInitialize(void); From 1f0820dd6a167e4fc40e38a91110db89d95e3a37 Mon Sep 17 00:00:00 2001 From: plutoo Date: Tue, 27 Feb 2018 00:34:08 +0100 Subject: [PATCH 24/24] Add missing include --- nx/include/switch.h | 1 + 1 file changed, 1 insertion(+) diff --git a/nx/include/switch.h b/nx/include/switch.h index 9bb1ad23..2390d23c 100644 --- a/nx/include/switch.h +++ b/nx/include/switch.h @@ -19,6 +19,7 @@ extern "C" { #include "switch/kernel/shmem.h" #include "switch/kernel/mutex.h" #include "switch/kernel/rwlock.h" +#include "switch/kernel/condvar.h" #include "switch/kernel/thread.h" #include "switch/kernel/virtmem.h" #include "switch/kernel/detect.h"