Mobile textures (SA/VC)
The mobile ports of GTA: San Andreas and GTA: Vice City use texture systems different from their PC and console counterparts in order to reduce the amount of storage space required for storing textures.
The information in this article is based on the formats found in the mobile version of GTA:SA, but most can also be applied to GTA VC.
Textures are grouped into separate databases based on which archive file they are in on non-mobile versions. New databases have been added to store textures exclusive to the mobile games, such as touch controls, updated splash screens and menu icons.
A single texture database is made up of several files:
name.txt
name.x.toc
name.x.dat
name.x.tmb
The four files listed above are named according to the archive they come from: name
is gta3
for textures from gta3.img
, gta_int
for textures
from gta_int.img
, and so on.
In these file names, x
refers to the file extension of the texture format used.
For example iOS devices use the PVRTC format, which means that x
is pvr
.
Contents
.txt
The .txt
file contains a list of all the textures in the database, along with various texture properties for each.
It also contains information about the database itself.
Properties in the .txt
file are set with propertyname=value
where value
may be an integer or string value.
There are two properties – png
and img
– that are used with hexadecimal numbers (e.g. png=72b88910
), but
these properties are never read by the game. It is possible that they are some sort of checksum or identifier for texture PNGs/other images.
The affiliate
property is set inside quotes – "affiliate=something"
.
The category line defines default values for texture properties as well as information about the database itself.
It must set the cat
property in order to be correctly identified as a category line. In GTA:SA, this line is the first in the file,
but this is not a requirement.
Texture lines begin with the quoted texture name, such as "ahoodfence2"
.
See the table below for a list of properties.
Property name | Type | Meaning |
---|---|---|
format |
Integer | Specifies the format of the texture. |
mipmode
|
Boolean | |
hassibling
|
Boolean | |
hasbias
|
Boolean | |
camnorm
|
Boolean | Unknown. Used for "thin" textures (e.g. leaves, wires). |
forcez
|
Boolean | Unknown. Used for some glass textures. |
decalz
|
Boolean | |
noalphatest
|
Boolean | Unknown. Used for some shadow textures. |
hasdetail
|
Integer | |
isdetail
|
Integer | |
detailtile
|
Integer | The number of times to tile the detail texture (unconfirmed). |
alphamode
|
Integer | 1, 2 or 3 |
streammode
|
Boolean | |
width
|
Integer | The width of the texture, measured in pixels. |
height
|
Integer | The height of the texture, measured in pixels. |
affiliate
|
String | A texture that this texture "points to". If a texture is defined as an affiliate, it simply redirects to whichever texture it points to. Affiliate textures do not have any texture data and typically do not define any other properties. |
.toc
Overview
The .toc
file is a table of offsets for the .dat
file (which contains all of the texture data). This file is not
required by the game; a new offset table can be created by reading the .dat
file. The purpose of the offset table is to accelerate
texture loading. A game running without prebuilt offset tables will likely suffer from small stutters, generally lower FPS, and textures loading
later than they should (leaving some objects untextured until the texture loads).
The offset table file stores the size of the .dat
file it is for in order to allow the game to ensure that the two files are compatible.
If the size given in the .toc
file does not match the actual .dat
size, the table will be discarded and the offsets will be
read directly from the data file instead.
The game is capable of generating offset table files for future use, but this code never runs in production builds.
Format
Offset files have a very basic format: one 32-bit unsigned integer (uint32
) for the size of the data file,
and then the rest of the file is consecutive uint32
offsets in the order of the entries in the .txt
file.
To read an offset file, code similar to the following pseudo-C++ may be used:
FILE *offsetFile = std::fopen(pathToOffsetFile, "rb");
if (!offsetFile) {
// Error: unable to open the file.
// ...
}
uint32 statedSize;
std::fread(&statedSize, 1, 4, offsetFile);
uint32 dataFileSize = /* the size of the data file */;
if (statedSize != dataFileSize) {
// Error: offset and data files do not match.
// ...
}
uint32 *offsetArray = new uint32[database.entryCount];
std::fread(offsetArray, 4, database.entryCount, offsetFile);
std::fclose(offsetFile);
// offsetArray now contains the offsets from the file.
In both error cases in the above code, the game will resort to reading the offsets from the data file.
.dat
The .dat
("data") file is a raw binary file that stores all of the texture data for the database.
Different image formats are used on different platforms – iOS devices store most textures in PVR format, but Android devices use different formats.
There is extra run-length encoding used to compress the textures. A decompression algorithm is below.
void ExpandRLE(uint8 *out, uint32 outSize, uint8 *in, uint32 segmentSize, uint32 rleIndicator) {
if (outSize == 0) {
return;
}
// Loop until our pointer to the output buffer reaches the end.
for (uint8 *endByte = out + outSize; out < endByte;) {
// If `indicator == rleIndicator`, the next segment is repeated.
uint8 indicator = in[0];
if (indicator == rleIndicator) {
// `repetitions` tells us how many times to repeat the segment.
uint8 repetitions = in[1];
if (repetitions != 0) {
// Append `repetitions` segments to the output buffer.
for (uint8 i = 0; i < repetitions; ++i) {
// Offset `in` by 2 to skip `indicator` and `repetitions`.
std::memcpy(out, in + 2, segmentSize);
// Advance the pointer so we're appending instead of just copying.
out += segmentSize;
}
// Each compressed section is `segmentSize + 2` bytes long. `segmentSize` is
// the size of the repeated data (which is present only once in the encoded
// data), and the two bytes are `indicator` and `repetitions`.
in += segmentSize + 2;
}
} else {
// Just copy the next segment over without repeating anything.
std::memcpy(out, in, segmentSize);
// We read one segment and wrote one segment, so advance both pointers to reflect this.
out += segmentSize;
in += segmentSize;
}
}
}