12 Commits
Author SHA1 Message Date
lzrdblzzrd bf37dd6974 Update README: fix defaults (game-mem=2G), remove SKIP RESTORE, add GAME DETECT, update game mode description 2026-06-23 11:20:55 +03:00
lzrdblzzrd 0e8d7333c1 Rewrite: simplify to strict algorithm, remove promoted map
- Remove promoted map entirely — processes are demoted when RSS < -mem
  and promoted when RSS >= -mem, no permanent state
- Remove trySetPrio closure — use setPrio directly for simplicity
- Remove curPrioWithHandle helper — inline procGetPriority.Call
- Remove GAME ... tracked as game (blocked) repeated log — log once
  on detection via GAME DETECT, silently skip on subsequent scans
- Game mode: log GAME DETECT once when process first exceeds game-mem
- gameSaved stores original priority before IDLE demote, restores on
  GAME MODE OFF or shutdown
- Delay between priority changes: 100ms
- Delete TZ.md (was a test for other AI)
2026-06-23 11:16:51 +03:00
lzrdblzzrd da13a6d549 Demote promoted processes when RSS drops below threshold
Previously, once a process was promoted to HIGH, it stayed HIGH forever
even if its RSS dropped well below the -mem threshold. Now promoted
processes are demoted back to NORMAL when RSS < -mem, and removed from
the promoted map.

Also: added TZ.md with full technical specification of the project.
2026-06-23 08:58:11 +03:00
lzrdblzzrd ea914101bb Fix: blocked processes still measured for game-mem detection
Previously, when SetPriorityClass failed for a process (e.g. anti-cheat),
it was added to 'blocked' and completely skipped in future scans —
including RSS measurement. This meant a game that started below game-mem
threshold and got blocked before reaching it would never trigger game mode.

Now pass 1 measures RSS for ALL processes (including blocked ones) and
adds them to gameProcs if they exceed game-mem. Pass 2 skips priority
changes for blocked processes but still tracks them as game processes.

In game mode, blocked game procs log 'tracked as game (blocked)' instead
of trying SetPriorityClass again.
2026-06-22 17:41:10 +03:00
lzrdblzzrd 565b751195 Rewrite game mode: two-pass scan, priority restoration, LimitedInformation access
- Use PROCESS_QUERY_LIMITED_INFORMATION instead of PROCESS_QUERY_INFORMATION
  to open processes (fixes Access Denied for KeePassXC, HD-Player, etc.)
- Separate query handle from set handle: trySetPrio reopens with
  PROCESS_SET_INFORMATION only when needed
- Two-pass scan: pass 1 collects RSS and detects game processes,
  pass 2 applies priorities with correct hasGame state
- Game mode saves original priorities in gameSaved map, restores them
  on GAME MODE OFF (not just NORMAL)
