Skip to content

Instantly share code, notes, and snippets.

@Phaeilo
Last active February 25, 2023 16:13
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Phaeilo/e96de1beb7e85b2fba25a42d2870abf6 to your computer and use it in GitHub Desktop.
Save Phaeilo/e96de1beb7e85b2fba25a42d2870abf6 to your computer and use it in GitHub Desktop.
Unpack .etq and .lpg files used by hamradiotrainer
#!/usr/bin/env python3
import struct
import sys
import hashlib
import zlib
with open("./hamradiotrainer.exe", "rb") as fh:
key = fh.read()
assert hashlib.sha256(key).hexdigest() == "b1615d0efc0ee2d1a1714df0dbb1eb3d1283c075db19e8b032044231303d3ae5"
key = key[0x149344:0x149344+59]
with open(sys.argv[1], "rb") as fh:
magic = fh.read(8)
assert magic == b"ETQ1\0\0\0\0"
while True:
magic = fh.read(4)
if len(magic) == 0:
break
assert magic == b"FI2\0"
rlen, flags = struct.unpack(">II", fh.read(8))
is_compressed = flags & (1 << 16) != 0
is_encrypted = flags & 1 != 0
md5 = fh.read(16)
fname_len = struct.unpack(">H", fh.read(2))[0]
fname = fh.read(fname_len)
assert len(fname) == fname_len
fname = fname.decode("ascii")
fsize = rlen - 4 - 16 - 2 - fname_len
fcontents = fh.read(fsize)
assert len(fcontents) == fsize
assert hashlib.md5(fcontents).digest() == md5
if not is_encrypted:
plaintext = fcontents
else:
i = fsize
j = (i >> 3) & 7
j += 3 if j == 0 else 0
plaintext = []
for x in fcontents:
x -= key[i % len(key)]
x += 0x100 if x < 0 else 0
plaintext.append(x)
i += j
plaintext = bytes(plaintext)
if is_compressed:
unc_size = struct.unpack(">I", plaintext[:4])[0]
plaintext = zlib.decompress(plaintext[4:])
assert len(plaintext) == unc_size
safe_fname = fname.replace("/", "_")
with open(safe_fname, "wb") as ofh:
ofh.write(plaintext)
print(fname, len(plaintext), plaintext[:16])
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment