diff --git a/HARDWARE-SKUs.md b/HARDWARE-SKUs.md index 6e1d7d1..8d6c543 100644 --- a/HARDWARE-SKUs.md +++ b/HARDWARE-SKUs.md @@ -20,18 +20,18 @@ Garmin wearables SKU numbers | fenix 5X/tactix Charlie | 006-B2604-00 | 16,000 MB | X | 006-B2663-00 | | 006-B2957-00 | 006-B2196-01 | | 006-B2605-00 | | Forerunner 935 | 006-B2691-00 | 64 MB | X | 006-B2665-00 | | 006-B2957-00 | 006-B2196-01 | | | | fenix 5/quatix 5 | 006-B2697-00 | 64 MB | X | 006-B2661-00 | | 006-B2957-00 | 006-B2196-01 | | | -| D2 Charlie | 006-B2819-00 | 16,000 MB | X | 006-B2663-00 | | 006-B2957-00 | 006-B2196-01 | | 006-B2820-00 | +| D2 Charlie | 006-B2819-00 | 16,000 MB | X | 006-B2663-00 | | 006-B3750-00 | 006-B2196-01 | | 006-B2820-00 | | Descent Mk1 | 006-B2859-00 | 16,000 MB | X | 006-B2664-00 | | 006-B1621-00 | 006-B2196-01 | | 006-B2869-00 | -| Forerunner 645 Music | 006-B2888-00 | 4,000 MB | X | 006-B2897-00 | 006-B2887-00 | 006-B1621-00 | 006-B2196-02 | 006-B2822-00 | | +| Forerunner 645 Music | 006-B2886-00 | 4,000 MB | X | 006-B2897-00 | 006-B2887-00 | 006-B1621-00 | 006-B2196-02 | 006-B2822-01 | | +| fenix 5S Plus | 006-B2900-00 | 16,000 MB | X | 006-B3013-00 | 006-B3153-00 | 006-B3750-00 | 006-B2196-02 | 006-B2822-01 | | | Forerunner 645M APAC | 006-B3004-00 | 4,000 MB | X | 006-B3009-00 | 006-B3199-00 | 006-B1621-00 | 006-B2196-02 | 006-B2822-00 | | -| fenix 5S Plus | 006-B2900-00 | 16,000 MB | X | 006-B3013-00 | 006-B3153-00 | 006-B2957-00 | 006-B2196-02 | 006-B2822-01 | | | Forerunner 245 Music | 006-B3077-00 | 4,000 MB | G3 | 006-B3079-00 | 006-B3205-00 | 006-B1621-00 | 006-B2196-03 | | | -| fenix 5 Plus | 006-B3110-00 | 16,000 MB | X | 006-B3014-00 | 006-B3153-00 | 006-B2957-00 | 006-B2196-02 | 006-B2822-01 | | -| fenix 5X Plus | 006-B3111-00 | 16,000 MB | POx | 006-B3015-00 | 006-B3153-00 | 006-B2957-00 | 006-B2196-02 | 006-B2822-01 | | +| fenix 5 Plus | 006-B3110-00 | 16,000 MB | X | 006-B3014-00 | 006-B3153-00 | 006-B3750-00 | 006-B2196-02 | 006-B2822-01 | | +| fenix 5X Plus | 006-B3111-00 | 16,000 MB | POx | 006-B3015-00 | 006-B3153-00 | 006-B3750-00 | 006-B2196-02 | 006-B2822-01 | | | Forerunner 945 | 006-B3113-00 | 16,000 MB | G3 | 006-B3114-00 | 006-B3303-00 | 006-B3107-00 | | | | -| D2 Delta S | 006-B3196-00 | 16,000 MB | X | 006-B3014-00 | 006-B3260-00 | 006-B2957-00 | 006-B2196-02 | 006-B2822-01 | | -| D2 Delta | 006-B3197-00 | 16,000 MB | X | 006-B3014-00 | 006-B3260-00 | 006-B2957-00 | 006-B2196-02 | 006-B2822-01 | | -| D2 Delta PX | 006-B3198-00 | 16,000 MB | POx | 006-B3014-00 | 006-B3260-00 | 006-B2957-00 | 006-B2196-02 | 006-B2822-01 | | +| D2 Delta S | 006-B3196-00 | 16,000 MB | X | 006-B3014-00 | 006-B3260-00 | 006-B3750-00 | 006-B2196-02 | 006-B2822-01 | | +| D2 Delta | 006-B3197-00 | 16,000 MB | X | 006-B3014-00 | 006-B3260-00 | 006-B3750-00 | 006-B2196-02 | 006-B2822-01 | | +| D2 Delta PX | 006-B3198-00 | 16,000 MB | POx | 006-B3014-00 | 006-B3260-00 | 006-B3750-00 | 006-B2196-02 | 006-B2822-01 | | | MARQ Driver | 006-B3246-00 | 32,000 MB | G3 | 006-B3252-00 | 006-B3253-00 | 006-B3107-00 | | | | | MARQ Aviator | 006-B3247-00 | 32,000 MB | G3 | 006-B3252-00 | 006-B3253-00 | 006-B3107-00 | | | | | MARQ Captain | 006-B3248-00 | 32,000 MB | G3 | 006-B3252-00 | 006-B3253-00 | 006-B3107-00 | | | | @@ -45,6 +45,7 @@ Garmin wearables SKU numbers | Fenix 6 Pro | 006-B3290-00 | 32,000 MB | G3 | 006-B3295-00 | 006-B3293-00 | | | | | | Fenix 6X Pro | 006-B3291-00 | 32,000 MB | G3 | 006-B3296-00 | 006-B3293-00 | | | | | | MARQ Adventurer | 006-B3624-00 | 32,000 MB | G3 | | | | | | | +| Venu Sq - Music | 006-B3596-00 | 4,000 MB | | 006-B3602-00 | 006-B3597-00 | 006-B3799-12 | 006-B2196-03 | | 006-B3598-00 | @@ -55,3 +56,4 @@ Notes * ANT/BLE/BT firmwares of the D2 Delta and fenix 5 Plus series are identical par the SKU number * GPS firmwares 1621 and 2957 are for the same chip, but 2957 has Galileo support, 1621 does not * GPS firmwares 3107 and 3506 are the new Sony chipset (introduced with the MARQ series) +* fenix 5 Plus and D2 Delta series switched GPS from 006-B2957-00 to 006-B3750-00 in recent firmwares diff --git a/README.md b/README.md index 19fdabc..57aaee8 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,11 @@ Garmin Firmware Tools ===================== -This is a parser and some tools for working with GCD files (firmware updates). +This is a parser and some tools for working with Garmin firmware updates (GCD/RGN files). It's in Python, so feel free to add cool new features and submit pull requests. -Thanks to TurboCCC, kunix and Alex W. for your work. +Thanks to TurboCCC, kunix and AlexWhiter for your work. Most info from: @@ -14,12 +14,22 @@ Most info from: * hours of looking at hex numbers +How YOU can help +---------------- + +If you'd like to help, feel free to get all the SKU numbers from your `GarminDevice.xml` +(numbers starting with 006-B...) and post them under "Issues" (create a new issue with the +name of your device). + +Of course, pull requests are much appreciated, too. + + Tools ----- -### gcdstruct.py [gcdfile] +### gcdstruct.py [gcdfile] / rgnstruct.py [rgnfile] -Will show the general structure of the GCD file and also validate the contained checksums, e.g.: +Will show the general structure of the GCD/RGN file and also validate the contained checksums, e.g.: ``` $ ./gcdstruct.py fenix5Plus_510.gcd @@ -167,3 +177,13 @@ Checks Express and WebUpdater for updates for the given hw_ids (1-4 digits) or f The first one is used as the main device in the query. Shouldn't make a big difference in the results, though. Special thanks to Alex W. for [his update check](https://github.com/AlexWhiter/GarminRelatedStuff). + + +### list_missing_hwids.py + +Shows a list of hw_ids not yet listed in the `devices.py`. It prepends a call to `get_updates.py` for an +easy way to check the update servers for new devices. + +To find future devices, you can supply a parameter (can be anything) and it will output 300 more hw_ids +after the last known. + diff --git a/TODO.md b/TODO.md new file mode 100644 index 0000000..8c5490f --- /dev/null +++ b/TODO.md @@ -0,0 +1,14 @@ +TODO +==== + +* RGN file support +* convert between RGN and GCD +* Express-like updater + * should be text interface or ncurses + * detect Garmin MTP devices or /GARMIN directory in mounted roots + * MTP support: https://github.com/wangjiezhe/pymtp + * fetch GarminDevice.xml + * upload to Express, list returned files + * ask user, then download files and push to device + * maybe: support for additional downloads (languages, icons, etc.) + * maybe: support for running under Windows (although there you have the real Express) diff --git a/binbase_find.py b/binbase_find.py new file mode 100644 index 0000000..69a3b0e --- /dev/null +++ b/binbase_find.py @@ -0,0 +1,113 @@ +#!/usr/bin/env python3 + +# https://github.com/mncoppola/ws30/blob/master/basefind.py + +import os +import re +import signal +import struct +import sys +from operator import itemgetter + +chars = "A-Za-z0-9/\\-:.,_$%'\"()[\]<> " +min_length = 10 +scores = [] +top_score = 0 + +regexp = bytes("[{}]{{{:d},}}".format(chars, min_length), "us-ascii") +pattern = re.compile(regexp) +regexpc = bytes("[{}]{{1,}}".format(chars), "us-ascii") +patternc = re.compile(regexpc) + +def get_strings(filename, size): + table = set() + offset = 0 + with open(filename, "rb") as f: + while True: + if offset >= size: + break + f.seek(offset) + try: + data = f.read(10) + except: + break + match = pattern.match(data) + if match: + f.seek(offset - 1) + try: + char = f.read(1) + except: + continue + if not patternc.match(char): + table.add(offset) + offset += len(match.group(0)) + offset += 1 + return table + +def get_pointers(filename): + table = {} + with open(filename, "rb") as f: + while True: + try: + value = struct.unpack("= (base + size): + continue + offset = ptr - base + if offset in str_table: + score += ptr_table[ptr] + if score: + scores.append((base, score)) + if score > top_score: + top_score = score + print("New highest score, 0x{:x}: {:d}".format(base, score)) + + high_scores(0, 0) diff --git a/binbase_kunix.py b/binbase_kunix.py new file mode 100644 index 0000000..2f06452 --- /dev/null +++ b/binbase_kunix.py @@ -0,0 +1,73 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +# Many thanks to kunix! + +""" +Calculates possible base address. +""" + +from struct import unpack +import os.path +import sys + +FILE = sys.argv[1] +OFFSET = 0 +if len(sys.argv) > 2: + OFFSET = int(sys.argv[2]) +BLOCKSIZE = 4096 + +END_MARKER = b"\xff\xff\x5a\xa5" + +first_block = True +end_marker_pos = 0xffffffff +print("Reading {} ...".format(FILE)) +with open(FILE, "rb") as f: + f.read(OFFSET) + while True: + block = f.read(BLOCKSIZE) + if first_block: + dw = unpack(" end_marker_pos: + print("Found a second endmarker! Using that one.") + end_marker_pos = found_pos + #break + if len(block) < BLOCKSIZE: + break + f.close() + +size = os.path.getsize(FILE) + +print("File is {} (0x{:x}) Bytes.".format(size, size)) +print("First double-words: 0x{:x} / 0x{:x} / 0x{:x} / 0x{:x} / 0x{:x}".format(dw[0], dw[1], dw[2], dw[3], dw[4])) +print("Assuming this is end marker location in memory: 0x{:x}".format(dw[1])) +print("Found end marker in file at: 0x{:x}".format(end_marker_pos)) + +base_addr = dw[1] - (end_marker_pos - OFFSET) +if base_addr < 0: + base_addr += 0xffffffff + +print("This would make Base address probably 0x{:x}".format(base_addr)) + +if base_addr % 4 != 0: + print("However, bad alignment. Calculated base address not aligned to doublewords.") + +if base_addr + size > 0xffffffff: + print("However, base address can't fit whole file.") + +# Assumes second dword points to hwid +#if dw[2] % 2 != 0 or dw[2] - base_addr >= end_marker_pos - 3: +# print("Align & Bounds dw2 wrong.") +# sys.exit(1) + +# Assumes third dword points to fwid +#if dw[3] % 2 != 0 or dw[3] - base_addr >= end_marker_pos - 3: +# print("Align & Bounds dw3 wrong.") +# sys.exit(1) + +# hwid = dw[2] - base_addr +# fwid = dw[3] - base_addr diff --git a/binsum.py b/binsum.py index 7e874b3..cd26013 100644 --- a/binsum.py +++ b/binsum.py @@ -35,7 +35,7 @@ with open(FILE, "rb") as f: start += 4 hwid = unpack(" (.*?) ", devlist) + +print("Found {} devices.".format(len(devices))) +#print(repr(devices)) + +f = open("kenfiles.txt", "wt") +f.write("# Download with: wget -x -nc -i kenfiles.txt\n\n") + +for dev in devices: + (url, devname) = dev + print("Checking updates for {} ...".format(devname.strip())) + url = url.strip() + (url, paramstr) = url.split("?", 1) + parampairs = paramstr.split("&") + params = {} + for p in parampairs: + (key, val) = p.split("=", 1) + params[key] = val + params["origin"] = "productUpdate" # gets added via JavaScript? + + devpage = get_url(BASE_URL + url, params, "kendev.html") + + updateLink = re.search(r"(/kenwood/site/softwareUpdates.*?)\\", devpage) + + if not updateLink: + print("### No updates for {} found.".format(devname)) + continue + + updateLink = updateLink.group(1) + #print(repr(updateLink)) + + updpage = get_url(BASE_URL + updateLink, {}, "kenupd.html") + #print(repr(updpage)) + + links = re.findall(r"(https?://.*?)[\\\"']", updpage) + + f.write("# {}\n".format(devname)) + for l in links: + if l.endswith(("favicon.ico", "garmin.png", "termsOfUse.htm", "privacy-statement", "/us/")): + continue + #print(repr(l)) + f.write(l) + f.write("\n") + f.write("\n") + +f.close() diff --git a/list_missing_hwids.py b/list_missing_hwids.py index 9a38b12..3aae76a 100644 --- a/list_missing_hwids.py +++ b/list_missing_hwids.py @@ -6,13 +6,19 @@ from grmn import devices import sys +largest_gap = -1 +gap_counter = 0 last_id = 0 missing = [] for i in range(0, 9999): if i in devices.DEVICES: last_id = i + if gap_counter > largest_gap: + largest_gap = gap_counter + gap_counter = 0 continue missing.append(i) + gap_counter += 1 missing_count = 0 cur_line = [] @@ -20,36 +26,37 @@ queue = [] for i in range(0, last_id+1): if i % 10 == 0: if len(cur_line) + len(queue) > 15: - print("./get_updates.py {}".format(" ".join(cur_line))) + print("./get_updates.py -q {}".format(" ".join(cur_line))) cur_line = queue else: cur_line += queue queue = [] - if not i in missing: + if i not in missing: continue queue.append("{:04}".format(i)) missing_count += 1 cur_line += queue if len(cur_line) > 0: - print("./get_updates.py {}".format(" ".join(cur_line))) + print("./get_updates.py -q {}".format(" ".join(cur_line))) +known_count = len(devices.DEVICES) print() -print("{} unknown ids.".format(missing_count)) -print("Last known id is: {:04}".format(last_id)) +print("{} known, {} unknown ids. Last known id is: {:04d}".format(known_count, missing_count, last_id)) +print("Largest gap is: {}".format(largest_gap)) if len(sys.argv) > 1: print("-" * 100) print("Here are some possible future ids:") - print("./get_updates.py", end="") + print("./get_updates.py -q", end="") cur_line = 0 - for i in range(last_id + 1, last_id + 200): + for i in range(last_id + 1, last_id + 300): if i % 10 == 0 and cur_line > 5: print() - print("./get_updates.py", end="") + print("./get_updates.py -q", end="") cur_line = 0 print(" {:04}".format(i), end="") cur_line += 1