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
|
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.
|
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:
|
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 binascii import hexlify
|
||||||
from struct import unpack
|
from struct import unpack
|
||||||
from grmn import ChkSum
|
from grmn import ChkSum, devices
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
FILE = sys.argv[1]
|
FILE = sys.argv[1]
|
||||||
@ -28,12 +28,6 @@ TLV_TYPES = {
|
|||||||
0xffff: "EOF marker",
|
0xffff: "EOF marker",
|
||||||
}
|
}
|
||||||
|
|
||||||
DEV_TYPES = {
|
|
||||||
1551: "fenix/D2/tactix",
|
|
||||||
2900: "fenix 5 Plus",
|
|
||||||
3196: "D2 Delta",
|
|
||||||
}
|
|
||||||
|
|
||||||
cksum = ChkSum()
|
cksum = ChkSum()
|
||||||
all_cksum_ok = True
|
all_cksum_ok = True
|
||||||
last_type6_fids = []
|
last_type6_fids = []
|
||||||
@ -46,12 +40,6 @@ def get_tlv_comment(ttype):
|
|||||||
else:
|
else:
|
||||||
return "Type {:04x} / {:d}".format(ttype, ttype)
|
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))
|
print("Opening {}".format(FILE))
|
||||||
|
|
||||||
def parseTLVheader(hdr):
|
def parseTLVheader(hdr):
|
||||||
@ -125,7 +113,7 @@ def parseTLV7(payload):
|
|||||||
fid = last_type6_fids[i]
|
fid = last_type6_fids[i]
|
||||||
fdesc = last_type6_fields[i]
|
fdesc = last_type6_fields[i]
|
||||||
if fid == 0x1009:
|
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:
|
elif fid == 0x2015:
|
||||||
print(" - {:>20}: {} Bytes".format(fdesc, v))
|
print(" - {:>20}: {} Bytes".format(fdesc, v))
|
||||||
else:
|
else:
|
||||||
@ -163,7 +151,7 @@ with open(FILE, "rb") as f:
|
|||||||
elif ttype == 0x02bd and not fw_all_done:
|
elif ttype == 0x02bd and not fw_all_done:
|
||||||
hw_id = unpack("H", payload[0x208:0x20a])[0]
|
hw_id = unpack("H", payload[0x208:0x20a])[0]
|
||||||
fw_ver = unpack("H", payload[0x20c:0x20e])[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))
|
print(" - Firmware version: {:04x} / {:d}".format(fw_ver, fw_ver))
|
||||||
fw_all_done = True
|
fw_all_done = True
|
||||||
else:
|
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
|
#!/usr/bin/env python3
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
"""
|
||||||
|
Calculates the Byte-checksum of a file and
|
||||||
|
shows if it's (last Byte) correct or not.
|
||||||
|
"""
|
||||||
|
|
||||||
from grmn import ChkSum
|
from grmn import ChkSum
|
||||||
import sys
|
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.chksum += sum(bytearray(data))
|
||||||
self.last_byte = data[-1]
|
self.last_byte = data[-1]
|
||||||
self.chksum &= 0xff
|
self.chksum &= 0xff
|
||||||
|
|
||||||
def add_from_file(self, filename: str, print_progress: bool = False, blocksize: int=16384):
|
def add_from_file(self, filename: str, print_progress: bool = False, blocksize: int=16384):
|
||||||
with open(filename, "rb") as f:
|
with open(filename, "rb") as f:
|
||||||
while True:
|
while True:
|
||||||
|
@ -663,6 +663,7 @@ DEVICES = {
|
|||||||
3109: "DriveAssist 51, APAC",
|
3109: "DriveAssist 51, APAC",
|
||||||
3112: "Edge 520 Plus",
|
3112: "Edge 520 Plus",
|
||||||
3115: "GPSMAP 64sc SiteSurvey",
|
3115: "GPSMAP 64sc SiteSurvey",
|
||||||
|
3126: "Instinct",
|
||||||
3134: "Fenix 5S Plus APAC",
|
3134: "Fenix 5S Plus APAC",
|
||||||
3135: "Fenix 5X Plus APAC",
|
3135: "Fenix 5X Plus APAC",
|
||||||
3139: "zumo 396, 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"
|
GCD_SIG = b"G\x41RM\x49Nd\00"
|
||||||
DEFAULT_COPYRIGHT = b"Copyright 1996-2017 by G\x61rm\x69n Ltd. or its subsidiaries."
|
DEFAULT_COPYRIGHT = b"Copyright 1996-2017 by G\x61rm\x69n Ltd. or its subsidiaries."
|
||||||
DEFAULT_FIRST_PADDING = 21
|
DEFAULT_FIRST_PADDING = 21
|
||||||
DEFAULT_ALIGN = 0x1000
|
DEFAULT_ALIGN = 0x1000 # second padding block pads until 0x1000
|
||||||
|
|
||||||
TLV_TYPES = {
|
TLV_TYPES = {
|
||||||
0x0001: "Checksum rectifier",
|
0x0001: "Checksum rectifier",
|
||||||
@ -28,23 +28,38 @@ TLV_TYPES = {
|
|||||||
0xffff: "EOF marker",
|
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:
|
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.type_id = type_id
|
||||||
|
self.offset = offset
|
||||||
self.comment = TLV_TYPES.get(type_id, "Type {:04x} / {:d}".format(type_id, type_id))
|
self.comment = TLV_TYPES.get(type_id, "Type {:04x} / {:d}".format(type_id, type_id))
|
||||||
self.length = expected_length
|
self.length = expected_length
|
||||||
self.value = None
|
self.value = None
|
||||||
|
self.is_parsed = False
|
||||||
if value is not None:
|
if value is not None:
|
||||||
self.value = bytes(value)
|
self.value = bytes(value)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def factory(header: bytes):
|
def factory(header: bytes, offset: int = None):
|
||||||
(type_id, length) = unpack("<HH", payload)
|
(type_id, length) = unpack("<HH", header)
|
||||||
if type_id == 0x0006:
|
if type_id == 0x0006:
|
||||||
return TLV6(type_id, length)
|
new_tlv = TLV6(type_id, length)
|
||||||
elif type_id == 0x0007:
|
elif type_id == 0x0007:
|
||||||
return TLV7(type_id, length)
|
new_tlv = TLV7(type_id, length)
|
||||||
return TLV(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):
|
def set_value(self, new_value: bytes):
|
||||||
self.value = new_value
|
self.value = new_value
|
||||||
@ -60,7 +75,7 @@ class TLV:
|
|||||||
def get_record_length(self):
|
def get_record_length(self):
|
||||||
# Length including record definition
|
# Length including record definition
|
||||||
return self.get_actual_length() + 4
|
return self.get_actual_length() + 4
|
||||||
|
|
||||||
def get_value(self):
|
def get_value(self):
|
||||||
return self.value
|
return self.value
|
||||||
|
|
||||||
@ -91,10 +106,6 @@ class TLV6(TLV):
|
|||||||
0x5003: ["", "End of definition marker"],
|
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):
|
def parse(self):
|
||||||
if len(self.value) % 2 != 0:
|
if len(self.value) % 2 != 0:
|
||||||
raise Exception("Invalid TLV6 payload length!")
|
raise Exception("Invalid TLV6 payload length!")
|
||||||
@ -147,8 +158,9 @@ class Gcd:
|
|||||||
if sig != GCD_SIG:
|
if sig != GCD_SIG:
|
||||||
raise Exception("Signature mismatch ({}, should be {})!".format(repr(sig), repr(GCD_SIG)))
|
raise Exception("Signature mismatch ({}, should be {})!".format(repr(sig), repr(GCD_SIG)))
|
||||||
while True:
|
while True:
|
||||||
|
cur_offset = f.tell()
|
||||||
header = f.read(4)
|
header = f.read(4)
|
||||||
tlv = TLV.factory(header)
|
tlv = TLV.factory(header, offset=cur_offset)
|
||||||
self.struct.append(tlv)
|
self.struct.append(tlv)
|
||||||
if tlv.type_id == 0xFFFF:
|
if tlv.type_id == 0xFFFF:
|
||||||
# End of file reached
|
# End of file reached
|
||||||
|
Loading…
x
Reference in New Issue
Block a user