mirror of https://github.com/nirenjan/libx52.git
126 lines
4.4 KiB
Python
Executable File
126 lines
4.4 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""Validate a compiled layout file, and optionally dump the contents"""
|
|
|
|
import argparse
|
|
import struct
|
|
import zlib
|
|
|
|
class X52LayoutError(Exception):
|
|
"""Exception raised when there's a flaw in the layout"""
|
|
|
|
class X52Layout:
|
|
"""X52Layout is a class that parses a compiled layout file, validates it
|
|
and dumps the mappings"""
|
|
|
|
def __init__(self, layout_file):
|
|
"""Load the layout from the layout file"""
|
|
with open(layout_file, 'rb') as lfd:
|
|
self.raw_layout = lfd.read()
|
|
|
|
if len(self.raw_layout) < 128:
|
|
raise X52LayoutError("layout file too short (%d bytes)" % len(self.raw_layout))
|
|
|
|
self.magic = self._load_magic()
|
|
self.version = self._load_version()
|
|
self.flags = self._load_flags()
|
|
self.count = self._load_count()
|
|
|
|
self._validate_checksum()
|
|
self.name = self._load_layout_name()
|
|
self.description = self._load_layout_description()
|
|
|
|
def _load_magic(self):
|
|
"""Load and validate the magic bytes"""
|
|
magic = struct.unpack_from('4s', self.raw_layout[0:4])
|
|
if magic[0] != b'X52L':
|
|
raise X52LayoutError("invalid magic identifier: (%s)" % magic)
|
|
|
|
return magic
|
|
|
|
def _load_version(self):
|
|
"""Load and validate the version bytes"""
|
|
version = struct.unpack_from(">H", self.raw_layout[4:6])
|
|
if version[0] != 1:
|
|
raise X52LayoutError("invalid version %d" % version)
|
|
|
|
return version[0]
|
|
|
|
def _load_flags(self):
|
|
"""Load and validate the flags"""
|
|
flags = struct.unpack_from('>H', self.raw_layout[6:8])[0]
|
|
should_be_zero = flags >> 2
|
|
if should_be_zero:
|
|
raise X52LayoutError("reserved flags set; upper bits should be 0; got 0x%04x" % flags)
|
|
|
|
return flags
|
|
|
|
def _load_count(self):
|
|
"""Load and validate the count"""
|
|
count = struct.unpack_from('>I', self.raw_layout[8:12])[0]
|
|
if count == 0 or count > 0x10FFFF:
|
|
raise X52LayoutError("invalid count 0x%x" % count)
|
|
|
|
expected_length = count * 2 + 128
|
|
if len(self.raw_layout) < expected_length:
|
|
raise X52LayoutError("Insufficient bytes: expected %d; got %d",
|
|
expected_length, len(self.raw_layout))
|
|
|
|
if len(self.raw_layout) > expected_length:
|
|
raise X52LayoutError("Input too long: expected %d; got %d",
|
|
expected_length, len(self.raw_layout))
|
|
|
|
return count
|
|
|
|
def _validate_checksum(self):
|
|
"""Load and validate the checksum"""
|
|
checksum = struct.unpack_from('>I', self.raw_layout[12:16])[0]
|
|
computed = zlib.crc32(self.raw_layout[:12])
|
|
computed = zlib.crc32(b'\0\0\0\0', computed)
|
|
computed = zlib.crc32(self.raw_layout[16:], computed) & 0xFFFFFFFF
|
|
if checksum != computed:
|
|
raise X52LayoutError("corrupted checksum; expected %04x; got %04x" % (checksum, computed))
|
|
|
|
@staticmethod
|
|
def _read_nul_terminated_string(array):
|
|
"""Read a nul terminated string from an array"""
|
|
format_str = f"{len(array)}s"
|
|
raw_string = struct.unpack_from(format_str, array)[0]
|
|
return raw_string.rstrip(b'\x00').decode('utf-8')
|
|
|
|
def _load_layout_name(self):
|
|
"""Load the name from the layout"""
|
|
name = self._read_nul_terminated_string(self.raw_layout[16:48])
|
|
if not name:
|
|
raise X52LayoutError("Missing layout name")
|
|
|
|
return name
|
|
|
|
def _load_layout_description(self):
|
|
"""Load the description from the layout"""
|
|
return self._read_nul_terminated_string(self.raw_layout[48:112])
|
|
|
|
def dump(self):
|
|
"""Dump the parsed layout file"""
|
|
print("name:", self.name)
|
|
print("description:", self.description)
|
|
print("magic:", self.magic)
|
|
print("version:", self.version)
|
|
print("flags: %#x" % self.flags)
|
|
print("count:", self.count)
|
|
|
|
def main():
|
|
"""Main function"""
|
|
parser = argparse.ArgumentParser(description="Parse and validate x52layout file")
|
|
parser.add_argument('-d', '--dump', action='store_true',
|
|
help='Dump decoded layout')
|
|
parser.add_argument('layout_file', metavar='layout-file',
|
|
help='Path to compiled layout file')
|
|
args = parser.parse_args()
|
|
|
|
layout = X52Layout(args.layout_file)
|
|
if args.dump:
|
|
layout.dump()
|
|
|
|
if __name__ == '__main__':
|
|
main()
|