GRP File Format (External)

What this is
This is a tutorial which goes over the format of externally saved GRP files (usually files with the extension "PTC" which you save to your SD card). This is NOT a tutorial on how to read or write to a GRP on Petit Computer Knowing the format of this file can help you generate your own GRPs using your computer, or else read in a GRP generated by Petit Computer and do whatever you like with it. Of course, the implementation details are left up to you; this is is simply a guide on the file structure format.

Credit
Yes, I'm going to go over the credits before beginning the tutorial! I'd like to thank Discostew for providing a thorough rundown of the file format as well as his code snippet for iterating through the GRP's rather nasty file structure. Without his information, this guide wouldn't exist. I'd also like to thank Calc84Maniac for helping me generate the MD5 hash for the data section. Thanks to this, you can implement whatever GRP manipulator without too much hassle (like SmileBoom's QR generator not accepting your GRP).

Overview (Taken largely from Discostew)
A GRP PTC file consists of a 48 byte header, followed by a 49152 byte data section (the 256 X 192 image portion). Quoted literally from Discostew, the file bytes are split up as follows (in order):

4 bytes    - "PX01" 4 bytes    - Data section size (for GRPs, it is a constant 49152) 4 bytes    - PTC file type (always 2) 8 bytes    - string - internal name of the GRP as read by Petit Computer 16 bytes   - MD5 hash string (based on Data section) 12 bytes   - "PETC0100RGRP" 49152 bytes - Specially formatted image data

Image Data Details
I'm going to skip the header real quick and go straight into how the data is formatted in the 49152 byte "image data" section, because some parts of the header rely on this part. Remember, the data here is just a bunch of numbers valued 0 to 255. However, instead of the data being stored linearly (or just all the rows of the image placed end-to-end, as most would think), it's stored in blocks. Quoted directly from Discostew:

"It doesn't store them in rows of 256 pixels each from left to right as you would expect a bitmap to be stored. Instead, it's a bit closer to how CHR data  is stored. The entire image is broken up into 8x8 pixel blocks, but it doesn't  end there. There are then 8x8 blocks from left-to-right, top-to-bottom, making  up a 64x64 section. Then, the entire image is made up of these sections in a  4x3 manner (to make 256x192 pixels)."

If that doesn't mean anything to you, Discostew has kindly provided the code which will iterate over the GRP's data in the correct order:

for (int j = 0; j < 3; j++) for (int i = 0; i < 4; i++) for (int y = 0; y < 8; y++) for (int x = 0; x < 8; x++) for (int yy = 0; yy < 8; yy++) for (int xx = 0; xx < 8; xx++) outStream[(j * 16384) + (i * 4096) + (y * 512) + (x * 64) + (yy * 8) + xx] = readStream[(j * 16384) + (i * 64) + (y * 2048) + (x * 8) + (yy * 256) + xx];

In this example, readStream is a linear array of values (such as a text file that was read in), and outStream is the chunk formatted GRP array.

Header details
The first 12 bytes can be represented by the following string in most modern programming languages:

"PX01\f\xC0\0\0\x02\0\0\0"

This first portion of the file will never change, so you can always confidently start with this

The 8 bytes of internal name MUST be exactly 8 bytes, even if the name does not take up that many characters. For instance, if we wanted our GRP to be named (internally) "MYGRP", we'd need to pad the name with NULL values (\0) until it is 8 bytes. In this case, we'd get

"MYGRP\0\0\0".

The next 16 bytes is the MD5 hash string, and I can't really tell you how to implement an MD5 hash generator, because I always just use a built-in function. Many languages give you a function to generate MD5 hash strings, so it's really just a matter of giving the MD5 generator the right input. In this case, the input is as follows (in order):

8 bytes    - "PETITCOM" 49152 bytes - The FORMATTED data section (outStream in the example code above) 12 bytes   - "PETC0100RGRP"

Just pass in the bytes above merged together in that order to the MD5 hash generator, and store the 16 byte MD5 hash after the GRP's internal name.