More tools.
This commit is contained in:
parent
701966277f
commit
da211328d3
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
__pycache__/
|
@ -1,11 +1,11 @@
|
||||
GCD Parser
|
||||
==========
|
||||
|
||||
This is a parser for GCD files (firmware updates from a well-known manufacturer).
|
||||
This is a parser and some tools for working with GCD files (firmware updates from a well-known manufacturer).
|
||||
|
||||
It's in Python, so feel free to add cool new features and submit pull requests.
|
||||
|
||||
Thanks to TurboCCC and kunix for your work.
|
||||
Thanks to TurboCCC, kunix and Alex W. for your work.
|
||||
|
||||
Most info from:
|
||||
|
||||
|
56
binsum.py
Normal file
56
binsum.py
Normal file
@ -0,0 +1,56 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Many thanks to Alex W. who figured this out!
|
||||
|
||||
"""
|
||||
Calculates the SHA1 of a fw_all.bin until the ending marker.
|
||||
"""
|
||||
|
||||
from hashlib import sha1
|
||||
from struct import unpack
|
||||
from grmn import devices
|
||||
import sys
|
||||
|
||||
FILE = sys.argv[1]
|
||||
BLOCKSIZE = 4096
|
||||
|
||||
END_MARKER = b"\xff\xff\x5a\xa5\xff\xff\xff\xff"
|
||||
|
||||
first_block = True
|
||||
past_end = False
|
||||
trailer = bytes()
|
||||
trailer_pos = -1
|
||||
|
||||
csum = sha1()
|
||||
print("Reading {} ...".format(FILE))
|
||||
with open(FILE, "rb") as f:
|
||||
while True:
|
||||
block = f.read(BLOCKSIZE)
|
||||
if first_block:
|
||||
start = block.find(b"\xff\xff\xff\xff\xf0\xb9\x9d\x38\x0f\x46\x62\xc7")
|
||||
if start < 0:
|
||||
print("No Fenix firmware header found.")
|
||||
else:
|
||||
start += 4
|
||||
hwid = unpack("<H", block[start+24:start+24+2])[0]
|
||||
fver = unpack("<H", block[start+28:start+28+2])[0]
|
||||
print("- Hardware ID: 0x{:04x} / {:d} ({})".format(hwid, hwid, devices.DEVICES.get(hwid, "Unknown device")))
|
||||
print("- Firmware Version: 0x{:04x} / {:04d}".format(fver, fver))
|
||||
first_block = False
|
||||
if END_MARKER in block:
|
||||
end_pos = block.find(END_MARKER)
|
||||
marker_end = end_pos + len(END_MARKER)
|
||||
past_end = True
|
||||
csum.update(block[0:marker_end])
|
||||
block = block[marker_end:]
|
||||
trailer_pos = f.tell() - len(block)
|
||||
if past_end:
|
||||
trailer += block
|
||||
else:
|
||||
csum.update(block)
|
||||
if len(block) < BLOCKSIZE:
|
||||
break
|
||||
f.close()
|
||||
print("Calculated SHA1: {}".format(csum.hexdigest()))
|
||||
print("SHA1 in file : {} (offset 0x{:x})".format(trailer[:20].hex(), trailer_pos))
|
@ -7,7 +7,7 @@ GCD_SIG = b"GARMINd\00"
|
||||
|
||||
from binascii import hexlify
|
||||
from struct import unpack
|
||||
from grmn import ChkSum
|
||||
from grmn import ChkSum, devices
|
||||
import sys
|
||||
|
||||
FILE = sys.argv[1]
|
||||
@ -28,12 +28,6 @@ TLV_TYPES = {
|
||||
0xffff: "EOF marker",
|
||||
}
|
||||
|
||||
DEV_TYPES = {
|
||||
1551: "fenix/D2/tactix",
|
||||
2900: "fenix 5 Plus",
|
||||
3196: "D2 Delta",
|
||||
}
|
||||
|
||||
cksum = ChkSum()
|
||||
all_cksum_ok = True
|
||||
last_type6_fids = []
|
||||
@ -46,12 +40,6 @@ def get_tlv_comment(ttype):
|
||||
else:
|
||||
return "Type {:04x} / {:d}".format(ttype, ttype)
|
||||
|
||||
def get_device(hwid):
|
||||
if hwid in DEV_TYPES:
|
||||
return DEV_TYPES[hwid]
|
||||
else:
|
||||
return "Unknown"
|
||||
|
||||
print("Opening {}".format(FILE))
|
||||
|
||||
def parseTLVheader(hdr):
|
||||
@ -125,7 +113,7 @@ def parseTLV7(payload):
|
||||
fid = last_type6_fids[i]
|
||||
fdesc = last_type6_fields[i]
|
||||
if fid == 0x1009:
|
||||
print(" - {:>20}: 0x{:04x} / {:d} ({})".format(fdesc, v, v, get_device(v)))
|
||||
print(" - {:>20}: 0x{:04x} / {:d} ({})".format(fdesc, v, v, devices.DEVICES.get(v, "Unknown device")))
|
||||
elif fid == 0x2015:
|
||||
print(" - {:>20}: {} Bytes".format(fdesc, v))
|
||||
else:
|
||||
@ -163,7 +151,7 @@ with open(FILE, "rb") as f:
|
||||
elif ttype == 0x02bd and not fw_all_done:
|
||||
hw_id = unpack("H", payload[0x208:0x20a])[0]
|
||||
fw_ver = unpack("H", payload[0x20c:0x20e])[0]
|
||||
print(" - Device ID: {:04x} / {:d} ({})".format(hw_id, hw_id, get_device(hw_id)))
|
||||
print(" - Device ID: {:04x} / {:d} ({})".format(hw_id, hw_id, devices.DEVICES.get(hw_id, "Unknown device")))
|
||||
print(" - Firmware version: {:04x} / {:d}".format(fw_ver, fw_ver))
|
||||
fw_all_done = True
|
||||
else:
|
18
gcdstruct.py
Normal file
18
gcdstruct.py
Normal file
@ -0,0 +1,18 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
Prints out the structure of the given GCD file.
|
||||
"""
|
||||
|
||||
from grmn import Gcd, ChkSum
|
||||
import sys
|
||||
|
||||
FILE = sys.argv[1]
|
||||
|
||||
print("Opening {}".format(FILE))
|
||||
|
||||
gcd = Gcd(FILE)
|
||||
|
||||
for i, tlv in enumerate(gcd.struct):
|
||||
print("#{:03d}: {}".format(i, tlv))
|
@ -1,6 +1,11 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
Calculates the Byte-checksum of a file and
|
||||
shows if it's (last Byte) correct or not.
|
||||
"""
|
||||
|
||||
from grmn import ChkSum
|
||||
import sys
|
||||
|
||||
|
33
gcksum_search.py
Normal file
33
gcksum_search.py
Normal file
@ -0,0 +1,33 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
Adds up all bytes from the beginning of a file and shows where
|
||||
a byte in the file matches the expected checksum at that location.
|
||||
"""
|
||||
|
||||
from grmn import ChkSum
|
||||
import sys
|
||||
|
||||
FILE = sys.argv[1]
|
||||
BLOCKSIZE = 4096
|
||||
OFFSET = 0x0
|
||||
|
||||
csum = ChkSum()
|
||||
prev_remainder = b"\x00"
|
||||
print("Reading {} ...".format(FILE))
|
||||
with open(FILE, "rb") as f:
|
||||
while True:
|
||||
start_pos = f.tell()
|
||||
block = f.read(BLOCKSIZE)
|
||||
block = block[OFFSET:]
|
||||
for i in range(0, len(block)):
|
||||
c = block[i:i+1]
|
||||
exp = bytes([csum.get_expected()])
|
||||
if c == exp:
|
||||
print("Found matching 0x{:02x} at 0x{:x} ({:x} + {:d}).".format(c[0], OFFSET + start_pos + i, OFFSET, start_pos + i))
|
||||
csum.add(bytes(c))
|
||||
if len(block) < BLOCKSIZE:
|
||||
break
|
||||
#break
|
||||
f.close()
|
@ -10,7 +10,7 @@ class ChkSum:
|
||||
self.chksum += sum(bytearray(data))
|
||||
self.last_byte = data[-1]
|
||||
self.chksum &= 0xff
|
||||
|
||||
|
||||
def add_from_file(self, filename: str, print_progress: bool = False, blocksize: int=16384):
|
||||
with open(filename, "rb") as f:
|
||||
while True:
|
||||
|
@ -663,6 +663,7 @@ DEVICES = {
|
||||
3109: "DriveAssist 51, APAC",
|
||||
3112: "Edge 520 Plus",
|
||||
3115: "GPSMAP 64sc SiteSurvey",
|
||||
3126: "Instinct",
|
||||
3134: "Fenix 5S Plus APAC",
|
||||
3135: "Fenix 5X Plus APAC",
|
||||
3139: "zumo 396, APAC",
|
||||
|
38
grmn/gcd.py
38
grmn/gcd.py
@ -10,7 +10,7 @@ import sys
|
||||
GCD_SIG = b"G\x41RM\x49Nd\00"
|
||||
DEFAULT_COPYRIGHT = b"Copyright 1996-2017 by G\x61rm\x69n Ltd. or its subsidiaries."
|
||||
DEFAULT_FIRST_PADDING = 21
|
||||
DEFAULT_ALIGN = 0x1000
|
||||
DEFAULT_ALIGN = 0x1000 # second padding block pads until 0x1000
|
||||
|
||||
TLV_TYPES = {
|
||||
0x0001: "Checksum rectifier",
|
||||
@ -28,23 +28,38 @@ TLV_TYPES = {
|
||||
0xffff: "EOF marker",
|
||||
}
|
||||
|
||||
# Typical structure:
|
||||
# first 0x1000 Bytes: GCD_SIG > 0x0001 > 0x0002 > 0x0003 > 0x0005 > 0x0001 > 0x0002
|
||||
# then: 0x0001 > ( 0x0006 > 0x0007 > 0x???? > 0x0001 ... ) > 0xffff
|
||||
|
||||
class TLV:
|
||||
def __init__(self, type_id: int, expected_length: int, value=None):
|
||||
def __init__(self, type_id: int, expected_length: int, value=None, offset: int=None):
|
||||
self.type_id = type_id
|
||||
self.offset = offset
|
||||
self.comment = TLV_TYPES.get(type_id, "Type {:04x} / {:d}".format(type_id, type_id))
|
||||
self.length = expected_length
|
||||
self.value = None
|
||||
self.is_parsed = False
|
||||
if value is not None:
|
||||
self.value = bytes(value)
|
||||
|
||||
@staticmethod
|
||||
def factory(header: bytes):
|
||||
(type_id, length) = unpack("<HH", payload)
|
||||
def factory(header: bytes, offset: int = None):
|
||||
(type_id, length) = unpack("<HH", header)
|
||||
if type_id == 0x0006:
|
||||
return TLV6(type_id, length)
|
||||
new_tlv = TLV6(type_id, length)
|
||||
elif type_id == 0x0007:
|
||||
return TLV7(type_id, length)
|
||||
return TLV(type_id, length)
|
||||
new_tlv = TLV7(type_id, length)
|
||||
else:
|
||||
new_tlv = TLV(type_id, length)
|
||||
new_tlv.offset = offset
|
||||
return new_tlv
|
||||
|
||||
def __str__(self):
|
||||
plural = ""
|
||||
if self.length != 1:
|
||||
plural = "s"
|
||||
return "TLV Type {:04x} at 0x{:x}, {:d} Byte{} - {}".format(self.type_id, self.offset, self.length, plural, self.comment)
|
||||
|
||||
def set_value(self, new_value: bytes):
|
||||
self.value = new_value
|
||||
@ -60,7 +75,7 @@ class TLV:
|
||||
def get_record_length(self):
|
||||
# Length including record definition
|
||||
return self.get_actual_length() + 4
|
||||
|
||||
|
||||
def get_value(self):
|
||||
return self.value
|
||||
|
||||
@ -91,10 +106,6 @@ class TLV6(TLV):
|
||||
0x5003: ["", "End of definition marker"],
|
||||
}
|
||||
|
||||
def __init__(self, type_id: int, expected_length: int, value=None):
|
||||
super.__init__(type_id, expected_length, value)
|
||||
self.is_parsed = False
|
||||
|
||||
def parse(self):
|
||||
if len(self.value) % 2 != 0:
|
||||
raise Exception("Invalid TLV6 payload length!")
|
||||
@ -147,8 +158,9 @@ class Gcd:
|
||||
if sig != GCD_SIG:
|
||||
raise Exception("Signature mismatch ({}, should be {})!".format(repr(sig), repr(GCD_SIG)))
|
||||
while True:
|
||||
cur_offset = f.tell()
|
||||
header = f.read(4)
|
||||
tlv = TLV.factory(header)
|
||||
tlv = TLV.factory(header, offset=cur_offset)
|
||||
self.struct.append(tlv)
|
||||
if tlv.type_id == 0xFFFF:
|
||||
# End of file reached
|
||||
|
Loading…
x
Reference in New Issue
Block a user