CAR

The **CAR** files are used in *Carnivores*, *Carnivores 2*, and *Carnivores Ice Age* as 3D model files for the animals and hunter, weapons, dropship, and wind indicator, and are located in the \HUNTDAT folder (in *Carnivores 2* and *Ice Age*, the weapon CAR files are instead located in \HUNTDAT\WEAPONS). The 3DF format is a simplified version of this format.

"CAR" may be short for "character"; note that it is *not* short for *Carnivores*, since the extension was used for a similar (though presumably slightly simpler) format in *Chasm: The Rift*, several years before development on *Carnivores* would have begun.

## Code

There are several parts of the source code that handle CAR files.

### Resources.cpp

This code loads CAR files.

```
void LoadCharacterInfo(TCharacterInfo &chinfo, char\* FName)
{
 ReleaseCharacterInfo(chinfo);

 HANDLE hfile = CreateFile(FName,
 GENERIC\_READ, FILE\_SHARE\_READ,
 NULL, OPEN\_EXISTING, FILE\_ATTRIBUTE\_NORMAL, NULL);

 if (hfile==INVALID\_HANDLE\_VALUE) {
 char sz[512];
 wsprintf( sz, "Error opening character file:\n%s.", FName );
 DoHalt(sz);
 }

 ReadFile(hfile, chinfo.ModelName, 32, &l, NULL);
 ReadFile(hfile, &chinfo.AniCount, 4, &l, NULL);
 ReadFile(hfile, &chinfo.SfxCount, 4, &l, NULL);

//============= read model =================//

 chinfo.mptr = (TModel\*) \_HeapAlloc(Heap, 0, sizeof(TModel));

 ReadFile( hfile, &chinfo.mptr->VCount, 4, &l, NULL );
 ReadFile( hfile, &chinfo.mptr->FCount, 4, &l, NULL );
 ReadFile( hfile, &chinfo.mptr->TextureSize, 4, &l, NULL );
 ReadFile( hfile, chinfo.mptr->gFace, chinfo.mptr->FCount<<6, &l, NULL );
 ReadFile( hfile, chinfo.mptr->gVertex, chinfo.mptr->VCount<<4, &l, NULL );

 int ts = chinfo.mptr->TextureSize;
 if (HARD3D) chinfo.mptr->TextureHeight = 256;
 else chinfo.mptr->TextureHeight = chinfo.mptr->TextureSize>>9;
 chinfo.mptr->TextureSize = chinfo.mptr->TextureHeight\*512;

 chinfo.mptr->lpTexture = (WORD\*) \_HeapAlloc(Heap, 0, chinfo.mptr->TextureSize);

 ReadFile(hfile, chinfo.mptr->lpTexture, ts, &l, NULL);

 DATASHIFT(chinfo.mptr->lpTexture, chinfo.mptr->TextureSize);
 GenerateModelMipMaps(chinfo.mptr);
 GenerateAlphaFlags(chinfo.mptr);
 //ApplyAlphaFlags(chinfo.mptr->lpTexture, 256\*256);
 //ApplyAlphaFlags(chinfo.mptr->lpTexture2, 128\*128);
//============= read animations =============//
 for (int a=0; a<chinfo.AniCount; a++) {
 ReadFile(hfile, chinfo.Animation[a].aniName, 32, &l, NULL);
 ReadFile(hfile, &chinfo.Animation[a].aniKPS, 4, &l, NULL);
 ReadFile(hfile, &chinfo.Animation[a].FramesCount, 4, &l, NULL);
 chinfo.Animation[a].AniTime = (chinfo.Animation[a].FramesCount \* 1000) / chinfo.Animation[a].aniKPS;
 chinfo.Animation[a].aniData = (short int\*)
 \_HeapAlloc(Heap, 0, (chinfo.mptr->VCount\*chinfo.Animation[a].FramesCount\*6) );

 ReadFile(hfile, chinfo.Animation[a].aniData, (chinfo.mptr->VCount\*chinfo.Animation[a].FramesCount\*6), &l, NULL);
 }

//============= read sound fx ==============//
 BYTE tmp[32];
 for (int s=0; s<chinfo.SfxCount; s++) {
 ReadFile(hfile, tmp, 32, &l, NULL);
 ReadFile(hfile, &chinfo.SoundFX[s].length, 4, &l, NULL);
 chinfo.SoundFX[s].lpData = (short int\*) \_HeapAlloc(Heap, 0, chinfo.SoundFX[s].length);
 ReadFile(hfile, chinfo.SoundFX[s].lpData, chinfo.SoundFX[s].length, &l, NULL);
 }

 for (int v=0; v<chinfo.mptr->VCount; v++) {
 chinfo.mptr->gVertex[v].x\*=2.f;
 chinfo.mptr->gVertex[v].y\*=2.f;
 chinfo.mptr->gVertex[v].z\*=-2.f;
 }

 CorrectModel(chinfo.mptr);

 ReadFile(hfile, chinfo.Anifx, 64\*4, &l, NULL);
 if (l!=256)
 for (l=0; l<64; l++) chinfo.Anifx[l] = -1;
 CloseHandle(hfile);
}

```

