Added gcddump to dump parsed GCD to files which can later be reassembled
into a GCD.
This commit is contained in:
parent
c0a1d833ba
commit
27a90e8b38
21
gcddump.py
Normal file
21
gcddump.py
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
"""
|
||||||
|
Dumps the structure of the given GCD file to file.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from grmn import Gcd
|
||||||
|
import sys
|
||||||
|
|
||||||
|
if len(sys.argv) < 3:
|
||||||
|
print("Syntax: {} GCDFILE DUMPFILE (extension .rcp will be added)".format(sys.argv[0]))
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
FILE = sys.argv[1]
|
||||||
|
OUTFILE = sys.argv[2]
|
||||||
|
|
||||||
|
print("Opening {}".format(FILE))
|
||||||
|
gcd = Gcd(FILE)
|
||||||
|
print("Dumping to {}.rcp".format(OUTFILE))
|
||||||
|
gcd.dump(OUTFILE)
|
50
grmn/gcd.py
50
grmn/gcd.py
@ -8,6 +8,7 @@ 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 # second padding block pads until 0x1000
|
DEFAULT_ALIGN = 0x1000 # second padding block pads until 0x1000
|
||||||
|
MAX_BLOCK_LENGTH = 0xff00 # binary blocks max len
|
||||||
|
|
||||||
# Typical structure:
|
# Typical structure:
|
||||||
# first 0x1000 Bytes: GCD_SIG > 0x0001 > 0x0002 > 0x0003 > 0x0005 > 0x0001 > 0x0002
|
# first 0x1000 Bytes: GCD_SIG > 0x0001 > 0x0002 > 0x0003 > 0x0005 > 0x0001 > 0x0002
|
||||||
@ -27,6 +28,7 @@ class Gcd:
|
|||||||
if self.filename is None:
|
if self.filename is None:
|
||||||
return False
|
return False
|
||||||
last_tlv6 = None
|
last_tlv6 = None
|
||||||
|
last_tlv7 = None
|
||||||
with open(self.filename, "rb") as f:
|
with open(self.filename, "rb") as f:
|
||||||
sig = f.read(8)
|
sig = f.read(8)
|
||||||
if sig != GCD_SIG:
|
if sig != GCD_SIG:
|
||||||
@ -46,6 +48,9 @@ class Gcd:
|
|||||||
last_tlv6 = tlv
|
last_tlv6 = tlv
|
||||||
elif tlv.type_id == 0x0007:
|
elif tlv.type_id == 0x0007:
|
||||||
tlv.set_tlv6(last_tlv6)
|
tlv.set_tlv6(last_tlv6)
|
||||||
|
last_tlv7 = tlv
|
||||||
|
elif tlv.is_binary:
|
||||||
|
tlv.set_tlv7(last_tlv7)
|
||||||
f.close()
|
f.close()
|
||||||
|
|
||||||
def add_tlv(self, new_tlv: TLV):
|
def add_tlv(self, new_tlv: TLV):
|
||||||
@ -98,3 +103,48 @@ class Gcd:
|
|||||||
else:
|
else:
|
||||||
print("☒ ONE OR MORE CHECKSUMS INVALID!")
|
print("☒ ONE OR MORE CHECKSUMS INVALID!")
|
||||||
return all_ok
|
return all_ok
|
||||||
|
|
||||||
|
def write_dump_block(self, f, name):
|
||||||
|
f.write("\n[BLOCK_{}]\n".format(name))
|
||||||
|
|
||||||
|
def write_dump_param(self, f, key, value, comment=None):
|
||||||
|
if comment is not None:
|
||||||
|
f.write("# {}\n".format(comment))
|
||||||
|
f.write("{} = {}\n".format(key, value))
|
||||||
|
|
||||||
|
def dump(self, output_basename: str):
|
||||||
|
output_file = "{}.rcp".format(output_basename)
|
||||||
|
ctr = 0
|
||||||
|
last_filename = None
|
||||||
|
with open(output_file, "wt") as f:
|
||||||
|
f.write("[GCD_DUMP]\n")
|
||||||
|
f.write("dump_by = grmn-gcd\n")
|
||||||
|
f.write("dump_ver = 1\n")
|
||||||
|
f.write("original_filename = {}\n\n".format(self.filename))
|
||||||
|
f.write("# [BLOCK_nn] headers are just for parsing/grouping purposes.\n")
|
||||||
|
f.write("# Lengths are informational only, they will be calculated upon reassembly.\n")
|
||||||
|
for tlv in self.struct:
|
||||||
|
if tlv.is_binary:
|
||||||
|
outfile = "{}_{:04x}.bin".format(output_basename, tlv.type_id)
|
||||||
|
if outfile != last_filename:
|
||||||
|
self.write_dump_block(f, str(ctr))
|
||||||
|
self.write_dump_param(f, "from_file", outfile)
|
||||||
|
for item in tlv.dump():
|
||||||
|
self.write_dump_param(f, item[0], item[1], item[2])
|
||||||
|
last_filename = outfile
|
||||||
|
with open(outfile, "wb") as of:
|
||||||
|
of.write(tlv.value)
|
||||||
|
else:
|
||||||
|
with open(outfile, "ab") as of:
|
||||||
|
of.write(tlv.value)
|
||||||
|
elif tlv.type_id == 0xffff:
|
||||||
|
# EOF marker
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
tlvinfo = tlv.dump()
|
||||||
|
if len(tlvinfo) > 0:
|
||||||
|
self.write_dump_block(f, str(ctr))
|
||||||
|
for item in tlvinfo:
|
||||||
|
self.write_dump_param(f, item[0], item[1], item[2])
|
||||||
|
ctr += 1
|
||||||
|
f.close()
|
||||||
|
85
grmn/tlv.py
85
grmn/tlv.py
@ -1,6 +1,7 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
from . import devices
|
from . import devices
|
||||||
|
from binascii import hexlify
|
||||||
from struct import pack, unpack
|
from struct import pack, unpack
|
||||||
|
|
||||||
TLV_TYPES = {
|
TLV_TYPES = {
|
||||||
@ -22,6 +23,7 @@ TLV_TYPES = {
|
|||||||
class TLV:
|
class TLV:
|
||||||
def __init__(self, type_id: int, expected_length: int, value=None, offset: int=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.is_binary = False
|
||||||
self.offset = offset
|
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
|
||||||
@ -33,10 +35,19 @@ class TLV:
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
def factory(header: bytes, offset: int = None):
|
def factory(header: bytes, offset: int = None):
|
||||||
(type_id, length) = unpack("<HH", header)
|
(type_id, length) = unpack("<HH", header)
|
||||||
if type_id == 0x0006:
|
if type_id == 0x0001:
|
||||||
|
new_tlv = TLV1(type_id, length)
|
||||||
|
elif type_id == 0x0002:
|
||||||
|
new_tlv = TLV2(type_id, length)
|
||||||
|
elif type_id == 0x0005:
|
||||||
|
new_tlv = TLV5(type_id, length)
|
||||||
|
elif type_id == 0x0006:
|
||||||
new_tlv = TLV6(type_id, length)
|
new_tlv = TLV6(type_id, length)
|
||||||
elif type_id == 0x0007:
|
elif type_id == 0x0007:
|
||||||
new_tlv = TLV7(type_id, length)
|
new_tlv = TLV7(type_id, length)
|
||||||
|
elif type_id in [0x0008, 0x0401, 0x0505, 0x0555, 0x0557, 0x02bd]:
|
||||||
|
new_tlv = TLVbinary(type_id, length)
|
||||||
|
new_tlv.is_binary = True
|
||||||
else:
|
else:
|
||||||
new_tlv = TLV(type_id, length)
|
new_tlv = TLV(type_id, length)
|
||||||
new_tlv.offset = offset
|
new_tlv.offset = offset
|
||||||
@ -73,6 +84,43 @@ class TLV:
|
|||||||
return header
|
return header
|
||||||
return header + self.value
|
return header + self.value
|
||||||
|
|
||||||
|
def dump(self):
|
||||||
|
"""Should return a list of key-value-comment tuples so the object can be recreated using create_from_dump() later."""
|
||||||
|
data = []
|
||||||
|
data.append(("type", "0x{:04x}".format(self.type_id), self.comment))
|
||||||
|
data.append(("length", self.get_actual_length(), None))
|
||||||
|
if self.value is not None:
|
||||||
|
hexstr = hexlify(self.value).decode("utf-8")
|
||||||
|
hexstr = " ".join(hexstr[i:i+2] for i in range(0, len(hexstr), 2))
|
||||||
|
data.append(("value", hexstr, None))
|
||||||
|
return data
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def create_from_dump(values):
|
||||||
|
"""Use data exported with dump() to recreate object."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
class TLV1(TLV):
|
||||||
|
def dump(self):
|
||||||
|
data = []
|
||||||
|
data.append(("type", "0x{:04x}".format(self.type_id), self.comment))
|
||||||
|
return data
|
||||||
|
|
||||||
|
class TLV2(TLV):
|
||||||
|
def dump(self):
|
||||||
|
data = []
|
||||||
|
data.append(("type", "0x{:04x}".format(self.type_id), self.comment))
|
||||||
|
data.append(("length", self.get_actual_length(), "Length of padding block"))
|
||||||
|
return data
|
||||||
|
|
||||||
|
class TLV5(TLV):
|
||||||
|
def dump(self):
|
||||||
|
data = []
|
||||||
|
data.append(("type", "0x{:04x}".format(self.type_id), self.comment))
|
||||||
|
data.append(("length", self.get_actual_length(), None))
|
||||||
|
data.append(("text", self.value.decode("utf-8"), None))
|
||||||
|
return data
|
||||||
|
|
||||||
class TLV6(TLV):
|
class TLV6(TLV):
|
||||||
"""
|
"""
|
||||||
Describes following TLV7:
|
Describes following TLV7:
|
||||||
@ -90,7 +138,7 @@ class TLV6(TLV):
|
|||||||
0x1014: ["H", "Field 1014"],
|
0x1014: ["H", "Field 1014"],
|
||||||
0x1015: ["H", "Field 1015"],
|
0x1015: ["H", "Field 1015"],
|
||||||
0x1016: ["H", "Field 1016 (WiFi fw)"],
|
0x1016: ["H", "Field 1016 (WiFi fw)"],
|
||||||
0x2015: ["L", "Block size"],
|
0x2015: ["L", "Binary length"],
|
||||||
0x5003: ["", "End of definition marker"],
|
0x5003: ["", "End of definition marker"],
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -119,6 +167,10 @@ class TLV6(TLV):
|
|||||||
txt += "\n - Field {:d}: {:04x} - {}".format(i+1, fid, self.fields[i])
|
txt += "\n - Field {:d}: {:04x} - {}".format(i+1, fid, self.fields[i])
|
||||||
return txt
|
return txt
|
||||||
|
|
||||||
|
def dump(self):
|
||||||
|
# Dump nothing as important info will be chained in binary dump
|
||||||
|
return []
|
||||||
|
|
||||||
class TLV7(TLV):
|
class TLV7(TLV):
|
||||||
def __init__(self, type_id: int, expected_length: int, value=None, offset: int=None):
|
def __init__(self, type_id: int, expected_length: int, value=None, offset: int=None):
|
||||||
super().__init__(type_id, expected_length, value, offset)
|
super().__init__(type_id, expected_length, value, offset)
|
||||||
@ -153,3 +205,32 @@ class TLV7(TLV):
|
|||||||
txt += "\n - Field {:d}: {:>20}: 0x{:04x} / {:d}".format(i+1, fdesc, v, v)
|
txt += "\n - Field {:d}: {:>20}: 0x{:04x} / {:d}".format(i+1, fdesc, v, v)
|
||||||
return txt
|
return txt
|
||||||
|
|
||||||
|
def dump(self):
|
||||||
|
# Dump nothing as important info will be chained in binary dump
|
||||||
|
return []
|
||||||
|
|
||||||
|
class TLVbinary(TLV):
|
||||||
|
def __init__(self, type_id: int, expected_length: int, value=None, offset: int=None):
|
||||||
|
super().__init__(type_id, expected_length, value, offset)
|
||||||
|
self.tlv7 = None
|
||||||
|
|
||||||
|
def set_tlv7(self, tlv7: TLV7):
|
||||||
|
self.tlv7 = tlv7
|
||||||
|
|
||||||
|
def dump(self):
|
||||||
|
data = []
|
||||||
|
# type is given in fields list from TLV7 already
|
||||||
|
if not self.tlv7.is_parsed:
|
||||||
|
self.tlv7.parse()
|
||||||
|
for i, pair in enumerate(self.tlv7.attr):
|
||||||
|
fdesc = self.tlv7.tlv6.fields[i]
|
||||||
|
valtype = self.tlv7.tlv6.format[i]
|
||||||
|
(fid, v) = pair
|
||||||
|
if valtype == "B":
|
||||||
|
valstr = "0x{:02x}".format(v)
|
||||||
|
elif valtype == "H":
|
||||||
|
valstr = "0x{:04x}".format(v)
|
||||||
|
else:
|
||||||
|
valstr = "0x{:08x}".format(v)
|
||||||
|
data.append(("0x{:04x}".format(fid), valstr, fdesc))
|
||||||
|
return data
|
||||||
|
Loading…
x
Reference in New Issue
Block a user