Skip to content

Instantly share code, notes, and snippets.

@RJNY
Last active February 15, 2026 15:01
Show Gist options
  • Select an option

  • Save RJNY/06cb791cd0d4d5ed39e3929cfcee6018 to your computer and use it in GitHub Desktop.

Select an option

Save RJNY/06cb791cd0d4d5ed39e3929cfcee6018 to your computer and use it in GitHub Desktop.

Apollo & Artemis - Pause / Resume scripts refactor

Reworked the pause/resume scripts for Apollo/Artemis.

Important

This is a personal refactor I'm sharing as-is. The original scripts have been used by more people and are the safer bet.

  • Race condition fix: flipped the lock file protocol so pause writes the PID and resume reads it back. Handles out-of-order events where resume fires before pause.
  • Least-privilege access: PROCESS_SUSPEND_RESUME (0x0800) instead of PROCESS_ALL_ACCESS. May fix "suspend stopped working" reports.
  • Error handling: helpers return a boolean instead of calling MsgBox + ExitApp, which was re-triggering the cleanup handler.
  • DllCall dedup: merged SuspendProcess/ResumeProcess into CallNtProcessFunc(pid, funcName). Also fixed a handle leak on failure paths.
  • Debug flag: MsgBox calls are off by default so they don't block Apollo's command pipeline.
  • Added steam.exe to exclusion list (Big Picture freezing issue from gist comments).
  • Process name lookup uses "ahk_pid " pid explicitly instead of AHK's "last found window".
  • Cleanup handler deletes the lock file on unexpected exit.
#Requires AutoHotkey v2.0
#SingleInstance Force
DetectHiddenWindows true
global Debug := false
global LockFile := A_Temp "\AutoPauseResume.lock"
global IsProcessSuspended := false
global IsProcessResumed := false
OnExit(Cleanup)
if FileExist(LockFile)
FileDelete(LockFile)
; ---------------------------------------------------------------------------
; Excluded processes (lowercase)
; ---------------------------------------------------------------------------
ExcludedProcesses := Map(
"explorer.exe", true,
"csrss.exe", true,
"winlogon.exe", true,
"svchost.exe", true,
"dwm.exe", true,
"cmd.exe", true,
"powershell.exe", true,
"chrome.exe", true,
"steam.exe", true,
"sublime_text.exe", true,
"sublime_merge.exe", true,
"steamwebhelper.exe", true,
"openconsole.exe", true,
"windowsterminal.exe", true,
"playnite.desktopapp.exe", true,
"playnite.fullscreenapp.exe", true
)
if !WinExist("A")
{
if Debug
MsgBox "No active window detected. Please focus a window to suspend."
ExitApp
}
pid := WinGetPID("A")
if !pid
{
if Debug
MsgBox "Failed to get the active window's PID."
ExitApp
}
processName := WinGetProcessName("ahk_pid " pid)
if !processName
{
if Debug
MsgBox "Failed to retrieve the active window's process name."
ExitApp
}
if ExcludedProcesses.Has(StrLower(processName))
ExitApp
; ---------------------------------------------------------------------------
; Suspend the process and write PID to lock file for resume.ahk
; ---------------------------------------------------------------------------
if !CallNtProcessFunc(pid, "NtSuspendProcess")
{
if Debug
MsgBox "Failed to suspend process '" processName "' (PID: " pid ")."
ExitApp
}
IsProcessSuspended := true
FileAppend(pid, LockFile) ; PID in lock file so resume.ahk can act independently
; Wait for resume.ahk to delete the lock file
SetTimer(CheckResumed, 500)
return
; ---------------------------------------------------------------------------
CheckResumed(*)
{
global IsProcessResumed
if !FileExist(LockFile)
{
IsProcessResumed := true
ExitApp
}
}
; ---------------------------------------------------------------------------
; Safety net: resume the process if this script exits unexpectedly.
; ---------------------------------------------------------------------------
Cleanup(exitReason, exitCode)
{
global pid, IsProcessSuspended, IsProcessResumed
if IsProcessSuspended && !IsProcessResumed
{
CallNtProcessFunc(pid, "NtResumeProcess")
if FileExist(LockFile)
FileDelete(LockFile)
}
}
; ---------------------------------------------------------------------------
; Calls NtSuspendProcess or NtResumeProcess on a PID.
; Uses PROCESS_SUSPEND_RESUME (0x0800) - the minimum required access right.
; Returns true on success, false on failure.
; ---------------------------------------------------------------------------
CallNtProcessFunc(pid, funcName)
{
hProc := DllCall("OpenProcess", "UInt", 0x0800, "Int", 0, "UInt", pid, "Ptr")
if !hProc
return false
hNtdll := DllCall("GetModuleHandle", "Str", "ntdll.dll", "Ptr")
if !hNtdll
{
DllCall("CloseHandle", "Ptr", hProc)
return false
}
pFunc := DllCall("GetProcAddress", "Ptr", hNtdll, "AStr", funcName, "Ptr")
if !pFunc
{
DllCall("CloseHandle", "Ptr", hProc)
return false
}
DllCall(pFunc, "Ptr", hProc)
DllCall("CloseHandle", "Ptr", hProc)
return true
}
#Requires AutoHotkey v2.0
#SingleInstance Force
LockFile := A_Temp "\AutoPauseResume.lock"
; No lock file means pause hasn't fired (or already cleaned up)
if !FileExist(LockFile)
ExitApp
lockContent := Trim(FileRead(LockFile))
if !lockContent
{
FileDelete(LockFile)
ExitApp
}
pid := Integer(lockContent)
CallNtProcessFunc(pid, "NtResumeProcess")
FileDelete(LockFile)
ExitApp
; ---------------------------------------------------------------------------
; Calls NtSuspendProcess or NtResumeProcess on a PID.
; Uses PROCESS_SUSPEND_RESUME (0x0800) - the minimum required access right.
; Returns true on success, false on failure.
; ---------------------------------------------------------------------------
CallNtProcessFunc(pid, funcName)
{
hProc := DllCall("OpenProcess", "UInt", 0x0800, "Int", 0, "UInt", pid, "Ptr")
if !hProc
return false
hNtdll := DllCall("GetModuleHandle", "Str", "ntdll.dll", "Ptr")
if !hNtdll
{
DllCall("CloseHandle", "Ptr", hProc)
return false
}
pFunc := DllCall("GetProcAddress", "Ptr", hNtdll, "AStr", funcName, "Ptr")
if !pFunc
{
DllCall("CloseHandle", "Ptr", hProc)
return false
}
DllCall(pFunc, "Ptr", hProc)
DllCall("CloseHandle", "Ptr", hProc)
return true
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment