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.
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
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($outputDir, 0777, true);
}
$name = pathinfo($filename, PATHINFO_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
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($outputDir, 0777, true);
}
// BMPs speichern
foreach ($pictures as $index => $pic) {
$bmpData = createBMP($pic['width'], $pic['height'], $pic['data'], $palette);
$name = strtoupper(pathinfo($filename, PATHINFO_FILENAME) . "_FON");
$indexFormatted = str_pad($index, 3, "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);
}
?>