Skip to content

Instantly share code, notes, and snippets.

@hamidb80
Last active April 12, 2024 18:42
Show Gist options
  • Save hamidb80/e911acb671c96840a754f22876c84bdb to your computer and use it in GitHub Desktop.
Save hamidb80/e911acb671c96840a754f22876c84bdb to your computer and use it in GitHub Desktop.
## originally writeen for brother Omar. thanks to:
## https://github.com/nramos0/binhex4-rs/
## https://gist.github.com/i-e-b/fe0a0158ae61973802a9
import std/[strutils, sequtils, os]
type
DecodedHqxContent = distinct string
Fork[T] = object
content: T
crc: uint16
HqxHeader = object
filename: string
Hqx = object
header: Fork[HqxHeader]
data, resource: Fork[string]
const
validChars = "!\"#$%&'()*+,-012345689@ABCDEFGHIJKLMNPQRSTUVXYZ[`abcdefhijklmpqr"
RLE_MARKER_BYTE = uint8 0x90
func circularInc[E: Ordinal](a: var E) =
a =
case a
of E.high: E.low
else: succ a
func `>..`(a, b: int): Slice[int] =
a+1 .. b
func `>..<`(a, b: int): Slice[int] =
a+1 .. b-1
func toNumber(str: string): int =
for c in str:
result = (result shl 8) + (ord c)
proc chop(s: string, chunks: openArray[int], start = 0): seq[string] =
var head = start
for len in chunks:
result.add s[head ..< head+len]
head.inc len
proc decode(c: char): uint8 =
uint8 validChars.find c
proc decode6(str: string): string =
var
decode_state: range[0..3] = 3
partial_b8 = uint8 0
has_rle = false
last_byte = uint8 0
for i, b6 in str:
case b6
of '\r', '\n': discard
else:
circularInc decode_state
let b6_decoded = decode(b6)
var data: uint8
case decode_state
of 0:
# cannot yet output a data byte
partial_b8 = b6_decoded shl 2
continue
of 1:
data = partial_b8 or (b6_decoded shr 4)
partial_b8 = (b6_decoded and 0b00001111) shl 4
of 2:
data = partial_b8 or (b6_decoded shr 2)
partial_b8 = (b6_decoded and 0b00000011) shl 6
of 3:
data = partial_b8 or b6_decoded
if not has_rle:
if data == RLE_MARKER_BYTE:
has_rle = true
else:
last_byte = data
result.add chr data
else:
if data == 0x00:
last_byte = RLE_MARKER_BYTE
result.add chr RLE_MARKER_BYTE
else:
while true:
data -= 1
if data == 0:
break
result.add chr last_byte
has_rle = false
proc fileNameLen(content: DecodedHqxContent): Positive =
content.string[0].ord
proc decodeHqxContent(fileContent: string):
tuple[intro: string, data: DecodedHqxContent] =
let
head = fileContent.find ':'
tail = fileContent.rfind ':'
data = fileContent[head >..< tail]
(fileContent[0..<head], DecodedHqxContent decode6 data)
proc headerLen(fileNameLen: int): int =
1 + fileNameLen + 1 + 4 + 4 + 2 + 4 + 4 + 2
proc toHqx(dhc: DecodedHqxContent): Hqx =
let
fnl = fileNameLen dhc
hl = headerLen fnl
headerChunks = dhc.string.chop [1, fnl, 1, 4, 4, 2, 4, 4, 2]
dataLen = toNumber headerChunks[6]
resourceLen = toNumber headerChunks[7]
dataContentIndex = hl ..< hl + datalen
dataCrcIndex = dataContentIndex.b >.. dataContentIndex.b + 2
resourceIndex = dataCrcIndex.b >.. dataCrcIndex.b + resourceLen
resourceCrcIndex = resourceIndex.b >.. resourceIndex.b + 2
result.header.crc = uint16 toNumber headerChunks[8]
result.data.crc = uint16 toNumber dhc.string[dataCrcIndex]
result.resource.crc = uint16 toNumber dhc.string[resourceCrcIndex]
result.header.content.filename = headerChunks[1]
result.data.content = dhc.string[dataContentIndex]
result.resource.content = dhc.string[resourceIndex]
# debugecho "header: ", headerChunks.mapit @it
# debugecho "decoded len: ", dhc.string.len
# debugecho "Data CRC: ", dataCrcIndex, ' ', @(dhc.string[dataCrcIndex]), ' ', result.data.crc
# debugecho "Resource CRC: ", (resourceLen, resourceIndex, resourceCrcIndex)
when isMainModule:
let
(intro, content) = decodeHqxContent readFile "./binhex4-rs/test/hex/a.hqx"
hqx = toHqx content
echo intro
echo hqx
discard existsOrCreateDir "./temp/"
writeFile "./temp" / hqx.header.content.filename, hqx.data.content
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment