Skip to content

Latest commit

 

History

History
354 lines (273 loc) · 21 KB

File metadata and controls

354 lines (273 loc) · 21 KB

DM-1702 Native Codeplug Binary Format

This document specifies the binary layout of the 245,760-byte (0x3C000) native codeplug image used by the Baofeng DM-1702 and DM-1702B radios. All offsets are file offsets (zero-based).

Source: Reverse-engineered from 8 independent OEM USB captures, Ghidra static analysis of the stock CPS executable, and cross-capture byte-level validation. The authoritative in-code reference is NativeCoverageAudit.cs.

Convention: Factory images contain GB2312 Chinese default text (e.g. 电话列表 = "Phone List", 信道 = "Channel") embedded in page headers and name tables. These are the firmware's factory-default labels and are treated as the source of truth for identifying the native purpose of each memory region. Where this project extends beyond what OEM captures demonstrate, the behavior is classified as structurally generated (extrapolated from validated patterns, not confirmed against OEM behavior).

Image Overview

Property Value
Total size 245,760 bytes (0x3C000)
Sector size 4,096 bytes (0x1000)
Sector count 60
Read/write block size 64 bytes
Byte order Little-endian (LE16/LE32) unless noted

Section Map

Offset Length Section
0x0000 0x3000 Non-CPS data (three-zone pattern: FF-filled, zero-filled, mixed)
0x3000 0x0002 Channel count (LE16: factory=1, Ryan=159)
0x3002 0x0010 Reserved (zero-filled in all captures)
0x3012 0x0FC0 Channel records — linear region (85 × 0x30 bytes, indices 0–84)
0x4000 0x0FFC Channel name table 1 (372 × 11 bytes, indices 0–371)
0x5000 0x0300 Configuration section (DMR ID, radio name, DTMF, keys, etc.)
0x5500 0x0100 GPS settings (16 entries × 16 bytes)
0x6000 0x0F00 Zone data — linear region (14 × 0x112 bytes, indices 0–13)
0x7000 0x0D80 Contact metadata
0x8000 0x1600 RX group / system data (RX groups, emergency, privacy, lone worker)
0xA000 0x1000 Quick text messages (count + stride 0x81 records)
0xB000 0x1000 Scan list data (up to 32 × 0x39-byte records; count at 0xB000)
0x2B000 var Zone data — overflow pages (14 zones per 4K page, indices 14+)
0xF000 0x1000 Channel records — overflow page 0 (84 slots, indices 85–168). OEM-validated. Factory header: 电话列表 1 ("Phone List 1")
0x10000 0x1000 Phone/DTMF data — 电话列表 2 ("Phone List 2"). Structurally generated: this project writes channel overflow page 1 here (indices 169–252)
0x11000 0x1000 Phone/DTMF data — 电话列表 3 ("Phone List 3"). Structurally generated: this project writes channel overflow page 2 here (indices 253–255)
0x12000 0x8000 Phone/DTMF system data (电话列表/电话系统, stride 0x30)
0x1B000 0x1000 Unknown (zero-filled in all captures)
0x1C000 0x2000 Channel name table 2 (372+ × 11 bytes, indices 372+; 信道 defaults)
0x1E000 0x0800 Channel–contact map (LE16 entries, 1 per channel)
0x1F000 0x4000 Contact region (paged: 170 records per 4K page, bitmap, sorted index)

Note: Page 0 at 0xF000 is OEM-validated for channel overflow records (Ryan capture: 74 records at indices 85–158). Pages 0x10000–0x1A000 contain phone/DTMF system data in all OEM captures — factory headers contain GB2312 电话列表 ("Phone List") and the data area holds phone list entries at stride 0x30. No OEM capture exceeds 169 channels; no Ghidra evidence confirms the OEM CPS writes channel records into pages 0x10000+. This project extrapolates the page 0 pattern into pages 1–2 (0x10000–0x11000) to support up to 256 channels — this is structurally generated behavior that overwrites phone/DTMF data. Pages 0x1C000–0x1DFFF contain a second channel name table (factory default: 信道 = "Channel" at stride 11).

Channel Records

Each channel occupies 48 bytes (stride 0x30). Channels are stored in two regions:

Linear Region (Indices 0–84)

85 records at 0x3010 + index × 0x30. Index 84's record at 0x3FD0 is the last before the name table at 0x4000.

Source of truth: Ghidra FUN_004191f0 confirms formula i * 0x30 + 0x3010. Cross-validated against ryan_whidbey_1.data channel 0 RX BCD at 0x3010.

Paged Region (Indices 85+)

