Last active
April 12, 2024 18:42
-
-
Save hamidb80/e911acb671c96840a754f22876c84bdb to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
## 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