Skip to content

Instantly share code, notes, and snippets.

@ajc2
Last active February 21, 2023 01:02
Show Gist options
  • Save ajc2/25258be3296847bc55cec9e27d13f053 to your computer and use it in GitHub Desktop.
Save ajc2/25258be3296847bc55cec9e27d13f053 to your computer and use it in GitHub Desktop.
PTC QRs and SD File Handling

PTC QRs and SD File Handling

Abstract

PTC uses two systems for import and export of files. SD files are generated when an PTC file is exported to the SD card. They simply consist of a header on top of internal PTC file formats. The intended purpose of SD files is to store metadata and a hash so the file can be validated and converted into a QR set. QR sets are used for distributing and importing PTC files by scanning them with the DSi/3DS camera. The information contained in the SD file wrapper is not stored literally in the QR data stream; it is used for validation and processed into a different format. Because QR codes have an upper bound on data storage, a PTC file has to be split into frames in a specific method; these frames are then converted into QR codes.

Note: All multibyte integers (like sizes) are little-endian. All literal byte values in this document (e.g. 50 54) are written in hexadecimal.

SD File Header

Files exported to SD card consist of a SD file header, and then the contents of the internal PTC file. The SD header consists of:

  • The bytes 50 58 30 31 (“PX01”)
  • Size of file segment (4 bytes)
  • Unknown (4 bytes, typically 0x00000000)
  • Internal filename (8 bytes, ASCII, end-padded with nulls)
  • MD5 hash

The MD5 hash is generated with the string “PETITCOM” added before the file contents.

Structure of PTC QR Sets

A QR set contains a sequence of binary chunks that when scanned, processed, concatenated in order, and decompressed, contains a PTC file (the file format used internally, not a SD file.) The official generator uses Version 20 (97x97) QR codes in binary mode with M level data correction. Any size and correction level should be compatible with PTC, as long as the camera can get a clear enough image to scan them.

The 500 Foot View

An entire QR set is, essentially, a data stream separated into frames. Each QR code’s payload starts with the 36-byte frame header, and then a fixed-size chunk of the data stream follows. The length of the data chunk depends on the QR format used. With the QR format used by PTC’s tools, this chunk is 630 bytes, but any QR format can be used. The data section of the last QR (or the only QR in a set of one) is probably not the exact length you expect (as otherwise it would have to be padded in some way) but this is easy to deal with.

QR Format

All QRs in the set start with a “frame header.” This guarantees the QRs are being scanned in the right order and that they are correctly formatted. It consists of:

  • the bytes 50 54 (“PT”)
  • a byte containing what QR this is in the set (the first is 1, the second is 2 etc.)
  • a byte containing the number of QRs in this sets
  • a MD5 hash of the contents section of this QR (16 bytes)
  • a MD5 hash of the contents of the entire encoded stream. (16 bytes)

Everything that follows is the /n/th slice of the data stream.

Data Stream Format

When all of the QRs are processed and assembled, they produce a data stream with the following format:

  • The filename for the contained PTC file (8 bytes, ASCII, end-padded with nulls)
  • The type of file contained (4 bytes, ASCII)
  • The length of the compressed zlib stream (4 bytes)
  • The length of the data stream when decompressed (4 bytes)
  • A zlib-compressed stream of the file contents.

The four-character type code string can be read from the source file. At the start of a PTC file (or right after the SD header) is a string, such as PETC0300RPRG. The last four bytes of this string (“RPRG”) are the type code to use. As mentioned previously, the filename is contained in the SD header.

Decoding a QR Set

To decode a QR set, use the following process.

Scan the QRs

Use software to scan the QRs into binary files (such as zbar.) Make sure this software supports binary mode and whatever QR format is used (typically as described above.) If you can, make sure you keep track of the order of QR codes.

Validate and Extract the Contents

Make sure the QRs are actually in the right format and have been decoded properly.

  • Check that the first two bytes are always “PT”
  • Check that the fourth byte is the same
  • Check that the third byte is always in 1..count and make sure you have the order right
  • Check that the contents section of each QR code is the same size
  • Check the MD5 hash of each contents section of each QR code
  • Check that the MD5 hash of the total data stream is the same in all codes
  • Extract the remainder of the bytes from the QR code. Again, make sure that the order is correct!

Process the Contents

Once all of the QR codes have been verified and the contents sections are available, you can join them and process the data stream.

  • Join all of the contents segments IN ORDER
  • Read the filename from the start of the contents
  • Read the type code after it
  • Read the length of the compressed file stream
  • Read the length of the decompressed stream
  • Use zlib to decompress the compressed file section of the data stream

— Now, you have successfully decoded a PTC file. The produced file is in the internal PTC format, without SD headers. SD headers are easy to produce if necessary. Afterwards you can validate the PTC format itself using whatever tool you have.

Generating a QR Set

To generate a QR set, use the following process:

Choose a QR Format

Before you do anything, you have to choose a QR format you want to generate. PTC’s official QR generator used the following settings:

  • Version 20 (97x97)
  • Error Correction level: M.

This affects both the size of the QR codes and the amount of data that fits in each. Note that the poor DSi/3DS camera may have issues picking up large sizes or lower levels of error correction. You also must use binary mode.

Read File and Prepare the Stream

Next, you have to read the PTC file and create the data stream that will be stored in the QR codes. Normally you’ll be getting a SD file for this, but the only piece of metadata you actually need is the internal filename. If you have a raw internal PTC file you can still generate a QR data stream, but you’ll have to supply a filename or use a dummy. It’s simple to handle both. When referring to “file” here, assume the SD header has been removed. **The QR data stream doesn’t actually contain an SD header.**

  • Record the length of the uncompressed file, but don’t insert it yet
  • Compress the file contents into a zlib stream, but don’t insert it yet
  • Read the filename and insert it at the start
  • Read the file type code and insert it
  • Insert the length of the compressed zlib stream
  • Insert the length of the uncompressed file
  • Insert the zlib stream.

Split the Data Stream

Now, you’ll have the QR data stream in a large buffer. You have to determine the length of the data segments stored in each QR code, and split them up. This depends on the QR format you chose earlier. For the typical PTC settings, each segment is 630 bytes. For your settings, it’s the number of bytes that QR can store, minus 36 (the length of each QR frame header.) If the total size of the data stream is less than or equal the limit, you don’t have to split anything up. When you do split, the last segment can be less than the maximum segment size. No padding is necessary; QR can handle it fine. The number of chunks is thus `ceil(streamLength / segmentSize)`.

  • Record the MD5 hash of the total data stream, and set it aside
  • For each segment of the data stream, prepend the following:
  • the bytes 50 54 (“PT”)
  • a byte indicating what number this segment is (starting at 1 in order)
  • a byte indicating how many QRs are in the set
  • the MD5 hash of this data segment
  • the MD5 hash of the total data stream.

It may be possible to do this step and the previous at the same time, depending on how you handle the zlib compression step. It wouldn’t matter either way, but it might be more efficient.

Generate the QRs

Using QR generating libraries or software, generate a QR of the desired settings for each data frame you’ve split up. Make sure you keep track of the order when putting them together in a set, so the user can read them without making a mistake. How you stitch them together into a single sheet (or if you do at all) is up to you.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment