GTA 1 File Formats:  SDT + (Audio) RAW

Back to main page

Description

RAW file in the AUDIO directory (and ONLY in the AUDIO directory) contain raw sound data.
SDT files contain the offsets, length, and bitrate of the individual sounds.

The SDT file does NOT contain all information that is required to convert to WAV.
The number of channels (1=mono, 2=stereo) as well as the bits per sample (8, 16) must be provided from outside (i.e. hardcoded in the EXE file), and is NOT part of the SDT file.

The values are:

Known files and their parameters

Format

Sdt {
    Sounds[var] {
        DWORD startOffset;
        DWORD length;
        DWORD bitrate
    }
}

PHP Script to decode files

<?php

$sdtFile 
= 'D:\\GTADATA\\UK\\AUDIO\\VOCALCOM.SDT';
$rawFile = 'D:\\GTADATA\\UK\\AUDIO\\VOCALCOM.RAW';
$outDir  = 'C:\\Temp\\';

if (!
file_exists($sdtFile) || !file_exists($rawFile)) {
    die(
"SDT or RAW file not found.\n");
}

if (!
is_dir($outDir)) {
    
mkdir($outDir, 0777, true);
}

$sdtData = file_get_contents($sdtFile);
$rawData = file_get_contents($rawFile);

$entrySize = 12;
$totalEntries = intdiv(strlen($sdtData), $entrySize);

echo 
"Found entries: $totalEntries\n";

$baseName = pathinfo($sdtFile, PATHINFO_FILENAME);

for (
$i = 0; $i < $totalEntries; $i++) {
    
$entry = substr($sdtData, $i * $entrySize, $entrySize);

    
// Little Endian 3x uint32
    
$data = unpack("Voffset/Vlength/Vsamplerate", $entry);

    
$offset = $data['offset'];
    
$length = $data['length'];
    
$rate   = $data['samplerate'];

    if (
$length <= 0) continue;

    
$audioData = substr($rawData, $offset, $length);

    if (
strlen($audioData) !== $length) {
        echo 
"Warnung: Invalid length at entry $i\n";
        continue;
    }

    if (
stripos($sdtFile,'LEVEL000') !== false) {
        
$numChannels = $i<3 ? 2 : 1;
        
$bitsPerSample = 16;
    } else {
        
$numChannels = 1;
        
$bitsPerSample = 8;
    }

    
$wavData = createWav($audioData, $rate, $numChannels, $bitsPerSample);

    
$index = str_pad($i + 1, 3, "0", STR_PAD_LEFT);
    
$outFile = "$outDir/{$baseName}-$index.wav";

    
file_put_contents($outFile, $wavData);

    echo 
"Exportiert: $outFile (Rate: $rate Hz)\n";
}

function 
createWav($audioData, $sampleRate, $numChannels, $bitsPerSample)
{
    
$header  = "RIFF"; // ChunkID
    
$header .= pack("V", 36 + strlen($audioData)); // ChunkSize
    
$header .= "WAVE"; // Format
    
$header .= "fmt "; // Subchunk1ID
    
$header .= pack("V", 16); // Subchunk1Size
    
$header .= pack("v", 1); // AudioFormat 1=PCM
    
$header .= pack("v", $numChannels); // NumChannels
    
$header .= pack("V", $sampleRate); // SampleRate
    
$header .= pack("V", $sampleRate * $numChannels * ($bitsPerSample / 8)); // ByteRate
    
$header .= pack("v", $numChannels * ($bitsPerSample / 8)); // BlockAlign
    
$header .= pack("v", $bitsPerSample); // BitsPerSample
    // Space for extra parameters (if PCM, then doesn't exist):
    //$header .= pack("v", strlen($ExtraParams)); // ExtraParamSize
    //$header .= $ExtraParams; // ExtraParams
    
$header .= "data"; // Subchunk2ID
    
$header .= pack("V", strlen($audioData)); // Subchunk2Size
    
return $header . $audioData; // Data
}