diff --git a/Source/sys-clk/common/include/SaltyNX.h b/Source/sys-clk/common/include/SaltyNX.h
new file mode 100644
index 00000000..06ebc50b
--- /dev/null
+++ b/Source/sys-clk/common/include/SaltyNX.h
@@ -0,0 +1,248 @@
+/*
+ * Copyright (c) MasaGratoR
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+#pragma once
+#include "ipc.h"
+
+Handle saltysd_orig;
+
+Result SaltySD_Connect() {
+ for (int i = 0; i < 200; i++) {
+ if (!svcConnectToNamedPort(&saltysd_orig, "SaltySD"))
+ return 0;
+ svcSleepThread(1000*1000);
+ }
+ return 1;
+}
+
+Result SaltySD_Term()
+{
+ Result ret;
+ IpcCommand c;
+
+ ipcInitialize(&c);
+ ipcSendPid(&c);
+
+ struct input
+ {
+ u64 magic;
+ u64 cmd_id;
+ u64 zero;
+ u64 reserved[2];
+ } *raw;
+
+ raw = (input*)ipcPrepareHeader(&c, sizeof(*raw));
+
+ raw->magic = SFCI_MAGIC;
+ raw->cmd_id = 0;
+ raw->zero = 0;
+
+ ret = ipcDispatch(saltysd_orig);
+
+ if (R_SUCCEEDED(ret))
+ {
+ IpcParsedCommand r;
+ ipcParse(&r);
+
+ struct output {
+ u64 magic;
+ u64 result;
+ } *resp = (output*)r.Raw;
+
+ ret = resp->result;
+ }
+
+ // Session terminated works too.
+ svcCloseHandle(saltysd_orig);
+ if (ret == 0xf601) return 0;
+
+ return ret;
+}
+
+Result SaltySD_CheckIfSharedMemoryAvailable(ptrdiff_t *offset, u64 size)
+{
+ Result ret = 0;
+
+ // Send a command
+ IpcCommand c;
+ ipcInitialize(&c);
+ ipcSendPid(&c);
+
+ struct input {
+ u64 magic;
+ u64 cmd_id;
+ u64 size;
+ u32 reserved[2];
+ } *raw;
+
+ raw = (input*)ipcPrepareHeader(&c, sizeof(*raw));
+
+ raw->magic = SFCI_MAGIC;
+ raw->cmd_id = 6;
+ raw->size = size;
+
+ ret = ipcDispatch(saltysd_orig);
+
+ if (R_SUCCEEDED(ret)) {
+ IpcParsedCommand r;
+ ipcParse(&r);
+
+ struct output {
+ u64 magic;
+ u64 result;
+ u64 offset;
+ } *resp = (output*)r.Raw;
+
+ ret = resp->result;
+
+ if (!ret)
+ {
+ *offset = resp->offset;
+ }
+ }
+
+ return ret;
+}
+
+Result SaltySD_GetSharedMemoryHandle(Handle *retrieve)
+{
+ Result ret = 0;
+
+ // Send a command
+ IpcCommand c;
+ ipcInitialize(&c);
+ ipcSendPid(&c);
+
+ struct input {
+ u64 magic;
+ u64 cmd_id;
+ u32 reserved[4];
+ } *raw;
+
+ raw = (input*)ipcPrepareHeader(&c, sizeof(*raw));
+
+ raw->magic = SFCI_MAGIC;
+ raw->cmd_id = 7;
+
+ ret = ipcDispatch(saltysd_orig);
+
+ if (R_SUCCEEDED(ret)) {
+ IpcParsedCommand r;
+ ipcParse(&r);
+
+ struct output {
+ u64 magic;
+ u64 result;
+ u64 reserved[2];
+ } *resp = (output*)r.Raw;
+
+ ret = resp->result;
+
+ if (!ret)
+ {
+ *retrieve = r.Handles[0];
+ }
+ }
+
+ return ret;
+}
+
+Result SaltySD_GetDisplayRefreshRate(uint8_t* refreshRate)
+{
+ Result ret = 0;
+
+ // Send a command
+ IpcCommand c;
+ ipcInitialize(&c);
+ ipcSendPid(&c);
+
+ struct input {
+ u64 magic;
+ u64 cmd_id;
+ u64 zero;
+ u64 reserved;
+ } *raw;
+
+ raw = (input*)ipcPrepareHeader(&c, sizeof(*raw));
+
+ raw->magic = SFCI_MAGIC;
+ raw->cmd_id = 10;
+ raw->zero = 0;
+
+ ret = ipcDispatch(saltysd_orig);
+
+ if (R_SUCCEEDED(ret)) {
+ IpcParsedCommand r;
+ ipcParse(&r);
+
+ struct output {
+ u64 magic;
+ u64 result;
+ u64 refreshRate;
+ u64 reserved;
+ } *resp = (output*)r.Raw;
+
+ ret = resp->result;
+
+ if (!ret)
+ {
+ *refreshRate = (uint8_t)(resp->refreshRate);
+ }
+ }
+
+ return ret;
+}
+
+Result SaltySD_SetDisplayRefreshRate(uint8_t refreshRate)
+{
+ Result ret = 0;
+
+ // Send a command
+ IpcCommand c;
+ ipcInitialize(&c);
+ ipcSendPid(&c);
+
+ struct input {
+ u64 magic;
+ u64 cmd_id;
+ u64 refreshRate;
+ u64 reserved;
+ } *raw;
+
+ raw = (input*)ipcPrepareHeader(&c, sizeof(*raw));
+
+ raw->magic = SFCI_MAGIC;
+ raw->cmd_id = 11;
+ raw->refreshRate = refreshRate;
+
+ ret = ipcDispatch(saltysd_orig);
+
+ if (R_SUCCEEDED(ret)) {
+ IpcParsedCommand r;
+ ipcParse(&r);
+
+ struct output {
+ u64 magic;
+ u64 result;
+ u64 reserved[2];
+ } *resp = (output*)r.Raw;
+
+ ret = resp->result;
+ }
+
+ return ret;
+}
\ No newline at end of file
diff --git a/Source/sys-clk/common/include/battery.h b/Source/sys-clk/common/include/battery.h
index b2c85253..dff0b08b 100644
--- a/Source/sys-clk/common/include/battery.h
+++ b/Source/sys-clk/common/include/battery.h
@@ -19,7 +19,6 @@
#include
#include
#include
-// Battery charging flags
typedef enum {
BatteryFlag_NoHub = BIT(0), // Hub is disconnected
BatteryFlag_Rail = BIT(8), // At least one Joy-con is charging from rail
@@ -27,7 +26,6 @@ typedef enum {
BatteryFlag_ACC = BIT(16) // Accessory
} BatteryChargeFlags;
-// Power Delivery Controller State (BM92T series)
typedef enum {
PDState_NewPDO = 1, // Received new Power Data Object
PDState_NoPD = 2, // No Power Delivery source is detected
@@ -47,14 +45,11 @@ typedef enum {
ChargerType_Apple_1000mA = 8,
ChargerType_Apple_2000mA = 9
} BatteryChargerType;
-
-// Power role (USB Power Delivery)
typedef enum {
PowerRole_Sink = 1, // Device is receiving power
PowerRole_Source = 2 // Device is providing power
} BatteryPowerRole;
-// Complete battery charge information structure
typedef struct {
int32_t InputCurrentLimit; // Input (Sink) current limit in mA
int32_t VBUSCurrentLimit; // Output (Source/VBUS/OTG) current limit in mA
@@ -64,7 +59,7 @@ typedef struct {
int32_t unk_x14; // Unknown field (possibly flags)
BatteryPDControllerState PDControllerState; // PD Controller State
int32_t BatteryTemperature; // Battery temperature in milli-Celsius
- int32_t RawBatteryCharge; // Battery charge in per cent-mille (100% = 100000)
+ int32_t RawBatteryCharge; // Battery charge in percentmille
int32_t VoltageAvg; // Average voltage in mV
int32_t BatteryAge; // Battery health (capacity full/design) in pcm
BatteryPowerRole PowerRole; // Current power role
@@ -74,36 +69,27 @@ typedef struct {
BatteryChargeFlags Flags; // Various status flags
} BatteryChargeInfo;
-// Helper macro to check if battery charging is enabled
#define IS_BATTERY_CHARGING_ENABLED(info) (((info)->unk_x14 >> 8) & 1)
-// Initialize the battery info driver
Result batteryInfoInitialize(void);
-// Cleanup the battery info driver
void batteryInfoExit(void);
-// Get complete battery charge information
Result batteryInfoGetChargeInfo(BatteryChargeInfo *out);
-// Get battery charge percentage (0-100)
Result batteryInfoGetChargePercentage(u32 *out);
-// Check if enough power is being supplied
Result batteryInfoIsEnoughPowerSupplied(bool *out);
-// Battery charge control functions
Result batteryInfoEnableCharging(void);
Result batteryInfoDisableCharging(void);
Result batteryInfoEnableFastCharging(void);
Result batteryInfoDisableFastCharging(void);
-// Helper functions to get human-readable strings
const char* batteryInfoGetChargerTypeString(BatteryChargerType type);
const char* batteryInfoGetPowerRoleString(BatteryPowerRole role);
const char* batteryInfoGetPDStateString(BatteryPDControllerState state);
-// Convenience functions for common values
static inline int batteryInfoGetTemperatureMiliCelsius(BatteryChargeInfo *info) {
return info->BatteryTemperature;
}
@@ -120,7 +106,6 @@ static inline bool batteryInfoIsCharging(BatteryChargeInfo *info) {
return IS_BATTERY_CHARGING_ENABLED(info);
}
-// String lookup tables
static const char* s_chargerTypeStrings[] = {
"None",
"Power Delivery",
@@ -187,7 +172,6 @@ static Result psmDisableFastBatteryCharging_internal(void) {
return serviceDispatch(&g_psmService, 11);
}
-// Public API implementations
Result batteryInfoInitialize(void) {
if (g_batteryInfoInitialized)
return 0;
diff --git a/Source/sys-clk/common/include/ipc.h b/Source/sys-clk/common/include/ipc.h
new file mode 100644
index 00000000..3015af32
--- /dev/null
+++ b/Source/sys-clk/common/include/ipc.h
@@ -0,0 +1,756 @@
+/**
+ * @file ipc.h
+ * @brief Inter-process communication handling
+ * @author plutoo
+ * @copyright libnx Authors (ISC License)
+ */
+#pragma once
+#include
+
+/// IPC input header magic
+#define SFCI_MAGIC 0x49434653
+/// IPC output header magic
+#define SFCO_MAGIC 0x4f434653
+
+/// IPC invalid object ID
+#define IPC_INVALID_OBJECT_ID UINT32_MAX
+
+///@name IPC request building
+///@{
+
+/// IPC command (request) structure.
+#define IPC_MAX_BUFFERS 8
+#define IPC_MAX_OBJECTS 8
+
+typedef enum {
+ BufferType_Normal=0, ///< Regular buffer.
+ BufferType_Type1=1, ///< Allows ProcessMemory and shared TransferMemory.
+ BufferType_Invalid=2,
+ BufferType_Type3=3 ///< Same as Type1 except remote process is not allowed to use device-mapping.
+} BufferType;
+
+typedef enum {
+ BufferDirection_Send=0,
+ BufferDirection_Recv=1,
+ BufferDirection_Exch=2,
+} BufferDirection;
+
+typedef enum {
+ IpcCommandType_Invalid = 0,
+ IpcCommandType_LegacyRequest = 1,
+ IpcCommandType_Close = 2,
+ IpcCommandType_LegacyControl = 3,
+ IpcCommandType_Request = 4,
+ IpcCommandType_Control = 5,
+ IpcCommandType_RequestWithContext = 6,
+ IpcCommandType_ControlWithContext = 7,
+} IpcCommandType;
+
+typedef enum {
+ DomainMessageType_Invalid = 0,
+ DomainMessageType_SendMessage = 1,
+ DomainMessageType_Close = 2,
+} DomainMessageType;
+
+/// IPC domain message header.
+typedef struct {
+ u8 Type;
+ u8 NumObjectIds;
+ u16 Length;
+ u32 ThisObjectId;
+ u32 Pad[2];
+} DomainMessageHeader;
+
+/// IPC domain response header.
+typedef struct {
+ u32 NumObjectIds;
+ u32 Pad[3];
+} DomainResponseHeader;
+
+
+typedef struct {
+ size_t NumSend; // A
+ size_t NumRecv; // B
+ size_t NumExch; // W
+ const void* Buffers[IPC_MAX_BUFFERS];
+ size_t BufferSizes[IPC_MAX_BUFFERS];
+ BufferType BufferTypes[IPC_MAX_BUFFERS];
+
+ size_t NumStaticIn; // X
+ size_t NumStaticOut; // C
+ const void* Statics[IPC_MAX_BUFFERS];
+ size_t StaticSizes[IPC_MAX_BUFFERS];
+ u8 StaticIndices[IPC_MAX_BUFFERS];
+
+ bool SendPid;
+ size_t NumHandlesCopy;
+ size_t NumHandlesMove;
+ Handle Handles[IPC_MAX_OBJECTS];
+
+ size_t NumObjectIds;
+ u32 ObjectIds[IPC_MAX_OBJECTS];
+} IpcCommand;
+
+/**
+ * @brief Initializes an IPC command structure.
+ * @param cmd IPC command structure.
+ */
+static inline void ipcInitialize(IpcCommand* cmd) {
+ *cmd = (IpcCommand){};
+}
+
+/// IPC buffer descriptor.
+typedef struct {
+ u32 Size; ///< Size of the buffer.
+ u32 Addr; ///< Lower 32-bits of the address of the buffer
+ u32 Packed; ///< Packed data (including higher bits of the address)
+} IpcBufferDescriptor;
+
+/// IPC static send-buffer descriptor.
+typedef struct {
+ u32 Packed; ///< Packed data (including higher bits of the address)
+ u32 Addr; ///< Lower 32-bits of the address
+} IpcStaticSendDescriptor;
+
+/// IPC static receive-buffer descriptor.
+typedef struct {
+ u32 Addr; ///< Lower 32-bits of the address of the buffer
+ u32 Packed; ///< Packed data (including higher bits of the address)
+} IpcStaticRecvDescriptor;
+
+/**
+ * @brief Adds a buffer to an IPC command structure.
+ * @param cmd IPC command structure.
+ * @param buffer Address of the buffer.
+ * @param size Size of the buffer.
+ * @param type Buffer type.
+ */
+static inline void ipcAddSendBuffer(IpcCommand* cmd, const void* buffer, size_t size, BufferType type) {
+ size_t off = cmd->NumSend;
+ cmd->Buffers[off] = buffer;
+ cmd->BufferSizes[off] = size;
+ cmd->BufferTypes[off] = type;
+ cmd->NumSend++;
+}
+
+/**
+ * @brief Adds a receive-buffer to an IPC command structure.
+ * @param cmd IPC command structure.
+ * @param buffer Address of the buffer.
+ * @param size Size of the buffer.
+ * @param type Buffer type.
+ */
+static inline void ipcAddRecvBuffer(IpcCommand* cmd, void* buffer, size_t size, BufferType type) {
+ size_t off = cmd->NumSend + cmd->NumRecv;
+ cmd->Buffers[off] = buffer;
+ cmd->BufferSizes[off] = size;
+ cmd->BufferTypes[off] = type;
+ cmd->NumRecv++;
+}
+
+/**
+ * @brief Adds an exchange-buffer to an IPC command structure.
+ * @param cmd IPC command structure.
+ * @param buffer Address of the buffer.
+ * @param size Size of the buffer.
+ * @param type Buffer type.
+ */
+static inline void ipcAddExchBuffer(IpcCommand* cmd, void* buffer, size_t size, BufferType type) {
+ size_t off = cmd->NumSend + cmd->NumRecv + cmd->NumExch;
+ cmd->Buffers[off] = buffer;
+ cmd->BufferSizes[off] = size;
+ cmd->BufferTypes[off] = type;
+ cmd->NumExch++;
+}
+
+/**
+ * @brief Adds a static-buffer to an IPC command structure.
+ * @param cmd IPC command structure.
+ * @param buffer Address of the buffer.
+ * @param size Size of the buffer.
+ * @param index Index of buffer.
+ */
+static inline void ipcAddSendStatic(IpcCommand* cmd, const void* buffer, size_t size, u8 index) {
+ size_t off = cmd->NumStaticIn;
+ cmd->Statics[off] = buffer;
+ cmd->StaticSizes[off] = size;
+ cmd->StaticIndices[off] = index;
+ cmd->NumStaticIn++;
+}
+
+/**
+ * @brief Adds a static-receive-buffer to an IPC command structure.
+ * @param cmd IPC command structure.
+ * @param buffer Address of the buffer.
+ * @param size Size of the buffer.
+ * @param index Index of buffer.
+ */
+static inline void ipcAddRecvStatic(IpcCommand* cmd, void* buffer, size_t size, u8 index) {
+ size_t off = cmd->NumStaticIn + cmd->NumStaticOut;
+ cmd->Statics[off] = buffer;
+ cmd->StaticSizes[off] = size;
+ cmd->StaticIndices[off] = index;
+ cmd->NumStaticOut++;
+}
+
+/**
+ * @brief Adds a smart-buffer (buffer + static-buffer pair) to an IPC command structure.
+ * @param cmd IPC command structure.
+ * @param pointer_buffer_size Pointer buffer size.
+ * @param buffer Address of the buffer.
+ * @param size Size of the buffer.
+ * @param index Index of buffer.
+ */
+static inline void ipcAddSendSmart(IpcCommand* cmd, size_t pointer_buffer_size, const void* buffer, size_t size, u8 index) {
+ if (pointer_buffer_size != 0 && size <= pointer_buffer_size) {
+ ipcAddSendBuffer(cmd, NULL, 0, BufferType_Normal);
+ ipcAddSendStatic(cmd, buffer, size, index);
+ } else {
+ ipcAddSendBuffer(cmd, buffer, size, BufferType_Normal);
+ ipcAddSendStatic(cmd, NULL, 0, index);
+ }
+}
+
+/**
+ * @brief Adds a smart-receive-buffer (buffer + static-receive-buffer pair) to an IPC command structure.
+ * @param cmd IPC command structure.
+ * @param pointer_buffer_size Pointer buffer size.
+ * @param buffer Address of the buffer.
+ * @param size Size of the buffer.
+ * @param index Index of buffer.
+ */
+static inline void ipcAddRecvSmart(IpcCommand* cmd, size_t pointer_buffer_size, void* buffer, size_t size, u8 index) {
+ if (pointer_buffer_size != 0 && size <= pointer_buffer_size) {
+ ipcAddRecvBuffer(cmd, NULL, 0, BufferType_Normal);
+ ipcAddRecvStatic(cmd, buffer, size, index);
+ } else {
+ ipcAddRecvBuffer(cmd, buffer, size, BufferType_Normal);
+ ipcAddRecvStatic(cmd, NULL, 0, index);
+ }
+}
+
+/**
+ * @brief Tags an IPC command structure to send the PID.
+ * @param cmd IPC command structure.
+ */
+static inline void ipcSendPid(IpcCommand* cmd) {
+ cmd->SendPid = true;
+}
+
+/**
+ * @brief Adds a copy-handle to be sent through an IPC command structure.
+ * @param cmd IPC command structure.
+ * @param h Handle to send.
+ * @remark The receiving process gets a copy of the handle.
+ */
+static inline void ipcSendHandleCopy(IpcCommand* cmd, Handle h) {
+ cmd->Handles[cmd->NumHandlesCopy++] = h;
+}
+
+/**
+ * @brief Adds a move-handle to be sent through an IPC command structure.
+ * @param cmd IPC command structure.
+ * @param h Handle to send.
+ * @remark The sending process loses ownership of the handle, which is transferred to the receiving process.
+ */
+static inline void ipcSendHandleMove(IpcCommand* cmd, Handle h) {
+ cmd->Handles[cmd->NumHandlesCopy + cmd->NumHandlesMove++] = h;
+}
+
+/**
+ * @brief Prepares the header of an IPC command structure.
+ * @param cmd IPC command structure.
+ * @param sizeof_raw Size in bytes of the raw data structure to embed inside the IPC request
+ * @return Pointer to the raw embedded data structure in the request, ready to be filled out.
+ */
+static inline void* ipcPrepareHeader(IpcCommand* cmd, size_t sizeof_raw) {
+ u32* buf = (u32*)armGetTls();
+ size_t i;
+ *buf++ = IpcCommandType_Request | (cmd->NumStaticIn << 16) | (cmd->NumSend << 20) | (cmd->NumRecv << 24) | (cmd->NumExch << 28);
+
+ u32* fill_in_size_later = buf;
+
+ if (cmd->NumStaticOut > 0) {
+ *buf = (cmd->NumStaticOut + 2) << 10;
+ }
+ else {
+ *buf = 0;
+ }
+
+ if (cmd->SendPid || cmd->NumHandlesCopy > 0 || cmd->NumHandlesMove > 0) {
+ *buf++ |= 0x80000000;
+ *buf++ = (!!cmd->SendPid) | (cmd->NumHandlesCopy << 1) | (cmd->NumHandlesMove << 5);
+
+ if (cmd->SendPid)
+ buf += 2;
+
+ for (i=0; i<(cmd->NumHandlesCopy + cmd->NumHandlesMove); i++)
+ *buf++ = cmd->Handles[i];
+ }
+ else {
+ buf++;
+ }
+
+ for (i=0; iNumStaticIn; i++, buf+=2) {
+ IpcStaticSendDescriptor* desc = (IpcStaticSendDescriptor*) buf;
+
+ uintptr_t ptr = (uintptr_t) cmd->Statics[i];
+ desc->Addr = ptr;
+ desc->Packed = cmd->StaticIndices[i] | (cmd->StaticSizes[i] << 16) |
+ (((ptr >> 32) & 15) << 12) | (((ptr >> 36) & 15) << 6);
+ }
+
+ for (i=0; i<(cmd->NumSend + cmd->NumRecv + cmd->NumExch); i++, buf+=3) {
+ IpcBufferDescriptor* desc = (IpcBufferDescriptor*) buf;
+ desc->Size = cmd->BufferSizes[i];
+
+ uintptr_t ptr = (uintptr_t) cmd->Buffers[i];
+ desc->Addr = ptr;
+ desc->Packed = cmd->BufferTypes[i] |
+ (((ptr >> 32) & 15) << 28) | ((ptr >> 36) << 2);
+ }
+
+ u32 padding = ((16 - (((uintptr_t) buf) & 15)) & 15) / 4;
+ u32* raw = (u32*) (buf + padding);
+
+ size_t raw_size = (sizeof_raw/4) + 4;
+ buf += raw_size;
+
+ u16* buf_u16 = (u16*) buf;
+
+ for (i=0; iNumStaticOut; i++) {
+ size_t off = cmd->NumStaticIn + i;
+ size_t sz = (uintptr_t) cmd->StaticSizes[off];
+
+ buf_u16[i] = (sz > 0xFFFF) ? 0 : sz;
+ }
+
+ size_t u16s_size = ((2*cmd->NumStaticOut) + 3)/4;
+ buf += u16s_size;
+ raw_size += u16s_size;
+
+ *fill_in_size_later |= raw_size;
+
+ for (i=0; iNumStaticOut; i++, buf+=2) {
+ IpcStaticRecvDescriptor* desc = (IpcStaticRecvDescriptor*) buf;
+ size_t off = cmd->NumStaticIn + i;
+
+ uintptr_t ptr = (uintptr_t) cmd->Statics[off];
+ desc->Addr = ptr;
+ desc->Packed = (ptr >> 32) | (cmd->StaticSizes[off] << 16);
+ }
+
+ return (void*) raw;
+}
+
+/**
+ * @brief Dispatches an IPC request.
+ * @param session IPC session handle.
+ * @return Result code.
+ */
+static inline Result ipcDispatch(Handle session) {
+ return svcSendSyncRequest(session);
+}
+
+///@}
+
+///@name IPC response parsing
+///@{
+
+/// IPC parsed command (response) structure.
+typedef struct {
+ IpcCommandType CommandType; ///< Type of the command
+
+ bool HasPid; ///< true if the 'Pid' field is filled out.
+ u64 Pid; ///< PID included in the response (only if HasPid is true)
+
+ size_t NumHandles; ///< Number of handles copied.
+ Handle Handles[IPC_MAX_OBJECTS]; ///< Handles.
+ bool WasHandleCopied[IPC_MAX_OBJECTS]; ///< true if the handle was moved, false if it was copied.
+
+ bool IsDomainRequest; ///< true if the the message is a Domain message.
+ DomainMessageType InMessageType; ///< Type of the domain message.
+ u32 InMessageLength; ///< Size of rawdata (for domain messages).
+ u32 InThisObjectId; ///< Object ID to call the command on (for domain messages).
+ size_t InNumObjectIds; ///< Number of object IDs (for domain messages).
+ u32 InObjectIds[IPC_MAX_OBJECTS]; ///< Object IDs (for domain messages).
+
+ bool IsDomainResponse; ///< true if the the message is a Domain response.
+ size_t OutNumObjectIds; ///< Number of object IDs (for domain responses).
+ u32 OutObjectIds[IPC_MAX_OBJECTS]; ///< Object IDs (for domain responses).
+
+ size_t NumBuffers; ///< Number of buffers in the response.
+ void* Buffers[IPC_MAX_BUFFERS]; ///< Pointers to the buffers.
+ size_t BufferSizes[IPC_MAX_BUFFERS]; ///< Sizes of the buffers.
+ BufferType BufferTypes[IPC_MAX_BUFFERS]; ///< Types of the buffers.
+ BufferDirection BufferDirections[IPC_MAX_BUFFERS]; ///< Direction of each buffer.
+
+ size_t NumStatics; ///< Number of statics in the response.
+ void* Statics[IPC_MAX_BUFFERS]; ///< Pointers to the statics.
+ size_t StaticSizes[IPC_MAX_BUFFERS]; ///< Sizes of the statics.
+ u8 StaticIndices[IPC_MAX_BUFFERS]; ///< Indices of the statics.
+
+ size_t NumStaticsOut; ///< Number of output statics available in the response.
+
+ void* Raw; ///< Pointer to the raw embedded data structure in the response.
+ void* RawWithoutPadding; ///< Pointer to the raw embedded data structure, without padding.
+ size_t RawSize; ///< Size of the raw embedded data.
+} IpcParsedCommand;
+
+/**
+ * @brief Parse an IPC command response into an IPC parsed command structure.
+ * @param r IPC parsed command structure to fill in.
+ * @return Result code.
+ */
+static inline Result ipcParse(IpcParsedCommand* r) {
+ u32* buf = (u32*)armGetTls();
+ u32 ctrl0 = *buf++;
+ u32 ctrl1 = *buf++;
+ size_t i;
+
+ r->IsDomainRequest = false;
+ r->IsDomainResponse = false;
+
+ r->CommandType = (IpcCommandType) (ctrl0 & 0xffff);
+ r->HasPid = false;
+ r->RawSize = (ctrl1 & 0x1ff) * 4;
+ r->NumHandles = 0;
+
+ r->NumStaticsOut = (ctrl1 >> 10) & 15;
+ if (r->NumStaticsOut >> 1) r->NumStaticsOut--; // Value 2 -> Single descriptor
+ if (r->NumStaticsOut >> 1) r->NumStaticsOut--; // Value 3+ -> (Value - 2) descriptors
+
+ if (ctrl1 & 0x80000000) {
+ u32 ctrl2 = *buf++;
+
+ if (ctrl2 & 1) {
+ r->HasPid = true;
+ r->Pid = *buf++;
+ r->Pid |= ((u64)(*buf++)) << 32;
+ }
+
+ size_t num_handles_copy = ((ctrl2 >> 1) & 15);
+ size_t num_handles_move = ((ctrl2 >> 5) & 15);
+
+ size_t num_handles = num_handles_copy + num_handles_move;
+ u32* buf_after_handles = buf + num_handles;
+
+ if (num_handles > IPC_MAX_OBJECTS)
+ num_handles = IPC_MAX_OBJECTS;
+
+ for (i=0; iHandles[i] = *(buf+i);
+ r->WasHandleCopied[i] = (i < num_handles_copy);
+ }
+
+ r->NumHandles = num_handles;
+ buf = buf_after_handles;
+ }
+
+ size_t num_statics = (ctrl0 >> 16) & 15;
+ u32* buf_after_statics = buf + num_statics*2;
+
+ if (num_statics > IPC_MAX_BUFFERS)
+ num_statics = IPC_MAX_BUFFERS;
+
+ for (i=0; iPacked;
+
+ r->Statics[i] = (void*) (desc->Addr | (((packed >> 12) & 15) << 32) | (((packed >> 6) & 15) << 36));
+ r->StaticSizes[i] = packed >> 16;
+ r->StaticIndices[i] = packed & 63;
+ }
+
+ r->NumStatics = num_statics;
+ buf = buf_after_statics;
+
+ size_t num_bufs_send = (ctrl0 >> 20) & 15;
+ size_t num_bufs_recv = (ctrl0 >> 24) & 15;
+ size_t num_bufs_exch = (ctrl0 >> 28) & 15;
+
+ size_t num_bufs = num_bufs_send + num_bufs_recv + num_bufs_exch;
+ r->Raw = (void*)(((uintptr_t)(buf + num_bufs*3) + 15) &~ 15);
+ r->RawWithoutPadding = (void*)((uintptr_t)(buf + num_bufs*3));
+
+ if (num_bufs > IPC_MAX_BUFFERS)
+ num_bufs = IPC_MAX_BUFFERS;
+
+ for (i=0; iPacked;
+
+ r->Buffers[i] = (void*) (desc->Addr | ((packed >> 28) << 32) | (((packed >> 2) & 15) << 36));
+ r->BufferSizes[i] = desc->Size;
+ r->BufferTypes[i] = (BufferType) (packed & 3);
+
+ if (i < num_bufs_send)
+ r->BufferDirections[i] = BufferDirection_Send;
+ else if (i < (num_bufs_send + num_bufs_recv))
+ r->BufferDirections[i] = BufferDirection_Recv;
+ else
+ r->BufferDirections[i] = BufferDirection_Exch;
+ }
+
+ r->NumBuffers = num_bufs;
+ return 0;
+}
+
+/**
+ * @brief Queries the size of an IPC pointer buffer.
+ * @param session IPC session handle.
+ * @param size Output variable in which to store the size.
+ * @return Result code.
+ */
+static inline Result ipcQueryPointerBufferSize(Handle session, size_t *size) {
+ u32* buf = (u32*)armGetTls();
+
+ buf[0] = IpcCommandType_Control;
+ buf[1] = 8;
+ buf[2] = 0;
+ buf[3] = 0;
+ buf[4] = SFCI_MAGIC;
+ buf[5] = 0;
+ buf[6] = 3;
+ buf[7] = 0;
+
+ Result rc = ipcDispatch(session);
+
+ if (R_SUCCEEDED(rc)) {
+ IpcParsedCommand r;
+ ipcParse(&r);
+
+ struct ipcQueryPointerBufferSizeResponse {
+ u64 magic;
+ u64 result;
+ u32 size;
+ } *raw = (struct ipcQueryPointerBufferSizeResponse*)r.Raw;
+
+ rc = raw->result;
+
+ if (R_SUCCEEDED(rc)) {
+ *size = raw->size & 0xffff;
+ }
+ }
+
+ return rc;
+}
+
+/**
+ * @brief Closes the IPC session with proper clean up.
+ * @param session IPC session handle.
+ * @return Result code.
+ */
+static inline Result ipcCloseSession(Handle session) {
+ u32* buf = (u32*)armGetTls();
+ buf[0] = IpcCommandType_Close;
+ buf[1] = 0;
+ return ipcDispatch(session);
+}
+
+/**
+ * @brief Clones an IPC session.
+ * @param session IPC session handle.
+ * @param unk Unknown.
+ * @param new_session_out Output cloned IPC session handle.
+ * @return Result code.
+ */
+static inline Result ipcCloneSession(Handle session, u32 unk, Handle* new_session_out) {
+ u32* buf = (u32*)armGetTls();
+
+ buf[0] = IpcCommandType_Control;
+ buf[1] = 9;
+ buf[2] = 0;
+ buf[3] = 0;
+ buf[4] = SFCI_MAGIC;
+ buf[5] = 0;
+ buf[6] = 4;
+ buf[7] = 0;
+ buf[8] = unk;
+
+ Result rc = ipcDispatch(session);
+
+ if (R_SUCCEEDED(rc)) {
+ IpcParsedCommand r;
+ ipcParse(&r);
+
+ struct ipcCloneSessionResponse {
+ u64 magic;
+ u64 result;
+ } *raw = (struct ipcCloneSessionResponse*)r.Raw;
+
+ rc = raw->result;
+
+ if (R_SUCCEEDED(rc) && new_session_out) {
+ *new_session_out = r.Handles[0];
+ }
+ }
+
+ return rc;
+}
+
+///@}
+
+///@name IPC domain handling
+///@{
+
+/**
+ * @brief Converts an IPC session handle into a domain.
+ * @param session IPC session handle.
+ * @param object_id_out Output variable in which to store the object ID.
+ * @return Result code.
+ */
+static inline Result ipcConvertSessionToDomain(Handle session, u32* object_id_out) {
+ u32* buf = (u32*)armGetTls();
+
+ buf[0] = IpcCommandType_Control;
+ buf[1] = 8;
+ buf[4] = SFCI_MAGIC;
+ buf[5] = 0;
+ buf[6] = 0;
+ buf[7] = 0;
+
+ Result rc = ipcDispatch(session);
+
+ if (R_SUCCEEDED(rc)) {
+ IpcParsedCommand r;
+ ipcParse(&r);
+
+ struct ipcConvertSessionToDomainResponse {
+ u64 magic;
+ u64 result;
+ u32 object_id;
+ } *raw = (struct ipcConvertSessionToDomainResponse*)r.Raw;
+
+ rc = raw->result;
+
+ if (R_SUCCEEDED(rc)) {
+ *object_id_out = raw->object_id;
+ }
+ }
+
+ return rc;
+}
+
+/**
+ * @brief Adds an object ID to be sent through an IPC domain command structure.
+ * @param cmd IPC domain command structure.
+ * @param object_id Object ID to send.
+ */
+static inline void ipcSendObjectId(IpcCommand* cmd, u32 object_id) {
+ cmd->ObjectIds[cmd->NumObjectIds++] = object_id;
+}
+
+/**
+ * @brief Prepares the header of an IPC command structure (domain version).
+ * @param cmd IPC command structure.
+ * @param sizeof_raw Size in bytes of the raw data structure to embed inside the IPC request
+ * @param object_id Domain object ID.
+ * @return Pointer to the raw embedded data structure in the request, ready to be filled out.
+ */
+static inline void* ipcPrepareHeaderForDomain(IpcCommand* cmd, size_t sizeof_raw, u32 object_id) {
+ void* raw = ipcPrepareHeader(cmd, sizeof_raw + sizeof(DomainMessageHeader) + cmd->NumObjectIds*sizeof(u32));
+ DomainMessageHeader* hdr = (DomainMessageHeader*) raw;
+ u32 *object_ids = (u32*)(((uintptr_t) raw) + sizeof(DomainMessageHeader) + sizeof_raw);
+
+ hdr->Type = DomainMessageType_SendMessage;
+ hdr->NumObjectIds = (u8)cmd->NumObjectIds;
+ hdr->Length = sizeof_raw;
+ hdr->ThisObjectId = object_id;
+ hdr->Pad[0] = hdr->Pad[1] = 0;
+
+ for(size_t i = 0; i < cmd->NumObjectIds; i++)
+ object_ids[i] = cmd->ObjectIds[i];
+ return (void*)(((uintptr_t) raw) + sizeof(DomainMessageHeader));
+}
+
+/**
+ * @brief Parse an IPC command request into an IPC parsed command structure (domain version).
+ * @param r IPC parsed command structure to fill in.
+ * @return Result code.
+ */
+static inline Result ipcParseDomainRequest(IpcParsedCommand* r) {
+ Result rc = ipcParse(r);
+ DomainMessageHeader *hdr;
+ u32 *object_ids;
+ if(R_FAILED(rc))
+ return rc;
+
+ hdr = (DomainMessageHeader*) r->Raw;
+ object_ids = (u32*)(((uintptr_t) hdr) + sizeof(DomainMessageHeader) + hdr->Length);
+ r->Raw = (void*)(((uintptr_t) r->Raw) + sizeof(DomainMessageHeader));
+
+ r->IsDomainRequest = true;
+ r->InMessageType = (DomainMessageType)(hdr->Type);
+ switch (r->InMessageType) {
+ case DomainMessageType_SendMessage:
+ case DomainMessageType_Close:
+ break;
+ default:
+ return MAKERESULT(Module_Libnx, LibnxError_DomainMessageUnknownType);
+ }
+
+ r->InThisObjectId = hdr->ThisObjectId;
+ r->InNumObjectIds = hdr->NumObjectIds > 8 ? 8 : hdr->NumObjectIds;
+ if ((uintptr_t)object_ids + sizeof(u32) * r->InNumObjectIds - (uintptr_t)armGetTls() >= 0x100) {
+ return MAKERESULT(Module_Libnx, LibnxError_DomainMessageTooManyObjectIds);
+ }
+ for(size_t i = 0; i < r->InNumObjectIds; i++)
+ r->InObjectIds[i] = object_ids[i];
+
+ return rc;
+}
+
+/**
+ * @brief Parse an IPC command response into an IPC parsed command structure (domain version).
+ * @param r IPC parsed command structure to fill in.
+ * @param sizeof_raw Size in bytes of the raw data structure.
+ * @return Result code.
+ */
+static inline Result ipcParseDomainResponse(IpcParsedCommand* r, size_t sizeof_raw) {
+ Result rc = ipcParse(r);
+ DomainResponseHeader *hdr;
+ u32 *object_ids;
+ if(R_FAILED(rc))
+ return rc;
+
+ hdr = (DomainResponseHeader*) r->Raw;
+ r->Raw = (void*)(((uintptr_t) r->Raw) + sizeof(DomainResponseHeader));
+ object_ids = (u32*)(((uintptr_t) r->Raw) + sizeof_raw);//Official sw doesn't align this.
+
+ r->IsDomainResponse = true;
+
+ r->OutNumObjectIds = hdr->NumObjectIds > 8 ? 8 : hdr->NumObjectIds;
+ if ((uintptr_t)object_ids + sizeof(u32) * r->OutNumObjectIds - (uintptr_t)armGetTls() >= 0x100) {
+ return MAKERESULT(Module_Libnx, LibnxError_DomainMessageTooManyObjectIds);
+ }
+ for(size_t i = 0; i < r->OutNumObjectIds; i++)
+ r->OutObjectIds[i] = object_ids[i];
+
+ return rc;
+}
+
+/**
+ * @brief Closes a domain object by ID.
+ * @param session IPC session handle.
+ * @param object_id ID of the object to close.
+ * @return Result code.
+ */
+static inline Result ipcCloseObjectById(Handle session, u32 object_id) {
+ IpcCommand c;
+ DomainMessageHeader* hdr;
+
+ ipcInitialize(&c);
+ hdr = (DomainMessageHeader*)ipcPrepareHeader(&c, sizeof(DomainMessageHeader));
+
+ hdr->Type = DomainMessageType_Close;
+ hdr->NumObjectIds = 0;
+ hdr->Length = 0;
+ hdr->ThisObjectId = object_id;
+ hdr->Pad[0] = hdr->Pad[1] = 0;
+
+ return ipcDispatch(session); // this command has no associated response
+}
+
+///@}
\ No newline at end of file
diff --git a/Source/sys-clk/common/include/sysclk/clock_manager.h b/Source/sys-clk/common/include/sysclk/clock_manager.h
index 362b6a85..351ca76a 100644
--- a/Source/sys-clk/common/include/sysclk/clock_manager.h
+++ b/Source/sys-clk/common/include/sysclk/clock_manager.h
@@ -45,6 +45,7 @@ typedef struct
u16 iddq[HorizonOCSpeedo_EnumMax];
GpuSchedulingMode gpuSchedulingMode;
bool isSysDockInstalled;
+ bool isSaltyNXInstalled;
u8 maxDisplayFreq;
u8 dramID;
bool isDram8GB;
diff --git a/Source/sys-clk/common/include/sysclk/config.h b/Source/sys-clk/common/include/sysclk/config.h
index 94e76626..03565282 100644
--- a/Source/sys-clk/common/include/sysclk/config.h
+++ b/Source/sys-clk/common/include/sysclk/config.h
@@ -66,6 +66,8 @@ typedef enum {
HorizonOCConfigValue_RAMVoltUsageDisplayMode,
+ HorizonOCConfigValue_VRR,
+
KipConfigValue_custRev,
// KipConfigValue_mtcConf,
KipConfigValue_hpMode,
@@ -250,6 +252,9 @@ static inline const char* sysclkFormatConfigValue(SysClkConfigValue val, bool pr
case HorizonOCConfigValue_RAMVoltUsageDisplayMode:
return pretty ? "RAM Voltage / Usage Display Mode" : "ram_volt_usage_display_mode";
+ case HorizonOCConfigValue_VRR:
+ return pretty ? "Variable Refresh Rate (VRR)" : "vrr";
+
// KIP config values
case KipConfigValue_custRev:
return pretty ? "Custom Revision" : "kip_cust_rev";
@@ -430,6 +435,7 @@ static inline uint64_t sysclkDefaultConfigValue(SysClkConfigValue val)
case HorizonOCConfigValue_GPUScheduling:
case HorizonOCConfigValue_LiveCpuUv:
case HorizonOCConfigValue_GPUSchedulingMethod:
+ case HorizonOCConfigValue_VRR:
return 0ULL;
case HocClkConfigValue_EristaMaxCpuClock:
return 1785ULL;
@@ -479,6 +485,7 @@ static inline uint64_t sysclkValidConfigValue(SysClkConfigValue val, uint64_t in
case HorizonOCConfigValue_EnableExperimentalSettings:
case HorizonOCConfigValue_LiveCpuUv:
case HorizonOCConfigValue_GPUSchedulingMethod:
+ case HorizonOCConfigValue_VRR:
return (input & 0x1) == input;
case KipConfigValue_custRev:
diff --git a/Source/sys-clk/common/src/display_refresh_rate.cpp b/Source/sys-clk/common/src/display_refresh_rate.cpp
index 1959190b..9c380d83 100644
--- a/Source/sys-clk/common/src/display_refresh_rate.cpp
+++ b/Source/sys-clk/common/src/display_refresh_rate.cpp
@@ -88,6 +88,7 @@ static const DockedTimings g_dockedTimings1080p[] = {
{8, 32, 40, 108, 8, 6, 0, 528880}, //220Hz CVT-RBv2
{8, 32, 40, 114, 8, 6, 0, 555680}, //230Hz CVT-RBv2
{8, 32, 40, 121, 8, 6, 0, 583200}, //240Hz CVT-RBv2
+ // technically you can go to 476hz, but in practice, why would you?
};
static const HandheldTimings g_handheldTimingsRETRO[] = {
diff --git a/Source/sys-clk/overlay/src/ui/gui/about_gui.cpp b/Source/sys-clk/overlay/src/ui/gui/about_gui.cpp
index ae566cd4..832332cd 100644
--- a/Source/sys-clk/overlay/src/ui/gui/about_gui.cpp
+++ b/Source/sys-clk/overlay/src/ui/gui/about_gui.cpp
@@ -25,6 +25,8 @@
tsl::elm::ListItem* SpeedoItem = NULL;
tsl::elm::ListItem* IddqItem = NULL;
tsl::elm::ListItem* sysdockStatusItem = NULL;
+tsl::elm::ListItem* saltyNXStatusItem = NULL;
+
ImageElement* CatImage = NULL;
HideableCategoryHeader* CatHeader = NULL;
HideableCustomDrawer* CatSpacer = NULL;
@@ -57,6 +59,10 @@ void AboutGui::listUI()
new tsl::elm::ListItem("sys-dock status:");
this->listElement->addItem(sysdockStatusItem);
+ saltyNXStatusItem =
+ new tsl::elm::ListItem("SaltyNX status:");
+ this->listElement->addItem(saltyNXStatusItem);
+
this->listElement->addItem(
new tsl::elm::CategoryHeader("Credits")
);
@@ -227,4 +233,5 @@ void AboutGui::refresh()
SpeedoItem->setValue(strings[0]);
IddqItem->setValue(strings[1]);
sysdockStatusItem->setValue(this->context->isSysDockInstalled ? "Installed" : "Not Installed");
+ saltyNXStatusItem->setValue(this->context->isSaltyNXInstalled ? "Installed" : "Not Installed");
}
\ No newline at end of file
diff --git a/Source/sys-clk/overlay/src/ui/gui/base_menu_gui.cpp b/Source/sys-clk/overlay/src/ui/gui/base_menu_gui.cpp
index 568ba7ea..e1167648 100644
--- a/Source/sys-clk/overlay/src/ui/gui/base_menu_gui.cpp
+++ b/Source/sys-clk/overlay/src/ui/gui/base_menu_gui.cpp
@@ -58,7 +58,7 @@ void BaseMenuGui::preDraw(tsl::gfx::Renderer* renderer) {
// All constants pre-calculated and cached
static constexpr const char* const labels[] = {
- "App ID", "Profile", "CPU", "GPU", "MEM", "SoC", "Board", "Skin", "Now", "Avg", "BAT", "PMIC", "FAN", "DISP"
+ "App ID", "Profile", "CPU", "GPU", "MEM", "SoC", "Board", "Skin", "Now", "Avg", "BAT", "PMIC", "FAN", "DISP", "FPS"
};
static constexpr u32 dataPositions[6] = {63-3+3, 200-1, 344-1-3, 200-1, 342-1, 321-1};
@@ -168,8 +168,10 @@ void BaseMenuGui::preDraw(tsl::gfx::Renderer* renderer) {
renderer->drawString(displayStrings[21], false, dataPositions[0], y, SMALL_TEXT_SIZE, tsl::infoTextColor); // Bat voltage
renderer->drawString(displayStrings[23], false, positions[2] - 2, y, SMALL_TEXT_SIZE, tsl::infoTextColor); // Bat Age
-
- renderer->drawString(displayStrings[26], false, dataPositions[2], y, SMALL_TEXT_SIZE, tsl::infoTextColor); // disp volt
+ if(this->context->isSaltyNXInstalled) {
+ renderer->drawString(labels[14], false, positions[4], y, SMALL_TEXT_SIZE, tsl::sectionTextColor); // FPS label
+ renderer->drawString(displayStrings[26], false, dataPositions[2], y, SMALL_TEXT_SIZE, tsl::infoTextColor); // FPS
+ }
y+=20;
}
@@ -285,9 +287,14 @@ void BaseMenuGui::refresh()
sprintf(displayStrings[24], "%u%%", context->partLoad[HocClkPartLoad_FAN]);
sprintf(displayStrings[25], "%u Hz", context->realFreqs[HorizonOCModule_Display]);
-
- //sprintf(displayStrings[26], "%u", context->speedos[HorizonOCSpeedo_CPU]);
-
+ if(this->context->isSaltyNXInstalled) {
+ if(context->fps == 254) {
+ strcpy(displayStrings[26], "N/A");
+ } else {
+ memset(displayStrings[26], 0, sizeof(displayStrings[26]));
+ sprintf(displayStrings[26], "%u", context->fps);
+ }
+ }
}
tsl::elm::Element* BaseMenuGui::baseUI()
diff --git a/Source/sys-clk/overlay/src/ui/gui/misc_gui.cpp b/Source/sys-clk/overlay/src/ui/gui/misc_gui.cpp
index 2198f018..0aeebdde 100644
--- a/Source/sys-clk/overlay/src/ui/gui/misc_gui.cpp
+++ b/Source/sys-clk/overlay/src/ui/gui/misc_gui.cpp
@@ -366,6 +366,13 @@ void MiscGui::listUI()
addConfigButton(HorizonOCConfigValue_RAMVoltUsageDisplayMode, "RAM Voltage Display Mode", ValueRange(0, 12, 1, "", 0), "RAM Voltage Display Mode", &thresholdsDisabled, {}, ramVoltDispModes, false);
+ addConfigButton(
+ HocClkConfigValue_LiteTDPLimit,
+ "Polling Interval",
+ ValueRange(50, 1000, 50, "ms", 1),
+ "Polling Interval",
+ &thresholdsDisabled
+ );
tsl::elm::ListItem* safetySubmenu = new tsl::elm::ListItem("Safety Settings");
safetySubmenu->setClickListener([](u64 keys) {
if (keys & HidNpadButton_A) {
@@ -536,6 +543,7 @@ public:
protected:
void listUI() override {
addConfigToggle(HorizonOCConfigValue_OverwriteRefreshRate, nullptr);
+ addConfigToggle(HorizonOCConfigValue_VRR, nullptr);
tsl::elm::CustomDrawer* warningText = new tsl::elm::CustomDrawer([](tsl::gfx::Renderer *renderer, s32 x, s32 y, s32 w, s32 h) {
renderer->drawString("\uE150 Enabling unsafe display", false, x + 20, y + 30, 18, tsl::style::color::ColorText);
renderer->drawString("refresh rates may cause stress", false, x + 20, y + 50, 18, tsl::style::color::ColorText);
diff --git a/Source/sys-clk/sysmodule/perms.json b/Source/sys-clk/sysmodule/perms.json
index 5b705463..b5c09894 100644
--- a/Source/sys-clk/sysmodule/perms.json
+++ b/Source/sys-clk/sysmodule/perms.json
@@ -6,11 +6,11 @@
"main_thread_stack_size": "0x0000C000",
"main_thread_priority": 16,
"default_cpu_id": 3,
- "process_category": 0,
+ "process_category": 1,
"is_retail": true,
"pool_partition": 2,
"is_64_bit": true,
- "address_space_type": 1,
+ "address_space_type": 3,
"filesystem_access": {
"permissions": "0xFFFFFFFFFFFFFFFF"
},
diff --git a/Source/sys-clk/sysmodule/src/clock_manager.cpp b/Source/sys-clk/sysmodule/src/clock_manager.cpp
index 8693ac7d..09bd4e8b 100644
--- a/Source/sys-clk/sysmodule/src/clock_manager.cpp
+++ b/Source/sys-clk/sysmodule/src/clock_manager.cpp
@@ -55,11 +55,11 @@ bool hasChanged = true;
ClockManager *ClockManager::instance = NULL;
Thread cpuGovernorTHREAD;
Thread gpuGovernorTHREAD;
+Thread vrrTHREAD;
u32 initialConfigValues[SysClkConfigValue_EnumMax]; // initial config. used for safety checks
bool kipAvailable = false;
bool isCpuGovernorInBoostMode = false;
-
ClockManager *ClockManager::GetInstance()
{
return instance;
@@ -100,6 +100,8 @@ ClockManager::ClockManager()
this->lastCsvWriteNs = 0;
this->sysDockIntegration = new SysDockIntegration;
+ this->saltyNXIntegration = new SaltyNXIntegration;
+
memset(&initialConfigValues, 0, sizeof(initialConfigValues));
this->GetKipData();
@@ -123,8 +125,15 @@ ClockManager::ClockManager()
-2
);
- threadStart(&cpuGovernorTHREAD);
- threadStart(&gpuGovernorTHREAD);
+ threadCreate(
+ &vrrTHREAD,
+ ClockManager::VRRThread,
+ this,
+ NULL,
+ 0x2000,
+ 0x3F,
+ -2
+ );
for(int i = 0; i < HorizonOCSpeedo_EnumMax; i++) {
this->context->speedos[i] = Board::getSpeedo((HorizonOCSpeedo)i);
@@ -135,13 +144,27 @@ ClockManager::ClockManager()
this->context->isDram8GB = Board::IsDram8GB();
Board::SetGpuSchedulingMode((GpuSchedulingMode)this->config->GetConfigValue(HorizonOCConfigValue_GPUScheduling), (GpuSchedulingOverrideMethod)this->config->GetConfigValue(HorizonOCConfigValue_GPUSchedulingMethod));
this->context->gpuSchedulingMode = (GpuSchedulingMode)this->config->GetConfigValue(HorizonOCConfigValue_GPUScheduling);
+
this->context->isSysDockInstalled = this->sysDockIntegration->getCurrentSysDockState();
+ this->context->isSaltyNXInstalled = this->saltyNXIntegration->getCurrentSaltyNXState();
+ if(this->context->isSaltyNXInstalled) {
+ this->saltyNXIntegration->LoadSaltyNX();
+ }
+
+
+ threadStart(&cpuGovernorTHREAD);
+ threadStart(&gpuGovernorTHREAD);
+ threadStart(&vrrTHREAD);
}
ClockManager::~ClockManager()
{
threadClose(&cpuGovernorTHREAD);
threadClose(&gpuGovernorTHREAD);
+ threadClose(&vrrTHREAD);
+
+ delete this->sysDockIntegration;
+ delete this->saltyNXIntegration;
delete this->config;
delete this->context;
}
@@ -459,6 +482,67 @@ void ClockManager::GovernorThread(void* arg) {
svcSleepThread(POLL_NS);
}
}
+
+void ClockManager::VRRThread(void* arg) {
+ ClockManager* mgr = static_cast(arg);
+ u8 tick = 0;
+ for (;;) {
+ if (!mgr->running || !mgr->config->GetConfigValue(HorizonOCConfigValue_VRR) || mgr->context->profile == SysClkProfile_Docked) {
+ continue;
+ }
+
+ if(Board::GetConsoleType() == HorizonOCConsoleType_Hoag) {
+ svcSleepThread(~0ULL);
+ continue;
+ }
+
+ std::scoped_lock lock{mgr->contextMutex};
+
+ u8 fps;
+
+ if(mgr->context->isSaltyNXInstalled) {
+ fps = mgr->saltyNXIntegration->GetFPS();
+ } else {
+ svcSleepThread(~0ULL); // effectively disable the thread if SaltyNX isn't installed, as there's no point in it running
+ continue;
+ }
+
+ if(fps == 254)
+ continue;
+ // if(appletGetFocusState() != AppletFocusState_InFocus) {
+ // Board::ResetToStockDisplay();
+ // continue;
+ // }
+ u8 maxDisplay;
+ if(Board::GetConsoleType() == HorizonOCConsoleType_Aula) {
+ maxDisplay = mgr->config->GetConfigValue(HorizonOCConfigValue_EnableUnsafeDisplayFreqs) ? 65 : 60;
+ } else {
+ maxDisplay = mgr->config->GetConfigValue(HorizonOCConfigValue_EnableUnsafeDisplayFreqs) ? 72 : 60;
+ }
+
+ u8 minDisplay = Board::GetConsoleType() == HorizonOCConsoleType_Aula ? 40 : 45;
+ if(fps >= minDisplay && fps <= maxDisplay)
+ Board::SetHz(HorizonOCModule_Display, fps);
+ else {
+ for(u32 i = 0; i < 10; i++) {
+ u32 compareHz = fps * i;
+ if(compareHz >= minDisplay && compareHz <= maxDisplay) {
+ Board::SetHz(HorizonOCModule_Display, compareHz);
+ break;
+ }
+ }
+ }
+ if(++tick > 10) {
+ Board::ResetToStockDisplay();
+ tick = 0;
+ svcSleepThread(10'000'000);
+ }
+
+ svcSleepThread(POLL_NS);
+ }
+}
+
+
GovernorState ClockManager::GetEffectiveGovernorState(GovernorState appState, GovernorState tempState)
{
if (tempState == GovernorState_Disabled)
@@ -623,6 +707,12 @@ void ClockManager::HandleFreqReset(SysClkModule module, bool isBoost) {
case SysClkModule_MEM:
Board::ResetToStockMem();
DVFSReset();
+ break;
+ case HorizonOCModule_Display:
+ if(this->config->GetConfigValue(HorizonOCConfigValue_OverwriteRefreshRate) && Board::GetConsoleType() != HorizonOCConsoleType_Hoag) {
+ Board::ResetToStockDisplay();
+ }
+ break;
default:
break;
}
@@ -888,7 +978,11 @@ bool ClockManager::RefreshContext()
if(Board::GetConsoleType() != HorizonOCConsoleType_Hoag)
Board::SetDisplayRefreshDockedState(this->context->profile == SysClkProfile_Docked);
-
+ if(this->context->isSaltyNXInstalled)
+ this->context->fps = saltyNXIntegration->GetFPS();
+ else
+ this->context->fps = 254; // N/A
+
return hasChanged;
}
diff --git a/Source/sys-clk/sysmodule/src/clock_manager.h b/Source/sys-clk/sysmodule/src/clock_manager.h
index 0294b99a..c287db17 100644
--- a/Source/sys-clk/sysmodule/src/clock_manager.h
+++ b/Source/sys-clk/sysmodule/src/clock_manager.h
@@ -33,6 +33,7 @@
#include "integrations.h"
class SysDockIntegration;
+class SaltyNXIntegration;
class ClockManager
{
public:
@@ -184,6 +185,13 @@ class ClockManager
*/
static void GovernorThread(void* arg);
+ /**
+ * Runs the VRR Algorithm
+ *
+ * @param arg Cast to ClockManager* for context
+ */
+ static void VRRThread(void* arg);
+
/**
* Gets the effective governor state from application/temporary override
*
@@ -257,4 +265,5 @@ class ClockManager
std::uint64_t lastPowerLogNs;
std::uint64_t lastCsvWriteNs;
SysDockIntegration *sysDockIntegration;
+ SaltyNXIntegration *saltyNXIntegration;
};
\ No newline at end of file
diff --git a/Source/sys-clk/sysmodule/src/integrations.cpp b/Source/sys-clk/sysmodule/src/integrations.cpp
index cd55661d..bd53423c 100644
--- a/Source/sys-clk/sysmodule/src/integrations.cpp
+++ b/Source/sys-clk/sysmodule/src/integrations.cpp
@@ -18,15 +18,93 @@
#include "integrations.h"
#include
+#include
+#include "process_management.h"
SysDockIntegration::SysDockIntegration() {
}
bool SysDockIntegration::getCurrentSysDockState() {
struct stat st = {0};
- if (stat("sdmc:/atmosphere/contents/42000000000000A0", &st) == 0 && S_ISDIR(st.st_mode)) {
- return true;
- } else {
- return false;
+ return stat("sdmc:/atmosphere/contents/42000000000000A0/flags/boot2.flag", &st) == 0;
+}
+
+SaltyNXIntegration::SaltyNXIntegration() {
+}
+
+void SaltyNXIntegration::LoadSaltyNX() {
+ if (!CheckPort())
+ return;
+ LoadSharedMemory();
+}
+
+bool SaltyNXIntegration::getCurrentSaltyNXState() {
+ struct stat st = {0};
+ return stat("sdmc:/atmosphere/contents/0000000000534C56/flags/boot2.flag", &st) == 0;
+}
+
+bool SaltyNXIntegration::CheckPort() {
+ Handle saltysd;
+
+ for (int i = 0; i < 67; i++) {
+ if (R_SUCCEEDED(svcConnectToNamedPort(&saltysd, "InjectServ"))) {
+ svcCloseHandle(saltysd);
+ break;
+ }
+ if (i == 66) return false;
+ svcSleepThread(1'000'000);
}
+
+ for (int i = 0; i < 67; i++) {
+ if (R_SUCCEEDED(svcConnectToNamedPort(&saltysd, "InjectServ"))) {
+ svcCloseHandle(saltysd);
+ return true;
+ }
+ svcSleepThread(1'000'000);
+ }
+
+ return false;
+}
+
+void SaltyNXIntegration::LoadSharedMemory() {
+ if (SaltySD_Connect())
+ return;
+ SaltySD_GetSharedMemoryHandle(&remoteSharedMemory);
+ SaltySD_Term();
+ shmemLoadRemote(&_sharedmemory, remoteSharedMemory, 0x1000, Perm_Rw);
+ if (!shmemMap(&_sharedmemory))
+ SharedMemoryUsed = true;
+}
+
+void SaltyNXIntegration::searchSharedMemoryBlock(uintptr_t base) {
+ ptrdiff_t search_offset = 0;
+ while (search_offset < 0x1000) {
+ NxFps = (NxFpsSharedBlock*)(base + search_offset);
+ if (NxFps->MAGIC == 0x465053)
+ return;
+ search_offset += 4;
+ }
+ NxFps = 0;
+}
+
+u64 prevTid = 0;
+u8 SaltyNXIntegration::GetFPS() {
+ if (!SharedMemoryUsed)
+ return 254;
+
+ u64 tid = ProcessManagement::GetCurrentApplicationId();
+ if (tid == 0)
+ return 254;
+
+ if (prevTid != tid) {
+ NxFps = 0;
+ prevTid = tid;
+ }
+
+ if (!NxFps) {
+ uintptr_t base = (uintptr_t)shmemGetAddr(&_sharedmemory);
+ searchSharedMemoryBlock(base);
+ }
+
+ return NxFps ? NxFps->FPS : 254;
}
\ No newline at end of file
diff --git a/Source/sys-clk/sysmodule/src/integrations.h b/Source/sys-clk/sysmodule/src/integrations.h
index 9c2d7ea9..0146bc36 100644
--- a/Source/sys-clk/sysmodule/src/integrations.h
+++ b/Source/sys-clk/sysmodule/src/integrations.h
@@ -34,4 +34,60 @@ public:
SysDockIntegration();
bool getCurrentSysDockState();
+};
+
+class SaltyNXIntegration {
+public:
+ struct resolutionCalls {
+ uint16_t width;
+ uint16_t height;
+ uint16_t calls;
+ };
+
+ struct NxFpsSharedBlock {
+ uint32_t MAGIC;
+ uint8_t FPS;
+ float FPSavg;
+ bool pluginActive;
+ uint8_t FPSlocked;
+ uint8_t FPSmode;
+ uint8_t ZeroSync;
+ uint8_t patchApplied;
+ uint8_t API;
+ uint32_t FPSticks[10];
+ uint8_t Buffers;
+ uint8_t SetBuffers;
+ uint8_t ActiveBuffers;
+ uint8_t SetActiveBuffers;
+ union {
+ struct {
+ bool handheld: 1;
+ bool docked: 1;
+ unsigned int reserved: 6;
+ } NX_PACKED ds;
+ uint8_t general;
+ } displaySync;
+ resolutionCalls renderCalls[8];
+ resolutionCalls viewportCalls[8];
+ bool forceOriginalRefreshRate;
+ bool dontForce60InDocked;
+ bool forceSuspend;
+ uint8_t currentRefreshRate;
+ float readSpeedPerSecond;
+ uint8_t FPSlockedDocked;
+ uint64_t frameNumber;
+ } NX_PACKED;
+
+ NxFpsSharedBlock* NxFps = 0;
+ SharedMemory _sharedmemory = {};
+ bool SharedMemoryUsed = false;
+ Handle remoteSharedMemory = 1;
+ SaltyNXIntegration();
+ void LoadSaltyNX();
+ bool getCurrentSaltyNXState();
+
+ bool CheckPort();
+ void LoadSharedMemory();
+ void searchSharedMemoryBlock(uintptr_t base);
+ u8 GetFPS();
};
\ No newline at end of file
diff --git a/Source/sys-clk/sysmodule/src/main.cpp b/Source/sys-clk/sysmodule/src/main.cpp
index 9ad50b16..6249172c 100644
--- a/Source/sys-clk/sysmodule/src/main.cpp
+++ b/Source/sys-clk/sysmodule/src/main.cpp
@@ -38,8 +38,11 @@
#include "ipc_service.h"
#define INNER_HEAP_SIZE 0x40000
+
extern "C"
{
+ void virtmemSetup(void);
+
extern std::uint32_t __start__;
std::uint32_t __nx_applet_type = AppletType_None;
@@ -62,6 +65,8 @@ extern "C"
fake_heap_start = (char*)addr;
fake_heap_end = (char*)addr + size;
+
+ virtmemSetup();
}
void __appInit(void)
@@ -88,6 +93,9 @@ extern "C"
rc = i2cInitialize();
if (R_FAILED(rc))
diagAbortWithResult(MAKERESULT(Module_Libnx, LibnxError_ShouldNotHappen));
+ rc = appletInitialize();
+ if (R_FAILED(rc))
+ diagAbortWithResult(MAKERESULT(Module_Libnx, LibnxError_ShouldNotHappen));
}
void __appExit(void)
@@ -97,6 +105,7 @@ extern "C"
i2cExit();
fsExit();
fsdevUnmountAll();
+ appletExit();
}
}