Overflow pages at 0xF000, each 4,096 bytes (0x1000). Each page has a 0x32-byte (50-byte) header followed by 84 channel record slots at stride 0x30.

For index i ≥ 85:
  pagedIndex = i - 85
  page = pagedIndex / 84
  slot = pagedIndex % 84
  offset = 0xF000 + page × 0x1000 + 0x32 + slot × 0x30

Page header layout (0x32 bytes):

  • Bytes 0x00–0x04: 00-01-02-03-04 (identifier, identical across all captures)

  • Bytes 0x05–0x10: FF-fill or channel metadata

  • Bytes 0x11–0x1F: zeros

  • Bytes 0x20–0x2B: GB2312 factory text — 电话列表 2 ("Phone List 2"). This is the firmware's label identifying the page's native purpose: phone/DTMF list data. The OEM CPS preserves this header when writing channel records into page 0 at 0xF000. Pages 1+ (0x10000+) contain phone/DTMF data in all OEM captures.

  • Bytes 0x2C–0x31: state/count bytes

  • Overflow pages: Page 0 at 0xF000 (indices 85–168) is OEM-validated (Ryan capture, 74 records). Pages 1–2 at 0x10000/0x11000 (indices 169–255) are structurally generated — this project extrapolates the page 0 record format into pages that natively hold phone/DTMF data (电话列表). No OEM capture has >169 channels; the true OEM maximum is unknown.

  • Record capacity: 85 linear + (3 × 84) paged = 337 record slots, capped at 256 channels (indices 0–255).

  • Name capacity: Table 1 at 0x4000 holds 372 names (indices 0–371). Table 2 at 0x1C000 holds indices 372+. 256 channels fit entirely within Table 1.

  • Channel count: Stored at 0x3000 as LE16 (factory=1, Ryan=159)

Channel Record Layout

Offset Size Field
+0x00 4 RX frequency (BCD, swapped-pair order: bytes [1,0,3,2])
+0x04 4 TX frequency (BCD, swapped-pair order: bytes [1,0,3,2])
+0x08 1 Mode / channel flags byte 1
+0x09 1 Power level / bandwidth flags
+0x0A 2 CTCSS/DCS RX tone
+0x0C 2 CTCSS/DCS TX tone
+0x0E 1 Color code (digital channels)
+0x0F 1 Time slot (digital channels)
+0x10 2 Contact index (LE16, digital channels)
+0x12 1 RX group index
+0x13 1 Scan list index
+0x14 1 GPS system index
+0x15–0x2F 27 Reserved / extended flags

BCD Frequency Encoding

Frequencies are stored as 4-byte BCD with a swapped-pair byte order. To decode:

  1. Read bytes at offsets [1, 0, 3, 2] (swap pairs).
  2. Extract each BCD nibble to form an 8-digit decimal string.
  3. Multiply by 10 to get frequency in Hz.

Example: bytes 62 14 00 25 → reordered 14 62 25 00 → BCD 14622500 → 146,225,000 Hz → 146.2250 MHz.

Channel Name Tables

Channel names are stored in two tables with identical 11-byte stride (ASCII/GB2312, zero-padded):

Table 1 at 0x4000 (indices 0–371, 372 × 11 = 4,092 bytes):

nameOffset = 0x4000 + index × 11      (for index 0–371)

Table 2 at 0x1C000 (indices 372+, contiguous across page boundary into 0x1D000):

nameOffset = 0x1C000 + (index - 372) × 11    (for index ≥ 372)
  • Table 1 capacity: 372 names (0x4000–0x4FFB). Factory defaults: entry 0–1 = English (Channel 1, Channel 2), entries 2–371 = GB2312 (信道 3, 信道 4, …).
  • Table 2 capacity: 372+ names (0x1C000–0x1DFFF, 8,192 bytes ÷ 11 = 744 slots). Factory defaults: entry 0 = null, entries 1–651 = GB2312 (信道 2). Entry 372 straddles the 0x1CFFC/0x1D000 page boundary, proving the table is contiguous.
  • Table 2 is unused in Ryan's capture: All entries match factory defaults (0 diffs between factory and Ryan).
  • Ghidra formula: ((index - 0x174) % 0x174) × 0xB — the modulo 0x174 (372) maps indices 0–371 to Table 1 offsets and indices 372–743 to Table 2 offsets.
  • CPS UI split: The CPS shows "Table1 / Table2" at index 50 (0x4226) within Table 1, but this is a UI-only division — the binary layout within each table is strictly linear.
  • Our serializer: Currently uses only Table 1 (indices 0–371). Table 2 support would be needed for >372 channel names.
