I usually define functions that correspond to the types/blocks in the defined file format and use those to read it piece by piece. For this, I created some readDWORD, readWORD, and readGUID functions. The GUID took a bit to figure out, and I'm still not 100% sure it's correct but it makes some sense and matches your example.
<?php
$file = 'test.utx';
$fp = fopen($file, 'rb');
$sig = readDWORD($fp);
if ($sig !== 0x9E2A83C1){
die('Invalid file format');
} else {
echo 'Valid file.' . PHP_EOL;
}
echo 'Version: ' . ($version = readWORD($fp)) . PHP_EOL;
echo 'License mode: ' . readWORD($fp) . PHP_EOL;
echo 'Package flags: ' . readDWORD($fp) . PHP_EOL;
echo 'Name count: ' . readDWORD($fp) . PHP_EOL;
echo 'Name offset: ' . readDWORD($fp) . PHP_EOL;
echo 'Export count: ' . readDWORD($fp) . PHP_EOL;
echo 'Export offset: ' . readDWORD($fp) . PHP_EOL;
echo 'Import count: ' . readDWORD($fp) . PHP_EOL;
echo 'Import offset: ' . readDWORD($fp) . PHP_EOL;
if ($version >= 68){
echo 'GUID: ' . readGUID($fp) . PHP_EOL;
}
function readDWORD($fp) : int{
return read($fp, 4, 'V');
}
function readWORD($fp) : int{
return read($fp, 2, 'v');
}
function readGUID($fp) : string{
$time_low=readDWORD($fp);
$time_mid=readWORD($fp);
$time_high_and_version=readWORD($fp);
$clk_seq_hi_res=read($fp, 1, 'C');
$clk_seq_low=read($fp, 1, 'C');
$node=fread($fp, 6);
return sprintf('%s-%s-%s-%s%s-%s'
, bin2hex(pack('N', $time_low))
, bin2hex(pack('n', $time_mid))
, bin2hex(pack('n', $time_high_and_version))
, bin2hex(pack('C', $clk_seq_hi_res))
, bin2hex(pack('C', $clk_seq_low))
, bin2hex($node)
);
}
function read($fp, int $length, string $code){
$bytes = fread($fp, $length);
$parsed = unpack($code . 'parsed', $bytes);
return $parsed['parsed'];
}
The file specification you linked says the file is encoded in Little Endian. The UUID RFC says the representation is in Big Endian so we read the multi-byte components as little endian then convert them to big endian for display.
output
Valid file.
Version: 127
License mode: 29
Package flags: 33
Name count: 31
Name offset: 72
Export count: 7
Export offset: 753
Import count: 6
Import offset: 711
GUID: e484d857-00b7-4107-a58a-36ff29f6a3a5