## Format

// header (52 bytes)

0x0000
byte
32
ModelName

texture name (often the same as the name of the CAR file, but not always - e.g. the texture name in ALLO.CAR is "Rapt73")`; the last 12 bytes are normally `msc: #`, where `#` is a number; see notes for more details

0x0020
uint32
4
AniCount

number of animations

0x0024
uint32
4
SfxCount

number of sounds

0x0028
uint32
4
VCount

number of vertices

0x002B
uint32
4
FCount

number of faces

0x0030
uint32
4
TextureSize

texture length in bytes

// for each face (64 bytes)

uint32
4
v1

vertex 1

uint32
4
v2

vertex 2

uint32
4
v3

vertex 3

uint32
4
tax

`v1` texture U coordinate

uint32
4
tbx

`v2` texture U coordinate

uint32
4
tcx

`v3` texture U coordinate

uint32
4
tay

`v1` texture V coordinate

uint32
4
tby

`v2` texture V coordinate

uint32
4
tcy

`v3` texture V coordinate

uint16
2
flags

bit
1
sfDoubleSide
0x0001
marks face as textured on both sides

bit
1
sfDarkBack
0x0002
marks face as having a dark back side

bit
1
sfOpacity
0x0004
marks face as transparent

bit
1
sfTransparent
0x0008
marks face as non-solid (bullets pass through harmlessly)

bit
1
sfMortal
0x0010
marks face as a target zone

bit
1
sfPhong
0x0020
marks face as Phong mapped

bit
1
sfEnvMap
0x0040
marks face as Environment mapped

bit
1
sfNeedVC
0x0080

bit
7

unused

bit
1
sfDark
0x8000
marks face as having a dark front side

uint16
2
DMask

appears to be unused by the games, possibly editor/tool-specific

uint32
4
Distant

unused in-game; the game sets its own `Distant` flags during tree sorting of faces

uint32
4
Next

unused in-game; the game sets its own `Next` flags during tree sorting of faces, where it points to the next face

uint32
4
group

appears to be unused by the games, possibly editor/tool-specific

byte
12
reserv
0x00
unused; reserved for future use

// for each vertex (16 bytes)

float
4

X coordinate

float
4

Y coordinate

float
4

Z coordinate

uint16
2
owner

bone to which vertex is attached

uint16
2
hide

whether the vertex is hidden in Designer 2; has no effect in-game

byte
TextureSize
texture

16-bit TGA-style encoding in BGRA5551 format, always 256 pixels wide

// for each animation (40 + (`FramesCount` \* `VCount` \* 6) bytes)

byte
32
aniName

name of the VTL file the animation was loaded from

uint32
4
aniKPS

keyframes per second

uint32
4
FramesCount

number of frames

byte
FramesCount \* VCount \* 6
aniData

// for each frame (`VCount` \* 6 bytes)

// for each vertex (6 bytes)

int16
2

X coordinate

int16
2

Y coordinate

int16
2

Z coordinate

// for each sound (36 + `length` bytes)

byte
32
name

uint32
4
length

byte
length

data (16-bit mono PCM @ 22050 Hz)

// animation/sound cross-reference table (256 bytes)

// for each entry (4 bytes)

int32
4

sound index (-1 if no sound mapped)

### Notes

* `ModelName` always consists of two parts: the first is the name of the model's texture, and the second is almost always `msc: #`.
	+ In many models, the texture name has an additional character(s) after the first null character(s); for example, PAR2.CAR in *Carnivores* and *Carnivores 2* has the name `Par2\x00e`.
	+ While in most models, the `msc:` string starts at 24 bytes (and is thus 8 bytes long), it instead starts at 20 bytes in many *Carnivores Ice Age* models (making it 12 bytes long); for example, Bear.car has the value `msc: 4\x00: 5`. In some cases it has embedded null bytes, similar to the texture name; for example, WEAPON2.CAR in *Carnivores* and X\_BOW.CAR in *Carnivores 2* (both for the X-Bow) have the value `msc: 4\x006`. EXPLO.CAR has the hex value `\xfa\x0f\x00\x00D\x02` instead of any "msc" string.