Index Range Table Offset Range
0–49 1 0x4000–0x4225
50–371 1 0x4226–0x4FFB
372–743 2 0x1C000–0x1DFFF

Contact Region

Contacts use a paged layout starting at offset 0x1F000:

  • Page size: 4,096 bytes (0x1000)
  • Records per page: 170 (24-byte records packed sequentially)
  • Contact record: 24 bytes — 16 bytes UTF-16LE name + 4 bytes call ID (LE32) + 4 bytes type/flags

Additional structures:

  • Contact bitmap: zero-indexed, one bit per contact slot
  • Sorted index table: LE16 entries providing alphabetical ordering

Contact Paging Formula

pageIndex = contactIndex / 170
offsetWithinPage = (contactIndex % 170) * 24
imageOffset = 0x1F000 + (pageIndex * 0x1000) + offsetWithinPage

Zone Data

Source: Ghidra rebuild_functions_decompiled.c FUN_0041f960 (lines 571–816), binary-confirmed against Ryan and baseline captures.

Zone data starts at offset 0x6000 with stride 0x112 (274 bytes). Maximum 250 zones (Ghidra: uVar2 == 0 || 0xFA < uVar2).

  • Zone count: Single byte at image offset 0x6000 (overlaps zone[0] record byte +0x00). Written LAST by the CPS after all records.
    • Ryan: 0x15 (21 zones). Factory: varies by capture.

Linear Region (Indices 0–13)

14 zone records at 0x6000 + index × 0x112. Ghidra locator: i * 0x112 + 0x6010 (points to name at +0x10).

Offset Size Field
+0x00 16 Reserved zeros. Zone[0]+0x00 is overwritten by the global zone count byte.
+0x10 16 Zone name (ASCII, zero-padded)
+0x20 1 Authoritative member count (firmware reads here: name_base + 0x10)
+0x21 128 Member list (64 × LE16, 1-based channel indices)
+0xA1 1 Echo member count
+0xA2 128 Echo member list (64 × LE16, mirrors +0x21)

Paged Region (Indices 14+)

Overflow pages starting at 0x2B000. 14 zones per page (0xE × 0x112 = 0xEFC ≤ 0x1000).

For index i ≥ 14:
  j = i - 14
  page = j / 14
  slot = j % 14
  offset = (page + 0x2B) × 0x1000 + slot × 0x112

Ghidra formula: ((i - 0xE) / 0xE + 0x2B) * 0x1000 + ((i - 0xE) % 0xE) * 0x112

Paged zone records omit the 16-byte reserved prefix — fields shift down by 0x10:

Offset Size Field
+0x00 16 Zone name (ASCII, zero-padded)
+0x10 1 Authoritative member count
+0x11 128 Member list (64 × LE16, 1-based channel indices)
+0x91 1 Echo member count
+0x92 128 Echo member list (64 × LE16)
  • Binary-confirmed: Ryan zone[14] "25k Repeater 2/2" at 0x2B000 with name at +0x00.
  • Members per zone: Max 64 (Ghidra: local_18 == 0 || 0x40 < local_18). Ryan zone[5] "GMRS" has 22 members.

Scan Lists

Source: Ghidra rebuild_functions_decompiled.c (lines 1090–1293), binary-confirmed against Ryan and baseline captures.

Scan list data at offset 0xB000, stride 0x39 (57 bytes), maximum 32 scan lists.

  • Scan list count: Single byte at image offset 0xB000 (overlaps scanList[0] record byte +0x00). Written LAST by the CPS after all records. Same pattern as zone count.
    • Ryan: 0x0E (14 scan lists). Baseline: 0x00 (zero-filled template area).
  • Area fill: Ryan's CPS FF-fills the 0x1000-byte area before writing records. Baseline's CPS zero-fills and writes 32 GB2312 template records.

Scan List Record Layout (0x39 bytes)

