Skip to content

QNMEA.dll Decompilation

Raw technical findings from reverse engineering QGNSS V1.8 using Ghidra. QGNSS is the Windows-only configuration tool provided by Quectel for their GNSS module families. These findings informed the design of the lc29h Linux replacement.

ComponentDescription
QGNSS.exeQt5 C++ application, 15,013 functions
QNMEA.dllNMEA parser library, 669 exported functions
RuntimeQt 5.x (QIODevice, QSettings, QByteArray, QString)

The DLL is a parser-only library — it contains no command construction logic. All command building happens in QGNSS.exe itself.

classDiagram
    NMEA_Base <|-- QGNSS_NMEA_Parse
    NMEA_Base <|-- NMEA_DT
    NMEA_Base <|-- Async_Read_Log
    NMEA_Base <|-- QLOG_Data_Center

    class QGNSS_NMEA_Parse {
        Main parser
        Dispatches by sentence ID
    }
    class NMEA_DT {
        Data types
        Field converters
    }
    class Async_Read_Log {
        Async read logging
    }
    class QLOG_Data_Center {
        Data logging/collection
    }

The parser uses a leftover-buffer pattern for handling partial NMEA sentences across read boundaries:

  1. New data arrives via an async read callback
  2. Data is appended to a persistent leftover buffer
  3. The buffer is scanned for $ (sentence start) and \r\n (sentence end)
  4. Complete sentences between these markers are extracted and dispatched by sentence ID lookup
  5. Partial data remaining after the last complete sentence is retained for the next read cycle

This is the standard approach for stream-oriented NMEA parsing over serial or TCP connections — the same pattern used in the lc29h Python CLI’s parser.

Talker ID table at virtual address 0x18001f8a0:

  • 24 entries, each 4 bytes (2-character ASCII ID + 2 bytes padding)
  • Maps talker prefixes (GP, GL, GA, GB, BD, QZ, GN, GQ, etc.) to internal constellation enums

Sentence ID table at virtual address 0x18001f7b8:

  • 7 entries mapping 3-character sentence types to enum values
  • Mapping: RMC=0, VTG=1, GGA=2, GSA=3, GSV=4, GNS=5, GLL=6

Extracted from initialized data sections and function prologues:

TALKERID_LEN = 2
SENTENCEID_LEN = 3
CHECKSUM_LEN = 2
RF_LEN = 2 (length of \r\n terminator)
MIN_NMEA_LEN = 12
MAX_NMEA_LEN = 82
NMEASTARTC = '$' (0x24)
NMEAENDC = '*' (0x2a)
NMEADOT = ',' (0x2c, field separator)
NMEARF = '\r\n'

MAX_NMEA_LEN=82 is the NMEA 0183 standard maximum sentence length including $ and \r\n. These constants match the specification exactly.

Two checksum implementations were identified:

Serial transmission variant (FUN_14024abd0):

  • Input: QByteArray (raw bytes of sentence body)
  • Operation: XOR all bytes
  • Output: Appends *XX\r\n to the input
  • Used when writing commands to the serial port via QIODevice::write

Display/logging variant (FUN_14024adc0):

  • Input: QString (Unicode sentence body)
  • Operation: XOR all character code points
  • Output: Appends *XX only (no CRLF terminator)
  • Used for command echo in the UI console

Both compute the standard NMEA XOR checksum. The only difference is framing: serial output includes \r\n, display output does not.

Command construction follows a consistent pattern:

  1. sprintf (or QString::arg) builds the sentence body (e.g., PAIR066,1,1,1,1,1)
  2. The serial-variant checksum function appends *XX\r\n
  3. QIODevice::write sends the complete framed command to the serial port

No intermediate validation or escaping. Commands are assembled from format strings with parameters interpolated directly.

Commands found in the QGNSS binary, organized by the UI page where they appeared:

CommandDescriptionVariants
PAIR003Cold start (clear all)AA, DA
PAIR004Hot startAA, DA
PAIR005Warm startAA, DA
PAIR006Cold start (keep config)AA, DA
PAIR007Factory resetAA, DA
PAIR021Query firmware versionAll
CommandDescription
PAIR066Set active constellations

PAIR066 uses positional boolean fields:

$PAIR066,gps,glo,gal,bds,qzss*CS

Each field is 0 (disabled) or 1 (enabled). Example enabling GPS, Galileo, and BeiDou only:

