diff --git a/grmn/__init__.py b/grmn/__init__.py index faab907..8fca0f9 100644 --- a/grmn/__init__.py +++ b/grmn/__init__.py @@ -1,4 +1,5 @@ """Library for firmware reading/writing.""" from .gcd import * +from .rgn import * from .chksum import * diff --git a/grmn/rgn.py b/grmn/rgn.py new file mode 100644 index 0000000..8e22c62 --- /dev/null +++ b/grmn/rgn.py @@ -0,0 +1,138 @@ +# -*- coding: utf-8 -*- +# Thanks to Herbert Oppmann (herby) for all your work! + +from .chksum import ChkSum +from .rgnbin import RgnBin +from struct import unpack +import configparser + +RGN_SIG = b"KpGr" + +# RGN structure might be: RGN > BIN or RGN > RGN > BIN +# RGN = outside hull +# BIN = firmware + hwid + checksum + +class ParseException(Exception): + pass + +class Rgn: + def __init__(self, filename: str=None): + self.filename = filename + self.struct = [] + if filename is not None: + self.load() + + def load(self): + if self.filename is None: + return False + last_tlv6 = None + last_tlv7 = None + with open(self.filename, "rb") as f: + sig = f.read(4) + if sig != RGN_SIG: + raise ParseException("Signature mismatch ({}, should be {})!".format(repr(sig), repr(RGN_SIG))) + self.version = unpack("<H", f.read(2))[0] + print("Version: {}".format(self.version)) + while True: + cur_offset = f.tell() + header = f.read(5) + if len(header) == 0: + print("End of file reached.") + break + (length, type_id) = unpack("<Lc", header) + print("Found record type: {} with {} Bytes length.".format(type_id, length)) + rec = RgnRecord.factory(type_id, length, offset=cur_offset) + payload = f.read(length) + rec.set_payload(payload) + self.add_rec(rec) + f.close() + + def add_rec(self, new_rec): + self.struct.append(new_rec) + + def print_struct(self): + """ + Prints the structure of the parsed GCD file + """ + pass + + def print_struct_full(self): + """ + Prints the structure of the parsed GCD file + """ + pass + + def validate(self, print_stats: bool=False): + """ + Checks and verifies all checksums in the GCD. + """ + # RGN has no checksum, but embedded BIN has + + def dump_to_files(self, output_basename: str): + pass + + @staticmethod + def from_recipe(recipe_file: str): + pass + + def save(self, filename): + pass + +class RgnRecord(): + def __init__(self, type_id, expected_length, payload=None, offset=None): + self.type_id = type_id + self.length = expected_length + self.is_binary = False + self.payload = payload + self.offset = offset + self.is_parsed = False + + def set_payload(self, new_payload): + self.payload = new_payload + + @staticmethod + def factory(type_id, length: int = None, offset: int = None): + if type_id == b"D": + new_rec = RgnRecordD(type_id, length) + elif type_id == b"A": + new_rec = RgnRecordA(type_id, length) + elif type_id == b"R": + new_rec = RgnRecordR(type_id, length) + new_rec.is_binary = True + else: + raise ParseException("Unknown record type: {} at offset 0x{:0x}".format(type_id, offset)) + new_rec.offset = offset + return new_rec + +class RgnRecordD(RgnRecord): + """ + Data record (2 Bytes) + - ushort - Version + """ + + def parse(self): + self.is_parsed = True + +class RgnRecordA(RgnRecord): + """ + Application record + - ushort - Application version + - string - Builder + - string - BuildDate + - string - BuildTime + """ + + def parse(self): + self.is_parsed = True + +class RgnRecordR(RgnRecord): + """ + Region record + - ushort - Region ID + - uint - Delay in ms + - uint - Region size (is record length - 10) + - byte[Region size] - Contents + """ + + def parse(self): + self.is_parsed = True diff --git a/grmn/rgnbin.py b/grmn/rgnbin.py new file mode 100644 index 0000000..2d5014d --- /dev/null +++ b/grmn/rgnbin.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- +# Thanks to Herbert Oppmann (herby) for all your work! + +from . import devices +from .chksum import ChkSum +from struct import unpack + +# RGN structure might be: RGN > BIN or RGN > RGN > BIN +# RGN = outside hull +# BIN = firmware + hwid + checksum + +class ParseException(Exception): + pass + +class RgnBin: + def __init__(self, filename: str=None): + self.filename = filename + self.struct = [] + if filename is not None: + self.load() + + def load(self): + if self.filename is None: + return False + with open(self.filename, "rb") as f: + f.close() diff --git a/rgnstruct.py b/rgnstruct.py new file mode 100644 index 0000000..ae0b140 --- /dev/null +++ b/rgnstruct.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +""" +Prints out the structure of the given RGN file. +""" + +from grmn import Rgn +import sys + +FILE = sys.argv[1] + +print("Opening {}".format(FILE)) + +rgn = Rgn(FILE) + +#rgn.print_struct() +#rgn.validate(True)