Complete protocol documentation for the Uniwill/Clevo laptop liquid cooling units used by XMG OASIS, TUXEDO Aquaris, and other rebrands. Covers both command and response sides.
Reverse-engineered from GCUService.exe (Uniwill's backend service, .NET IL, 402k lines) by decompiling with ikdasm. This is the first public documentation of the response protocol - existing projects (watercooler-manager, TUXEDO Control Center, LPPController) only implement the command side.
The cooling unit uses BLE GATT with the Nordic UART Service (NUS).
CoolingSystem LCT21001(Mk1)CoolingSystem LCT22002(Mk2)
| UUID | Role |
|---|---|
6e400001-b5a3-f393-e0a9-e50e24dcca9e |
NUS Service |
6e400002-b5a3-f393-e0a9-e50e24dcca9e |
TX - host writes commands here |
6e400003-b5a3-f393-e0a9-e50e24dcca9e |
RX - host subscribes to notifications here |
The host (Control Center) connects in this order:
- Scan for BLE devices, match by stored MAC address
- Connect to the BLE device
- Discover GATT services, find NUS service (
6e400001-...) - Discover characteristics:
- Store TX (
6e400002-...) for writing commands - Enable notifications on RX (
6e400003-...) for receiving responses
- Store TX (
- Write
"sw"(bytes73 77, no0xFEframing) to TX - Device responds on RX with a firmware version string (UTF-8, no framing)
- Host parses the version string. Connection is only considered established after this step.
Step 5-7 are mandatory. If the device doesn't respond to "sw", the connection never completes.
After the connection is established, the host immediately restores the last saved state: pump duty + voltage, fan duty, head LED color + mode, fan LED color + mode. The device should expect a burst of commands right after the handshake.
The firmware version string must be at least 18 characters. The host calls Substring(17) to extract the version part. Example:
CoolingSystem FW V1.00.00
^-- version extracted from here
All commands are 8-byte packets written to the TX characteristic:
[0xFE] [CMD] [ON/OFF] [P1] [P2] [P3] [P4] [0xEF]
0xFE = start marker, 0xEF = end marker.
Commands used by the host (GCUService.exe):
| CMD | Name | byte[2] | byte[3] | byte[4] | byte[5] | byte[6] |
|---|---|---|---|---|---|---|
0x19 |
System Mode | on/off | mode | param | 0 | 0 |
0x1B |
Fan Control | on/off | duty (0-100) | 0 | 0 | 0 |
0x1C |
Pump Control | on/off | duty (0-100) | volt_code | 0 | 0 |
0x1E |
Head LED | on/off | R (0-255) | G (0-255) | B (0-255) | mode |
0x32 |
Query Meter | 0 | 0 | 0 | 0 | 0 |
0x33 |
Fan LED | on/off | R (0-255) | G (0-255) | B (0-255) | mode |
0x38 |
Line-off | 0 | 0 | 0 | 0 | 0 |
Additional commands found in the firmware but never sent by the host. Two firmware versions were analyzed:
- LCT21001 v1.0.1.7 (40KB, from
BT_DFU_FW/15/) - LCT22002 v2.0.0.4 (53KB, from
BT_DFU_FW/20/)
The LCT22002 firmware is significantly larger and supports more commands. CMD 0x33 (Fan LED) is absent from LCT21001 but present in LCT22002, confirming that the firmware evolves across versions and models.
| CMD | Name | byte[2] | byte[3] | byte[4] | LCT21001 | LCT22002 |
|---|---|---|---|---|---|---|
0x1A |
System Enable | on/off | 0 | 0 | Yes | Yes |
0x1D |
LED Zone 2 | on/off | 0 | 0 | Yes | Yes |
0x1F |
Reset All | 0 | 0 | 0 | Yes | Yes |
0x30 |
Send Telemetry | 0 | 0 | 0 | Yes | Yes |
0x34 |
Sensor Config | on/off | param1 | param2 | Yes | Yes |
0x35 |
Timer Config | on/off | value | 0 | Yes | Yes |
LCT22002-only additions:
| CMD | Name | byte[2] | byte[3] | byte[4] | byte[5] | byte[6] |
|---|---|---|---|---|---|---|
0x33 |
Fan LED | on/off | R | G | B | zone (1-6) |
0x37 |
FDS Write | 1 | data0 | data1 | data2 | record key offset (key = byte[6] + 0x1111) |
CMD 0x31 exists in the dispatch but is a no-op (immediately exits). It is only meaningful as a response code (device -> host).
CMD 0x33 in the LCT22002 has 6 sub-zones selected by byte[6] (values 1-6), each controlling a different RAM flag. This is more complex than the simple mode byte in the host's implementation.
CMD 0x37 writes to the Nordic FDS (Flash Data Storage) key-value store. File ID is 0x2222 (hardcoded at 0xADBE: movw r1, 0x2222), record key is byte[6] + 0x1111 (range 0x1111-0x1210). Bytes[3-5] are 3 bytes of data, a 4th byte is an auto-incrementing counter from RAM 0x20003760. Only executes when byte[2] == 1. Data is staged in RAM at 0x20004940, record key at 0x2000493E, write flag at 0x20004919. The FDS subsystem handles the actual flash write with wear leveling. This is persistent configuration storage (survives power cycles), not raw flash write or code execution. The host (GCUService) never sends this command.
A second packet format using 30-byte frames instead of 8-byte. Validated by byte[0]=0xFA and byte[0x1D]=0xAF.
- byte[1]: boolean-normalized, stored to
0x20003645(extended config enable) - bytes[2-25] (24 bytes): copied to RAM
0x2000362C-0x20003643 - bytes[0x1A-0x1C] (3 bytes): copied to RAM
0x20003646-0x20003648
Total: 27 bytes of configuration data written to device RAM. The purpose of this data is unknown from the firmware analysis alone - requires correlation with the host or hardware testing.
Plus the firmware version query: write 73 77 (ASCII "sw", 2 bytes, no 0xFE/0xEF framing).
| Bytes | Command |
|---|---|
FE 1B 01 DD 00 00 00 EF |
Fan ON, DD = duty (0-100) |
FE 1B 00 00 00 00 00 EF |
Fan OFF |
FE 1C 01 DD VV 00 00 EF |
Pump ON, DD = duty (0-100), VV = voltage code |
FE 1C 00 00 00 00 00 EF |
Pump OFF |
FE 1E 01 RR GG BB MM EF |
Head LED ON, RGB color, MM = mode |
FE 1E 00 00 00 00 00 EF |
Head LED OFF |
FE 33 01 RR GG BB MM EF |
Fan LED ON, RGB color, MM = mode |
FE 33 00 00 00 00 00 EF |
Fan LED OFF |
FE 32 00 00 00 00 00 EF |
Query meter state |
FE 19 00 01 00 00 00 EF |
Sleep (sent when clearing paired device) |
FE 19 01 04 50 00 00 EF |
Start priming (water fill) |
FE 19 00 04 00 00 00 EF |
Stop priming |
FE 38 00 00 00 00 00 EF |
Line-off (sent on disconnect, not sleep) |
73 77 |
Query firmware version (no framing) |
Firmware-only packets (not sent by the host, found in firmware binary):
| Bytes | Command |
|---|---|
FE 1A 01 00 00 00 00 EF |
System enable (on) |
FE 1A 00 00 00 00 00 EF |
System enable (off) |
FE 1D 01 00 00 00 00 EF |
LED zone 2 on |
FE 1D 00 00 00 00 00 EF |
LED zone 2 off |
FE 1F 00 00 00 00 00 EF |
Reset all outputs to defaults |
FE 30 00 00 00 00 00 EF |
Request telemetry notification (25-byte response) |
FE 34 01 P1 P2 00 00 EF |
Sensor config enable, P1 and P2 written to RAM |
FE 34 00 00 00 00 00 EF |
Sensor config disable |
FE 35 01 VV 00 00 00 EF |
Timer config enable, VV written to RAM, calls function |
FE 35 00 00 00 00 00 EF |
Timer config disable |
byte[4] of the pump command (0x1C):
| Voltage | Code |
|---|---|
| 7V | 2 |
| 8V | 3 |
| 11V | 0 |
| 12V | 1 |
byte[6] of head LED (0x1E) and fan LED (0x33):
| Mode | Value |
|---|---|
| Static | 0 |
| Breathe | 1 |
| Colorful | 2 |
| Breathe Color | 3 |
| byte[2] | byte[3] | Meaning |
|---|---|---|
| 0 | 1 | Sleep |
| 1 | 4 | Start priming, byte[4] = speed (0x50) |
| 0 | 4 | Stop priming |
These commands are handled by the nRF52832 firmware but never sent by the Windows host service. Found by disassembling ble_app_uart_pca10040_s132.bin using r2 -a arm -b 16. Handler details below are from the LCT21001 firmware (40KB). The LCT22002 firmware (53KB) was also fully analyzed - its dispatch contains the same commands plus 0x33 (Fan LED) and 0x37 (Flash Write), with RAM addresses shifted to different offsets.
Reads byte[2], normalizes to boolean (0 or 1), writes to RAM 0x20002b01. Clears RAM 0x200033c8 to 0. No other side effects.
Reads byte[2], normalizes to boolean, writes to RAM 0x20003399. Clears RAM 0x20003398 to 0. Structurally identical to 0x1A but writes to different RAM addresses. The RAM addresses are in the 0x200033xx range, adjacent to the head LED (0x1E) variables.
Note: CMD 0x33 (Fan LED, sent by the host) is not in the LCT21001 v1.0.1.7 dispatch but is present in the LCT22002 v2.0.0.4 dispatch. The relationship between 0x1D and 0x33 is unknown without testing on hardware.
Takes no parameters. Calls function at 0x4454 and returns. No packet bytes are read beyond the CMD byte.
Takes no parameters. Copies a 25-byte template from flash (0x2efb8), processes it through function 0x84c2, then sends it as a BLE GATT notification via sd_ble_gatts_hvx (0x7704). Retries on NRF_ERROR_RESOURCES (0x13). Uses the same code path as the "sw" handler. The 25-byte response format and contents of the template are not yet analyzed.
Three parameters: byte[2] = enable (boolean) -> RAM 0x20003c0b, byte[3] -> 32-bit write to RAM 0x20003c0c, byte[4] -> 32-bit write to RAM 0x20003c10. No function call after writing.
Two parameters: byte[2] = enable (boolean) -> RAM 0x20003c18, byte[3] -> 32-bit write to RAM 0x20002b44. Calls function 0x7cd8 after writing (unlike 0x34 which does not call any function).
Responses are BLE notifications on the RX characteristic. The host parser distinguishes two types based on the first byte.
Format: FE [31|32] XX SS 00 00 00 EF
- byte[1] =
0x31: unsolicited push from the device (periodic or on state change) - byte[1] =
0x32: response to aQuery Metercommand - byte[2]: unused (parser ignores it)
- byte[3] =
SS: the meter state value
Both 0x31 and 0x32 are parsed identically - the host reads byte[3] and fires a meter state event.
Meter state values:
| SS | Meaning |
|---|---|
| 2 | Flow OK. Host sets IsMeterNormal = true. |
| Anything else | Error. Host sets IsMeterNormal = false, but only if the pump is currently running. If the pump is off, the error is ignored. |
When IsMeterNormal goes false, the host disables GPU overclocking (via SetStatusSS(false)) and shows an error in the UI.
The host does not poll meter state. It expects the device to push 0x31 notifications on its own. The Query Meter command (0x32) exists in the code but is never called automatically.
If the first byte is not 0xFE, the entire payload is decoded as a UTF-8 string and stored as the firmware version. This is the response to the "sw" query sent during the connection handshake.
if (bytes[0] == 0xFE)
{
if (bytes[1] == 0x32 || bytes[1] == 0x31)
{
meter_state = bytes[3];
Event_Meter_State(meter_state);
}
// Other 0xFE packets: ignored
}
else
{
fw_version = UTF8.GetString(bytes);
MergeFWversion(fw_version);
}void OnMeterState(int state)
{
if (!connected) return;
if (state == 2)
is_meter_normal = true;
else if (pump_volt > 0)
is_meter_normal = false;
SetHWOCStatus(is_meter_normal);
BroadcastStatus();
}When the user triggers "Input Water" in the Control Center, the host runs this sequence:
- Send
FE 19 01 04 50 00 00 EF(start priming) - Fan ON at 50% duty
- Loop 8 times:
- Pump OFF, wait 500ms
- Pump ON (duty=100, volt=8V), wait 5500ms
- Pump OFF, wait 2500ms
- Send
FE 19 00 04 00 00 00 EF(stop priming) - Restore normal operating mode
Total duration: ~68 seconds. The timing is entirely host-controlled. The device just needs to accept the pump/fan commands as they come.
What a custom board emulating the cooling unit needs to implement.
Device name must be CoolingSystem LCT21001 or CoolingSystem LCT22002. Expose the Nordic UART Service with TX and RX characteristics.
Accept writes. If 8 bytes with 0xFE/0xEF framing, dispatch on byte[1]:
0x1B- Set fan duty0x1C- Set pump duty + voltage0x1E- Set head LED color + mode0x33- Set fan LED color + mode0x19- System mode (sleep / priming)0x32- Respond with current meter state0x38- Shutdown
If 2 bytes 73 77 ("sw"), respond with firmware version string.
| When | Send | Format |
|---|---|---|
Host sends 73 77 |
CoolingSystem FW V1.00.00 |
UTF-8 string, no framing. At least 18 chars. Mandatory for connection. |
| Flow state changes | FE 31 00 SS 00 00 00 EF |
SS=2 means OK, anything else means error. Push this, host doesn't poll. |
Host sends FE 32 ... |
FE 32 00 SS 00 00 00 EF |
Same SS value as above. |
If you don't have a flow sensor, always send SS = 2.
That's it. The host tracks everything else (pump duty, fan duty, LED settings) on its own side.
The Windows service also reads Embedded Controller registers via WMI. Not relevant for BLE implementations, but documented for completeness.
| Address | Description | Format |
|---|---|---|
0x07C7 |
LC support enable | Bit 1 = enable/disable |
0x07E6 |
Fan duty readback | Bit 7 = valid, bits 0-6 = value |
0x07E7 |
Pump voltage readback | Bit 7 = valid, bits 0-6 = value |
Everything above is extracted from GCUService.exe, the Uniwill backend service at Program Files/OEM/Control Center/UniwillService/MyControlCenter/. Decompiled with ikdasm into 402,742 lines of CIL.
ntfscat /dev/nvme0n1p3 \
"Program Files/OEM/Control Center/UniwillService/MyControlCenter/GCUService.exe" \
> GCUService.exe
ikdasm GCUService.exe > gcu_service_il.txt # 402,742 linesKey classes: Demo.BLE, Demo.BLE_STATE, GCUService.MyFan.BTCooling.LiquidCooling, GCUService.MyFan.BTCooling.LiquidHWOC.
// Demo.BLE constructor
IL_0001: ldstr "CoolingSystem LCT21001"
IL_000c: ldstr "CoolingSystem LCT22002"
IL_0017: ldstr "6e400001-b5a3-f393-e0a9-e50e24dcca9e"
IL_0027: ldstr "6e400002-b5a3-f393-e0a9-e50e24dcca9e"
IL_0037: ldstr "6e400003-b5a3-f393-e0a9-e50e24dcca9e"
// Demo.BLE_STATE
.field public static literal valuetype Demo.BLE_STATE DeviceNotReady = int32(0)
.field public static literal valuetype Demo.BLE_STATE DeviceIsReady = int32(1)
.field public static literal valuetype Demo.BLE_STATE Disconnected = int32(2)
.field public static literal valuetype Demo.BLE_STATE Scanning = int32(3)
.field public static literal valuetype Demo.BLE_STATE Connecting = int32(4)
.field public static literal valuetype Demo.BLE_STATE IsConnectable = int32(5)
.field public static literal valuetype Demo.BLE_STATE Connected = int32(6)
.data cil I_005F71F4 = bytearray (FE 1C 01 00 00 00 00 EF) // Pump ON
.data cil I_005F71FC = bytearray (FE 19 01 04 50 00 00 EF) // Start Priming
.data cil I_005F7204 = bytearray (FE 1E 01 00 00 00 00 EF) // LED ON
.data cil I_005F7218 = bytearray (FE 32 00 00 00 00 00 EF) // Query Meter
.data cil I_005F7220 = bytearray (FE 19 00 01 00 00 00 EF) // Sleep
.data cil I_005F7228 = bytearray (FE 1B 00 00 00 00 00 EF) // Fan OFF
.data cil I_005F723C = bytearray (FE 1B 01 00 00 00 00 EF) // Fan ON
.data cil I_005F7244 = bytearray (FE 1E 00 00 00 00 00 EF) // LED OFF
.data cil I_005F7258 = bytearray (FE 33 01 00 00 00 00 EF) // Fan LED ON
.data cil I_005F7260 = bytearray (FE 19 00 04 00 00 00 EF) // Stop Priming
.data cil I_005F7280 = bytearray (FE 1C 00 00 00 00 00 EF) // Pump OFF
.data cil I_005F728C = bytearray (FE 33 00 00 00 00 00 EF) // Fan LED OFF
.data cil I_005F7294 = bytearray (FE 38 00 00 00 00 00 EF) // Line-off
IL_0273: ldstr "sw"
IL_0278: ldc.i4.0 // BinaryStringEncoding.Utf8
IL_0279: call CryptographicBuffer::ConvertStringToBinary(string, BinaryStringEncoding)
IL_0283: call instance Task<bool> Demo.BLE::WriteBufferToSelectedCharacteristicAsync(IBuffer)
IL_000a: ldelem.u1 // bytes[0]
IL_000b: ldc.i4 0xfe // compare with 0xFE
IL_0010: bne.un.s IL_0068 // if != 0xFE -> UTF-8 branch
IL_0014: ldelem.u1 // bytes[1]
IL_0015: ldc.i4.s 50 // 0x32
IL_0017: beq.s IL_0020 // if == 0x32 -> meter state
IL_001b: ldelem.u1 // bytes[1]
IL_001c: ldc.i4.s 49 // 0x31
IL_001e: bne.un.s IL_0057 // if != 0x31 -> skip
IL_0022: ldc.i4.3 // index 3
IL_0023: ldelem.u1 // bytes[3] = meter_state
IL_0024: call instance void Demo.BLE::set_meter_state(int32)
IL_0052: callvirt instance void Report_Meter_State_Handler::Invoke(int32)
// UTF-8 branch:
IL_0069: call System.Text.Encoding::get_UTF8()
IL_006f: callvirt instance string System.Text.Encoding::GetString(uint8[])
IL_0074: call instance void Demo.BLE::set_BTDevFWversion(string)
IL_0095: call instance void Demo.BLE::MergeFWversion(string)
IL_0001: ldfld bool LiquidCooling::_Connected
IL_0006: brfalse.s IL_0042 // if (!connected) return
IL_0009: ldc.i4.2 // compare state with 2
IL_000a: bne.un.s IL_0015 // if != 2 -> else
IL_000d: ldc.i4.1 // true
IL_000e: stfld bool LiquidCooling::IsMeterNormal
IL_0016: ldfld int32 LiquidCooling::_PumpVolt
IL_001c: ble.s IL_0025 // if (PumpVolt <= 0) skip
IL_001f: ldc.i4.0 // false
IL_0020: stfld bool LiquidCooling::IsMeterNormal
IL_0037: call instance void LiquidHWOC::SetStatusSS(bool)
IL_003d: call instance void LiquidCooling::SendLCsettings()
IL_009e: ldc.i4.s 17
IL_00a0: callvirt instance string System.String::Substring(int32)
IL_00a5: stfld string Demo.BLE::LC_FWver // version starts at char 17
IL_0151: ldc.i4.6 // BLE_STATE.Connected
IL_0152: callvirt instance void Report_BLE_State_Handler::Invoke(BLE_STATE)
IL_0001: ldc.i4.s 11 -> code = 0
IL_000a: ldc.i4.s 12 -> code = 1
IL_0013: ldc.i4.7 -> code = 2
IL_001a: (default) -> code = 3
// Template: I_005F71F4 (FE 1C 01 00 00 00 00 EF)
IL_0038: ldc.i4.3 // byte[3] = duty
IL_003c: ldc.i4.4 // byte[4] = volt_code
.field public static literal uint16 CoolingModeECAddress = uint16(0x07C7)
.field public static literal uint16 ADDR_LC_FAN_VALUE = uint16(0x07E6)
.field public static literal uint16 ADDR_LC_PUMP_VALUE = uint16(0x07E7)
// ReadPumpVolt:
IL_0008: ldstr "LiquidCooling" // WMI namespace
IL_000d: ldc.i4 0x7e7 // EC register
IL_001a: ldc.i4.7 // bit 7 = valid flag
IL_0024: ldc.i4.s 127 // & 0x7F = value
Two firmware versions analyzed:
- LCT21001 v1.0.1.7 (40KB) from
BT_DFU_FW/15/LCT21001_DFU-AP_v1.0.1.7.zip - LCT22002 v2.0.0.4 (53KB) from
BT_DFU_FW/20/LCT22002_DFU-AP_v2.0.0.4.zip
Both are nRF52832 ARM Cortex-M4 Thumb, SoftDevice s132. Disassembled with r2 -a arm -b 16.
LCT21001 dispatch at 0x44A0:
0x44A8: cmp r2, 0xfe ; byte[0] == start marker?
0x44AE: cmp r3, 0xef ; byte[7] == end marker?
0x44BE: cmp r3, 0x1a ; CMD 0x1A - System Enable
0x44DE: cmp r3, 0x1b ; CMD 0x1B - Fan Control
0x451A: cmp r3, 0x1c ; CMD 0x1C - Pump Control
0x45DE: cmp r3, 0x1d ; CMD 0x1D - LED Zone 2
0x45FC: cmp r3, 0x1e ; CMD 0x1E - Head LED
0x465E: cmp r3, 0x1f ; CMD 0x1F - Reset All
0x4670: cmp r3, 0x30 ; CMD 0x30 - Send Telemetry
0x46D4: cmp r3, 0x19 ; CMD 0x19 - System Mode
0x47D4: cmp r3, 0x32 ; CMD 0x32 - Query Meter
0x47E0: cmp r3, 0x34 ; CMD 0x34 - Sensor Config
0x47FC: cmp r3, 0x35 ; CMD 0x35 - Timer Config
0x4818: cmp r3, 0x38 ; CMD 0x38 - Line-off
; ASCII dispatch:
0x4566: cmp r2, 0x73 ; byte[0] == 's'
0x456C: cmp r3, 0x77 ; byte[1] == 'w'
LCT22002 dispatch at 0x62D2. Three protocols: standard 8-byte (0xFE/0xEF), ASCII "sw" (0x73/0x77), and extended 30-byte (0xFA/0xAF):
; Standard 8-byte protocol
0x62D2: cmp r3, 0xfe ; byte[0] == start marker?
0x62D8: cmp r3, 0xef ; byte[7] == end marker?
0x62F6: cmp r3, 0x1a ; CMD 0x1A - System Enable
0x6424: cmp r3, 0x1b ; CMD 0x1B - Fan Control
0x645E: cmp r3, 0x1c ; CMD 0x1C - Pump Control
0x64B2: cmp r3, 0x1d ; CMD 0x1D - LED Zone 2
0x64CE: cmp r3, 0x1e ; CMD 0x1E - Head LED
0x652E: cmp r3, 0x1f ; CMD 0x1F - Reset All
0x65E4: cmp r3, 0x30 ; CMD 0x30 - Send Telemetry
0x6646: cmp r3, 0x19 ; CMD 0x19 - System Mode
0x668A: cmp r3, 0x31 ; CMD 0x31 - no-op (exits immediately)
0x6690: cmp r3, 0x32 ; CMD 0x32 - Query Meter
0x669C: cmp r3, 0x33 ; CMD 0x33 - Fan LED (not in LCT21001)
0x6746: cmp r3, 0x34 ; CMD 0x34 - Sensor Config
0x6762: cmp r3, 0x35 ; CMD 0x35 - Timer Config
0x677E: cmp r3, 0x37 ; CMD 0x37 - Flash Write (not in LCT21001)
0x67B8: cmp r3, 0x38 ; CMD 0x38 - Line-off
; ASCII protocol
0x6320: cmp r3, 0x73 ; byte[0] == 's'
0x6326: cmp r3, 0x77 ; byte[1] == 'w'
; Extended 30-byte protocol
0x6398: cmp r3, 0xfa ; byte[0] == 0xFA
0x63A0: cmp r3, 0xaf ; byte[0x1D] == 0xAF
Example handler - CMD 0x1A (LCT21001):
0x44BE: cmp r3, 0x1a ; CMD == 0x1A?
0x44C0: bne 0x44de ; no -> next CMD
0x44C2: ldr r1, [0x4724] ; log string
0x44C6: bl 0x9c0 ; log
0x44CA: ldr r3, [r5, 0x10] ; packet pointer
0x44CC: ldr r2, [0x4728] ; r2 = 0x20002b01
0x44CE: ldrb r3, [r3, 2] ; r3 = byte[2]
0x44D0: subs r3, 0 ; normalize to boolean
0x44D2: it ne
0x44D4: movs r3, 1
0x44D6: strb r3, [r2] ; store to 0x20002b01
Example handler - CMD 0x30 Send Telemetry (LCT21001):
0x4672: bl 0x9c0 ; log
; Copy 25-byte template from flash 0x2efb8 to stack
0x4688: bl 0x84c2 ; format response buffer
; Send BLE notification:
0x46A4: bl 0x7704 ; sd_ble_gatts_hvx()
0x46A8: cmp r0, 8 ; NRF_ERROR_INVALID_STATE -> exit
0x46AE: cmp r0, 0x13 ; NRF_ERROR_RESOURCES -> retry
0x46B4: cmp r0, 5 ; NRF_ERROR_NOT_FOUND -> exit
0x46B8: cmp r0, 0 ; NRF_SUCCESS -> exit
| File | Path | Type | Description |
|---|---|---|---|
GCUService.exe |
OEM/Control Center/UniwillService/MyControlCenter/ |
.NET IL | Backend service (fully decompiled) |
GamingCenter3_Cross.dll |
UWP package | UWP AOT | Frontend (strings only) |
M2Mqtt.Net.dll |
Same as GCUService | .NET IL | Local IPC MQTT library |
BTSavingSettings.json |
MyControlCenter/BTSavingSettings/ |
JSON | Paired device MAC |
LCSavingSettings.json |
MyControlCenter/LCSavingSettings/ |
JSON | LC settings |
LiquidHWOC.json |
MyControlCenter/LiquidHWOC/ |
JSON | GPU OC settings |
BT_DFU_FW.zip |
OEM/Control Center/ZipFiles/ |
ZIP | Nordic DFU firmware |
BT_DFU_Tool.zip |
OEM/Control Center/ZipFiles/ |
ZIP | Nordic DFU tool |
ble_app_uart_pca10040_s132.bin |
Inside BT_DFU_FW/15/LCT21001_DFU-AP_v1.0.1.7.zip |
ARM Thumb | nRF52832 LCT21001 firmware (40KB, fully analyzed) |
ble_app_uart_pca10040_s132.bin |
Inside BT_DFU_FW/20/LCT22002_DFU-AP_v2.0.0.4.zip |
ARM Thumb | nRF52832 LCT22002 firmware (53KB, dispatch analyzed) |