Difference between revisions of "SFX (SA)"

From GTAMods Wiki
Jump to navigation Jump to search
m
(Undo previous addition but leave grammar changes)
 
(17 intermediate revisions by 2 users not shown)
Line 1: Line 1:
 
{{Research}}
 
{{Research}}
 +
San Andreas uses a hierarchical system for managing sound effects, as opposed to the flat arrays of effects used in [[SFX|previous titles]]. Instead of individual sounds, SA has ''packages'' which group sounds based on where they are used in the game, and these sounds are further separated into ''banks'' based on what they are used for, with each bank containing up to 400 sounds.
  
San Andreas introduces a different method of dealing with SFX compared to it's [[SFX|antecessor]], now not only sound indices are available but packages and banks.
+
The bank/package system allows the game to only load the sound(s) needed at the moment – in some instances, the game only loads a single sound from a bank (though it is more common for entire banks to be loaded at a time).
  
Packages are a way to organize the SFX data in different files and banks are a way to organize SFX data in one package. Each bank contains up to 200 sounds. Normally the entire bank is loaded at once, but depending on some circumstances the game might load only a single sound from the bank.
+
== File formats ==
 +
<code>.dat</code> files are SFX config files. They describe the layout of the audio effects system in terms of packages, banks and individual sounds. They are stored in the <code>audio/CONFIG</code> directory. All <code>.dat</code> files have names with 8 characters (minus the extension).
  
== File Formats ==
+
=== PakFiles.dat ===
 +
<code>PakFiles.dat</code> ("Package Files") is a list of package names. Each name takes up 52 bytes.
 +
 
 +
<syntaxhighlight lang="go">
 +
// package count = file size / 52
 +
type PackageList struct {
 +
Packages []string
 +
}
 +
</syntaxhighlight>
 +
 
 +
The package name is always null-terminated, and in the stock files the name is padded up to 12 bytes with <code>0xCD</code> values. The rest of the 52 bytes are zeros. The game works in exactly the same way without the <code>0xCD</code> bytes, but their presence may indicate that <code>PakFiles.dat</code> is actually a dumped array of 52-byte structures rather than an array of strings.
 +
 
 +
The package names are the same as their file names inside the <code>audio/SFX</code> directory. In other files and also in the game, each package is referred to by its 0-based index in the package list (0, 1, 2, etc.).
 +
 
 +
=== BankLkup.dat ===
 +
<code>BankLkup.dat</code> ("Bank Lookup") contains an array of metadata structures for all banks in all packages.
 +
 
 +
<syntaxhighlight lang="go">
 +
// 12 bytes
 +
type BankMeta struct {
 +
// Index of package in PakFiles.dat
 +
PackageIndex uint8
 +
Padding      [3]uint8
 +
 
 +
// Bank header location in package file.
 +
BankHeaderOffset uint32
 +
 
 +
// Total size of sounds in bank.
 +
BankSize uint32
 +
}
 +
</syntaxhighlight>
 +
 
 +
Note that the game uses hardcoded indices to reference specific banks, so changes in order can lead to problems.
  
The SFX config formats are pure dumps of game structures, and so contains a lot of irrelevant data. They are stored in the <code>audio/CONFIG</code> directory and have a <code>.dat</code> extension.
+
The <code>BankSize</code> field must be updated if the size of the bank changes, especially if the size increases, because the game works out the length of each sound the same way as the <code>calculateBufferSize</code> function displayed below. If <code>BankSize</code> is too big, the game will read too far on the last sound, which often causes a segmentation fault.
  
=== PakFiles.dat ===
+
=== Packages ===
 +
Package files contain banks, which contain sound data (including the raw PCM data). Packages are in the <code>audio/SFX</code> directory and have no file extension.
 +
 
 +
An unmodified game has the following packages:
 +
* <code>FEET</code>
 +
* <code>GENRL</code>
 +
* <code>PAIN_A</code>
 +
* <code>SCRIPT</code>
 +
* <code>SPC_EA</code>
 +
* <code>SPC_FA</code>
 +
* <code>SPC_GA</code>
 +
* <code>SPC_NA</code>
 +
* <code>SPC_PA</code>
 +
 
 +
Each bank stored in a package file has two parts: the header and the PCM buffers. The PCM buffers follow the header.
  
This file is used to ''enumerate'' [[#SFX Packages|SFX Packages]], it's structure is just a list of file names.
+
==== Sound structure ====
 +
Most of the bank header is made up of sound metadata, which has the following structure:
 +
<syntaxhighlight lang="go">
 +
type SoundMeta struct {
 +
// Offset of the PCM buffer from the end of the bank header.
 +
BufferOffset uint32
  
repeat (any amount) times:
+
// Where the start of the loop is (in samples).
    char[52]    File Name (relative to the <code>audio/SFX</code> directory)
+
LoopOffset int32
  
The first named package has the index 0, the second named package the index 1 and so on.
+
// Sample rate (measured in Hz).
 +
SampleRate uint16
  
=== BankLkup.dat ===
+
// Audio headroom. Defines how much louder than average
 +
//  this sound effect is expected to go.
 +
Headroom int16
 +
}
 +
</syntaxhighlight>
 +
The <code>Headroom</code> value tells the game how loud the sound will be. This allows the game to adjust what volume to play the sound at in order to prevent the audio clipping. A value of 0 tells the game that the sound is about average volume, with negative values meaning the sound is quieter and positive values meaning it's louder.
  
This file is used to ''enumerate'' global bank numbers with it's respective SFX Package.
+
<code>LoopOffset</code> is the position of the start of the looped part of the sound, measured in samples. This value is used to give looped sounds an "introduction" – for example, the sound of a car's engine starting up should have an unlooped ignition sound, but the sound of the engine running after starting should be looped. If <code>LoopOffset</code> is <code>-1</code>, the sound does not loop.
  
repeat (any amount) times:
+
==== Bank structure ====
    uint8_t    SFX Package index (as specified in [[#PakFiles.dat|PakFiles.dat]])
+
The actual bank header structure is as follows:
    uint8_t[3]  Unused      (padding)
+
<syntaxhighlight lang="go">
    uint32_t    Bank offset (where this bank header starts on the package file)
+
// 4084 bytes (including SoundMeta array)
    uint32_t    Bank size   (the size of this entire bank without the header)
+
type BankHeader struct {
 +
// The number of sounds in the bank. Must be <= 400.
 +
NumSounds uint16
 +
Padding   uint16
  
The game uses global indices to lookup for banks internally, so changing the order of the enumeration will cause problems.
+
// Sound metadata.
 +
Sounds [400]SoundMeta
 +
}
 +
</syntaxhighlight>
 +
The <code>BankHeader</code> structure's location is specified by the <code>BankHeaderOffset</code> value of the bank's <code>BankMeta</code> structure. The maximum number of sounds in each bank is 400.
  
=== SFX Packages ===
+
The audio sample buffers are all in ''signed 16-bit mono PCM'' format. This is the same format as in previous titles, with the only difference between SA and GTA 2/III/VC's sound systems being that SA stores just the PCM data along with a <code>SoundMeta</code> structure rather than storing a full WAV file. The <code>SoundMeta</code> structure contains enough data to construct <code>.wav</code> files but at less of a storage cost than the actual WAV header format, allowing more sounds to be used in the game.
  
Those are the files responsible for storing all the raw sound data. They are at the <code>audio/SFX</code> directory with no extension.
+
The offset of a sound's PCM buffer can be calculated with the following function:
 +
<syntaxhighlight lang="go">
 +
func (sound *SoundMeta) getPCMOffset(bankInfo *BankMeta) uint32 {
 +
// BankHeaderOffset is the offset of the start of the header,
 +
//  so the end of the header is 4804 bytes after.
 +
return bankInfo.BankHeaderOffset + 4804 + sound.BufferOffset
 +
}
 +
</syntaxhighlight>
  
The file structure is basically many bank headers (at ''any offset'') followed by it's sound buffers. The offset where each bank header is located is specified by the file [[#BankLkup.dat|BankLkup.dat]].
+
Since the <code>SoundMeta</code> structure does not specify the size of the buffer, it must be calculated.
 +
In the following code, <code>bankAudioSize</code> is <code>BankMeta.BankSize</code> for the current bank.
  
uint16_t        Number of sounds in this bank (maximum of 200)
+
<syntaxhighlight lang="go">
uint16_t        Unused (padding)
+
func calculateBufferSize(bankHeader *BankHeader, bankAudioSize uint32, soundIndex int) uint32 {
repeat (400) times:
+
sound := bankHeader.Sounds[soundIndex]
    uint32_t    Sound buffer offset (relative to the end of this header)
 
    uint32_t    Loop offset (in samples)
 
    uint16_t    Sample rate
 
    uint16_t    Sound headroom
 
  
Following this header are the buffer for the sounds in this bank. Each buffer should be in raw ''Mono PCM 16-bits'' format.
+
var length uint32
 +
if soundIndex >= int(bankHeader.NumSounds-1) {
 +
// This is the final sound in the bank, so we need to use the size of the bank
 +
//  to calculate the length of the sound's buffer.
 +
length = bankAudioSize - sound.BufferOffset
 +
} else {
 +
// length = this sound offset - next sound offset
 +
// This works because the buffers are stored contiguously in the file.
 +
length = bankHeader.Sounds[soundIndex+1].BufferOffset - sound.BufferOffset
 +
}
  
Even though there is space in the header for 400 sounds, only 200 are allowed.
+
return length
 +
}
 +
</syntaxhighlight>
  
 
=== BankSlot.dat ===
 
=== BankSlot.dat ===
 
 
A bank slot is capable of storing some SFX data, this data can be either one entire bank or one single sound from a bank. There is a total of 45 bank slots.
 
A bank slot is capable of storing some SFX data, this data can be either one entire bank or one single sound from a bank. There is a total of 45 bank slots.
  
 
Each bank slot is used by a specific audio class of the game, for example 10 slots are used for vehicle banks, one slot for the bank of explosions, 4 slots for script speeches (which stores single sounds), and so on.
 
Each bank slot is used by a specific audio class of the game, for example 10 slots are used for vehicle banks, one slot for the bank of explosions, 4 slots for script speeches (which stores single sounds), and so on.
  
uint16_t    Number of Slots (''must be 45'')
+
The <code>BankSlot.dat</code> file's structure is
repeat (Number of Slots) times
+
 
    uint32_t    Sum of all buffer size before this slot (ignored)
+
<syntaxhighlight lang="go">
    uint32_t    Buffer size for this slot
+
type SlotFile struct {
    uint32_t    Unknown (related to feet sounds)
+
// Always 45
    uint32_t    Unknown (related to feet sounds)
+
NumSlots uint16
    byte[4804]  Ignored
+
Slots   [45]Slot
 +
}
 +
</syntaxhighlight>
 +
 
 +
where <code>Slot</code> is
 +
 
 +
<syntaxhighlight lang="go">
 +
type Slot struct {
 +
// Sum of all buffer sizes before this slot (i.e. the offset).
 +
BufferOffset uint32
 +
 
 +
// Buffer size for this slot.
 +
BufferSize uint32
  
Each slot owns a buffer capable of storing some amount of sound data, if this size is lower than the size of the data stored in the slot, sound artifacts will occur in-game.
+
// {-1, -1} on disk. Related to feet sounds?
 +
Unknown [2]int32
 +
 +
Ignored [4804]byte
 +
}
 +
</syntaxhighlight>
 +
 
 +
Each slot owns a buffer capable of storing some amount of sound data. If this size is lower than the size of the data stored in the slot, sound artifacts will occur in-game.
  
 
By default, those buffer sizes are arbitrary values, but it can safely be the size of the highest bank stored in the slot or the size of the highest sound stored in the slot, depending if the slot is used to store a sound or a bank.
 
By default, those buffer sizes are arbitrary values, but it can safely be the size of the highest bank stored in the slot or the size of the highest sound stored in the slot, depending if the slot is used to store a sound or a bank.
 +
 +
==Mobile==
 +
The mobile version of GTA: San Andreas replaces the previous raw sound storage system with <code>.osw</code> and <code>.osw.idx</code> files. An OSW file is actually an uncompressed ZIP file, and can be opened with most ZIP viewers/extractors. The <code>.osw.idx</code> file stores a table of contents for the entries in the OSW file with the same name.
 +
 +
All of the game's sounds are present inside the OSW files in MP3 format. This compression allows all of the OSW files containing sound effects to take up just 731MB on the mobile version, whereas the combined size of the SFX files in the PC version is 2.3GB. It may be worth noting that the OSW files themselves do not contribute to this decreased size because they are not compressed.
 +
 +
The index files begin with the number of entries:
 +
 +
<syntaxhighlight lang="c++">
 +
uint32_t entryCount;
 +
</syntaxhighlight>
 +
 +
The rest of the file is made up of the same repeated structure:
 +
<syntaxhighlight lang="c++">
 +
struct IndexEntry {
 +
    // Absolute offsets from the beginning of the .osw file.
 +
    uint32_t oswOffset;
 +
    uint32_t oswSize;
 +
 +
    // File name length is dynamic.
 +
    uint16_t nameLength;
 +
    char name[nameLength];
 +
};
 +
</syntaxhighlight>
 +
 +
However, as the length of the file name is dynamic, the structure cannot be read from a buffer directly and code similar to the following must instead be used to read an entry structure:
 +
<syntaxhighlight lang="c++">
 +
struct IndexEntry {
 +
    uint32_t oswOffset;
 +
    uint32_t oswSize;
 +
 +
    uint16_t nameLength;
 +
    std::string name;
 +
 +
    explicit IndexEntry(char *&buf) {
 +
        oswOffset = *(uint32_t *)buf;
 +
        buf += 4;
 +
 +
        oswSize = *(uint32_t *)buf;
 +
        buf += 4;
 +
 +
        nameLength = *(uint16_t *)buf;
 +
        buf += 2;
 +
 +
        name = std::string(buf, buf + nameLength);
 +
        buf += nameLength;
 +
    }
 +
};
 +
</syntaxhighlight>
 +
 +
The <code>BankLkup.dat</code>, <code>BankSlot.dat</code> and <code>PakFiles.dat</code> files are present on mobile and are identical to the PC versions.
  
 
==Scripting==
 
==Scripting==
 
 
Sounds can be used in [[SCM]] with the following [[opcode]]s:
 
Sounds can be used in [[SCM]] with the following [[opcode]]s:
  
Line 76: Line 225:
 
* [[03D1]] &ndash; Plays the loaded sound
 
* [[03D1]] &ndash; Plays the loaded sound
 
* [[03D2]] &ndash; Checks if the loaded sound has finished
 
* [[03D2]] &ndash; Checks if the loaded sound has finished
* [[097A]] &ndash; Plays a audio event
+
* [[097A]] &ndash; Plays an audio event
* [[097B]] &ndash; Plays a audio event in an object
+
* [[097B]] &ndash; Plays an audio event in an object
* [[09F1]] &ndash; Plays a audio event in a character
+
* [[09F1]] &ndash; Plays an audio event in a character
* [[09F7]] &ndash; Plays a audio event in a vehicle
+
* [[09F7]] &ndash; Plays an audio event in a vehicle
 
* [[09D6]] &ndash; Makes a character say a scripted speech
 
* [[09D6]] &ndash; Makes a character say a scripted speech
  
Line 91: Line 240:
  
 
==External links==
 
==External links==
 
+
* [http://web.archive.org/web/20151026050556/http://pdescobar.home.comcast.net/~pdescobar/gta/saat/sfx_dir.html GTA:SA SFX Directory]
* [http://www.zazmahall.de/ZAZGTASANATORIUM/GTASA_SFX_Directory.htm GTA:SA SFX Directory]
 
 
* [http://pastebin.com/zBrdUpAW Default contents of PakFiles.dat]
 
* [http://pastebin.com/zBrdUpAW Default contents of PakFiles.dat]
 
* [http://pastebin.com/drKr6YCP Default contents of BankLkup.dat]
 
* [http://pastebin.com/drKr6YCP Default contents of BankLkup.dat]
 
* [http://pastebin.com/nM6Ztem1 Default contents of BankSlot.dat]
 
* [http://pastebin.com/nM6Ztem1 Default contents of BankSlot.dat]
 
  
 
{{N|SA}}
 
{{N|SA}}
 
[[Category:File Formats]][[Category:Audio Formats]]
 
[[Category:File Formats]][[Category:Audio Formats]]
 
[[Category:GTA SA]]
 
[[Category:GTA SA]]

Latest revision as of 13:53, 2 October 2021

San Andreas uses a hierarchical system for managing sound effects, as opposed to the flat arrays of effects used in previous titles. Instead of individual sounds, SA has packages which group sounds based on where they are used in the game, and these sounds are further separated into banks based on what they are used for, with each bank containing up to 400 sounds.

The bank/package system allows the game to only load the sound(s) needed at the moment – in some instances, the game only loads a single sound from a bank (though it is more common for entire banks to be loaded at a time).

File formats

.dat files are SFX config files. They describe the layout of the audio effects system in terms of packages, banks and individual sounds. They are stored in the audio/CONFIG directory. All .dat files have names with 8 characters (minus the extension).

PakFiles.dat

PakFiles.dat ("Package Files") is a list of package names. Each name takes up 52 bytes.

// package count = file size / 52
type PackageList struct {
	Packages []string
}

The package name is always null-terminated, and in the stock files the name is padded up to 12 bytes with 0xCD values. The rest of the 52 bytes are zeros. The game works in exactly the same way without the 0xCD bytes, but their presence may indicate that PakFiles.dat is actually a dumped array of 52-byte structures rather than an array of strings.

The package names are the same as their file names inside the audio/SFX directory. In other files and also in the game, each package is referred to by its 0-based index in the package list (0, 1, 2, etc.).

BankLkup.dat

BankLkup.dat ("Bank Lookup") contains an array of metadata structures for all banks in all packages.

// 12 bytes
type BankMeta struct {
	// Index of package in PakFiles.dat
	PackageIndex uint8
	Padding      [3]uint8

	// Bank header location in package file.
	BankHeaderOffset uint32

	// Total size of sounds in bank.
	BankSize uint32
}

Note that the game uses hardcoded indices to reference specific banks, so changes in order can lead to problems.

The BankSize field must be updated if the size of the bank changes, especially if the size increases, because the game works out the length of each sound the same way as the calculateBufferSize function displayed below. If BankSize is too big, the game will read too far on the last sound, which often causes a segmentation fault.

Packages

Package files contain banks, which contain sound data (including the raw PCM data). Packages are in the audio/SFX directory and have no file extension.

An unmodified game has the following packages:

  • FEET
  • GENRL
  • PAIN_A
  • SCRIPT
  • SPC_EA
  • SPC_FA
  • SPC_GA
  • SPC_NA
  • SPC_PA

Each bank stored in a package file has two parts: the header and the PCM buffers. The PCM buffers follow the header.

Sound structure

Most of the bank header is made up of sound metadata, which has the following structure:

type SoundMeta struct {
	// Offset of the PCM buffer from the end of the bank header.
	BufferOffset uint32

	// Where the start of the loop is (in samples).
	LoopOffset int32

	// Sample rate (measured in Hz).
	SampleRate uint16

	// Audio headroom. Defines how much louder than average
	//  this sound effect is expected to go.
	Headroom int16
}

The Headroom value tells the game how loud the sound will be. This allows the game to adjust what volume to play the sound at in order to prevent the audio clipping. A value of 0 tells the game that the sound is about average volume, with negative values meaning the sound is quieter and positive values meaning it's louder.

LoopOffset is the position of the start of the looped part of the sound, measured in samples. This value is used to give looped sounds an "introduction" – for example, the sound of a car's engine starting up should have an unlooped ignition sound, but the sound of the engine running after starting should be looped. If LoopOffset is -1, the sound does not loop.

Bank structure

The actual bank header structure is as follows:

// 4084 bytes (including SoundMeta array)
type BankHeader struct {
	// The number of sounds in the bank. Must be <= 400.
	NumSounds uint16
	Padding   uint16

	// Sound metadata.
	Sounds [400]SoundMeta
}

The BankHeader structure's location is specified by the BankHeaderOffset value of the bank's BankMeta structure. The maximum number of sounds in each bank is 400.

The audio sample buffers are all in signed 16-bit mono PCM format. This is the same format as in previous titles, with the only difference between SA and GTA 2/III/VC's sound systems being that SA stores just the PCM data along with a SoundMeta structure rather than storing a full WAV file. The SoundMeta structure contains enough data to construct .wav files but at less of a storage cost than the actual WAV header format, allowing more sounds to be used in the game.

The offset of a sound's PCM buffer can be calculated with the following function:

func (sound *SoundMeta) getPCMOffset(bankInfo *BankMeta) uint32 {
	// BankHeaderOffset is the offset of the start of the header,
	//  so the end of the header is 4804 bytes after.
	return bankInfo.BankHeaderOffset + 4804 + sound.BufferOffset
}

Since the SoundMeta structure does not specify the size of the buffer, it must be calculated. In the following code, bankAudioSize is BankMeta.BankSize for the current bank.

func calculateBufferSize(bankHeader *BankHeader, bankAudioSize uint32, soundIndex int) uint32 {
	sound := bankHeader.Sounds[soundIndex]

	var length uint32
	if soundIndex >= int(bankHeader.NumSounds-1) {
		// This is the final sound in the bank, so we need to use the size of the bank
		//  to calculate the length of the sound's buffer.
		length = bankAudioSize - sound.BufferOffset
	} else {
		// length = this sound offset - next sound offset
		// This works because the buffers are stored contiguously in the file.
		length = bankHeader.Sounds[soundIndex+1].BufferOffset - sound.BufferOffset
	}

	return length
}

BankSlot.dat

A bank slot is capable of storing some SFX data, this data can be either one entire bank or one single sound from a bank. There is a total of 45 bank slots.

Each bank slot is used by a specific audio class of the game, for example 10 slots are used for vehicle banks, one slot for the bank of explosions, 4 slots for script speeches (which stores single sounds), and so on.

The BankSlot.dat file's structure is

type SlotFile struct {
	// Always 45
	NumSlots uint16
	Slots    [45]Slot
}

where Slot is

type Slot struct {
	// Sum of all buffer sizes before this slot (i.e. the offset).
	BufferOffset uint32

	// Buffer size for this slot.
	BufferSize uint32

	// {-1, -1} on disk. Related to feet sounds?
	Unknown [2]int32
	
	Ignored [4804]byte
}

Each slot owns a buffer capable of storing some amount of sound data. If this size is lower than the size of the data stored in the slot, sound artifacts will occur in-game.

By default, those buffer sizes are arbitrary values, but it can safely be the size of the highest bank stored in the slot or the size of the highest sound stored in the slot, depending if the slot is used to store a sound or a bank.

Mobile

The mobile version of GTA: San Andreas replaces the previous raw sound storage system with .osw and .osw.idx files. An OSW file is actually an uncompressed ZIP file, and can be opened with most ZIP viewers/extractors. The .osw.idx file stores a table of contents for the entries in the OSW file with the same name.

All of the game's sounds are present inside the OSW files in MP3 format. This compression allows all of the OSW files containing sound effects to take up just 731MB on the mobile version, whereas the combined size of the SFX files in the PC version is 2.3GB. It may be worth noting that the OSW files themselves do not contribute to this decreased size because they are not compressed.

The index files begin with the number of entries:

uint32_t entryCount;

The rest of the file is made up of the same repeated structure:

struct IndexEntry {
    // Absolute offsets from the beginning of the .osw file.
    uint32_t oswOffset;
    uint32_t oswSize;

    // File name length is dynamic.
    uint16_t nameLength;
    char name[nameLength];
};

However, as the length of the file name is dynamic, the structure cannot be read from a buffer directly and code similar to the following must instead be used to read an entry structure:

struct IndexEntry {
    uint32_t oswOffset;
    uint32_t oswSize;

    uint16_t nameLength;
    std::string name;

    explicit IndexEntry(char *&buf) {
        oswOffset = *(uint32_t *)buf;
        buf += 4;

        oswSize = *(uint32_t *)buf;
        buf += 4;

        nameLength = *(uint16_t *)buf;
        buf += 2;

        name = std::string(buf, buf + nameLength);
        buf += nameLength;
    }
};

The BankLkup.dat, BankSlot.dat and PakFiles.dat files are present on mobile and are identical to the PC versions.

Scripting

Sounds can be used in SCM with the following opcodes:

  • 018C – Plays a sound on a specific location
  • 018E – Stops the played sound
  • 03CF – Loads a sound
  • 03D0 – Checks if sound has been loaded
  • 03D1 – Plays the loaded sound
  • 03D2 – Checks if the loaded sound has finished
  • 097A – Plays an audio event
  • 097B – Plays an audio event in an object
  • 09F1 – Plays an audio event in a character
  • 09F7 – Plays an audio event in a vehicle
  • 09D6 – Makes a character say a scripted speech

Tools

  • San Andreas Audio Toolkit – Allows you to modify SFX Packages – It contains a bug related to bank slots causing sound artifacts
  • GTA Net GTAForums: Mod Loader – Allows you to easily change SFX data and fixes the sound artifacts on big sounds.
  • GTA Net GTAForums: Dynamic SFX – Fixes sound artifacts caused by sounds bigger than the original.

See also

External links