// case study

How FanPilot is built

A case study in reverse engineering a vendor’s fan control, putting a phone in front of it, and making sure nothing breaks the hardware along the way.

From the panel to the fan: The Gigabyte Control Center’s client library talks over .NET Remoting (IPC) to the EasyTune Engine Service, which writes the curve into the ITE IT8689E chip. FanPilot uses the same client library, then serves the panel over HTTP and SSE to the paired phone.
  1. Phone / tabletpaired by QR · HTTP + SSE
  2. FanPilotservice · web panel · safety rules
  3. Gigabyte.Engine.EnvironmentControl.dllGCC client SDK, loaded by reflection
  4. EasyTuneEngineService.NET Remoting · ipc://GigabyteEngine · SYSTEM, admins only
  5. ITE IT8689E Super I/OSmartGuardian · the curve runs in hardware

The problem

On Gigabyte boards, the only way to change the fan curves from Windows is the Gigabyte Control Center: a desktop app, on the PC itself. FanPilot set out to do the same from a phone on the same Wi-Fi, without replacing the vendor’s service and without shipping any of its files.

Finding the way in

The analysis started from the shipped artifacts: metadata and IL of the .NET assemblies, reflection-only API dumps and targeted decompilation, then confirmed at runtime with small probes (tools/Probe*.cs). The reference machine was a B760M GAMING X AX with an i5-13400.

The architecture turned out to be a client and a service talking over .NET Remoting:

  • GCC.exe is only the UI. It loads Gigabyte.Engine.EnvironmentControl.dll, a client SDK of *Module classes.
  • Those modules connect to ipc://GigabyteEngine/EnvironmentControl/<Object>, published by EasyTuneEngineService, a Windows service running as SYSTEM.
  • The service does the real work, down to Gigabyte.EnvironmentControl.LowPinCount.dll, which talks to the board’s ITE IT8689E Super I/O chip over the LPC ports.

Two details mattered right away:

  • The IPC port’s ACL admits Administrators only: a non-elevated process gets “access denied”. FanPilot asks for elevation for that reason.
  • The first call on a new connection fails with “the pipe is being closed”; a retry works. FanPilot retries once.

Reading the sensors

HardwareMonitorControlModule.GetCurrentMonitoredData returns every reading as {Type, Title, DeviceUUID, Value, Unit}: on the test board, 23 items, from CPU and VRM temperatures to fan RPM, voltages, clocks and CPU power. The GPU isn’t there, so FanPilot reads NVIDIA cards through the driver’s own nvml.dll.

Writing a curve

The Control Center picks a “generation” of fan control from the board’s group number. On this board it is the third, and the module that works is SmartGuardianFanControlCoordinateSystemModule: Get and Set a curve per header, choose which sensor drives it, read temperatures and RPM side by side.

A curve is a SmartFanDataPointSet: a mode and a list of points, X in °C and Y as PWM from 0 to 255. Inside the service, Set goes through a converter that turns it into the chip’s native SmartGuardian parameters. That has a big consequence: the curve runs in hardware, and keeps running with FanPilot, and even Windows, closed.

The point that had to be 255

The first write test “didn’t work”: every fan went to full speed. The converter, read from SmartGuardianFanConfigConverter, explained why. It maps the points like this:

Point Becomes
P[0].X the temperature at which the fan turns off
P[1] start temperature and start PWM
P[2..n-2] “extra vectors”: start, PWM and slope of each segment
P[n-1] the full-speed temperature, and its Y must be 255

With fewer than three points, or a last point below 255, the conversion aborts silently and the service writes an all-zero configuration, which the chip treats as maximum speed. FanPilot’s editor therefore always ends a curve at 100%, and its validation refuses anything the chip would misread.

The fix was confirmed on the real board: System 1 set to 40% up to 70 °C went from 2352 to 1573 RPM in 8 seconds, and back up when the original curve was restored.

Built without the vendor’s files

FanPilot doesn’t redistribute or compile against any Gigabyte file. A thin layer, GigabyteApi, resolves the Control Center’s types and members by reflection from the copy already installed on the PC, checks the whole contract at startup, and caches the handles. If a Control Center update changes something, FanPilot says exactly what is missing in the panel and carries on through LibreHardwareMonitor. One build serves every board, and the CI can build it.

Every other board

Boards without the Control Center go through LibreHardwareMonitor and the PawnIO driver, which FanPilot offers to install with winget. There the chip only takes a duty cycle and keeps no curve, so FanPilot runs the curves itself, once a second. Each curve has a response (Fast, Balanced or Smooth): the temperature is smoothed, the fan only slows down after the PC cools a few degrees, and the speed changes gradually. Closing FanPilot, a sensor without a reading or repeated failures hand every fan back to the BIOS.

On Linux (beta), FanPilot reaches the board through the kernel’s hwmon drivers (nct6775, it87, asus-ec-sensors…) and the GPU through NVML or amdgpu, as a systemd service.

Both paths sit behind one interface, IBoard, so the rest of FanPilot doesn’t care which one it is talking to.

The app around it

  • A service, started with Windows before logon, restarted by Windows if it dies, plus a tray icon and a terminal interface.
  • A web panel served by FanPilot itself over HTTP, with live readings pushed by Server-Sent Events.
  • QR pairing: a single-use 144-bit code shown on the PC, then a session cookie of which only a hash is stored. The server answers only requests addressed to the PC (against DNS rebinding) and checks origin and a custom header on every write (against CSRF).
  • Safety rules in one place: at least 20% on every curve, 100% at the last point by 90 °C, pumps kept at 100% or a chosen minimum, a 15-second watchdog that re-applies your curve if something overwrites it.

How it’s tested

  • 308 C# test cases (xUnit), with no hardware and no Administrator, against a fake board with scenarios: the demo board, a worst case with eight fans and long names, an unsupported board, a service that is down, a slow one.
  • Unit tests for the panel’s JavaScript (Node’s test runner): curve math, i18n, sensors.
  • 70 end-to-end tests in 15 Playwright spec files, run in Edge: the curve editor by mouse, keyboard and touch, pairing, alerts, history, panel mode, the MQTT integration, reconnection after a restart, accessibility with axe-core, and screenshots at 320, 390 and 1024 px in both themes.
  • --hardware-test on the real board: fixed speed, a watched curve, Turbo, the GPU fan and a restart, never below 40%, refusing the CPU fan and pumps, stopping if the CPU or GPU passes 80 °C, and putting everything back.
  • CI on every push (GitHub Actions): build, all of the above as Administrator, and a smoke test of the release zip.
Fan curves you drag

Sources: docs/REVERSE_ENGINEERING.md and the design specs in the repository. REVERSE_ENGINEERING.md