$PAIR066,1,0,1,1,0*CS
CommandDescription
PAIR470Query EPO status
PAIR471Inject EPO data segment (20 hex fields)
PAIR472Erase EPO data
PAIR590Time assist injection
PAIR600Position assist injection
PAIR864Trigger EPO file download

PAIR471 segments use 20 hex-encoded fields per command. The full EPO file is chunked and sent as sequential PAIR471 commands:

$PAIR471,field0,field1,...,field19*CS

PAIR590 injects time assistance:

$PAIR590,year,month,day,hour,minute,second*CS

PAIR600 uses hardcoded uncertainty values in the QGNSS binary, though the protocol specification allows them to be set explicitly.

Quectel proprietary commands found in UI construction code:

CommandDescription
PQTMVERNOQuery firmware/hardware/SDK version
CommandDescription
PQTMCFGPORTConfigure port baud rate and protocol
PQTMCFGNMEAMSGConfigure per-message NMEA output
PQTMCFGMSGRATESet output rate for any message type
CommandDescription
PQTMCFGEAMASKSet satellite elevation mask angle
PQTMCFGCLAMPINGEnable/disable static speed clamping
PQTMCFGODOEnable/disable odometer
PQTMRESETODOReset odometer
PQTMCFGGEOFENCEConfigure geofence circles
CommandDescription
PQTMCFGDRMODESet DR mode (GNSS-only, DR-only, auto)
PQTMCFGFWDEnable/disable UART forwarding
PQTMCFGWHEELTICKConfigure wheel tick input
PQTMCFGEINSMSGEnable/disable extended INS messages
CommandDescription
PQTMCFGCANConfigure CAN bus interface
PQTMCFGCANFILTERConfigure CAN message filters
PQTMCFGVEHDBCConfigure vehicle DBC for DR
CommandDescription
PQTMSAVEPARSave parameters to flash
PQTMRESTOREPARRestore factory defaults
PQTMGNSSSUSPENDSuspend GNSS engine
PQTMGNSSRESUMEResume GNSS engine
PQTMGNSSRESTARTRestart GNSS engine
PQTMFWUPGRADEEnter firmware upgrade mode

The NTRIP client in QGNSS was implemented directly over TCP sockets (not via Qt’s HTTP classes):

  • HTTP request templates were hardcoded as string literals in the binary
  • Basic authentication used standard base64 encoding
  • User-Agent was "NTRIP QGNSS"
  • Connection settings were persisted via QSettings under the Ntrip/ registry prefix:
    • Ntrip/IP, Ntrip/Port, Ntrip/Username, Ntrip/Password, Ntrip/Mountpoint
  • Auto-reconnect on disconnect with no backoff logic

Extracted from string constants in the QGNSS binary:

FilenameContents
QGPS.DATGPS extended ephemeris
QG_R.DATGLONASS extended ephemeris
QGA.DATGalileo extended ephemeris
QBD2.DATBeiDou extended ephemeris

Download source: wpepodownload.mediatek.com

The EPO injection protocol (used with PAIR471) wraps data in a binary frame:

+----------+----------+---------+---------+----------+----------+
| Preamble | MsgID | Length | Payload | Checksum | Tail |
| 2 bytes | 2 bytes | 2 bytes | N bytes | 1 byte | 2 bytes |
+----------+----------+---------+---------+----------+----------+
| 0x04 0x24| | LE16 | | XOR | 0xAA 0x44|
  • Preamble: Fixed bytes 0x04 0x24
  • MsgID: 2-byte message identifier
  • Length: 2-byte little-endian payload length
  • Payload: EPO data for the segment
  • Checksum: Single-byte XOR checksum
  • Tail marker: 0xAA 0x44

The host reads .DAT files, splits them into segments fitting the PAIR471 field limit, wraps each in the binary frame, hex-encodes the fields, and sends them sequentially.

QGNSS ships with a ModelInfo.json that declares per-model capabilities:

FieldTypeDescription
namestringModel identifier (e.g., LC29HAA)
isNMEA_CMDboolSupports NMEA command protocol
FullstringFull cold start command (empty = not supported)
WarmstringWarm start command
HotstringHot start command
ColdstringCold start command
AGNSSboolSupports AGNSS online (EPO injection)
AGNSS_OLboolSupports AGNSS offline (EASY prediction)
DRboolDead reckoning flag
NTRIPboolNTRIP client support enabled in QGNSS
TTFFboolTTFF measurement support
baudRatestringDefault baud rate
configurationboolAdvanced configuration page enabled