-
Notifications
You must be signed in to change notification settings - Fork 50
Expand file tree
/
Copy pathcalcsum.py
More file actions
161 lines (107 loc) · 4.72 KB
/
Copy pathcalcsum.py
File metadata and controls
161 lines (107 loc) · 4.72 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
from __future__ import annotations
import argparse
import os
from dataclasses import dataclass, asdict, astuple
from enum import Enum, auto
from mmap import mmap
from struct import pack, unpack
from sys import exit
HEADER_MAGIC = 0x12345678
HEADER_RESERVED = 0x332255ff
HEADER_LENGTH = 20
PAYLOAD_BLOCK_1_LENGTH = 0x2ffe
PAYLOAD_BLOCK_2_LENGTH = 0x1000
PAYLOAD_BLOCK_3_LENGTH = lambda x: x - (PAYLOAD_BLOCK_1_LENGTH + PAYLOAD_BLOCK_2_LENGTH + HEADER_LENGTH)
FULL_PAYLOAD_BLOCK_1_OFFSET = 0x1002
FULL_PAYLOAD_BLOCK_2_OFFSET = 0x1c000
FULL_PAYLOAD_BLOCK_3_OFFSET = 0x1d000
UPDATE_PAYLOAD_BLOCK_1_OFFSET = HEADER_LENGTH
UPDATE_PAYLOAD_BLOCK_2_OFFSET = HEADER_LENGTH + PAYLOAD_BLOCK_1_LENGTH
UPDATE_PAYLOAD_BLOCK_3_OFFSET = HEADER_LENGTH + PAYLOAD_BLOCK_1_LENGTH + PAYLOAD_BLOCK_2_LENGTH
class FirmwareType(Enum):
UNKNOWN = auto()
FULL = auto()
UPDATE = auto()
@dataclass(slots=True)
class Header:
magic: int = HEADER_MAGIC
length: int = 0
header_sum: int = 0
payload_sum: int = 0
reserved: int = HEADER_RESERVED
def __setattr__(self, name: str, value: object) -> None:
if name == 'magic' and value != HEADER_MAGIC:
raise ValueError('Invalid magic value')
elif name == 'reserved' and value != HEADER_RESERVED:
raise ValueError('Invalid reserved value')
object.__setattr__(self, name, value)
if all(hasattr(self, attr) for attr in self.__slots__) and \
name in ('length', 'payload_sum'):
object.__setattr__(self, 'header_sum', self.calc_sum())
def __bytes__(self) -> bytes:
return self.to_bytes()
def __str__(self) -> str:
return "\n".join(f"{key.replace('_', ' ')}: {value:08x}" for key, value in asdict(self).items())
def to_bytes(self) -> bytes:
return pack('>5I', *astuple(self))
def calc_sum(self) -> int:
buffer = bytearray(self.to_bytes())
buffer[8:12] = bytearray(4)
return sum(buffer)
@property
def dirty(self) -> bool:
return self.header_sum != self.calc_sum()
@classmethod
def from_bytes(cls, buffer: bytes) -> Header:
if len(buffer) != HEADER_LENGTH:
raise ValueError('Invalid header length')
return cls(*unpack('>5I', buffer))
def main() -> None:
parser = argparse.ArgumentParser(description='SWTG Firmware Checksum Calculator')
parser.add_argument('-u', '--update', help='re-calculate sums', action='store_true')
parser.add_argument('firmware', type=argparse.FileType('r+b'))
args = parser.parse_args()
print_or_exit = print if args.update else exit
with args.firmware as f, mmap(f.fileno(), 0) as mm:
binary = FirmwareType.UNKNOWN
buffer = mm.read(2)
match int.from_bytes(buffer, byteorder='little'):
case 0x4000:
binary = FirmwareType.FULL
case 0x3412:
buffer += mm.read(HEADER_LENGTH - 2)
try:
header = Header.from_bytes(buffer)
except Exception as e:
exit(e)
binary = FirmwareType.UPDATE if (header.length + HEADER_LENGTH) == mm.size() else FirmwareType.UNKNOWN
if binary is FirmwareType.UNKNOWN:
exit('Invalid binary')
offset, length = (FULL_PAYLOAD_BLOCK_1_OFFSET, PAYLOAD_BLOCK_1_LENGTH) if binary is FirmwareType.FULL else (UPDATE_PAYLOAD_BLOCK_1_OFFSET, PAYLOAD_BLOCK_1_LENGTH)
mm.seek(offset, os.SEEK_SET)
payload_sum = sum(mm.read(length))
offset, length = (FULL_PAYLOAD_BLOCK_2_OFFSET, PAYLOAD_BLOCK_2_LENGTH) if binary is FirmwareType.FULL else (UPDATE_PAYLOAD_BLOCK_2_OFFSET, PAYLOAD_BLOCK_2_LENGTH)
mm.seek(offset, os.SEEK_SET)
payload_sum += sum(mm.read(length))
offset = FULL_PAYLOAD_BLOCK_3_OFFSET if binary is FirmwareType.FULL else UPDATE_PAYLOAD_BLOCK_3_OFFSET
mm.seek(offset, os.SEEK_SET)
try:
header = Header.from_bytes(mm.read(HEADER_LENGTH))
except Exception as e:
exit(e)
payload_sum += 0xff * HEADER_LENGTH
payload_sum += sum(mm.read(PAYLOAD_BLOCK_3_LENGTH(header.length)))
if header.payload_sum != payload_sum:
print_or_exit('Incorrect payload sum')
print(header)
if args.update:
print('Updating checksums...')
print('Old header: {}'.format(bytes(header).hex()))
header.payload_sum = payload_sum # header sum will update as well
print('New header: {}'.format(bytes(header).hex()))
if binary is FirmwareType.UPDATE:
mm[:HEADER_LENGTH] = bytes(header)
mm[offset:offset + HEADER_LENGTH] = bytes(header)
exit(0)
if __name__ == '__main__':
main()