- Demote ALL non-game processes to IDLE in game mode (not just promoted)
- 50ms delay between priority changes for smooth transitions
- Replace isAlive/procName with pidMap from allProcs snapshot
- Replace mutex/MessageBox single-instance with killOtherInstances
- Defaults: -mem=512M, -game-mem=4G
- Fix double CloseHandle and handle leak in trySetPrio
- Update README with new defaults and game mode restoration behavior
2026-06-22 17:24:21 +03:00
lzrdblzzrd b5885791fa Fix process entry struct, add demote logic, block protected processes, improve logging 2026-06-21 15:11:15 +03:00
lzrdblzzrd d4da33ca5e fix: ErrorAlreadyExists 1832->183, wrap syscall errors, signal handling, mutex cleanup, log sync 2026-06-20 23:41:59 +03:00
lzrdblzzrd 33dfd4b4c5 fix: processEntry32 DefaultHeapID uint32 instead of uintptr 2026-06-19 23:24:01 +03:00
lzrdblzzrd b999b7ec4f refactor: error handling, single rss call, stale PID cleanup, utf16 trim, O_APPEND log 2026-06-19 23:20:10 +03:00
lzrdblzzrd d0cafe1332 log: move log file to temp directory with fallback 2026-06-19 21:39:45 +03:00
lzrdblzzrd c3cb8ae847 rewrite: single file, zero deps, Windows API, logging, CLI flags 2026-06-19 21:37:01 +03:00
lzrdblzzrd c8648da296 updated with opencode 2026-06-17 02:15:43 +03:00
6 changed files with 676 additions and 90 deletions
+11
View File
@@ -0,0 +1,11 @@
*.exe
*.log
*.exe~
*.swp
*.swo
*~
.env
.idea/
.vscode/
.DS_Store
Thumbs.db
+101 -1
View File
@@ -1,3 +1,103 @@
# autoPriority # autoPriority
A program that automatically sets a high priority for game processes Monitors process memory usage on Windows and automatically adjusts CPU priority: promotes memory-heavy processes to HIGH and demotes everything else to NORMAL. Optionally enters **Game Mode** when a process exceeds a higher threshold, boosting it to HIGH and setting all other processes to IDLE.
**Windows only.**
## How it works
### Normal mode
Every scan interval the program iterates over all running processes:
| RSS vs `-mem` | Current priority | Action |
|---|---|---|
| ≥ threshold | anything except HIGH | → HIGH (logged as `PROMOTE`) |
| ≥ threshold | already HIGH | skip |
| < threshold | ABOVE_NORMAL, HIGH, or REALTIME | → NORMAL (logged as `DEMOTE`) |
| < threshold | NORMAL, BELOW_NORMAL, or IDLE | skip |
### Game mode (`-game-mem`)
When any process reaches or exceeds the `-game-mem` threshold:
1. That process → **HIGH** (logged as `GAME`)
2. All other processes → **IDLE** (original priorities saved)
3. Log: `GAME MODE ON`
When all such processes close:
1. All processes that were demoted during game mode are **restored to their original priority** (logged as `RESTORE`)
2. `GAME MODE OFF` logged
3. Normal mode resumes — priorities recalculated by `-mem` rules
If `SetPriorityClass` or `OpenProcess` fails for a process (e.g., anti-cheat protection, system processes), the process is added to an in-memory exclusion list and never touched again (logged as `BLOCK`). Blocked processes are still measured for RSS each scan — if a blocked process exceeds `-game-mem`, it triggers game mode without attempting to change its priority. When the process exits, it is automatically removed from the list.
On shutdown, all processes demoted during game mode are restored to their saved original priority.
## Build
Requires Go 1.26+.
```
# Standard build (with console window)
go build -o autopriority.exe .
# Background build (no console, minimal size)
go build -ldflags="-H windowsgui -s -w" -o autopriority.exe .
```
## Usage
```
autopriority [flags]
```
| Flag | Default | Description |
|-------------|---------------|----------------------------------|
| `-mem` | 512M | Memory threshold (e.g. 512M, 1G, 2048M) |
| `-game-mem` | 2G | Game threshold (e.g. 2G, 4G). Must be > `-mem`. 0 = disabled |
| `-interval` | 1 minute | Scan interval (min 10s) |
| `-dry-run` | false | Log only, don't change priority |
Examples:
```
# Run with 1 GB threshold, scanning every 30 seconds
autopriority -mem=1G -interval=30s
# Run with defaults (512M threshold, 2G game threshold)
autopriority
# Game mode: normal threshold 512M, game threshold 4G
autopriority -mem=512M -game-mem=4G
# Dry run — log decisions without changing anything
autopriority -dry-run
```
## Log
Log is always written to `%TEMP%\autopriority.log`. A new log file is created on each run (previous log is deleted).
Log entries:
| Prefix | Meaning |
|---|---|
| `PROMOTE` | Priority raised to HIGH (normal mode) |
| `DEMOTE` | Priority lowered to NORMAL (normal mode) |
| `GAME DETECT` | Process first detected as exceeding game-mem threshold |
| `GAME` | Process set to HIGH or IDLE (game mode) |
| `GAME MODE ON` | Game mode activated |
| `GAME MODE OFF` | Game mode deactivated — processes restored |
| `BLOCK` | OpenProcess or SetPriorityClass failed; process added to exclusion list |
| `RESTORE` | Process restored to original/saved priority (game mode exit or shutdown) |
| `[DRY-RUN]` | Would change priority (dry-run mode) |
## Auto-start
Open **Win+R**, type `shell:startup`, and press Enter. Then place a shortcut to `autopriority.exe` in the folder that opens.
## Dependencies
None. Uses only Windows API (kernel32, psapi) via raw syscall.
+2 -12
View File
@@ -1,13 +1,3 @@
module autoPriority module autopriority
go 1.19 go 1.26
require (
github.com/go-ole/go-ole v1.2.6 // indirect
github.com/postfinance/single v0.0.2 // indirect
github.com/shirou/gopsutil v3.21.11+incompatible // indirect
github.com/tklauser/go-sysconf v0.3.11 // indirect
github.com/tklauser/numcpus v0.6.0 // indirect
github.com/yusufpapurcu/wmi v1.2.2 // indirect
golang.org/x/sys v0.4.0 // indirect
)
-22
View File
@@ -1,22 +0,0 @@
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/postfinance/single v0.0.2 h1:YLjLxxeGDnsW93oK4CxxOFSVOOiBi1OyoK4ZTl5biJw=
github.com/postfinance/single v0.0.2/go.mod h1:OYWUsdMIZK9eQyZYpAsMHN+j6+jXJ6RFUNqIWH0oC5U=
github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI=
github.com/shirou/gopsutil v3.21.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/tklauser/go-sysconf v0.3.11 h1:89WgdJhk5SNwJfu+GKyYveZ4IaJ7xAkecBo+KdJV0CM=
github.com/tklauser/go-sysconf v0.3.11/go.mod h1:GqXfhXY3kiPa0nAXPDIQIWzJbMCB7AmcWpGR8lSZfqI=
github.com/tklauser/numcpus v0.6.0 h1:kebhY2Qt+3U6RNK7UqpYNA+tJ23IBEGKkB7JQBfDYms=
github.com/tklauser/numcpus v0.6.0/go.mod h1:FEZLMke0lhOUG6w2JadTzp0a+Nl8PF/GFkQ5UVIcaL4=
github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg=
github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18=
golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+562
View File
@@ -0,0 +1,562 @@
//go:build windows
package main
import (
"flag"
"fmt"
"os"
"os/signal"
"path/filepath"
"strconv"
"strings"
"syscall"
"time"
"unsafe"
)
const (
CreateToolhelp32SnapshotProcess = 0x00000002
ProcessTerminate = 0x00000001
ProcessSetInformation = 0x00000200
ProcessQueryLimitedInformation = 0x00001000
PriorityClassIdle = 0x00000040
PriorityClassNormal = 0x00000020
PriorityClassHigh = 0x00000080
PriorityClassAboveNormal = 0x00008000
PriorityClassRealtime = 0x00000100
PriorityClassBelowNormal = 0x00004000
ErrorNoMoreFiles = 18
)
type processEntry32 struct {
Size uint32
CntUsage uint32
PID uint32
DefaultHeapID uintptr
ModuleID uint32
CntThreads uint32
ParentPID uint32
PrioClass int32
Flags uint32
ExeFile [260]uint16
}
type processMemoryCounters struct {
CBM uint32
PageFaultCount uint32
PeakWorkingSetSize uintptr
WorkingSetSize uintptr
QuotaPeakPagedPoolUsage uintptr
QuotaPagedPoolUsage uintptr
QuotaPeakNonPagedPoolUsage uintptr
QuotaNonPagedPoolUsage uintptr
PeakPagefileUsage uintptr
PagefileUsage uintptr
PrivateUsage uintptr
}
type procInfo struct {
PID uint32
Name string
RSS uint64
}
type savedPrio struct {
name string
prio uint32
}
var (
k32 = syscall.NewLazyDLL("kernel32.dll")
ps = syscall.NewLazyDLL("psapi.dll")
procCreateSnap = k32.NewProc("CreateToolhelp32Snapshot")
procProcess32First = k32.NewProc("Process32FirstW")
procProcess32Next = k32.NewProc("Process32NextW")
procGetMemInfo = ps.NewProc("GetProcessMemoryInfo")
procSetPriority = k32.NewProc("SetPriorityClass")
procGetPriority = k32.NewProc("GetPriorityClass")
procOpenProcess = k32.NewProc("OpenProcess")
procCloseHandle = k32.NewProc("CloseHandle")
procTerminate = k32.NewProc("TerminateProcess")
)
func closeH(h syscall.Handle) {
_, _, _ = procCloseHandle.Call(uintptr(h))
}
func openProc(pid uint32, acc uint32) (syscall.Handle, error) {
r, _, e := procOpenProcess.Call(uintptr(acc), 0, uintptr(pid))
if r == 0 {
return 0, fmt.Errorf("OpenProcess(%d) failed: %w", pid, e)
}
return syscall.Handle(r), nil
}
func setPrio(pid uint32, cls uint32) error {
h, err := openProc(pid, ProcessSetInformation)
if err != nil {
return err
}
defer closeH(h)
r, _, e := procSetPriority.Call(uintptr(h), uintptr(cls))
if r == 0 {
return fmt.Errorf("SetPriorityClass(%d) failed: %w", pid, e)
}
return nil
}
func utf16Trim(p []uint16) string {
for i, v := range p {
if v == 0 {
return syscall.UTF16ToString(p[:i])
}
}
return syscall.UTF16ToString(p)
}
func parseMemSize(s string) (uint64, error) {
s = strings.TrimSpace(s)
if s == "" {
return 0, fmt.Errorf("empty value")
}
var suffix byte
switch s[len(s)-1] {
case 'k', 'K':
suffix = 'K'
s = s[:len(s)-1]
case 'm', 'M':
suffix = 'M'
s = s[:len(s)-1]
case 'g', 'G':
suffix = 'G'
s = s[:len(s)-1]
}
n, err := strconv.ParseUint(s, 10, 64)
if err != nil {
return 0, err
}
switch suffix {
case 'K':
return n * 1024, nil
case 'M':
return n * 1024 * 1024, nil
case 'G':
return n * 1024 * 1024 * 1024, nil
default:
return n, nil
}
}
func formatMemSize(b uint64) string {
if b >= 1024*1024*1024 {
return fmt.Sprintf("%.1fGB", float64(b)/(1024*1024*1024))
}
if b >= 1024*1024 {
return fmt.Sprintf("%dMB", b/1024/1024)
}
return fmt.Sprintf("%dKB", b/1024)
}
func isAboveNormal(c uint32) bool {
return c == PriorityClassAboveNormal ||
c == PriorityClassHigh ||
c == PriorityClassRealtime
}
func prioName(c uint32) string {
switch c {
case PriorityClassIdle:
return "IDLE"
case PriorityClassBelowNormal:
return "BELOW_NORMAL"
case PriorityClassNormal:
return "NORMAL"
case PriorityClassAboveNormal:
return "ABOVE_NORMAL"
case PriorityClassHigh:
return "HIGH"
case PriorityClassRealtime:
return "REALTIME"
default:
return fmt.Sprintf("0x%X", c)
}
}
func allProcs() ([]procInfo, error) {
snap, _, e := procCreateSnap.Call(CreateToolhelp32SnapshotProcess, 0)
if snap == 0 {
return nil, fmt.Errorf("CreateToolhelp32Snapshot failed: %w", e)
}
defer closeH(syscall.Handle(snap))
pe := processEntry32{Size: uint32(unsafe.Sizeof(processEntry32{}))}
r, _, e := procProcess32First.Call(snap, uintptr(unsafe.Pointer(&pe)))
if r == 0 {
return nil, fmt.Errorf("Process32First failed: %w", e)
}
var out []procInfo
for {
name := utf16Trim(pe.ExeFile[:])
if name != "" {
out = append(out, procInfo{PID: pe.PID, Name: name})
}
pe.Size = uint32(unsafe.Sizeof(processEntry32{}))
r, _, e = procProcess32Next.Call(snap, uintptr(unsafe.Pointer(&pe)))
if r == 0 {
if e == syscall.Errno(ErrorNoMoreFiles) {
break
}
return nil, fmt.Errorf("Process32Next failed: %w", e)
}
}
return out, nil
}
func killOtherInstances() {
myPID := os.Getpid()
procs, err := allProcs()
if err != nil {
return
}
myName := filepath.Base(os.Args[0])
for _, p := range procs {
if p.PID != uint32(myPID) && filepath.Base(p.Name) == myName {
h, err := openProc(p.PID, ProcessTerminate)
if err == nil {
procTerminate.Call(uintptr(h), 0)
closeH(h)
}
}
}
}
func main() {
memStr := flag.String("mem", "512M", "memory threshold (e.g. 512M, 1G, 2048M)")
gameMemStr := flag.String("game-mem", "2G", "game memory threshold (e.g. 2G, 4G). Must be greater than -mem. 0 = disabled.")
interval := flag.Duration("interval", time.Minute, "scan interval")
dryRun := flag.Bool("dry-run", false, "log only, do not change priorities")
flag.Parse()
if *interval < 10*time.Second {
*interval = 10 * time.Second
}
mem, err := parseMemSize(*memStr)
if err != nil {
fmt.Fprintf(os.Stderr, "autoPriority: invalid -mem value: %v\n", err)
os.Exit(1)
}
gameMem, err := parseMemSize(*gameMemStr)
if err != nil {
fmt.Fprintf(os.Stderr, "autoPriority: invalid -game-mem value: %v\n", err)
os.Exit(1)
}
if gameMem > 0 && gameMem <= mem {
fmt.Fprintf(os.Stderr, "autoPriority: -game-mem must be greater than -mem\n")
os.Exit(1)
}
killOtherInstances()
time.Sleep(100 * time.Millisecond)
var logFile *os.File
logPath := filepath.Join(os.TempDir(), "autopriority.log")
os.Remove(logPath)
f, err := os.OpenFile(logPath, os.O_CREATE|os.O_WRONLY|os.O_EXCL, 0644)
if err == nil {
logFile = f
defer logFile.Close()
}
ts := func() string {
return time.Now().Format("02.01.2006 15:04:05")
}
logf := func(format string, a ...any) {
if logFile == nil {
return
}
fmt.Fprintf(logFile, "[%s] ", ts())
fmt.Fprintf(logFile, format+"\n", a...)
logFile.Sync()
}
if gameMem > 0 {
logf("autoPriority started (mem=%s, game-mem=%s, interval=%s, dry-run=%v)",
formatMemSize(mem), formatMemSize(gameMem), *interval, *dryRun)
} else {
logf("autoPriority started (mem=%s, interval=%s, dry-run=%v)",
formatMemSize(mem), *interval, *dryRun)
}
if err := setPrio(uint32(os.Getpid()), PriorityClassIdle); err != nil {
logf("warning: could not set own priority to IDLE: %v", err)
} else {
logf("own priority set to IDLE")
}
blocked := make(map[uint32]string)
gameProcs := make(map[uint32]string)
gameSaved := make(map[uint32]savedPrio)
gameMode := false
defer func() {
if gameMode {
restoreProcs, _ := allProcs()
rm := make(map[uint32]string, len(restoreProcs))
for _, p := range restoreProcs {
rm[p.PID] = p.Name
}
for pid, sv := range gameSaved {
if cur, ok := rm[pid]; !ok || cur != sv.name {
continue
}
if err := setPrio(pid, sv.prio); err != nil {
logf("RESTORE %s (PID %d) -> %s error: %v", sv.name, pid, prioName(sv.prio), err)
} else {
logf("RESTORE %s (PID %d) -> %s", sv.name, pid, prioName(sv.prio))
}
}
}
logf("autoPriority stopped")
}()
myPID := uint32(os.Getpid())
ticker := time.NewTicker(*interval)
defer ticker.Stop()
stop := make(chan os.Signal, 1)
signal.Notify(stop, os.Interrupt, syscall.SIGTERM)
scan := func() {
procs, err := allProcs()
if err != nil {
logf("allProcs error: %v", err)
return
}
pidMap := make(map[uint32]string, len(procs))
for _, p := range procs {
pidMap[p.PID] = p.Name
}
for pid := range blocked {
if _, ok := pidMap[pid]; !ok {
delete(blocked, pid)
}
}
for pid, name := range gameProcs {
if cur, ok := pidMap[pid]; !ok || cur != name {
delete(gameProcs, pid)
}
}
for pid, sv := range gameSaved {
if cur, ok := pidMap[pid]; !ok || cur != sv.name {
delete(gameSaved, pid)
}
}
var list []procInfo
for i := range procs {
p := &procs[i]
if p.PID == myPID || p.PID == 0 {
continue
}
h, err := openProc(p.PID, ProcessQueryLimitedInformation)
if err != nil {
if _, ok := blocked[p.PID]; !ok {
blocked[p.PID] = p.Name
logf("BLOCK %s (PID %d): %v (added to exclusion list)", p.Name, p.PID, err)
}
continue
}
var m processMemoryCounters
m.CBM = uint32(unsafe.Sizeof(m))
r, _, e := procGetMemInfo.Call(uintptr(h), uintptr(unsafe.Pointer(&m)), uintptr(unsafe.Sizeof(m)))
closeH(h)
if r == 0 {
if _, ok := blocked[p.PID]; !ok {
logf("GetProcessMemoryInfo(%d) error: %v", p.PID, e)
}
continue
}
p.RSS = uint64(m.WorkingSetSize)
list = append(list, *p)
if gameMem > 0 && p.RSS >= gameMem {
if _, ok := gameProcs[p.PID]; !ok {
gameProcs[p.PID] = p.Name
logf("GAME DETECT %s (PID %d) RSS=%s", p.Name, p.PID, formatMemSize(p.RSS))
}
}
}
hasGame := gameMem > 0 && len(gameProcs) > 0
for i := range list {
p := &list[i]
_, isBlocked := blocked[p.PID]
_, isGame := gameProcs[p.PID]
if hasGame && isGame {
if isBlocked {
continue
}
if *dryRun {
logf("[DRY-RUN] %s (PID %d) RSS=%s — would set HIGH (game)", p.Name, p.PID, formatMemSize(p.RSS))
continue
}
h, err := openProc(p.PID, ProcessQueryLimitedInformation)
if err != nil {
continue
}
cur, _, _ := procGetPriority.Call(uintptr(h))
closeH(h)
if cur != 0 && uint32(cur) == PriorityClassHigh {
continue
}
if err := setPrio(p.PID, PriorityClassHigh); err != nil {
blocked[p.PID] = p.Name
logf("BLOCK %s (PID %d): %v (added to exclusion list)", p.Name, p.PID, err)
logf("GAME %s (PID %d) RSS=%s — tracked as game (priority change failed)", p.Name, p.PID, formatMemSize(p.RSS))
} else {
logf("GAME %s (PID %d) RSS=%s -> HIGH", p.Name, p.PID, formatMemSize(p.RSS))
time.Sleep(100 * time.Millisecond)
}
continue
}
if hasGame {
if isBlocked {
continue
}
if *dryRun {
logf("[DRY-RUN] %s (PID %d) RSS=%s — would set IDLE (game)", p.Name, p.PID, formatMemSize(p.RSS))
continue
}
h, err := openProc(p.PID, ProcessQueryLimitedInformation)
if err != nil {
continue
}
cur, _, _ := procGetPriority.Call(uintptr(h))
closeH(h)
if cur == 0 || uint32(cur) == PriorityClassIdle {
continue
}
if err := setPrio(p.PID, PriorityClassIdle); err != nil {
blocked[p.PID] = p.Name
logf("BLOCK %s (PID %d): %v (added to exclusion list)", p.Name, p.PID, err)
} else {
if _, saved := gameSaved[p.PID]; !saved {
gameSaved[p.PID] = savedPrio{name: p.Name, prio: uint32(cur)}
}
logf("GAME %s (PID %d) RSS=%s, %s -> IDLE", p.Name, p.PID, formatMemSize(p.RSS), prioName(uint32(cur)))
time.Sleep(100 * time.Millisecond)
}
continue
}
if p.RSS < mem {
if isBlocked {
continue
}
h, err := openProc(p.PID, ProcessQueryLimitedInformation)
if err != nil {
continue
}
cur, _, _ := procGetPriority.Call(uintptr(h))
closeH(h)
if cur == 0 || !isAboveNormal(uint32(cur)) {
continue
}
if *dryRun {
logf("[DRY-RUN] %s (PID %d) RSS=%s, priority=%s — would set NORMAL", p.Name, p.PID, formatMemSize(p.RSS), prioName(uint32(cur)))
continue
}
if err := setPrio(p.PID, PriorityClassNormal); err != nil {
blocked[p.PID] = p.Name
logf("BLOCK %s (PID %d): %v (added to exclusion list)", p.Name, p.PID, err)
} else {
logf("DEMOTE %s (PID %d) RSS=%s, %s -> NORMAL", p.Name, p.PID, formatMemSize(p.RSS), prioName(uint32(cur)))
time.Sleep(100 * time.Millisecond)
}
continue
}
if isBlocked {
continue
}
h, err := openProc(p.PID, ProcessQueryLimitedInformation)
if err != nil {
continue
}
cur, _, _ := procGetPriority.Call(uintptr(h))
closeH(h)
if cur != 0 && uint32(cur) == PriorityClassHigh {
continue
}
origStr := ""
if cur != 0 {
origStr = prioName(uint32(cur))
}
if *dryRun {
logf("[DRY-RUN] %s (PID %d) RSS=%s, priority=%s — would set HIGH", p.Name, p.PID, formatMemSize(p.RSS), origStr)
continue
}
if err := setPrio(p.PID, PriorityClassHigh); err != nil {
blocked[p.PID] = p.Name
logf("BLOCK %s (PID %d): %v (added to exclusion list)", p.Name, p.PID, err)
} else {
logf("PROMOTE %s (PID %d) RSS=%s, %s -> HIGH", p.Name, p.PID, formatMemSize(p.RSS), origStr)
time.Sleep(100 * time.Millisecond)
}
}
if gameMem > 0 {
newHasGame := len(gameProcs) > 0
if newHasGame && !gameMode {
gameMode = true
logf("GAME MODE ON")
}
if !newHasGame && gameMode {
gameMode = false
logf("GAME MODE OFF")
for pid, sv := range gameSaved {
if cur, ok := pidMap[pid]; !ok || cur != sv.name {
delete(gameSaved, pid)
continue
}
if *dryRun {
logf("[DRY-RUN] RESTORE %s (PID %d) -> %s", sv.name, pid, prioName(sv.prio))
} else {
if err := setPrio(pid, sv.prio); err != nil {
logf("RESTORE %s (PID %d) -> %s error: %v", sv.name, pid, prioName(sv.prio), err)
} else {
logf("RESTORE %s (PID %d) -> %s", sv.name, pid, prioName(sv.prio))
time.Sleep(100 * time.Millisecond)
}
}
delete(gameSaved, pid)
}
}
}
}
scan()
for {
select {
case <-stop:
logf("received shutdown signal")
return
case <-ticker.C:
scan()
}
}
}
-55
View File
@@ -1,55 +0,0 @@
package main
/*
#include <windows.h>
BOOL SetPriority(DWORD pid, int priority) {
HANDLE h = OpenProcess(PROCESS_ALL_ACCESS, 0, pid);
if (!h) return FALSE;
BOOL success = SetPriorityClass(h, priority);
CloseHandle(h);
return success;
}
*/
import "C"
import (
"github.com/postfinance/single"
"github.com/shirou/gopsutil/process"
"os"
"runtime"
"syscall"
"time"
"unsafe"
)
func MessageBox(lpText string, lpCaption string, uType int) int {
var convertedCaption, _ = syscall.UTF16PtrFromString(lpCaption)
var convertedTitle, _ = syscall.UTF16PtrFromString(lpText)
returnCode, _, _ := syscall.NewLazyDLL("user32.dll").NewProc("MessageBoxW").Call(
uintptr(0),
uintptr(unsafe.Pointer(convertedCaption)),
uintptr(unsafe.Pointer(convertedTitle)),
uintptr(uType),
)
return int(returnCode)
}
var lockFile, _ = single.New("autoPriority")
func main() {
if err := lockFile.Lock(); err != nil {
MessageBox("The program is already running", "Only one instance of the program can be run\nYou can close it via the Task Manager", 48)
os.Exit(1)
}
C.SetPriority(C.DWORD(os.Getpid()), 0x00000040)
for range time.Tick(time.Minute) {
processes, _ := process.Processes()
for _, proc := range processes {
memoryInfo, err := proc.MemoryInfo()
if err == nil && memoryInfo.RSS >= (0.5*1024*1024*1024) {
C.SetPriority(C.DWORD(proc.Pid), 0x00000080)
}
}
runtime.GC()
}
}