Skip to content

Commit 9ab264b

Browse files
authored
Merge pull request #24 from Rovey/explicit-logon-install
fix: no install at start, and the logon task without schtasks.exe
2 parents d299710 + 678e865 commit 9ab264b

12 files changed

Lines changed: 159 additions & 77 deletions

‎CMakeLists.txt‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ add_library(hw STATIC
8686
src/hw/update_io.hpp src/hw/update_io.cpp
8787
)
8888
target_include_directories(hw PUBLIC src)
89-
target_link_libraries(hw PUBLIC core d3d11 dxgi d3dcompiler shell32 ole32 winhttp bcrypt version
89+
target_link_libraries(hw PUBLIC core d3d11 dxgi d3dcompiler shell32 ole32 oleaut32 taskschd winhttp bcrypt version
9090
PRIVATE gao_warnings gao_hardening)
9191

9292
# UI-independent application logic shared by both executables.

‎README.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,7 @@ Only to look for a new version: one request to `api.github.com` when the app sta
220220
<details>
221221
<summary><b>How do I move to a new version?</b></summary>
222222

223-
From 0.3.1 on, click **Update available** at the bottom left of the window. From an older version, download the zip and start `GpuAutoOptimizer.exe` from it: it offers to replace the older copy that runs in the tray, and it updates the copy that starts at logon by itself.
223+
From 0.3.1 on, click **Update available** at the bottom left of the window. From an older version, download the zip and start `GpuAutoOptimizer.exe` from it: it offers to replace the older copy that runs in the tray, and then updates the copy that starts at logon.
224224
</details>
225225

226226
<details>

‎docs/development.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ The applied offset is always at least one step below the confirmed edge. Profile
100100
| Fans through NVML, stop zone via the driver | NVIDIA's legacy NVAPI fan API is gone on RTX 20-series and newer; NVML's `nvmlDeviceSetFanSpeed_v2` is public and verified by reading the target back. Below the stop threshold the driver owns the fans, so no failure of this app can leave them stopped. |
101101
| Tray app plus logon task | Driver settings are volatile: a reboot or driver reset clears them. The logon task starts the tray app, whose watchdog keeps the tune applied; three crashing logons in a row switch it off. |
102102
| An update is one click, and never more than that | The app asks GitHub for the latest release (`core/update`) and shows one line in the sidebar; nothing is downloaded until it is clicked. The zip must come from this project's releases over HTTPS and match the SHA-256 the GitHub API publishes for it, and both executables in it must carry the version the release claims. The order of these steps and every refusal are `install_release` in `core/update`, which gets its steps as callbacks and is tested with a fake source; a release without a usable SHA-256 is refused there too, not only by the parser, because an empty expectation would equal the hash of a file that cannot be read. Elevated, the download is unpacked under `%ProgramData%\GpuAutoOptimizer\update`, which only administrators can write, so nothing can be swapped between the check and the copy. The executables are replaced where they run from (a running one is renamed aside) and the app starts again with `--resume`, which keeps a tune that is applied under the watchdog. The trust this rests on is the GitHub account: the executables are not code-signed. |
103-
| A newer copy takes over from an older one | The app is single-instance, and the copy in the tray usually runs elevated. A copy that is started later reads the running one's version from a window property (builds before 0.3.1 have none and count as older) and, when it is newer itself, offers to replace it: it starts again elevated with `--replace`, asks the old copy to exit through its own tray-menu command (which hands the fans back and stops a run cleanly) and ends it only when it does not go and no optimize run is under way. An elevated start from outside Program Files also replaces the logon copy when apply-at-logon is on and that copy is not newer (`update_logon_copy`): a profile saved by a new build must be one the logon copy accepts. |
103+
| A newer copy takes over from an older one | The app is single-instance, and the copy in the tray usually runs elevated. A copy that is started later reads the running one's version from a window property (builds before 0.3.1 have none and count as older) and, when it is newer itself, offers to replace it: it starts again elevated with `--replace`, asks the old copy to exit through its own tray-menu command (which hands the fans back and stops a run cleanly) and ends it only when it does not go and no optimize run is under way. The logon copy follows when apply-at-logon is on and that copy is not newer (`update_logon_copy`), because a profile saved by a new build must be one the logon copy accepts; but only after something the user did with the new build: an update, taking over from an older copy, or a run that saved a profile. A copy that was merely started installs nothing and says in its log that the logon copy is older. Until 0.4.0 the copy installed itself the moment it started elevated, and the task was created by starting `schtasks.exe`: on 2026-10-10 Windows Defender judged exactly that sequence in a build nobody had seen before (the app restarting itself elevated, copying itself to Program Files and creating a logon task with the highest rights, all within a second) as `Behavior:Win32/Execution.A!ml`, and removed the build, the logon copy and the task (hardware check 83). The task is now registered through the Task Scheduler's own interface (`ITaskFolder::RegisterTask`), and nothing is started for it. |
104104
| A logon apply that ends cleanly is no strike | Strikes count logons that may have crashed the machine. An apply that was refused before any write, or failed and reset the card to stock, did not: the strike it recorded up front is taken back. Only a failed reset keeps it. Before 0.3.1 three refused logons read as `disabled after 3 crashes`. |
105105
| An undervolt is a flat top on the curve, found like a core offset | The driver lets a program offset the frequency of each point of the voltage/frequency curve; voltages cannot be written. To run a clock on less voltage, a lower point (the anchor) is raised to that clock and no point above it runs more, so the card has no reason to ask for more voltage. The points below keep the built-in curve, so idle clocks stay as they are. No voltage lock is set: the lock call hard-froze the reference RTX 4070 in the Python version. The write (`apply_flat_top`) uses the units the reference card stores (kHz, one to one; hardware check 61) and estimates nothing. What protects a card that stores them differently is the order: the first round raises a point only half of the way, so a card that moves twice as far lands on the target and not above it, and a card that did not move the anchor by what was written (within one step of its clock grid) is refused before the full step; each later round adds a point's own remaining gap as read back, which also follows a curve that shifts with temperature; no offset is ever written beyond what the plan asks plus two grid steps; and a card that has not arrived after four rounds is refused and cleared. (A first version estimated the units from the read-back; a review showed that estimate could push the anchor far above the target, and it was removed before any write ran on hardware.) A reset to stock zeroes a shape on the curve first and judges it by reading back; a plain core offset, which is the same offset on every point, still goes with the offset reset alone. The search (`find_undervolt`) keeps the clock the card runs under load at stock and moves the anchor one point down at a time. What a candidate asks of the card is its raise, the anchor's frequency above its built-in one, which is a core offset seen at one point; so the core search's rules carry over unchanged: first failure ends the descent, 30 s confirm, the same margin on the raise, 300 s soak, journal entry (`"uv"`) before the write, any failure ends at stock. The core offset and the curve are one table (hardware check 61), so a profile will carry one or the other. First version: `gao --undervolt` only, not saved, not in the window; no write has been run on hardware yet (checks 62 to 65). The curve calls are undocumented NVAPI, as the offsets are; the write procedure and the search are this project's own, with Green Curve (aufkrawall/green-curve, MIT) read as a worked example of what goes wrong on real cards. |
106106
| Dear ImGui on DX11 | One small binary with no runtime, and the D3D11 device is in the process anyway for the stress load. |

0 commit comments

Comments
 (0)