Skip to content

Block structure in HexView

Maurits Lamers edited this page Jan 14, 2019 · 1 revision

The following NodeJS script will take an unxored PT session, break it down in blocks, and displays the content as well as the description of a block (if known). In case of nested blocks, it will first display the entire parent block, then increase the indent level and display all the nested blocks.

Example output:


bitcode 0010111100101011
number of top level blocks 108
total amount of blocks 4771
Description:
Block type 0x0100 (1)
Block size 4
Content type 0x8483 (33668) 
84 83 00 00                                                                 ....
Block type 0x0300 (3)
Block size 103
Content type 0x0300 (3) 
03 00 01 0c 00 00 00 50 72 6f 20 54 6f 6f 6c 73 20 4c 45 03 00 00 00 08 00     .......Pro Tools LE......
00 00 00 00 00 00 00 00 00 00 09 00 00 00 38 2e 30 2e 30 66 33 31 34 01     ..............8.0.0f314.
07 00 00 00 52 65 6c 65 61 73 65 00 16 00 00 00 50 72 6f 20 54 6f 6f 6c     ....Release.....Pro Tool
73 20 53 65 73 73 69 6f 6e 20 46 69 6c 65 03 00 05 00 00 00 4d 61 63 4f     s Session File......MacO
53 00 00 00 00 02                                                           S.....
Block type 0x0600 (6)
Block size 267
Content type 0x6720 (8295) session info, path of session
67 20 00 00 00 00 2a 00 00 00 d0 75 d8 d1 8f 54 7b dc 00 00 0a 00 00 00 07     g ....*....u...T{........
00 00 00 49 6e 66 6f 20 23 31 07 00 00 00 49 6e 66 6f 20 23 32 07 00 00     ...Info #1....Info #2...
00 49 6e 66 6f 20 23 33 07 00 00 00 49 6e 66 6f 20 23 34 07 00 00 00 49     .Info #3....Info #4....I
6e 66 6f 20 23 35 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00     nfo #5..................
00 00 00 00 00 00 04 00 00 00 06 00 00 00 53 59 53 54 45 4d 05 00 00 00     ..............SYSTEM....
55 73 65 72 73 04 00 00 00 77 69 6c 6c 04 00 00 00 67 6f 6f 64 12 00 00     Users....will....good...
00 67 6f 6f 64 70 6c 61 79 6c 69 73 74 73 32 2e 70 74 66 00 00 00 00 2a     .goodplaylists2.ptf....*
00 00 00 c9 0c db d1 fd 41 1f 05 05 00 00 00 06 00 00 00 53 59 53 54 45     ........A..........SYSTE
4d 05 00 00 00 55 73 65 72 73 04 00 00 00 77 69 6c 6c 04 00 00 00 67 6f     M....Users....will....go
6f 64 11 00 00 00 67 6f 6f 64 70 6c 61 79 6c 69 73 74 73 2e 70 74 66 00     od....goodplaylists.ptf.
00 00 00 2a 00 00 00 ed ee de d1 cd d0 fb 51 00 00 00 00 00 01 00 00 00     ...*..........Q.........
01 00                                                                       ..
Block type 0x0300 (3)
Block size 344
Content type 0x1925 (9497) 
19 25 01 00 00 00 00 00 00 00 00 00 01 00 00 00 03 00 03 00 00 00 00 00 01     .%.......................
00 00 00 34 00 00 00 00 00 00 2a 00 00 00 ef 75 d8 d1 9b 12 f5 ee 01 00     ...4......*....u........
00 00 00 01 00 00 00 35 00 00 00 00 00 00 2a 00 00 00 39 76 d8 d1 fb d9     .......5......*...9v....
dd 1a 02 00 00 00 00 01 00 00 00 36 00 00 00 00 00 00 2a 00 00 00 66 76     ...........6......*...fv
d8 d1 a2 d2 9c 35 03 00 00 5a 02 00 20 00 00 00 1a 25 00 00 01 00 00 00     .....5...Z.. ....%......
34 00 00 00 00 00 00 2a 00 00 00 ef 75 d8 d1 9b 12 f5 ee 01 00 00 00 00     4......*....u...........
5a 02 00 20 00 00 00 1a 25 00 00 01 00 00 00 35 00 00 00 00 00 00 2a 00     Z.. ....%......5......*.
00 00 39 76 d8 d1 fb d9 dd 1a 02 00 00 00 00 5a 02 00 20 00 00 00 1a 25     ..9v...........Z.. ....%
00 00 01 00 00 00 36 00 00 00 00 00 00 2a 00 00 00 66 76 d8 d1 a2 d2 9c     ......6......*...fv.....
35 03 00 00 00 00 03 00 00 00 5a 02 00 20 00 00 00 1a 25 00 00 01 00 00     5.........Z.. ....%.....
00 34 00 00 00 00 00 00 2a 00 00 00 ef 75 d8 d1 9b 12 f5 ee 01 00 00 00     .4......*....u..........
00 5a 02 00 20 00 00 00 1a 25 00 00 01 00 00 00 35 00 00 00 00 00 00 2a     .Z.. ....%......5......*
00 00 00 39 76 d8 d1 fb d9 dd 1a 02 00 00 00 00 5a 02 00 20 00 00 00 1a     ...9v...........Z.. ....
25 00 00 01 00 00 00 36 00 00 00 00 00 00 2a 00 00 00 66 76 d8 d1 a2 d2     %......6......*...fv....
9c 35 03 00 00 00 00                                                        .5.....
  Block type 0x0200 (2)
  Block size 32
  Content type 0x1a25 (9498) 
  1a 25 00 00 01 00 00 00 34 00 00 00 00 00 00 2a 00 00 00 ef 75 d8 d1 9b 12     .%......4......*....u....
  f5 ee 01 00 00 00 00                                                        .......
  Block type 0x0200 (2)
  Block size 32
  Content type 0x1a25 (9498) 
  1a 25 00 00 01 00 00 00 35 00 00 00 00 00 00 2a 00 00 00 39 76 d8 d1 fb d9     .%......5......*...9v....

Code:

#!/usr/bin/env node

const filename = process.argv[2];

const fs = require('fs');
const path = require('path');

/** @type {Buffer} */
const file = fs.readFileSync(filename);
// gets buffer

// check header
console.log('bitcode', file.toString('ascii',1,17));
if (file[0] !== 0x03 || file.toString('ascii',1,17) !== "0010111100101011") {
  console.log("Not a protools file");
  return
}

const isBigEndian = !!file[17]; // 0 if LE, 1 if BE

function makeHexChar (byte) {
  let b = byte.toString(16);
  return b.length === 1 ? "0" + b : b;
}

function makeHexString (...nums) {
  var s = nums.map(makeHexChar);
  return `0x${s.join("")}`;
}

function readBlockAt (buffer, pos, parentBlock) {
  if (buffer[pos] !== 0x5a) {
    console.log('weird, an unfinished block, breaking at ', pos);
    return;
  }
  const blockType = isBigEndian ? file.readUInt16BE(pos + 1) : file.readUInt16LE(pos + 1);
  const blockSize = isBigEndian ? file.readUInt32BE(pos + 3) : file.readUInt32LE(pos + 3);
  const contentType = isBigEndian ? file.readUInt16BE(pos + 7) : file.readUInt16LE(pos + 7);
  const totalBlockSize = 1 + 2 + 4 + blockSize;

  const blockTypeString = isBigEndian ?
    makeHexString(file.readUInt8(pos + 2), file.readUInt8(pos + 1)) :
    makeHexString(file.readUInt8(pos + 1), file.readUInt8(pos + 2)) ;
  const contentTypeString = isBigEndian ?
    makeHexString(file.readUInt8(pos + 8), file.readUInt8(pos + 7)) :
    makeHexString(file.readUInt8(pos + 7), file.readUInt8(pos + 8));

  const childBlocks = [];
  const blockContent = buffer.slice(pos + 1 + 2 + 4, pos + totalBlockSize);
  const ret = {
    pos,
    blockType, blockSize, totalBlockSize,
    contentType, blockTypeString, contentTypeString,
    blockContent, childBlocks,
    parentBlock
  };
  // now we recursively dive down the block
  let contentPos = buffer.indexOf(0x5a, pos + 1 + 2 + 4);
  while (contentPos !== -1 && contentPos < pos + totalBlockSize) {
    let b = readBlockAt (buffer, contentPos, ret);
    if (b) {
      childBlocks.push(b);
      contentPos = contentPos + b.totalBlockSize;
    }
    else contentPos += 1;
    contentPos = buffer.indexOf(0x5a, contentPos);
  }
  return ret;
}





// 18 and 19 are some bytes of which is unclear what they mean
// then at 20 we get the first 5a, we split everything up in blocks from there
const blocks = [];
let i = 20;
let done = false;
do  {
  let block = readBlockAt(file, i);
  if (!block) {
    break;
  }
  blocks.push(block);
  i += block.totalBlockSize || 1; // one for marker, two for type, 4 for block size int
} while (i < file.length)


var totalBlockAmount = blocks.reduce(function r (prev, next) {
  return prev + 1 + next.childBlocks.reduce(r, 0);
}, 0);

console.log("number of top level blocks", blocks.length);
console.log('total amount of blocks', totalBlockAmount);

const contentTypes = {
  "0x3000": {
    description: "product info, including version",
  },
  "0x0410": {
    description: "audio file list, always block type 2",
  },
  "0x1a27": {
    description: "only occurs in 12, contains 'Markers'",
  },
  "0x2810": {
    description: "session sample rate",
  },
  "0x6720": {
    description: "session info, path of session",
  },
  // "0x1925": ""
  "0x1125": {
    description: "Snaps block"
  }
  // "0x5620":
}



// idea is to create a nested schema
function describeBlock (block, indentLevel) {
  const contentType = contentTypes[block.contentTypeString];
  const description = contentType? contentType.description: "";
  const buffer = block.blockContent;
  const hexData = [];
  let hexString = "";
  const maxWidth = 24;
  let stringData = "";
  let byte;
  const divider = "".padStart(4, " ");
  for (let i = 0; i < buffer.length; i += 1) {
    byte = buffer.readUInt8(i);
    hexString += makeHexChar(byte) + " ";
    stringData += byte >= 32 && byte <= 126 ? String.fromCharCode(byte) : ".";
    // stringData += buffer.toString('ascii', i);
    if (i > 0 && i % maxWidth === 0) {
      hexString += divider + stringData;
      hexData.push(hexString);
      hexString = "";
      stringData = "";
    }
    if (i === buffer.length - 1) {
      hexString = hexString.padEnd(24*3, " ") + divider + stringData;
      hexData.push(hexString);
    }
  }


  const template = [
    `Block type ${block.blockTypeString} (${block.blockType})`,
    `Block size ${block.blockSize}`,
    `Content type ${block.contentTypeString} (${block.contentType}) ${description}`,
  ].concat(hexData);
  const indent = "".padStart(indentLevel*2, " ");
  let ret = template.map(s => indent + s);
  if (block.childBlocks) {
    block.childBlocks.forEach(cb => {
      ret = ret.concat(describeBlock(cb, indentLevel + 1));
    })
  }
  return ret.join("\n");
}

console.log('Description:');
blocks.forEach(b => {
  console.log(describeBlock(b, 0));
});
Clone this wiki locally