Offset Size Field Evidence
+0x00 1 0xFF from area fill (scanList[0]'s +0x00 is overwritten by the global count) Ryan SL[1]: 0xFF; baseline: 0x00
+0x01 11 Name (10 ASCII chars + pad byte) Ryan SL[0]: "NOAA WX ", SL[1]: "MURS"
+0x0C 1 Authoritative member count (Ghidra: puVar13[-7] as loop bound) Ryan SL[0]: 0x08, SL[1]: 0x06
+0x0D 1 Flags (high nibble gates priority channel ref at +0x10) Baseline: 0x03, Ryan: 0x10
+0x0E 1 Constant 0x06 All captures
+0x0F 1 Constant 0x00 All captures
+0x10 2 Priority channel 1 reference (LE16, CPS-maintained) Ghidra: participates in channel-remap
+0x12 4 Priority channel 2 + reserved (CPS-maintained) Ghidra: channel-remap
+0x16 1 Constant 0x0A All captures
+0x17 1 Priority bitmap low (0x00=none, 0x80=has priority) Ryan SL[0]: 0x80, SL[1]: 0xFF
+0x18 1 Priority bitmap high / per-member digital flags Ryan SL[0]: 0xFF, SL[1]: 0xFF
+0x19 32 Member list (16 × LE16, 1-based channel indices) Ryan SL[0]: ch1,1,2,3… (16 max)

RX Groups

RX group data at offset 0xC000 uses a split layout:

  • Group metadata (name, member count) at the start of the page.
  • Member contact index lists at a later offset within the same page.

Phone/DTMF System Data (0x10000–0x1A000)

Identified by GB2312 default text analysis. This region was previously (incorrectly) documented as channel overflow pages.

  • 0xE000–0xE1FF: Phone system records (电话系统 = "Phone System", stride 0x50, 4 entries)
  • 0xE200–0xEFFF: Phone list names (电话列表 = "Phone List", stride 0x30, 256 entries in factory)
  • 0xF000: Channel overflow page 0 (NOT phone data despite 电话列表 text in its header — see above)
  • 0x10000–0x10FFF: Phone list data continuation (FF-fill header, stride 0x30 records)
  • 0x11000–0x11FFF: Phone list names continuation (电话列表 2 repeating at stride 0x30)
  • 0x12000–0x1A000: Additional phone/DTMF data pages (same FF+01 header pattern as 0x10000)
  • 0x1B000: Zero-filled in all captures (purpose unknown)

Not serialized: Our codeplug serializer does not write phone/DTMF system data. Under the clean-write architecture (Dm1702NativeImageBuilder.Build()), these regions are zeroed. If DTMF phone system data needs to be preserved, the user must save and reload from an OEM .data file.

Configuration Section

The configuration section at offset 0x5000 (1,280 bytes) contains all radio parameters. Every field below is OEM-validated via cross-capture byte-level comparison of 8 .data files unless noted otherwise. Offsets are relative to 0x5000.

Source of truth: Dm1702NativeConfigSerializer.cs (serializer), NativeCoverageAudit.md (per-field evidence).

Offset Size Field Evidence
+0x00 1 Backlight duration (0=Off, 1=10s, 2=15s, 3=20s, 4=5s, 5=Always) Cross-capture: baseline=1, eng=5
+0x01 1 Constant 0x32 (all 8 captures) OEM constant
+0x02 1 Analog squelch level (0–15) Cross-capture: baseline=5, ryan=12
+0x03 1 Packed bitfield: b0=VOX enable, b2=features modified flag, b5=keypad lock, b6=CTCSS tail revert Cross-capture: baseline=0x00, eng=0x67, ryan=0x24
+0x04 4 DMR ID (LE32) Cross-capture: ryan=3176775
+0x07 1 b6=Show channel number Cross-capture: baseline=0x40, ryan=0x00
+0x0B 1 Clock / misc flags (0x48=clock on, 0x47=clock off) Cross-capture: baseline=0x47, ryan=0x48
+0x0C 1 Default power level (0=Low, 1=Medium, 2=High) Cross-capture validated
+0x0D 1 Packed: b4=always set, b2+b3=battery saver, b0+b1=lone worker echo Cross-capture: baseline=0x10, ryan=0x1C
+0x0E 1 Digital squelch level (0–15, default=5) Cross-capture: baseline=0x05
+0x10 16 Date stamp (ASCII) Per-write timestamp
+0x20 12 Band limit constants (factory: 37 08 ... 60 ...) All 8 captures identical
+0x08 1 VOX level (0–10) Cross-capture: eng=0x03
+0x09 1 Language (0=English, 1=Chinese) Cross-capture validated
+0x0A 1 TX timeout (0–15, ×15s) Cross-capture validated
+0x110 16 Radio name (ASCII, zero-padded) ryan="K0RPW", baseline=zeros
+0x120 16 TX preamble duration Cross-capture validated
+0x140 1 Mic gain Cross-capture validated
+0x150 14 Key assignment table (7 keys × 2 bytes: short press, long press) Cross-capture: 9/14 bytes differ between captures
+0x180 16 Radio name (ASCII, duplicate at alternate offset) ryan="K0RPW"
+0x192 10 DTMF PTT ID (ASCII, zero-padded) Cross-capture: default "2345678"
+0x19C 8 DTMF Kill Code (ASCII) Cross-capture validated
+0x1A4 8 DTMF Revive Code (ASCII) Cross-capture validated
+0x1C0 16 Startup intro line 1 (ASCII) ryan="Pacific NW"
+0x1D0 16 Startup intro line 2 (ASCII) ryan=zeros

Non-CPS Data (0x0000–0x300F)

The first 0x3010 bytes follow a three-zone pattern observed across all captures:

  1. FF-filled region — factory default padding
  2. Zero-filled region — cleared by firmware
  3. Mixed-content region — radio-internal data not written by CPS

This region is preserved byte-for-byte during codeplug writes.

Sector Markers

Each 4K sector ends with a marker byte at offset sectorBase + 0xFFF. These markers are written by Dm1702NativeSectorSerializer using a fixed table of observed OEM values.

GB2312 Chinese Default Text

The factory OEM CPS populates default entity names using GB2312-encoded Chinese text. Entry #1 of each entity type typically uses an English name; entry #2 and beyond use Chinese. These labels are useful for identifying which image region holds which entity type.

Encoding: GB2312 (code page 936). Each Chinese character is a double-byte pair with high byte 0xA1–0xF7 and low byte ≥ 0xA1. Mixed with ASCII in the same name field (e.g., DMR系统 1).

Translation Table

GB2312 Text Hex Bytes English Translation Image Region Default Pattern Count (Factory)
信道 D0 C5 B5 C0 Channel 0x4000 (Channel Name Table) 信道 3, 信道 4, … (entries 1–2 are English Channel 1, Channel 2) 1,022
区域 C7 F8 D3 F2 Zone 0x6000 (Zone Names) 区域 2, 区域 3, … (entry 1 is English Zone 1) 4
列表 C1 D0 B1 ED List 0x8000 (RX Group List Names) 列表 2, 列表 3, … (entry 1 is English List 1) 28
DMR系统 44 4D 52 CF B5 CD B3 DMR System 0x8640 (DMR/Digital Signaling System) DMR系统 1, DMR系统 2, … 6
模拟系统 C4 A3 C4 E2 CF B5 CD B3 Analog System 0x8700 (Analog Signaling System) 模拟系统 1, 模拟系统 2, … 4
两音系统 C1 BD D2 F4 CF B5 CD B3 Two-Tone System 0x9000 (2-Tone Signaling System) 两音系统 1, 两音系统 2, … 15
联系人 C1 AA CF B5 C8 CB Contact 0x9400 (Contact Names) 联系人 1, 联系人 2, … 33
扫描列表 C9 A8 C3 E8 C1 D0 B1 ED Scan List 0xB000 (Scan List Names) 扫描列表2, 扫描列表3, … (entry 1 is English List1) 30
电话系统 B5 E7 BB B0 CF B5 CD B3 Phone System 0xE000 (DTMF Phone System) 电话系统 1, 电话系统 2, … 4
电话列表 B5 E7 BB B0 C1 D0 B1 ED Phone List 0xE200 (Phone/DTMF List Names) 电话列表 1, 电话列表 2, … 256
联系列表 C1 AA CF B5 C1 D0 B1 ED Contact List 0x26000 (Extended Contact List) 联系列表 1, 联系列表 2, … 109
状态列表 D7 B4 CC AC C1 D0 B1 ED Status List 0x29000 (Status Message List) 状态列表 1, 状态列表 2, … 101

Individual Character Glossary

Character Pinyin English
xìn message / signal
dào channel / path
area / zone
region / domain
liè column / list
biǎo table / list
system / relate
tǒng system / unify
model / analog
simulate / imitate
liǎng two / both
yīn sound / tone
lián connect / link
rén person
sǎo sweep / scan
miáo trace / describe
diàn electric
huà speech / phone
zhuàng state / status
tài condition / state

Observations

  • Entry #1 is English, #2+ is Chinese: Consistent across all entity types in the factory image. The CPS appears to initialize the first slot with an English default name and all subsequent slots with GB2312 Chinese.
  • Ryan's capture has fewer GB2312 hits (9 unique strings vs. 12 in factory) because user-programmed names overwrite the Chinese defaults with ASCII text.
  • 傲斜 is a false positive: Bytes B0 C1 D0 B1 appear in Ryan's scan list data region (0x3993) as part of record data fields, not intentional text. The scanner's double-byte heuristic matches these coincidentally.
  • Default count reveals slot capacity: The repetition count of each Chinese default string in the factory image reflects the total number of pre-initialized slots for that entity type (e.g., 256 phone list entries, 109 contact list entries, 101 status messages).