MKS-TFT are a series of touchscreens used by 3d printers.
The interface icons / pictures can be changed by loading .bin files from an sd-card.
The .bin files contain picture data.
The firmware used by MKS-TFT is partially open source.
Unfortunately, the picture implementation is non-free / proprietary.
In this project I’ll try to reverse engineer the .bin format and implement a simple commandline picture converter.
(1) Reverse engineer the .bin format:
- Procure sample .bin files (note: make sure they are open source / free, so that we don’t violate anything)
- Analyze the .bin file:
- Picture data, raw or compressed? (note: Because MKS-TFT is an embedded system, the pixel data is probably stored in some RAW format.)
- If anything fails, abandon ship and pick a new project.
(2) Implement a simple picture converter from some standard picture format:
- Depending on the .bin format, pick:
- license(s), language
- picture format & library (unless I write my own parser/writer)
- Github page, main and build
- picture read & write
- bin read & write
- commandline parameters
Sample .bin files:
License: Creative Commons - Attribution - Share Alike
Here are some visualizations of the binary files:
(made with binvis.io)
They don’t seem to have a header.
logo.bin is 153600 bytes.
The rest .bin files are 16224 bytes. If the bin files contained compressed data, they would probably have different filesizes.
Another bit of information from: Bin · Issue #10 · majurca/MKS-TFT28-NEW-PICTURES · GitHub
The display has a resolution of 320x240 (76800 pixels)
The buttons have a resolution of 78x204 (8112 pixels)
There is also a mention of 16-bit color.
153600 bytes / 76800 pixels = 2 bytes/pixel
16224 bytes / 8112 pixels = 2 bytes/pixel
(Almost always 2 bytes are 16 bits)
What a coincidence.
Here is my current theory:
The .bin files don’t have a header.
If true, the OS relies on the filename/filesize to determine the resolution.
The picture is stored in a raw format:
RGB in a 555 or 565 bit layout
Or if there is a transparency channel:
It’s also entirely possible that they are using a different subpixel order, like BGR, GRB, … .
If the .bin files contain raw pixel data, I may be able to concatenate a header and get a usable picture.
For this experiment, I’ll be using about.bin and about.svg from the source pictures.
A filetype that supports 16-bit color and RAW is bmp.
To first aquire a bmp header:
I used GIMP to convert about.svg to a bmp.
There are three ways to store 16-bit color in bmp:
So we have 3 headers to try out:
dd if=about.bmp of=header.bin ibs=1 count=138
(note: 138 bytes was the bmp header)
For each of them I did:
cat header.bin about.bin > aboutpic.bmp
The picture is upside down, but we can conclude that the .bin format is RGB565.
After ~700 lines of code (sloccount), It’s still pre-alpha, but It can now read and write bmp565 images:
It can also complain about bad input files and provide some error info.
There is a chance it might partially work in unusual CPUs that have 16-bit bytes.
Very impressive so far. Bravo.
- Added support for commandline parameters
- Made an icon for README
- Various fixes and improvements
It is now in alpha phase.
Over the next months I’ll refactor and test the code, and write documentation.
I’ll test it in various linux distros, non-glibc void, FreeBSD, HaikuOS, OpenIndiana and RedoxOS.
I also want to try out input fuzzing with AFLplusplus.
I’m trying to push the code to it’s limits.
Theoretically speaking, the maximum BMP file size is 2^32-1.
The header takes up at least 66 bytes, leaving up to 4294967229 bytes for the picture data.
Accounting for 2 bytes per pixel, we get 2147483614 pixels.
For a square picture the maximum resolution is 46340 x 46340.
Let’s try to generate such a picture:
The dying plug-in may have messed up GIMP’s internal state.
Allocated 20GB of ram and crashed during export
I guess we have to do it the hard way.
fallocate -l 4294791200 max.bin
./pixmap565 -w 46340 -i max.bin -o max.bmp
The header had bad data caused by two bugs.
After another 1m 45s I had a 4GB bmp picture.
Gnome Image Viewer returns: ‘bogus header data’
Shotwell: ‘Photo source file missing’
¯\_( • - • )_/¯
Krita: ‘The file format cannot be parsed’
GIMP: ‘is not a valid BMP file’
I haven’t found any program that will display it.
Now let’s do the reverse:
./pixmap565 -i max.bmp -o max2.bin
There was a false assert.
2m later, I had a 2nd bin file.
The .bin checksums match.
test the BMP format using
file max.bmp with the extended info options. It sounds like your header creation is not 100%, be aware of “maximum value lengths” as well as byte order. Triple check your header construction against multiple definition sources, BMP is pretty old now, so it should be well defined across many sources.
PS nice project BTW
File returns ‘data’ or ‘GLS_BINARY_LSB_FIRST’, when the file_size field exceeds 2^20.
Bad news everyone:
Integers can have padding bits.
The padding bits may be used for parity checks.
I’m not even sure if the value bits are contiguous.
sizeof returns the entire size (including value and padding bits)
It should now work on big endian.
A list of all the tests I ran:
architecture | os | compiler
x86-64 (vm) | debian, freebsd, haiku, openindiana, void (non-glibc) | gcc
risc-v (vm), mips (vm) | debian | gcc
x86-64 | debian | gcc, clang, pcc, tcc
I created a 100x100 bmp image.
The x/y resolution is stored using signed values.
With a hex editor we can change the positive resolution:
64 00 00 00 (= 100)
to a negative one:
9c ff ff ff ff (= -100)
(assuming that the signed representation system is 1’s complement)
If we do this on the vertical resolution, we will get a working bmp image.
If we do this on the horizontal resolution:
The “file” command reads it correctly:
I don’t think I should prohibit negative resolutions; they are supposed to be signed.