From 71150ff70bedb087109186479646f329cddaaeaa Mon Sep 17 00:00:00 2001 From: Jonathan Levy Date: Sun, 19 Jul 2026 01:27:44 +0200 Subject: [PATCH 1/3] hide the rich presence while the console sleeps, fixes #11 --- Sources/SwitchRPC/source/discord.cpp | 4 + Sources/SwitchRPC/source/main.cpp | 208 ++++++++++++++++++++------- 2 files changed, 158 insertions(+), 54 deletions(-) diff --git a/Sources/SwitchRPC/source/discord.cpp b/Sources/SwitchRPC/source/discord.cpp index beb02b9..e29c8b5 100644 --- a/Sources/SwitchRPC/source/discord.cpp +++ b/Sources/SwitchRPC/source/discord.cpp @@ -147,6 +147,10 @@ bool sendRequest(const char* url, const char* method, struct curl_slist* headers curl_easy_setopt(curl, CURLOPT_POSTFIELDS, body); curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L); curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L); + // don't let a request hang forever - matters for the pre-sleep cleanup, + // it shouldn't hold up the console going to sleep + curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 5L); + curl_easy_setopt(curl, CURLOPT_TIMEOUT, 10L); // temporary fix to failing dns resolution struct curl_slist *dns_cache = NULL; diff --git a/Sources/SwitchRPC/source/main.cpp b/Sources/SwitchRPC/source/main.cpp index 1a59f39..1f0d44f 100644 --- a/Sources/SwitchRPC/source/main.cpp +++ b/Sources/SwitchRPC/source/main.cpp @@ -73,6 +73,50 @@ Result get_app_info(AppInfo *info) { } +// sleep/wake via psc. +// clocks are useless here - the whole sysmodule is frozen while asleep so time() +// and the system tick don't move. psc gives us a real event instead. the +// ReadySleep one fires *before* we sleep while wifi is still up, which is the +// only moment we can kill the discord session so it doesn't hang around during +// sleep. can't do the network call on this thread (tiny stack, and acking late +// would stall the sleep), so it just pokes the main thread and waits. +// id 0x5250 is a random unused id (system ones stop at 127), deps {fs, nifm} so +// we go down before them on sleep and come back after them on wake. +static PscPmModule g_pscModule; +static Thread g_pscThread; +static bool g_pscReady = false; +static volatile bool g_pscThreadRun = true; +static volatile bool g_wokeFromSleep = false; +static volatile bool g_sleepPending = false; +static volatile bool g_asleep = false; +static UEvent g_pscWakeMain; // psc -> main: "wake up and check your flags" +static UEvent g_sleepPrepDone; // main -> psc: "presence gone, ok to sleep" + +static void pscThreadFunc(void*) { + while (g_pscThreadRun) { + // 1s timeout so we can bail out on shutdown + if (R_FAILED(eventWait(&g_pscModule.event, 1000000000ULL))) continue; + + PscPmState state; + u32 flags = 0; + if (R_FAILED(pscPmModuleGetRequest(&g_pscModule, &state, &flags))) continue; + + if (state == PscPmState_ReadySleep) { + // let the main thread kill the presence while wifi is still up, wait + // a bit for it, then ack - never block the sleep for long + g_sleepPending = true; + ueventSignal(&g_pscWakeMain); + waitSingle(waiterForUEvent(&g_sleepPrepDone), 4000000000ULL); // up to 4s + } else if (state == PscPmState_Awake) { + g_wokeFromSleep = true; + ueventSignal(&g_pscWakeMain); + } + + pscPmModuleAcknowledge(&g_pscModule, state); + } +} + + #ifdef __cplusplus extern "C" { #endif @@ -152,6 +196,7 @@ void __appInit(void) pmdmntInitialize(); nsInitialize(); setInitialize(); + pscmInitialize(); // Close the service manager session. smExit(); @@ -160,6 +205,16 @@ void __appInit(void) // Service deinitialization. void __appExit(void) { + // stop the psc thread first, we're leaving anyway + g_pscThreadRun = false; + if (g_pscReady) { + threadWaitForExit(&g_pscThread); + threadClose(&g_pscThread); + pscPmModuleFinalize(&g_pscModule); + pscPmModuleClose(&g_pscModule); + } + pscmExit(); + discordDeleteHeadlessSession(); writeToLog("[SwitchRPC] Sysmodule exiting. Goodbye!"); @@ -191,69 +246,114 @@ int main(int argc, char* argv[]) AppInfo lastInfo = {0}; time_t last_update_time = 0; - // Start by cleaning up stale sessions + // Start by cleaning up stale sessions discordCleanupStaleSessions(); - + waitForNetworkReady(); - while (true) { - AppInfo info = {0}; - Result rc = get_app_info(&info); - - bool is_game_running = R_SUCCEEDED(rc) && info.tid != 0; - - if (is_game_running) { - // user opened a game or switched a game or switched from home menu to game (tid changed) - if (info.tid != lastInfo.tid) { - // if lastInfo.tid != 0, we already had a session running, so we include the token to update it. - // if lastInfo.tid == 0, it's a brand new session, so no token is included. - bool has_existing_session = (lastInfo.tid != 0); - - writeToLog("[SwitchRPC] Game state changed! New TID: %016llX, Name: %s. Previous TID: %016llX", - (unsigned long long)info.tid, info.title_name, (unsigned long long)lastInfo.tid); - - lastInfo = info; - last_update_time = time(NULL); - - // create/update session. - discordCreateHeadlessSession(info.tid, std::string(info.title_name), has_existing_session); - } - // the same game is still open. refresh. - else { - time_t current_time = time(NULL); - // check time interval - if (current_time - last_update_time >= REFRESH_INTERVAL) { - writeToLog("[SwitchRPC] Periodic session refresh triggered for TID: %016llX", (unsigned long long)info.tid); - last_update_time = current_time; - - // refresh session with session token that we have if any. - discordCreateHeadlessSession(info.tid, std::string(info.title_name), true); - } + + // register with psc and start the thread that watches for sleep/wake + { + ueventCreate(&g_pscWakeMain, true); + ueventCreate(&g_sleepPrepDone, true); + static const u32 pscDeps[] = { PscPmModuleId_Fs, PscPmModuleId_Nifm }; + Result prc = pscmGetPmModule(&g_pscModule, (PscPmModuleId)0x5250, pscDeps, + sizeof(pscDeps) / sizeof(pscDeps[0]), true); + if (R_SUCCEEDED(prc)) { + if (R_SUCCEEDED(threadCreate(&g_pscThread, pscThreadFunc, NULL, NULL, 0x4000, 0x2C, -2))) { + threadStart(&g_pscThread); + g_pscReady = true; + writeToLog("[SwitchRPC] PSC power-state monitoring active."); + } else { + // no ack thread = we'd stall power transitions, so unregister + pscPmModuleFinalize(&g_pscModule); + pscPmModuleClose(&g_pscModule); + writeToLog("[SwitchRPC] Warning: could not start PSC thread; sleep detection disabled."); } } else { - // user isn't in a game. if we had a session before, delete it since the game closed, and reset lastInfo. - if (lastInfo.tid != 0) { - writeToLog("[SwitchRPC] Game closed or returned to Home Menu. Clearing session for TID: %016llX", (unsigned long long)lastInfo.tid); - waitForNetworkReady(); - - lastInfo = {0}; - last_update_time = 0; - - discordDeleteHeadlessSession(); + writeToLog("[SwitchRPC] Warning: pscmGetPmModule failed (0x%08X); sleep detection disabled.", prc); + } + } + + while (true) { + // just woke up - rebuild presence for whatever's running, fresh timer + if (g_wokeFromSleep) { + g_wokeFromSleep = false; + g_asleep = false; + writeToLog("[SwitchRPC] Wake from sleep (PSC). Waiting for network, then rebuilding session."); + waitForNetworkReady(); + + discordDeleteHeadlessSession(); + discordCleanupStaleSessions(); + + lastInfo = {0}; + last_update_time = 0; + } + + // asleep = presence already gone, don't poll or we'd just recreate it + // right before the console freezes + if (!g_asleep) { + AppInfo info = {0}; + Result rc = get_app_info(&info); + + bool is_game_running = R_SUCCEEDED(rc) && info.tid != 0; + + if (is_game_running) { + // user opened a game or switched a game or switched from home menu to game (tid changed) + if (info.tid != lastInfo.tid) { + // if lastInfo.tid != 0, we already had a session running, so we include the token to update it. + // if lastInfo.tid == 0, it's a brand new session, so no token is included. + bool has_existing_session = (lastInfo.tid != 0); + + writeToLog("[SwitchRPC] Game state changed! New TID: %016llX, Name: %s. Previous TID: %016llX", + (unsigned long long)info.tid, info.title_name, (unsigned long long)lastInfo.tid); + + lastInfo = info; + last_update_time = time(NULL); + + // create/update session. + discordCreateHeadlessSession(info.tid, std::string(info.title_name), has_existing_session); + } + // the same game is still open. refresh. + else { + time_t current_time = time(NULL); + // check time interval + if (current_time - last_update_time >= REFRESH_INTERVAL) { + writeToLog("[SwitchRPC] Periodic session refresh triggered for TID: %016llX", (unsigned long long)info.tid); + last_update_time = current_time; + + // refresh session with session token that we have if any. + discordCreateHeadlessSession(info.tid, std::string(info.title_name), true); + } + } + } else { + // user isn't in a game. if we had a session before, delete it since the game closed, and reset lastInfo. + if (lastInfo.tid != 0) { + writeToLog("[SwitchRPC] Game closed or returned to Home Menu. Clearing session for TID: %016llX", (unsigned long long)lastInfo.tid); + waitForNetworkReady(); + + lastInfo = {0}; + last_update_time = 0; + + discordDeleteHeadlessSession(); + } } } - // sleep detection - time_t sleep_start = time(NULL); - svcSleepThread(10 * 1000 * 1000 * 1000ULL); - time_t sleep_end = time(NULL); + // wait ~10s, or wake early if psc pokes us + waitSingle(waiterForUEvent(&g_pscWakeMain), 10ULL * 1000 * 1000 * 1000); - if (sleep_end - sleep_start > 15) { - writeToLog("[SwitchRPC] Wake from sleep detected! Sleep took %lld seconds. Waiting for network...", (long long)(sleep_end - sleep_start)); - waitForNetworkReady(); - writeToLog("[SwitchRPC] Network ready after sleep, clearing sessions."); + // about to sleep, wifi still up - kill the presence now before we freeze, + // stop polling until we wake, then let the psc thread ack + if (g_sleepPending) { + g_sleepPending = false; + writeToLog("[SwitchRPC] Sleep imminent (PSC). Clearing Discord presence before sleep."); + + discordDeleteHeadlessSession(); + lastInfo = {0}; + last_update_time = 0; + g_asleep = true; - last_update_time = 0; - discordCleanupStaleSessions(); // cleanup any sessions we created prior. + ueventSignal(&g_sleepPrepDone); } } From c2c6efcfa3ba9f255fe55c95745bea2910e19c0a Mon Sep 17 00:00:00 2001 From: Jonathan Levy Date: Sun, 19 Jul 2026 01:28:57 +0200 Subject: [PATCH 2/3] fix double free, re-enable time module, proper logout, stop sessions stacking --- Sources/SwitchRPC/source/discord.cpp | 28 +++++++++++++++---- Sources/SwitchRPC/source/discord.hpp | 8 +++++- Sources/SwitchRPC/source/logging.cpp | 2 +- Sources/SwitchRPC/source/main.cpp | 40 +++++++++++++++++++++------- 4 files changed, 61 insertions(+), 17 deletions(-) diff --git a/Sources/SwitchRPC/source/discord.cpp b/Sources/SwitchRPC/source/discord.cpp index e29c8b5..324a2b3 100644 --- a/Sources/SwitchRPC/source/discord.cpp +++ b/Sources/SwitchRPC/source/discord.cpp @@ -32,7 +32,9 @@ void getRefreshTokenFromFile() { std::ifstream file("sdmc:/config/switchrpc_token"); if (!file.is_open()) { writeToLog("[Discord] No refresh token found at sdmc:/config/switchrpc_token"); - return; + // keep any in-memory token: logout deletes the file first, and we still + // need to authenticate to tear the sessions down before forgetting it + return; } std::string line; @@ -365,19 +367,21 @@ void discordCreateHeadlessSession(u64 titleId, std::string titleName, const bool std::string response; bool success = authenticatedRequest("https://discord.com/api/v9/users/@me/headless-sessions", "POST", headers, body, &response); + + // free it once here - used to get freed twice on a failed no-token request + // and trash the heap + json_object_put(json_body); + if (!success) { writeToLog("[Discord] Failed to create/update headless session!"); - json_object_put(json_body); - // if the attempt happened with an existing session token, try again without it in case the token was the issue if (includeToken) { writeToLog("[Discord] Retrying headless session creation without session token..."); return discordCreateHeadlessSession(titleId, titleName, false); } + return; } - json_object_put(json_body); - json_object* json_response = json_tokener_parse(response.c_str()); if (json_response == NULL) { writeToLog("[Discord] Failed to parse headless session response JSON!"); @@ -427,3 +431,17 @@ void discordDeleteHeadlessSession() { sessionTokenExpiry = 0; writeToLog("[Discord] Session deleted successfully."); } + +void discordLogout() { + writeToLog("[Discord] Logging out - dropping session and stored tokens."); + // delete the active session + any leftovers. these authenticate, which may + // refresh (and rewrite) the token file, so we still have valid auth here. + discordDeleteHeadlessSession(); + discordCleanupStaleSessions(); + // now forget everything and make sure we stay logged out even if a refresh + // just rewrote the token file + refreshToken = ""; + authToken = ""; + authTokenExpiry = 0; + remove("sdmc:/config/switchrpc_token"); +} diff --git a/Sources/SwitchRPC/source/discord.hpp b/Sources/SwitchRPC/source/discord.hpp index 3ba3055..cf7766d 100644 --- a/Sources/SwitchRPC/source/discord.hpp +++ b/Sources/SwitchRPC/source/discord.hpp @@ -26,4 +26,10 @@ void discordDeleteHeadlessSession(); * Deletes all known sessions by clearing the sessions file. * Call this on startup or when waking from sleep to clean up any sessions that should no longer be active. */ -void discordCleanupStaleSessions(); \ No newline at end of file +void discordCleanupStaleSessions(); + +/** + * Tears down the active session and forgets all stored tokens. + * Call when the user logs out from the config app. + */ +void discordLogout(); \ No newline at end of file diff --git a/Sources/SwitchRPC/source/logging.cpp b/Sources/SwitchRPC/source/logging.cpp index aa41ed4..6dcdd46 100644 --- a/Sources/SwitchRPC/source/logging.cpp +++ b/Sources/SwitchRPC/source/logging.cpp @@ -24,7 +24,7 @@ void writeToLog(const char* format, ...) { } TimeLocationName location; timeGetDeviceLocationName(&location); - TimeZoneRule rule; + static TimeZoneRule rule; // 16kb, keep it off the tiny sysmodule stack timeLoadTimeZoneRule(&location, &rule); TimeCalendarTime t; timeToCalendarTime(&rule, timestamp, &t, nullptr); diff --git a/Sources/SwitchRPC/source/main.cpp b/Sources/SwitchRPC/source/main.cpp index 1f0d44f..92a3330 100644 --- a/Sources/SwitchRPC/source/main.cpp +++ b/Sources/SwitchRPC/source/main.cpp @@ -166,7 +166,7 @@ void __appInit(void) if (R_FAILED(rc)) diagAbortWithResult(MAKERESULT(Module_Libnx, LibnxError_InitFail_FS)); - // timeInitialize(); + timeInitialize(); // time() for log timestamps, the refresh timer and token expiry nifmInitialize(NifmServiceType_System); // Disable this if you don't want to use the SD card filesystem. @@ -225,7 +225,7 @@ void __appExit(void) nsExit(); pmdmntExit(); nifmExit(); - // timeExit(); + timeExit(); // Close extra services you added to __appInit here. fsdevUnmountAll(); // Disable this if you don't want to use the SD card filesystem. @@ -237,7 +237,14 @@ void __appExit(void) #endif -const int REFRESH_INTERVAL = 15 * 60; +// logged in = the config app left a refresh token on the sd card +static bool isLoggedIn() { + FILE* f = fopen("sdmc:/config/switchrpc_token", "r"); + if (f) { fclose(f); return true; } + return false; +} + +const int REFRESH_INTERVAL = 15 * 60; int main(int argc, char* argv[]) { @@ -245,6 +252,7 @@ int main(int argc, char* argv[]) AppInfo lastInfo = {0}; time_t last_update_time = 0; + bool wasLoggedIn = false; // Start by cleaning up stale sessions discordCleanupStaleSessions(); @@ -289,9 +297,20 @@ int main(int argc, char* argv[]) last_update_time = 0; } + // user hit "log out" in the config app (token file is gone) - drop the + // session and stop pushing until they log back in + bool loggedIn = isLoggedIn(); + if (wasLoggedIn && !loggedIn && !g_asleep) { + writeToLog("[SwitchRPC] Logged out, clearing presence."); + discordLogout(); + lastInfo = {0}; + last_update_time = 0; + } + wasLoggedIn = loggedIn; + // asleep = presence already gone, don't poll or we'd just recreate it // right before the console freezes - if (!g_asleep) { + if (!g_asleep && loggedIn) { AppInfo info = {0}; Result rc = get_app_info(&info); @@ -300,18 +319,19 @@ int main(int argc, char* argv[]) if (is_game_running) { // user opened a game or switched a game or switched from home menu to game (tid changed) if (info.tid != lastInfo.tid) { - // if lastInfo.tid != 0, we already had a session running, so we include the token to update it. - // if lastInfo.tid == 0, it's a brand new session, so no token is included. - bool has_existing_session = (lastInfo.tid != 0); - writeToLog("[SwitchRPC] Game state changed! New TID: %016llX, Name: %s. Previous TID: %016llX", (unsigned long long)info.tid, info.title_name, (unsigned long long)lastInfo.tid); + // kill the previous game's session first, otherwise they stack + // up on discord's side (and the new game gets a fresh timer) + if (lastInfo.tid != 0) { + discordDeleteHeadlessSession(); + } + lastInfo = info; last_update_time = time(NULL); - // create/update session. - discordCreateHeadlessSession(info.tid, std::string(info.title_name), has_existing_session); + discordCreateHeadlessSession(info.tid, std::string(info.title_name), false); } // the same game is still open. refresh. else { From f94e9e3a09cc7d5a9eb6b357815614974ee4acd3 Mon Sep 17 00:00:00 2001 From: Jonathan Levy Date: Sun, 19 Jul 2026 22:32:01 +0200 Subject: [PATCH 3/3] keep the playtime timer running across sleep instead of resetting on wake --- Sources/SwitchRPC/source/discord.cpp | 14 +++++++-- Sources/SwitchRPC/source/discord.hpp | 3 +- Sources/SwitchRPC/source/main.cpp | 43 +++++++++++++++++++++------- 3 files changed, 45 insertions(+), 15 deletions(-) diff --git a/Sources/SwitchRPC/source/discord.cpp b/Sources/SwitchRPC/source/discord.cpp index 324a2b3..fcf004e 100644 --- a/Sources/SwitchRPC/source/discord.cpp +++ b/Sources/SwitchRPC/source/discord.cpp @@ -325,7 +325,7 @@ void discordCleanupStaleSessions() { } } -void discordCreateHeadlessSession(u64 titleId, std::string titleName, const bool includeToken) { +void discordCreateHeadlessSession(u64 titleId, std::string titleName, u64 startEpochSec, const bool includeToken) { writeToLog("[Discord] Creating/Updating session for TID: %016llX (%s)", (unsigned long long)titleId, titleName.c_str()); // tries to fetch eshop icon, else falls back to tinfoil media icon @@ -347,7 +347,15 @@ void discordCreateHeadlessSession(u64 titleId, std::string titleName, const bool json_object_object_add(json_activity, "platform", json_object_new_string("desktop")); json_object_object_add(json_activity, "name", json_object_new_string(titleName.c_str())); json_object_object_add(json_activity, "state", json_object_new_string("Nintendo Switch")); - + + // elapsed timer: discord shows (now - start). we pass a start moved forward + // to exclude sleep, so it counts real playtime. timestamps are in ms. + if (startEpochSec != 0) { + json_object* json_timestamps = json_object_new_object(); + json_object_object_add(json_timestamps, "start", json_object_new_int64((int64_t)startEpochSec * 1000)); + json_object_object_add(json_activity, "timestamps", json_timestamps); + } + json_object* json_assets = json_object_new_object(); json_object_object_add(json_assets, "large_image", json_object_new_string(iconUrl.c_str())); json_object_object_add(json_activity, "assets", json_assets); @@ -377,7 +385,7 @@ void discordCreateHeadlessSession(u64 titleId, std::string titleName, const bool // if the attempt happened with an existing session token, try again without it in case the token was the issue if (includeToken) { writeToLog("[Discord] Retrying headless session creation without session token..."); - return discordCreateHeadlessSession(titleId, titleName, false); + return discordCreateHeadlessSession(titleId, titleName, startEpochSec, false); } return; } diff --git a/Sources/SwitchRPC/source/discord.hpp b/Sources/SwitchRPC/source/discord.hpp index cf7766d..60b1b35 100644 --- a/Sources/SwitchRPC/source/discord.hpp +++ b/Sources/SwitchRPC/source/discord.hpp @@ -12,9 +12,10 @@ * Creates or updates a Discord Headless Session to show the current game. * * @param titleId The Title ID of the currently running game. + * @param startEpochSec Unix time (seconds) the elapsed timer should count from; 0 to omit it. * @param includeToken Whether to include the current sessionToken in the request. */ -void discordCreateHeadlessSession(u64 titleId, std::string titleName, const bool includeToken); +void discordCreateHeadlessSession(u64 titleId, std::string titleName, u64 startEpochSec, const bool includeToken); /** * Deletes the active Discord Headless Session. diff --git a/Sources/SwitchRPC/source/main.cpp b/Sources/SwitchRPC/source/main.cpp index 92a3330..7fae7d5 100644 --- a/Sources/SwitchRPC/source/main.cpp +++ b/Sources/SwitchRPC/source/main.cpp @@ -244,6 +244,13 @@ static bool isLoggedIn() { return false; } +// current unix time in seconds (real, RTC-backed) +static u64 nowSec() { + u64 t = 0; + timeGetCurrentTime(TimeType_Default, &t); + return t; +} + const int REFRESH_INTERVAL = 15 * 60; int main(int argc, char* argv[]) @@ -253,6 +260,8 @@ int main(int argc, char* argv[]) AppInfo lastInfo = {0}; time_t last_update_time = 0; bool wasLoggedIn = false; + u64 accumulatedAwake = 0; // seconds of awake playtime banked for the current game + u64 chunkStart = 0; // utc seconds the current awake play chunk started // Start by cleaning up stale sessions discordCleanupStaleSessions(); @@ -283,18 +292,21 @@ int main(int argc, char* argv[]) } while (true) { - // just woke up - rebuild presence for whatever's running, fresh timer + // just woke up - resume the same game, continuing its playtime timer + // (the sleep period is excluded, see the sleep handler below) if (g_wokeFromSleep) { g_wokeFromSleep = false; g_asleep = false; - writeToLog("[SwitchRPC] Wake from sleep (PSC). Waiting for network, then rebuilding session."); waitForNetworkReady(); - - discordDeleteHeadlessSession(); discordCleanupStaleSessions(); - lastInfo = {0}; - last_update_time = 0; + if (lastInfo.tid != 0) { + writeToLog("[SwitchRPC] Wake from sleep (PSC). Resuming session for TID: %016llX", (unsigned long long)lastInfo.tid); + chunkStart = nowSec(); + u64 displayStart = chunkStart - accumulatedAwake; // move start forward to skip the sleep + discordCreateHeadlessSession(lastInfo.tid, std::string(lastInfo.title_name), displayStart, false); + last_update_time = time(NULL); + } } // user hit "log out" in the config app (token file is gone) - drop the @@ -329,9 +341,11 @@ int main(int argc, char* argv[]) } lastInfo = info; + accumulatedAwake = 0; + chunkStart = nowSec(); last_update_time = time(NULL); - discordCreateHeadlessSession(info.tid, std::string(info.title_name), false); + discordCreateHeadlessSession(info.tid, std::string(info.title_name), chunkStart, false); } // the same game is still open. refresh. else { @@ -341,8 +355,9 @@ int main(int argc, char* argv[]) writeToLog("[SwitchRPC] Periodic session refresh triggered for TID: %016llX", (unsigned long long)info.tid); last_update_time = current_time; - // refresh session with session token that we have if any. - discordCreateHeadlessSession(info.tid, std::string(info.title_name), true); + // refresh session, keeping the same elapsed start + u64 displayStart = chunkStart - accumulatedAwake; + discordCreateHeadlessSession(info.tid, std::string(info.title_name), displayStart, true); } } } else { @@ -353,6 +368,8 @@ int main(int argc, char* argv[]) lastInfo = {0}; last_update_time = 0; + accumulatedAwake = 0; + chunkStart = 0; discordDeleteHeadlessSession(); } @@ -368,9 +385,13 @@ int main(int argc, char* argv[]) g_sleepPending = false; writeToLog("[SwitchRPC] Sleep imminent (PSC). Clearing Discord presence before sleep."); + // bank the awake time played so far so the timer resumes minus sleep. + // keep lastInfo so we know which game to resume on wake. + if (lastInfo.tid != 0) { + accumulatedAwake += nowSec() - chunkStart; + } + discordDeleteHeadlessSession(); - lastInfo = {0}; - last_update_time = 0; g_asleep = true; ueventSignal(&g_sleepPrepDone);