* The `sfPhong` and `sfEnvMap` flags are actually assigned values of `0x30` and `0x50` in the game source, meaning models with these flags will also set `sfMortal` on the same faces. Fortunately, weapons are the only objects to use the `sfPhong` and `sfEnvMap` flags. Furthermore, the code will still work correctly if these flags are set but `sfMortal` is not. Animator uses the correct values of `0x20` and `0x40` for these flags.
* The alpha channel in the image is unused; instead, the game engine uses the alpha flag to determine which triangles are transparent on a model.
* Each entry in the animation/sound cross-reference table corresponds to an animation. Because of this, sounds are assigned to the animations sequentially, only one sound can be assigned to each animation, and the order of animations in the table cannot be changed.

## MAXScript

Rexhunter99 wrote a MAXScript for importing CAR files into 3ds Max.

```
--(c) RexHunter99 2009

utility GeomExp "Carnivores MAXScript"
(
 group "Import"
 (
 button carImport "Import CAR" toolTip: "Import the entire scene"
 )
 group "Export"
 (
 button x3dfExport "Export 3DF" toolTip: "Export the entire scene"
 button vtlExport "Export VTL" toolTip: "Export the animated scene"
 )
 group "About"
 (
 label lab1 "Carnivores MAXScript"
 label lab2 "Version 1.2"
 label lab3 "Made by Rexhunter99"
 )

 fn ReadCAR fname =
 (
 --Define some variables
 cname = "NULL"
 msc = "msc: 0"
 num\_tris = 0
 num\_verts = 0
 num\_anims = 0
 num\_sounds = 0
 num\_bones = 0
 tri\_list = #()
 vert\_list = #()
 tex\_list = #()
 tc\_x = #()
 tc\_y = #()
 tex\_size = 256\*256\*2

 --Read the damned file >\_<
 f = fopen fname "rb"

 --Read the header
 cname = ReadString f
 fseek f 24 #seek\_set
 msc = ReadString f
 fseek f 32 #seek\_set
 num\_anims = ReadLong f #signed
 num\_sounds = ReadLong f #signed
 num\_verts = ReadLong f #signed
 num\_tris = ReadLong f #signed
 tex\_size = ReadLong f #signed

 for t=1 to num\_tris do (
 v1 = ReadLong f --Vert 1
 v2 = ReadLong f --Vert 2
 v3 = ReadLong f --Vert 3
 tx1 = ReadLong f --U1
 tx2 = ReadLong f --U2
 tx3 = ReadLong f --U3
 ty1 = ReadLong f --V1
 ty2 = ReadLong f --V2
 ty3 = ReadLong f --V3
 flags = ReadLong f --Flags
 fseek f 4 #seek\_cur
 par = ReadLong f --Parent
 fseek f 4 #seek\_cur
 fseek f 4 #seek\_cur
 fseek f 4 #seek\_cur
 fseek f 4 #seek\_cur
 append tri\_list ([v1+1,v2+1,v3+1])
 append tc\_x ((tx1/256.0) as float)
 append tc\_x ((tx2/256.0) as float)
 append tc\_x ((tx3/256.0) as float)
 append tc\_y ((ty1/256.0) as float)
 append tc\_y ((ty2/256.0) as float)
 append tc\_y ((ty3/256.0) as float)
 )
 for v=1 to num\_verts do (
 x = ReadFloat f --X
 y = ReadFloat f --Z
 z = ReadFloat f --Y
 b = ReadLong f --Bone

 if b>num\_bones then
 num\_bones = b

 append vert\_list ([-x,z,y])
 )

 fclose f

 mat = multiMaterial numsubs:1 name:cname
 m = mesh name:cname position:[0, 0, 0] scale:[1, 1, 1] \
 faces:tri\_list vertices:vert\_list material:mat

 setNumTVerts m (num\_tris \* 3)
 for f = 1 to (num\_tris \* 3) do
 (
 setTVert m (f+0) tc\_x[f+0] tc\_y[f+0] 0
 )

 -- Create our texture vertice faces
 buildTVFaces m
 for f = 1 to (num\_tris-1) do
 (
 v1 = ((f - 1) \* 3) + 1
 v2 = ((f - 1) \* 3) + 2
 v3 = ((f - 1) \* 3) + 3
 setTVFace m f [v1,v2,v3]
 )
 )

 fn Save3DF fname =
 (
 if fname == undefined then exit

 obj = 0
 obones = #()
 numbones = 0

 format "$objects.count = %\n" $objects.count

 for o=1 to $objects.count do
 (
 if $objects[o].name[1]=="#" then
 (
 append obones $objects[o]
 numbones += 1
 format "  % %\n" $objects[o].name $objects[o].parent
 )
 else
 obj = $objects[o]
 )

 if $selection[1] == undefined then
 (
 obj = $objects[1]
 )
 else
 (
 obj = $selection[1]
 )

 format "BoneCount = %\n" numbones
 numbones = 0

 fp = fopen fname "wb"

 WriteLong fp obj.numverts #unsigned
 WriteLong fp obj.numfaces #unsigned
 WriteLong fp numbones #unsigned
 WriteLong fp (256\*512) #unsigned

 for i=1 to obj.numfaces do
 (
 f = getface obj i
 t = gettvface obj i
 tx = gettvert obj t.x
 ty = gettvert obj t.y
 tz = gettvert obj t.z
 WriteLong fp (f.z-1) #unsigned
 WriteLong fp (f.y-1) #unsigned
 WriteLong fp (f.x-1) #unsigned
 WriteLong fp (tz.x \* 256) #unsigned
 WriteLong fp (ty.x \* 256) #unsigned
 WriteLong fp (tx.x \* 256) #unsigned
 WriteLong fp (tz.y \* 256) #unsigned
 WriteLong fp (ty.y \* 256) #unsigned
 WriteLong fp (tx.y \* 256) #unsigned
 WriteLong fp 0
 WriteLong fp 0
 WriteLong fp -1
 WriteLong fp 0
 WriteLong fp 0
 WriteLong fp 0
 WriteLong fp 0
 )

 for i=1 to obj.numverts do
 (
 v = getvert obj i
 WriteFloat fp (-v.x)
 WriteFloat fp v.z
 WriteFloat fp v.y
 --Get the bone if the mesh is Skinned
 WriteLong fp 0
 )

 for i=1 to numbones do
 (
 for c=1 to 32 do
 (
 if c >= obones[i].name.length then
 WriteByte fp 0
 else
 WriteByte fp obones[i].name[c]
 )
 WriteFloat fp obones[i].x
 WriteFloat fp obones[i].y
 WriteFloat fp obones[i].z
 WriteShort fp -1
 WriteShort fp 0
 )

 texfp = fopen obj.material.diffuseMap.fileName "rb"
 fseek texfp 18 #seek\_set

 for i=1 to 256\*256 do
 (
 pixel = ReadShort texfp #unsigned
 WriteShort fp pixel #unsigned
 )

 fclose texfp

 fclose fp
 )

 fn SaveVTL fname =
 (
 if $selection[1] == undefined then
 (
 obj = $objects[1]
 )
 else
 (
 obj = $selection[1]
 )

 if fname == undefined then exit

 num\_frames = ( 1 + animationRange.end - animationRange.start )

 fp = fopen fname "wb"

 WriteLong fp obj.numverts #unsigned
 WriteLong fp 20 #unsigned
 WriteLong fp num\_frames #unsigned

 --Animation
 sliderTime = animationRange.start
 for i=animationRange.start to animationRange.end do
 (
 for j=1 to obj.numverts do
 (
 v = getvert obj j
 WriteShort fp ((-v.x\*16) as integer)
 WriteShort fp ((v.z\*16) as integer)
 WriteShort fp ((v.y\*16) as integer)
 )
 sliderTime+=1f
 )
 sliderTime = animationRange.start

 fclose fp
 )

 on carImport pressed do
 (
 clearListener()

 --Find a file
 fname = getOpenFileName "Open a CAR file" \
 types:"CAR (.car)|\*.car|"

 ReadCAR fname
 )

 on x3dfExport pressed do
 (
 clearListener()

 --Find a file
 fname = getSaveFileName "Save a 3DF file" \
 types:"3D File (.3DF)|\*.3DF|"

 Save3DF fname
 )

 on vtlExport pressed do
 (
 clearListener()

 --Find a file
 fname = getSaveFileName "Save a VTL file" \
 types:"Vertex Transformation List (.VTL)|\*.VTL|"

 SaveVTL fname
 )
)

```

## External links

* The .car files on La saga de Carnivores
* Carnivores 1, 2 and Ice Age .car files thread on The Carnivores Saga