gemini: Bump to kernel v6.6
[openwrt/openwrt.git] / scripts / moxa-encode-fw.py
1 #! /usr/bin/env python3
2 # SPDX-License-Identifier: GPL-2.0-or-later
3
4 import argparse
5 import struct
6
7 from binascii import crc32
8 from dataclasses import dataclass
9 from itertools import cycle
10 from typing import List
11
12
13 def xor(data: bytes) -> bytes:
14 passphrase = "Seek AGREEMENT for the date of completion.\0"
15 pw = cycle(bytearray(passphrase.encode('ascii')))
16 return bytearray(b ^ next(pw) for b in data)
17
18
19 def add_fw_header(data: bytes, magic: int, hwid: int, build_id: int,
20 offsets: List[int]) -> bytes:
21 unknown_1 = 0x01
22 unknown_2 = 0x0000
23 unknown_3 = 0x00000000
24 unknown_4 = 0x01000000
25 file_crc = crc(data, 0)
26
27 header_struct = struct.Struct('>QIBBHIIIIII' + 'I' * len(offsets))
28 header_size = header_struct.size
29 file_size = header_size + len(data)
30
31 header_offsets = map(lambda x: x + header_size, offsets)
32
33 header_data = header_struct.pack(magic, file_size, unknown_1, len(offsets),
34 unknown_2, hwid, build_id, unknown_3,
35 build_id, unknown_4, *header_offsets,
36 file_crc)
37 return header_data + data
38
39
40 def add_file_header(data: bytes, filename: str, build_id: int) -> bytes:
41 unknown1 = 0x01000000
42 unknown2 = 0x00000000
43 file_crc = crc(data, 0)
44
45 header_struct = struct.Struct(">16sIIIII")
46 file_size = header_struct.size + len(data)
47
48 header_data = header_struct.pack(filename.encode('ascii'), file_size,
49 unknown1, build_id, unknown2, file_crc)
50 return header_data + data
51
52
53 def crc(data: bytes, init_val: int) -> int:
54 return 0xffffffff ^ (crc32(data, 0xffffffff ^ init_val))
55
56
57 @dataclass
58 class Partition:
59 name: str
60 size: int
61
62
63 def main():
64 partitions = [
65 Partition(name='kernel', size=2048 * 1024),
66 Partition(name='root', size=9216 * 1024),
67 Partition(name='userdisk', size=3076 * 1024),
68 ]
69
70 parser = argparse.ArgumentParser(prog='moxa-encode-fw',
71 description='MOXA IW firmware encoder')
72 parser.add_argument('-i', '--input', required=True, type=str, help='Firmware file')
73 parser.add_argument('-o', '--output', required=True, type=str, help="Output path for encoded firmware file")
74 parser.add_argument('-m', '--magic', required=True, type=lambda x: int(x,0), help="Magic for firmware header")
75 parser.add_argument('-d', '--hwid', required=True, type=lambda x: int(x,0), help="Hardware id of device")
76 parser.add_argument('-b', '--buildid', required=True, type=lambda x: int(x,0), help="Build id of firmware")
77 args = parser.parse_args()
78
79 with open(args.input, 'rb') as input_file:
80 firmware = bytearray(input_file.read())
81
82 offsets = []
83 pos_input = 0
84 pos_output = 0
85 firmware_seg = bytearray()
86
87 for partition in partitions:
88 part_data = firmware[pos_input:pos_input + partition.size]
89
90 # just to make sure that no partition is empty
91 if len(part_data) == 0:
92 part_data = bytearray([0x00])
93
94 header = add_file_header(part_data, partition.name, args.buildid)
95 firmware_seg += header
96
97 offsets.append(pos_output)
98 pos_input += partition.size
99 pos_output += len(header)
100
101 moxa_firmware = add_fw_header(firmware_seg, args.magic, args.hwid, args.buildid, offsets)
102
103 encrypted = xor(moxa_firmware)
104 with open(args.output, 'wb') as output_file:
105 output_file.write(encrypted)
106
107
108 if __name__ == '__main__':
109 main()