GTA 1 File Formats:  FON

Back to main page

Description

The "FON" files are files that contain a sequence of images with a constant height.
They are used for Fonts and the moving parts in the cut scenes.

Format

FON file {
    Header {
        byte numberOfPictures;
        byte heightOfAllPictures;
    }

    Picture[numberOfPictures] {
        byte widthOfPicture;
        byte[heightOfAllPictures*widthOfPicture] rawIndexedPictureData
    }
    
    Palette {
        Color[256] {
            byte red;
            byte green;
            byte blue;
        }
    }
}

PHP Script to extract FON files to a single PNG file

<?php


function convertFON($filename)
{
    
$outputDir dirname($filename).'/';

    
$data file_get_contents($filename);
    if (
$data === false) {
        die(
"Fehler beim Lesen der Datei.");
    }

    
$offset 0;

    
$numberOfPictures ord($data[$offset++]);
    
$height ord($data[$offset++]);

    
$pictures = [];
    
$totalWidth 0;

    
// Bilder lesen
    
for ($i 0$i $numberOfPictures$i++) {
        
$width ord($data[$offset++]);
        
$size $width $height;

        
$pixelData substr($data$offset$size);
        
$offset += $size;

        
$pictures[] = [
            
'width' => $width,
            
'data' => $pixelData
        
];

        
$totalWidth += $width;
    }

    
// Palette lesen
    
$palette = [];
    for (
$i 0$i 256$i++) {
        
$r ord($data[$offset++]);
        
$g ord($data[$offset++]);
        
$b ord($data[$offset++]);

        
$palette[] = [$r$g$b];
    }

    
// === Kombiniertes Bild erstellen ===
    
$combinedData str_repeat("\x00"$totalWidth $height);

    
$currentX 0;

    foreach (
$pictures as $pic) {
        for (
$y 0$y $height$y++) {
            
$srcOffset $y $pic['width'];
            
$dstOffset $y $totalWidth $currentX;

            
$row substr($pic['data'], $srcOffset$pic['width']);

            for (
$x 0$x $pic['width']; $x++) {
                
$combinedData[$dstOffset $x] = $row[$x];
            }
        }

        
$currentX += $pic['width'];
    }

    
// Output-Ordner
    
if (!is_dir($outputDir)) {
        
mkdir($outputDir0777true);
    }

    
$name pathinfo($filenamePATHINFO_FILENAME);

    
// === BMP speichern ===
    //$bmpData = createBMP($totalWidth, $height, $combinedData, $palette);
    //file_put_contents("$outputDir/{$name}_FON.bmp", $bmpData);

    // === PNG speichern === (via GD)
    
savePNG("$outputDir/{$name}_FON.png"$totalWidth$height$combinedData$palette);

    echo 
"Done: {$name}_FON.png\n";
}


function 
createBMP($width$height$pixelData$palette)
{
    
$rowSize = ($width 3) & ~3// auf 4 Byte ausrichten
    
$pixelArraySize $rowSize $height;
    
$paletteSize 256 4;
    
$fileSize 14 40 $paletteSize $pixelArraySize;

    
$bmp '';

    
// === BMP HEADER ===
    
$bmp .= "BM"// Signature
    
$bmp .= pack('V'$fileSize);
    
$bmp .= pack('v'0);
    
$bmp .= pack('v'0);
    
$bmp .= pack('V'14 40 $paletteSize);

    
// === DIB HEADER ===
    
$bmp .= pack('V'40); // Header size
    
$bmp .= pack('V'$width);
    
$bmp .= pack('V'$height);
    
$bmp .= pack('v'1); // planes
    
$bmp .= pack('v'8); // bpp
    
$bmp .= pack('V'0); // compression
    
$bmp .= pack('V'$pixelArraySize);
    
$bmp .= pack('V'2835); // X ppm
    
$bmp .= pack('V'2835); // Y ppm
    
$bmp .= pack('V'256); // colors used
    
$bmp .= pack('V'0);

    
// Palette (BGR0)
    
foreach ($palette as [$r$g$b]) {
        
$bmp .= chr($b) . chr($g) . chr($r) . chr(0);
    }

    
// Pixel (bottom-up)
    
for ($y $height 1$y >= 0$y--) {
        
$row substr($pixelData$y $width$width);
        
$bmp .= $row;

        
$padding $rowSize $width;
        if (
$padding 0) {
            
$bmp .= str_repeat("\x00"$padding);
        }
    }

    return 
$bmp;
}


