commit 2d6428446e79ea7176dca85fa5306cdcea9df64a Author: Lauri Võsandi Date: Sat Jan 21 09:53:09 2023 +0200 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..abc55b3 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +samples/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..c60f708 --- /dev/null +++ b/README.md @@ -0,0 +1,44 @@ +# Intro + +Mobitec flip-disc displays use MC27C1001 EPROM-s to store the +firmware (?) and fonts. +In the `firmware/` directory you can find two ROM dumps. + +# Useful commands + +To extract font sections: + +``` +dd if=mobitec-1682-r7-bib-01979-r3.bin of=mobitec-1682-r7-bib-01979-r3-fonts.bin skip=64 bs=1024 +dd if=mobitec-01682-r7-bib-01369-r9.bin of=mobitec-01682-r7-bib-01369-r9-fonts.bin skip=64 bs=1024 +``` + +For diffing font sections: + +``` +dhex mobitec-1682-r7-bib-01979-r3-fonts.bin mobitec-01682-r7-bib-01369-r9-fonts.bin +``` + +# Dump structure + +The firmware seems to be located in the first 7KiB and the font data +starts at 0x10000 offset. + +The font beginning marker seem to consist of a character from `a` to `z` +followed by at least 60 null characters. +They don't seem aligned or indexed in any way so easiest is just to scan +for known markers. + +After the beginning marker you can find the lookup table which maps +printable ASCII characters to a glyph address. +The lookup table is 190 bytes (95x 16-bit addresses) which correspond +to ASCII characters 32 up to 126. +Not all fonts include all characters, missing glyphs are represented by address 0. +Note that the glyph address is relative to the beginning of font structure, +beginning marker inclusive. + +At the glyph address first width and height of the glyph are found represented +by two 8-bit integers. The actual bitmap data follows in 8x8 pixel tiles +packed together as 8 bytes. + + diff --git a/extract-fonts.py b/extract-fonts.py new file mode 100644 index 0000000..49d6a27 --- /dev/null +++ b/extract-fonts.py @@ -0,0 +1,122 @@ +import json +import cv2 +import numpy as np + +""" +dd if=mobitec-1682-r7-bib-01979-r3.bin of=mobitec-1682-r7-bib-01979-r3-fonts.bin skip=64 bs=1024 +dd if=mobitec-01682-r7-bib-01369-r9.bin of=mobitec-01682-r7-bib-01369-r9-fonts.bin skip=64 bs=1024 +dhex mobitec-1682-r7-bib-01979-r3-fonts.bin mobitec-01682-r7-bib-01369-r9-fonts.bin +""" + +src = "mobitec-1682-r7-bib-01979-r3" +src = "mobitec-01682-r7-bib-01369-r9" + +with open("firmware/%s.bin" % src, "rb") as fh: + buf = fh.read()[65536:] + + +def scan_fonts(buf): + """ + Font start markers seem to be ascii character a..z followed by at least 60 nulls + """ + font_index = buf[0] + assert font_index == 0x61 + prev = 0 + done = False + while not done: + needle = b"%c" % (font_index + 1) + needle += 50 * b"\x00" + try: + font_offset = buf.index(needle) + except ValueError: + break + font_body = buf[prev:] + done = True + else: + font_body = buf[prev:font_offset] + if done: + break + font_marker_length = len(needle) + font_body = font_body[font_marker_length:] + while font_body.startswith(b"\x00"): + font_body = font_body[1:] + font_marker_length += 1 + yield font_index, font_body, font_marker_length + prev = font_offset + font_index += 1 + + +meta = [] +for font_index, font_body, font_marker_length in scan_fonts(buf): + print() + print("Parsing font %02x" % font_index) + + # Looks like yet another marker + assert font_body[0] == 3 + assert font_body[1] == 1 + + # Look up table consists of 95 16-bit integers + glyph_offsets = np.frombuffer(font_body[2:192], dtype=np.uint16) + prev = 0 + slices = [] + errors = set() + + for index, glyph_offset in enumerate(glyph_offsets): + if glyph_offset != 0: + glyph_offset -= font_marker_length + print("Found %d undecoded bytes between lookup table and first glyph" % len(font_body[192:192 + glyph_offset])) + break + + characters_present = "" + + for index, glyph_offset in enumerate(glyph_offsets): + if glyph_offset == 0: + characters_present += "_" + # No glyph + meta.append(None) + continue + else: + # The index goes from space (32) up to ~ (126) + character = chr(index + 32) + characters_present += "+" + + # Glyphs are relative to the font marker start + glyph_offset -= font_marker_length + + try: + height, width = font_body[glyph_offset], font_body[glyph_offset + 1] + except IndexError: + print("Failed to look up glyph for %s", repr(character)) + else: + jah = np.frombuffer((font_body[glyph_offset + 2:])[::-1], dtype=np.uint8)[::-1] + a = np.unpackbits(jah, axis=0) + try: + img = a[:width * 16].reshape((width, 16, 1)) + img = np.hstack([img[:, 8:, :], img[:, :8, :]]) + meta.append({ + "width": width, + "height": height, + "bitmap": img.tolist() + }) + img = 255 * img + + slices.append(img) + except ValueError: + pass + else: + blank_image = np.zeros((1, 16, 1), np.uint8) + blank_image[::] = 0 + slices.append(blank_image) + + if glyph_offset <= prev: + print("Glyph offsets not ordered") + prev = glyph_offset + print("Glyphs present:", characters_present) + img = np.vstack(slices) + + img = np.flip(np.swapaxes(img, 1, 0), 0) + s = img.shape * np.array(5) + img = cv2.resize(img, dsize=(s[1], s[0]), interpolation=cv2.INTER_NEAREST) + cv2.imwrite("samples/%s-%02x.png" % (src, font_index), img) + with open("samples/%s-%02x.json" % (src, font_index), "w") as fh: + fh.write(json.dumps({"glyphs": meta})) diff --git a/firmware/mobitec-01682-r7-bib-01369-r9.bin b/firmware/mobitec-01682-r7-bib-01369-r9.bin new file mode 100644 index 0000000..3df1d83 Binary files /dev/null and b/firmware/mobitec-01682-r7-bib-01369-r9.bin differ diff --git a/firmware/mobitec-1682-r7-bib-01979-r3.bin b/firmware/mobitec-1682-r7-bib-01979-r3.bin new file mode 100644 index 0000000..12bf494 Binary files /dev/null and b/firmware/mobitec-1682-r7-bib-01979-r3.bin differ