function 
savePNG($filename$width$height$pixelData$palette)
{
    
$img imagecreatetruecolor($width$height);

    
// Palette in GD konvertieren
    
$gdPalette = [];
    foreach (
$palette as $i => [$r$g$b]) {
        
$gdPalette[$i] = imagecolorallocate($img$r$g$b);
    }

    
// Pixel setzen
    
for ($y 0$y $height$y++) {
        for (
$x 0$x $width$x++) {
            
$index ord($pixelData[$y $width $x]);
            
imagesetpixel($img$x$y$gdPalette[$index]);
        }
    }

    
imagepng($img$filename);
    
imagedestroy($img);
}


// === AUFRUF ===
$files glob("D:\\GTADATA\\*");
foreach (
preg_grep('/\.fon$/i'$files) as $fon_file) {
    
convertFON($fon_file);
}

PHP Script to extract FON files to BMP file series

<?php

function readFON($filename$outputDir)
{
    
$data file_get_contents($filename);
    if (
$data === false) {
        die(
"Error reading file: $filename.");
    }

    
$offset 0;

    
// Header lesen
    
$numberOfPictures ord($data[$offset++]);
    
$height ord($data[$offset++]);

    echo 
"Pictures: $numberOfPictures\n";
    echo 
"Height: $height\n";

    
$pictures = [];

    
// Bilder lesen
    
for ($i 0$i $numberOfPictures$i++) {
        
$width ord($data[$offset++]);
        
$size $width $height;

        
$pixelData substr($data$offset$size);
        
$offset += $size;

        
$pictures[] = [
            
'width' => $width,
            
'height' => $height,
            
'data' => $pixelData
        
];
    }

    
// Palette lesen
    
$palette = [];
    for (
$i 0$i 256$i++) {
        
$r ord($data[$offset++]);
        
$g ord($data[$offset++]);
        
$b ord($data[$offset++]);

        
$palette[] = [$r$g$b];
    }

    
// Output-Ordner erstellen
    
if (!is_dir($outputDir)) {
        
mkdir($outputDir0777true);
    }

    
// BMPs speichern
    
foreach ($pictures as $index => $pic) {
        
$bmpData createBMP($pic['width'], $pic['height'], $pic['data'], $palette);
        
$name strtoupper(pathinfo($filenamePATHINFO_FILENAME) . "_FON");
        
$indexFormatted str_pad($index3"0"STR_PAD_LEFT);
        
file_put_contents("$outputDir/{$name}_{$indexFormatted}.bmp"$bmpData);
    }

    echo 
"Finished!\n";
}

function 
createBMP($width$height$pixelData$palette)
{
    
$rowSize = ($width 3) & ~3// auf 4 Byte ausrichten
    
$pixelArraySize $rowSize $height;
    
$paletteSize 256 4;
    
$fileSize 14 40 $paletteSize $pixelArraySize;

    
$bmp '';

    
// === BMP HEADER ===
    
$bmp .= "BM"// Signature
    
$bmp .= pack('V'$fileSize);
    
$bmp .= pack('v'0);
    
$bmp .= pack('v'0);
    
$bmp .= pack('V'14 40 $paletteSize);

    
// === DIB HEADER ===
    
$bmp .= pack('V'40); // Header size
    
$bmp .= pack('V'$width);
    
$bmp .= pack('V'$height);
    
$bmp .= pack('v'1); // planes
    
$bmp .= pack('v'8); // bpp
    
$bmp .= pack('V'0); // compression
    
$bmp .= pack('V'$pixelArraySize);
    
$bmp .= pack('V'2835); // X ppm
    
$bmp .= pack('V'2835); // Y ppm
    
$bmp .= pack('V'256); // colors used
    
$bmp .= pack('V'0);

    
// === PALETTE === (BGR0)
    
foreach ($palette as [$r$g$b]) {
        
$bmp .= chr($b) . chr($g) . chr($r) . chr(0);
    }

    
// === PIXELS === (BMP ist bottom-up!)
    
for ($y $height 1$y >= 0$y--) {
        
$row substr($pixelData$y $width$width);
        
$bmp .= $row;

        
// Padding
        
$padding $rowSize $width;
        if (
$padding 0) {
            
$bmp .= str_repeat("\x00"$padding);
        }
    }

    return 
$bmp;
}


// === AUFRUF ===
$files glob("D:\\GTADATA\\*");
foreach (
preg_grep('/\.fon$/i'$files) as $fon_file) {
    echo 
"File: $fon_file\n";
    
$dirname dirname($fon_file).'\\_EXTRACT\\';
    if (!
is_dir($dirname)) mkdir($dirname);
    
readFON($fon_file$dirname);
}

?>