// Open SanAndreas
// (c) 2006-2008 Stanislav "listener" Golovin
// 
// configuration files reader
#include <stdafx.h>
#include <stddef.h>
#include <assert.h>
#include "ogMath.h"
#include "ogMemory.h"
#include "ogRW.h"
#include "ogModels.h"
#include "ogTextures.h"
#include "ogHud.h"
#include "ogFX.h"
#include "ogFiles.h"
#include "ogZones.h"
#include "ogGameObjects.h"

#include "gtaInterface.h"

#pragma warning(disable : 4996)	// sscanf/str[n]cpy
#pragma warning(disable : 6031) // sscanf return value ignored
#pragma warning(disable : 6054)	// string might not be zero-terminated 

// TODO: времянки. Если сложные - выносятся в gtaInterface, если простые - разбираются
/// static int (*readIdeObjs)(char *) = (int (__cdecl *)(char *))0x5B3C60;
/// static int (*readIdeTobj)(char *) = (int (__cdecl *)(char *))0x5B3DE0;

static void (*_405780)() = (void (__cdecl *)())0x405780;
static void (*_410EC0)() = (void (__cdecl *)())0x410EC0;
static void (*_410E00)() = (void (__cdecl *)())0x410E00;
void (*readTrackFiles)() = (void (__cdecl *)())0x6F7440;	

void (*_53DED0)(char *, char *) = (void (__cdecl *)(char *, char *))0x53DED0;

void (*_openImgFile)(char *, bool) = (void (__cdecl *)(char *, bool))0x407610;
// static void (*loadColFile)(char * filename, BYTE param) = (void (__cdecl *)(char *, BYTE))0x5B4E60; 
static void (*loadModelFile)(char * filename) = (void (__cdecl *)(char *))0x5B39D0; 
/// static void (*loadIdeFile)(char * filename) = (void (__cdecl *)(char *))0x5B8400; 
/// static void (*loadIplFile)(char * filename) = (void (__cdecl *)(char *))0x5B8700; 


/// static void (*setModelIndices)() = (void (__cdecl *)())0x5B57C0;
/// static void (*loadObjectDat)(char * filename, bool) = (void (__cdecl *)(char *, bool))0x5B5360;
/// static void (*setupVehicleInfo) () = (void (__cdecl *)())0x5B8F00;
static void (*setupImgStreaming)() = (void (__cdecl *)())0x5B8AD0;
static void (*_4113D0)() = (void (__cdecl *)())0x4113D0;	// 

/// static void (*readCarcolsDat) () = (void (__cdecl *)())0x5B6890;
static void (*updateLoadingBar) () = (void (__cdecl *)())0x590D00;
/// static void (*readCarmodsDat)() = (void (__cdecl *)())0x5B65A0;
static void (*_5D5BC0)() = (void (__cdecl *)())0x5D5BC0;

// TODO: mode to ogModels.cpp
// Предопределенные colmodels
static CColModel * colModelWeap = (CColModel *)0x968FD0;	
static CColModel * colModelHier = (CColModel *)0x968A00; 
static CColModel * colModelPeds = (CColModel *)0x968DF0; 

CullZoneReflection * cullZonesR = (CullZoneReflection *)0xC815C0;	// [0x48]
CullZone * cullZones = (CullZone *)0xC81F50;	// [0x514]
CullZone * cullZones3 = (CullZone *)0xC81C80;	// cull zones with flags 0x80 or 0x800 [0x28]
int * cullZoneRCount = (int *)0xC87AC4;
int * cullZone2Count = (int *)0xC87AC8;
int * cullZone3Count = (int *)0xC87AC0;	// cull zones with flags 0x80 or 0x800

OcclusionBox * occlBoxes = (OcclusionBox *)0xC73FA0;
OcclusionBox * occlBoxInt = (OcclusionBox *)0xC73CC8;
int * occlBoxCount = (int *)0xC73F98;
int * occlBoxIntCount = (int *)0xC73CC4;
WORD * _8D5D68 = (WORD *)0x8D5D68;

CGarage * garages = (CGarage *)0x96C048;	// [50]
int * garageCount = (int *)0x96C024;

CTexture ** whiteTexture = (CTexture **)0xB4E3EC;	
CTexDictionary ** vehicleTxd = (CTexDictionary **)0xB4E688;	// используется еще в одном коллбэке TODO: move to texture module
CTexture ** texVehiclelights128 = (CTexture **)0xB4E68C;
CTexture ** texVehiclelightson128 = (CTexture **)0xB4E690;
RGBA * vehicleColors = (RGBA *)0xB4E480;

// IDE
///static int (*readIdeAnim)(char * line) = (int (__cdecl *)(char *))0x5B40C0;
int (*getPedTypeByName)(char * name) = (int (__cdecl *)(char *))0x608790;
static int (*getPedStatTypeByName)(char * name) = (int (__cdecl *)(char *))0x608790;
int * animationStylesCount = (int *)0xB4EA28;
static int (*getPedModelByName)(char * name) = (int (__cdecl *)(char *))0x5B6D40;
static int (*getPedVoiceTypeByName)(char * name) = (int (__cdecl *)(char *))0x4E3C60;
static int (*getPedVoiceByName)(char * name, short voiceType) = (int (__cdecl *)(char *, short))0x4E3CD0;
char * (*getAnimationStyleName)(int index) = (char * (__cdecl *)(int))0x4D3A20;

static int (*readIde2dfx)(char * line) = (int (__cdecl *)(char *))0x5B7670;
/// static int (*readIdeTxdp)(char * line) = (int (__cdecl *)(char *))0x5B75E0;
static int (*readIdePathData0)(char *, int, int) = (int (__cdecl *)(char *, int, int))0x5B41F0;
static int (*readIdePathData)(char *, int, int, int) = (int (__cdecl *)(char *, int, int, int))0x5B4380;

// WORD wheelSets[4];
WORD * wheelSetSizes = (WORD *)0xB4E470;	// number of wheels in each carmods set
WORD * wheelSets = (WORD *)0xB4E3F8;		// WORD [15][4]

// IPL
int *	_iplInstBlockNum = (int *)0x8E3F00;	// = 0;
void *** _iplInstBlocks = (void ***)0x8E3F08;	// [40]
static int *	_iplInstNum = (int *)0xBCC0D8;
static void **	_iplInst = (void **)0xBCC0E0;	// [4096]

/// static void *	(*readIplInst)(char * line) = (void * (__cdecl *)(char *))0x538690;
/// static void		(*readIplZone)(char * line) = (void (__cdecl *)(char *))0x5B4AB0;
/// static void		(*readIplCull)(char * line) = (void (__cdecl *)(char *))0x5B4B40;
/// static void		(*readIplOccl)(char * line, char * filename) = (void (__cdecl *)(char *, char *))0x5B4C80;
/// static void		(*readIplGrge)(char * line) = (void (__cdecl *)(char *))0x5B4530;
static void		(*readIplAuzo)(char * line) = (void (__cdecl *)(char *))0x5B4D70;
static void		(*readIplEnex)(char * line) = (void (__cdecl *)(char *))0x5B8030;
static void		(*readIplPick)(char * line) = (void (__cdecl *)(char *))0x5B47B0;
static void		(*readIplCars)(char * line, int) = (void (__cdecl *)(char *, int))0x5B4740;
static void		(*readIplJump)(char * line) = (void (__cdecl *)(char *))0x5B45D0;
static void		(*readIplTcyc)(char * line) = (void (__cdecl *)(char *))0x5B81D0;
static void	 (*_5B51E0)(DWORD) = (void (__cdecl *)(DWORD))0x5B51E0;
static void  (*_405110)(int blockIndex) = (void (__cdecl *)(int))0x405110;
static DWORD (*_404DE0)(char * filename, int blockIndex, void ** insts) = (DWORD (__cdecl *)(char *, int, void **))0x404DE0;

//=============================================================================
static char lineBuffer[512];
char * getConfigLine (FILE * f) {	// >> 536F80 (74)	
	if (!fgets (lineBuffer, 512, f)) 
		return NULL;
	char * s;
	for (s = lineBuffer; *s; s++)
		if (*s < ' ' || *s == ',')
			*s = ' ';
	for (s = lineBuffer; *s; s++)
		if (*s > ' ')
			break;
	return s;	
}

static char messageBuffer[256];
static void showProgressMessage (char * text) {	// TODO: доделать
	char * param = strchr (text, '\\');
	param = param ? param+1 : text;
	sprintf_s (messageBuffer, 256, "# Loading %s\n", param);	// TODO: remove '\n'	
	debug (messageBuffer);	

	_53DED0 ("Loading the game ", messageBuffer);
}

//=============================================================================
//	"TEXDICTION" section
// NOTE: не используется в SA 
static CTexDictionary * loadTxdFile (char * filename) {	// >> 5B3860
	CRwFilenameStream s(filename);
	CTexDictionary * txd = NULL;
	if (s.isValid ()) 
		if (s.findChunk (0x16, NULL, NULL))	// TODO: chunkid
			txd = texMan.readTxdChunk (&s);
	return txd ? txd : new CTexDictionary ();
}
//=============================================================================
//	"IMG" section

//=============================================================================
//	"COLFILE" section

//=============================================================================
//	"MODELFILE" section
/*
static void loadModelFile(char * filename) {
	CRwFilenameStream s(filename);
	if (!s.isValid () {
		debug ("Unable to open model file '%s'\n", filename);
		return;
	}
	CClump * clump = s.readClump();
	if (clump) {
		RpClumpForAllAtomics (clump, _5B3930, clump);
		delete clump;
	}
}
*/
//=============================================================================
// "IDE" section

//-----------------------------------------------------------------------------
// 'objs'
// TODO: теоретически, можно не сбрасывать флаги
static void setModelGraphicsFlags (CModelBase * model, DWORD flags) {
	if (flags & 0x0C)	// mfALPHATRANSPARENCY1|mfALPHATRANSPARENCY2
		model->flags |= 2;
	else
		model->flags &= ~2;

	if (flags & 0x08)	// mfALPHATRANSPARENCY2
		model->flags |= 4;
	else
		model->flags &= ~4;

	if (flags & 0x40)	// mfDISABLESHADOWMESH
		model->flags |= 8;
	else
		model->flags &= ~8;

	if (flags & 0x80)	// mfDISABLECOLLISIONMESH
		model->flags |= 0x10;
	else 
		model->flags &= ~0x10;

	if (flags & 0x200000)	// mfNOBACKFACECULLING
		model->flags &= ~0x40;
	else
		model->flags |= 0x40;
}

void setModelFlags (CModelBase * model, DWORD flags) {	// >> 5B3B20
	setModelGraphicsFlags (model, flags);

	if (flags & 1)	// mfWETEFFECT
		model->flags |= 0x100;
	else 
		model->flags &= ~0x100;

	if (flags & 0x200) {	// mfBREAKABLEGLASS
		model->flags &= 0xA7FF;
		model->flags |= 0x2000;	// 13 (4)
	}
	if (flags & 0x400) {	// mfBREAKABLECRACKEDGLASS
		model->flags &= 0xAFFF;
		model->flags |= 0x2800;	// 13, 11 (5)
	}
	if (flags & 0x800) {	// mfGARAGEDOOR
		model->flags &= 0xBFFF;
		model->flags |= 0x3800;	// 13, 12, 11 (7)
	}
	if (flags & 0x2000) {	// mfSMALLVEGETATION
		model->flags &= 0x8FFF;
		model->flags |= 0x0800;	// 11 (1)
	}
	if (flags & 0x4000) {	// mfSTANDARDVEGETATION
		model->flags &= 0x97FF;
		model->flags |= 0x1000;	// 12 (2)
	}
	if (flags & 0x8000)		// mfGENERICRENDERER
		model->flags |= 0x400;
	else 
		model->flags &= ~0x400;

	if (flags & 0x100000) {	// mfGRAFFITY
		model->flags &= 0xB7FF;
		model->flags |= 0x3000;	//  (6)
	}
	if (flags & 0x400000) {	// unknown
		model->flags &= 0xDFFF;
		model->flags |= 0x5800;	// 14, 12, 11 (B)
	}
	if (flags & 0x80000) {	// unknown
		model->flags &= 0xD7FF;
		model->flags |= 0x5000;	// 14, 12 (A)
	}		
}

static int readIdeObjs (char * line) {	
	int	objID;	// unique object ID (integer) 
	int objCount = 1;
	float drawDist;
	float drawDist2;
	float drawDist3;
	DWORD flags;
	char modelName[24];
	char texName[24];


	int fieldCount = sscanf (line, "%d %s %s %f %d", &objID, modelName, texName, &drawDist, &flags);	// try parse line without object count
	if (fieldCount != 5 || drawDist < 4.0f) {
		if (sscanf (line, "%d %s %s %d", &objID, modelName, texName, &objCount) != 4)
			return -1;	// invalid line format
		if (objCount == 1) 
			sscanf (line, "%d %s %s %d %f %d", &objID, modelName, texName, &objCount, &drawDist, &flags);
		else if (objCount == 2)
			sscanf (line, "%d %s %s %d %f %f %d", &objID, modelName, texName, &objCount, &drawDist, &drawDist2, &flags);
		else if (objCount == 3)
			sscanf (line, "%d %s %s %d %f %f %f %d", &objID, modelName, texName, &objCount, &drawDist, &drawDist2, &drawDist3, &flags);
	}

	///debug ("OBJS: %d %s %s %d %f %08x\n", objID, modelName, texName, objCount, drawDist, flags);
	// В зависимости от поля flags (bit 12) создаем объект либо с одним, либо с двумя атомиками
	CModelBase * model = flags & 0x1000 ? CModelObjs2c::allocate (objID) : CModelObjs::allocate (objID);
	model->drawDistance = drawDist;
	model->modelNameHash = crc32FromUpcaseString ((BYTE *)modelName);
	/// debug ("Model: %s, ID = %d, hash = %08x\n", modelName, objID, model->modelNameHash);
	model->setTxd (texName);
	setModelFlags (model, flags);
	return objID;
}
//-----------------------------------------------------------------------------
// 'tobj'
static int readIdeTobj (char * line) {
	int	objID;	// unique object ID (integer) 
	int objCount = 1;
	float drawDist;
	float drawDist2;
	float drawDist3;
	DWORD flags;
	char modelName[24];
	char texName[24];
	int timeOn;
	int timeOff;

	int fieldCount = sscanf (line, "%d %s %s %f %d %d %d", &objID, modelName, texName, &drawDist, &flags, &timeOn, &timeOff);	// try parse line without object count
	if (fieldCount != 7 || drawDist < 4.0f) {
		if (sscanf (line, "%d %s %s %d", &objID, modelName, texName, &objCount) != 4)
			return -1;	// invalid line format
		if (objCount == 1) 
			sscanf (line, "%d %s %s %d %f %d %d %d", &objID, modelName, texName, &objCount, &drawDist, &flags, &timeOn, &timeOff);
		else if (objCount == 2)
			sscanf (line, "%d %s %s %d %f %f %d %d %d", &objID, modelName, texName, &objCount, &drawDist, &drawDist2, &flags, &timeOn, &timeOff);
		else if (objCount == 3)
			sscanf (line, "%d %s %s %d %f %f %f %d %d %d", &objID, modelName, texName, &objCount, &drawDist, &drawDist2, &drawDist3, &flags, &timeOn, &timeOff);
	}
	///debug ("TOBJ: %d %s %s %d %f %08x %d-%d\n", objID, modelName, texName, objCount, drawDist, flags, timeOn, timeOff);
	CModelBase * model = CModelTobj::allocate (objID);
	CModelTimeinfo * timeinfo = model->getTimeinfo();
	model->drawDistance = drawDist;
	model->modelNameHash = crc32FromUpcaseString ((BYTE *)modelName);
	model->setTxd (texName);
	timeinfo->timeOn = (BYTE)timeOn;
	timeinfo->timeOff = (BYTE)timeOff;
	setModelFlags (model, flags);
	CModelTimeinfo * pairedTimeinfo = timeinfo->initPairedModel (modelName);	// TODO: inline it ?
	if (pairedTimeinfo)	
		pairedTimeinfo->pairedModel = objID;
	return objID;	
}
//-----------------------------------------------------------------------------
// 'hier'
static int readIdeHier (char * line) {
	int objID;
	char modelName[24];
	char texName[24];

	if (sscanf (line, "%d %s %s", &objID, modelName, texName) != 3)
		return -1;

	///debug ("HIER: %d %s %s\n", objID, modelName, texName);
	CModelBase * model = CModelHier::allocate (objID);
	model->modelNameHash = crc32FromUpcaseString ((BYTE *)modelName);
	model->setTxd (texName);
	model->setColModel (colModelHier, false);
	return objID;
}
//-----------------------------------------------------------------------------
// 'anim'
static int readIdeAnim (char * line) {
	int objID;
	char modelName[24];
	char texName[24];
	char animName[16];
	float drawDist;
	DWORD flags;

	if (sscanf (line, "%d %s %s %s %f %d", &objID, modelName, texName, animName, &drawDist, &flags) != 6)
		return -1;

	/// debug ("ANIM: %d %s %s %s %f %08x\n", objID, modelName, texName, animName, drawDist, flags);
	CModelBase * model = CModelHier::allocate (objID);
	model->drawDistance = drawDist;
	model->modelNameHash = crc32FromUpcaseString ((BYTE *)modelName);
	model->setTxd (texName);
	model->initIfpName (animName);	

	setModelGraphicsFlags (model, flags);
	if (flags & 0x20) // mfINTERIOROBJECT
		model->flags |= 0x400;
	else
		model->flags &= ~0x400;

	if (strcmp (animName, "null"))
		model->flags |= 0x100;

	return objID;
}

//-----------------------------------------------------------------------------
// 'weap'
static int readIdeWeap (char * line) {
	int objID;
	char modelName[24];
	char texName[24];
	char animName[16];
	int p1;
	float drawDist;

	sscanf (line, "%d %s %s %s %d %f", &objID, modelName, texName, animName, &p1, &drawDist);
	///debug ("WEAP: %d %s %s %s %f\n", objID, modelName, texName, animName, drawDist);
	CModelBase * model = CModelWeap::allocate (objID);
	model->drawDistance = drawDist;
	model->modelNameHash = crc32FromUpcaseString ((BYTE *)modelName);
	model->setTxd (texName);
	model->initIfpName (animName);	
	model->setColModel (colModelWeap, false);
	return objID;
}
//-----------------------------------------------------------------------------
// 'peds'
// NOTE: поскольку эта функция используется только в одном месте - можно написать ее здесь как отдельную функцию или заинлайнить

static int readIdePeds (char * line) {
	int objID;
	char modelName[24];
	char texName[24];
	char pedType[24];
	char statName[24];
	char animGroup[24];
	int carMask;
	int flags;
	char animFile[16];
	int radio1, radio2;
	char voiceType[20];
	char voice1[60];
	char voice2[60];
	sscanf (line, "%d %s %s %s %s %s %x %x %s %d %d %s %s %s", 
		&objID, modelName, texName, pedType, statName, animGroup, &carMask, &flags, animFile, &radio1, &radio2, voiceType, voice1, voice2);

	CModelPeds * model = CModelPeds::allocate (objID);
	model->modelNameHash = crc32FromUpcaseString ((BYTE *)modelName);
	model->setTxd (texName);
	model->initIfpName (animFile);	
	model->setColModel (colModelPeds, false);
	model->pedType = getPedTypeByName (pedType);
	model->statType = getPedStatTypeByName (statName);
	
	for (int i =0 ; i < *animationStylesCount; i++)
		if (!strcmp (getAnimationStyleName (i), animGroup)) {
			model->animType = i;
			break;
		}
	
	model->carMask = carMask;
	model->flags = flags;
	model->radio1 = (BYTE)radio1+1;
	model->radio2 = (BYTE)radio2+1;
	model->pedModel = getPedModelByName (modelName);
	model->voiceType = getPedVoiceTypeByName (voiceType);
	model->voice1 = model->voice3 = getPedVoiceByName (voice1, model->voiceType);
	model->voice2 = getPedVoiceByName (voice2, model->voiceType);

	return objID;
}

//-----------------------------------------------------------------------------
// 'cars'
static int readIdeCars (char * line) {
	int		objID;
	char	modelName[24];
	char	texName[24];
	char	type[8];
	char	handlingID[16];
	char	gameName[32];
	char	anims[16];
	char	vclass[16];
	int		frq;
	int		vflags;
	int		comprules;
	int		_p1;
	float	_p2;
	float	_p3;
	int		_p4;

	sscanf (line, "%d %s %s %s %s %s %s %s %d %d %x %d %f %f %d", 
		&objID, modelName, texName, type, handlingID, gameName, anims, vclass, &frq, &vflags, &comprules, &_p1, &_p2, &_p3, &_p4);

	CModelCars * model = CModelCars::allocate (objID);
	model->modelNameHash = crc32FromUpcaseString ((BYTE *)modelName);
	model->setTxd (texName);
	CTxdEntry * txd = (CTxdEntry *)((*txdPool)->at (model->txdIndex));
	txd->parentIndex = (*txdPool)->indexByNameAlways ("vehicle");		
	model->initIfpName (anims);	
	
	for (char * p = gameName; *p; p++)
		if (*p == '_')
			*p = ' ';

	strncpy (model->gamename, gameName, 8);
	model->vflags = vflags;
	model->comprules = comprules;
	model->handlingIndex = CHandling::findVehicleIndex2 (handlingID);

	model->_p1 = _p1;	// NOTE: правильно ли это?
	model->_p2 = _p2;
	model->_p3 = _p3;
	model->_p4 = _p4;

	if (!strncmp (type, "car", 4)) 
		model->type = 0;
	else if (!strncmp (type, "mtruck", 7))
		model->type = 1;
	else if (!strncmp (type, "quad", 5))
		model->type = 2;
	else if (!strncmp (type, "heli", 5))
		model->type = 3;
	else if (!strncmp (type, "plane", 6))
		model->type = 4;
	else if (!strncmp (type, "boat", 5))	// здесь не устанавливались p1..p3
		model->type = 5;
	else if (!strncmp (type, "train", 6))
		model->type = 6;
	else if (!strncmp (type, "f_heli", 7)) 
		model->type = 3;
	else if (!strncmp (type, "f_plane", 8)) {
		model->type = 8;
		model->_p2 = model->_p3 = 1.0;
	} else if (!strncmp (type, "bike", 5)) {
		model->type = 9;
		model->_f58 = (float)_p1;	// соответственно, _p1 просто так не устанавливался
	} else if (!strncmp (type, "bmx", 4)) {
		model->type = 10;
		model->_f58 = (float)_p1;
	} else if (!strncmp (type, "trailer", 8))
		model->type = 11;
	else
		debug ("readIdeCars: ERROR: unknown vehicle type '%s'\n", type);
	
	if (!strncmp (vclass, "normal", 7)) 
		model->vclass = 0;
	else if (!strncmp (vclass, "poorfamily", 11)) 
		model->vclass = 1;
	else if (!strncmp (vclass, "richfamily", 11)) 
		model->vclass = 2;
	else if (!strncmp (vclass, "executive", 10)) 
		model->vclass = 3;
	else if (!strncmp (vclass, "worker", 7)) 
		model->vclass = 4;
	else if (!strncmp (vclass, "big", 4)) 
		model->vclass = 5;
	else if (!strncmp (vclass, "taxi", 5)) 
		model->vclass = 6;
	else if (!strncmp (vclass, "moped", 6)) 
		model->vclass = 7;
	else if (!strncmp (vclass, "motorbike", 10)) 
		model->vclass = 8;
	else if (!strncmp (vclass, "leisureboat", 12)) 
		model->vclass = 9;
	else if (!strncmp (vclass, "workerboat", 11)) 
		model->vclass = 10;
	else if (!strncmp (vclass, "bicycle", 8)) 
		model->vclass = 11;
	else if (!strncmp (vclass, "ignore", 7))  {
		model->vclass = 255;
		return objID;
	} else
		debug ("readIdeCars: ERROR: unknown vehicle class '%s'\n", vclass);

	model->frq = frq;
	return objID;
}

//-----------------------------------------------------------------------------
// 'txdp'
static void readIdeTxdp (char * line) {
	char	name[32];
	char	parentName[32];
	sscanf (line, "%s %s", name, parentName);

	CTxdEntry * txdEntry = (*txdPool)->at ((*txdPool)->indexByNameAlways (name));
	if (txdEntry)
		txdEntry->parentIndex = (*txdPool)->indexByNameAlways (parentName);
}

static int readIdePathHead(char * line, int * p1) {	// >> 5B41C0
	int d1;
	char s[80];
	sscanf (line, "%d %d %s", p1, &d1, s);
	return d1;
}

//=============================================================================
//	.ide reader
static void loadIdeFile (char * filename) {	// >> 5B8400
	int _v10C	= -1;
	int _v108;
	int _v104 = 0x7FFFFFFF;

	FILE * f = fopen (filename, "rb");
	if (!f) {
		debug ("loadIdeFile: Unable to open '%s'\n", filename);
		return ;
	}
	debug ("Loading %s ...\n", filename);
	DWORD tag = 0;	// group type: 'objs', ...

	// последовательно читаем строки .ide:
	while (char * line = getConfigLine (f)) {
		if ('#' == *line || '\0' == *line)
			continue;	// skip comment

		if (tag == 0) {
			tag = *(DWORD *)line;	// get 4 chars from input string
			continue;
		} 
		
		if (((*(DWORD *)line) & 0x00FFFFFF) == 0x646e65) {	// 'end' ?
			tag = 0;	// end of block
			continue;
		}
		
		int num;
		int path = -1;	// номер строки в блоке path
		int pathid;
		switch (tag) {
		case 0x736a626f: // 'objs' - 1
			num = readIdeObjs (line);
			break;
		case 0x6a626f74: // 'tobj' - 3
			num = readIdeTobj (line);
			break;
		case 0x70616577: // 'weap' - 4
			num = readIdeWeap (line);
			break;
		case 0x72656968: // 'hier' - 5
			num = readIdeHier (line);
			break;
		case 0x6d696e61: // 'anim' - 6
			num = readIdeAnim (line);
			break;
		case 0x73726163: // 'cars' - 7
			num = readIdeCars (line);
			break;
		case 0x73646570: // 'peds' - 8
			num = readIdePeds (line);
			break;
		case 0x68746170: // 'path' - 9 (GTA3 only)
			if (path == -1) {
				pathid = readIdePathHead (line, &_v108);
				path = 0;	
			} else {
				if (_v108 == 0) 
					readIdePathData0 (line, pathid, path);
				else if (_v108 == 1 || _v108 == 2)
					readIdePathData (line, pathid, path, _v108 == 1 ? 0 : 1);				
				if (++path >= 12)
					path = -1;
			}
			break;
		case 0x78666432: // '2dfx' - 10
			readIde2dfx (line);
			num = -1;
			break;
		case 0x70647874: // 'txdp' - 11
			readIdeTxdp (line);
			num = -1;
			break;

		default:
			continue;
		}
		if (num == -1)
			continue;
		if (num > _v10C)
			_v10C = num;
		if (num < _v104)
			_v104 = num;

	}
	fclose (f);
}

//=============================================================================
//	"IPL" section - setup models before processing IPL itself

// fill object indices struct
// TODO: remove type-converting wrapper
static void getModelIndexByName1 (char * name, short* p) {
	int id;
	getModelIndexByName (name, &id);
	*p = (short)id;
}

static void setModelIndices () {
	getModelIndexByName1 ("fire_hydrant", &(objectIndices->miFireHydrant));
	getModelIndexByName1 ("phonesign", &(objectIndices->miPhonesign));
	getModelIndexByName1 ("noparkingsign1", &(objectIndices->miNoparkingsign));
	getModelIndexByName1 ("bussign1", &(objectIndices->miBussign1));
	getModelIndexByName1 ("DYN_ROADBARRIER_6", &(objectIndices->miDynRoadbarrier6));
	getModelIndexByName1 ("la_fuckcar1", &(objectIndices->miLaFuckcar1));
	getModelIndexByName1 ("la_fuckcar2", &(objectIndices->miLaFuckcar2));
	getModelIndexByName1 ("dump1", &(objectIndices->miDump1));
	getModelIndexByName1 ("trafficcone", &(objectIndices->miTrafficcone));
	getModelIndexByName1 ("newsstand1", &(objectIndices->miNewsstand1));
	
	getModelIndexByName1 ("postbox1", &(objectIndices->miPostbox1));
	getModelIndexByName1 ("bin1", &(objectIndices->miBin1));
	getModelIndexByName1 ("wastebin", &(objectIndices->miWastebin));
	getModelIndexByName1 ("phonebooth1", &(objectIndices->miPhonebooth1));
	getModelIndexByName1 ("parkingmeter", &(objectIndices->miParkingmeter));
	getModelIndexByName1 ("parkingmeterg", &(objectIndices->miParkingmeterG));
	getModelIndexByName1 ("mall_fans", &(objectIndices->miMallFans));
	getModelIndexByName1 ("htl_fan_rotate_nt", &(objectIndices->miHtlFanRotateNt));
	getModelIndexByName1 ("htl_fan_rotate_dy", &(objectIndices->miHtlFanRotateDy));
	getModelIndexByName1 ("hotroomfan", &(objectIndices->miHotroomfan));
	
	getModelIndexByName1 ("trafficlight1", &(objectIndices->miTrafficlight1));
	getModelIndexByName1 ("MTraffic4", &(objectIndices->miMTraffic4));
	getModelIndexByName1 ("MTraffic1", &(objectIndices->miMTraffic1));
	getModelIndexByName1 ("vgsstriptlights1", &(objectIndices->miVgsstriplights1));
	getModelIndexByName1 ("MTraffic2", &(objectIndices->miMtraffic2));
	getModelIndexByName1 ("lamppost1", &(objectIndices->miLamppost1));
	getModelIndexByName1 ("lamppost2", &(objectIndices->miLamppost2));
	getModelIndexByName1 ("lamppost3", &(objectIndices->miLamppost3));
	getModelIndexByName1 ("doublestreetlght1", &(objectIndices->miDoublestreetlight1));
	getModelIndexByName1 ("Streetlamp1", &(objectIndices->miStreetlamp1));
	
	getModelIndexByName1 ("Streetlamp2", &(objectIndices->miStreetlamp2));
	getModelIndexByName1 ("doc_crane_cab0", &(objectIndices->miDocCraneCab0));
	getModelIndexByName1 ("doc_crane_cab01", &(objectIndices->miDocCraneCab01));
	getModelIndexByName1 ("doc_crane_cab02", &(objectIndices->miDocCraneCab02));
	getModelIndexByName1 ("doc_crane_cab03", &(objectIndices->miDocCraneCab03));
	getModelIndexByName1 ("boatcranelg0", &(objectIndices->miBoatcraneleg0));
	getModelIndexByName1 ("LODnetopa0", &(objectIndices->miLODnetopa0));
	getModelIndexByName1 ("package1", &(objectIndices->miPackage1));
	getModelIndexByName1 ("Money", &(objectIndices->miMoney));
	getModelIndexByName1 ("barrel1", &(objectIndices->miBarrel1));
	
	getModelIndexByName1 ("barrel2", &(objectIndices->miBarrel2));
	getModelIndexByName1 ("DYN_TV_2", &(objectIndices->miBDynTv2));
	getModelIndexByName1 ("briefcase", &(objectIndices->miBriefcase));
	getModelIndexByName1 ("wglasssmash", &(objectIndices->miWglasssmash));
	getModelIndexByName1 ("glassfx_composh", &(objectIndices->miGlassfxComposh));
	getModelIndexByName1 ("adrenaline", &(objectIndices->miAdrenaline));
	getModelIndexByName1 ("bodyarmour", &(objectIndices->miBodyarmor));
	getModelIndexByName1 ("info", &(objectIndices->miInfo));
	getModelIndexByName1 ("health", &(objectIndices->miHealth));
	getModelIndexByName1 ("bonus", &(objectIndices->miBonus));
	
	getModelIndexByName1 ("bribe", &(objectIndices->miBribe));
	getModelIndexByName1 ("killfrenzy", &(objectIndices->miKillfrenzy));
	getModelIndexByName1 ("camerapickup", &(objectIndices->miCamerapickup));
	getModelIndexByName1 ("gun_para", &(objectIndices->miGunPara));
	getModelIndexByName1 ("para_pack", &(objectIndices->miParaPack));
	getModelIndexByName1 ("bigdollar", &(objectIndices->miBigdollar));
	getModelIndexByName1 ("pickupsave", &(objectIndices->miPickupsave));
	getModelIndexByName1 ("property_locked", &(objectIndices->miPropertyLocked));
	getModelIndexByName1 ("property_fsale", &(objectIndices->miPropertyFsale));
	getModelIndexByName1 ("clothesp", &(objectIndices->miClosesp));
	
	getModelIndexByName1 ("killfrenzy2plyr", &(objectIndices->miKillfrenzy2plyr));
	getModelIndexByName1 ("2player", &(objectIndices->mi2player));
	getModelIndexByName1 ("bollardlight", &(objectIndices->miBollardlight));
	getModelIndexByName1 ("bar_barrier10", &(objectIndices->miBarBarrier10));
	getModelIndexByName1 ("bar_barrier12", &(objectIndices->miBarBarrier12));
	getModelIndexByName1 ("bouy", &(objectIndices->miBouy));
	getModelIndexByName1 ("parktable1", &(objectIndices->miParktable1));
	getModelIndexByName1 ("CJ_OYSTER", &(objectIndices->miCjOyster));
	getModelIndexByName1 ("cj_horse_Shoe", &(objectIndices->miCjHorseShoe));
	getModelIndexByName1 ("wheel_or1", &(objectIndices->miWheelOr1));
	
	getModelIndexByName1 ("nto_b_s", &(objectIndices->miNtoBS));
	getModelIndexByName1 ("nto_b_l", &(objectIndices->miNtoBL));
	getModelIndexByName1 ("nto_b_tw", &(objectIndices->miNtoBTw));
	getModelIndexByName1 ("lamppost1", &(objectIndices->miLamppost1_1));
	getModelIndexByName1 ("mlamppost", &(objectIndices->miMlamppost));
	getModelIndexByName1 ("roadworkbarrier1", &(objectIndices->miRoadworkbarrier));
	getModelIndexByName1 ("littleha_police", &(objectIndices->miLittlehaPolice));
	getModelIndexByName1 ("telgrphpole02", &(objectIndices->miTelgrphpole02));
	getModelIndexByName1 ("trafficlight1", &(objectIndices->miTrafficlight1));
	getModelIndexByName1 ("parkbench1", &(objectIndices->miParkbench1));
	
	getModelIndexByName1 ("Money", &(objectIndices->miMoney));
	getModelIndexByName1 ("od_lightbeam", &(objectIndices->miOdLightbeam));
	getModelIndexByName1 ("ap_radar1_01", &(objectIndices->miApRadar101));
	getModelIndexByName1 ("rcbomb", &(objectIndices->miRcbomb));
	getModelIndexByName1 ("beachball", &(objectIndices->miBeachball));
	getModelIndexByName1 ("sandcastle1", &(objectIndices->miSandcastle1));
	getModelIndexByName1 ("sandcastle2", &(objectIndices->miSandcastle2));
	getModelIndexByName1 ("jellyfish", &(objectIndices->miJellyfish));
	getModelIndexByName1 ("jellyfish01", &(objectIndices->miJellyfish01));
	getModelIndexByName1 ("fish1single", &(objectIndices->miFish1single));
	
	getModelIndexByName1 ("fish1s", &(objectIndices->miFish1s));
	getModelIndexByName1 ("fish2single", &(objectIndices->miFish2single));
	getModelIndexByName1 ("fish2s", &(objectIndices->miFish2s));
	getModelIndexByName1 ("fish3single", &(objectIndices->miFish3single));
	getModelIndexByName1 ("fish3s", &(objectIndices->miFish3s));
	getModelIndexByName1 ("turtle", &(objectIndices->miTurtle));
	getModelIndexByName1 ("dolphin", &(objectIndices->miDolphin));
	getModelIndexByName1 ("shark", &(objectIndices->miShark));
	getModelIndexByName1 ("submarine", &(objectIndices->miSubmarine));
	getModelIndexByName1 ("Esc_step", &(objectIndices->miEscStep));
	
	getModelIndexByName1 ("Esc_step8", &(objectIndices->miEscStep8));
	getModelIndexByName1 ("lounge_wood_up", &(objectIndices->miLoungeWoodUp));
	getModelIndexByName1 ("lounge_towel_up", &(objectIndices->miLoungeTowelUp));
	getModelIndexByName1 ("lounge_wood_dn", &(objectIndices->miLoungeWoodDn));
	getModelIndexByName1 ("lotion", &(objectIndices->miLotion));
	getModelIndexByName1 ("beachtowel01", &(objectIndices->miBeachtowel01));
	getModelIndexByName1 ("beachtowel02", &(objectIndices->miBeachtowel02));
	getModelIndexByName1 ("beachtowel03", &(objectIndices->miBeachtowel03));
	getModelIndexByName1 ("beachtowel04", &(objectIndices->miBeachtowel04));
	getModelIndexByName1 ("blimp_night", &(objectIndices->miBlimpNight));
	
	getModelIndexByName1 ("blimp_day", &(objectIndices->miBlimpDay));
	getModelIndexByName1 ("yt_main_body", &(objectIndices->miYtMainBody));
	getModelIndexByName1 ("yt_main_body2", &(objectIndices->miYtMainBody2));
	getModelIndexByName1 ("mil_samsite", &(objectIndices->miMilSamsite));
	getModelIndexByName1 ("samsite_SFXRF", &(objectIndices->miMilSamsiteSFXRF));
	getModelIndexByName1 ("traincross2", &(objectIndices->miTraincross2));
	getModelIndexByName1 ("traincross1", &(objectIndices->miTraincross1));
	getModelIndexByName1 ("MagnoCrane_03", &(objectIndices->miMagnoCrane03));
	getModelIndexByName1 ("TwrCrane_M_02", &(objectIndices->miTwrCraneM02));
	getModelIndexByName1 ("quarry_cranearm", &(objectIndices->miQuarryCranearm));
	
	getModelIndexByName1 ("kmb_container_red", &(objectIndices->miKmbContainerRed));
	getModelIndexByName1 ("kmb_container_yel", &(objectIndices->miKmbContainerYel));
	getModelIndexByName1 ("kmb_container_blue", &(objectIndices->miKmbContainerBlue));
	getModelIndexByName1 ("barrel4", &(objectIndices->miBarrel4));
	getModelIndexByName1 ("kmb_container_open", &(objectIndices->miKmbContainerOpen));
	getModelIndexByName1 ("blockpallet", &(objectIndices->miBlockpallet));
	getModelIndexByName1 ("magnocrane5_SFS", &(objectIndices->miMagnocrane5SFS));
	getModelIndexByName1 ("kmb_deadarm", &(objectIndices->miKmbDeadarm));
	getModelIndexByName1 ("kmb_deadleg", &(objectIndices->miKmbDeadleg));
	getModelIndexByName1 ("kmb_deadhead", &(objectIndices->miKmbDeadhead));
	
	getModelIndexByName1 ("kmb_deadtorso", &(objectIndices->miKmbDeadtorso));
	getModelIndexByName1 ("grasshouse", &(objectIndices->miGrasshouse));
	getModelIndexByName1 ("grassplant", &(objectIndices->miGrassplant));
	getModelIndexByName1 ("INDUS_MAGNET", &(objectIndices->miIndusMagnet));
	getModelIndexByName1 ("CJ_PLAYER_HARNESS", &(objectIndices->miCjPlayerHarness));
	getModelIndexByName1 ("dyn_quarryrock01", &(objectIndices->miDynQuarryrock01));
	getModelIndexByName1 ("dyn_quarryrock02", &(objectIndices->miDynQuarryrock02));
	getModelIndexByName1 ("dyn_quarryrock03", &(objectIndices->miDynQuarryrock03));
	getModelIndexByName1 ("KMB_ATM3", &(objectIndices->miKmbAtm3));
	getModelIndexByName1 ("dead_tied_cop", &(objectIndices->miDeadTiedCop));
	
	getModelIndexByName1 ("bonyrd_windsock", &(objectIndices->miBonyrdwindsock));
	getModelIndexByName1 ("DYN_WREKING_BALL", &(objectIndices->mimDynWrekingBall));
	getModelIndexByName1 ("bomb", &(objectIndices->miBomb));
	getModelIndexByName1 ("mini_magnet", &(objectIndices->miMiniMagnet));
	getModelIndexByName1 ("ab_carcass", &(objectIndices->miAbCarcass));
	getModelIndexByName1 ("wong_dish", &(objectIndices->miWongDish));
	getModelIndexByName1 ("CJ_BEAR_BOTTLE", &(objectIndices->miCjBearBottle));
	getModelIndexByName1 ("CJ_CIGGY", &(objectIndices->miCjCiggy));
	getModelIndexByName1 ("CJ_TRAFFIC_LIGHT3", &(objectIndices->miCjTrafficLight3));
	getModelIndexByName1 ("CJ_TRAFFIC_LIGHT4", &(objectIndices->miCjTrafficLight4));
	
	getModelIndexByName1 ("CJ_TRAFFIC_LIGHT5", &(objectIndices->miCjTrafficLight5));
	getModelIndexByName1 ("GAY_TRAFFIC_LIGHT", &(objectIndices->miGayTrafficLight));
	getModelIndexByName1 ("imy_shash_wall", &(objectIndices->miImyShashWall));
	getModelIndexByName1 ("flare", &(objectIndices->miFlare));
	getModelIndexByName1 ("hydralics", &(objectIndices->miHydralics));
	getModelIndexByName1 ("stereo", &(objectIndices->miStereo));
	getModelIndexByName1 ("BBALL_COL", &(objectIndices->miBballCol));
	getModelIndexByName1 ("k_poolballcue", &(objectIndices->miKPoolballcue));
	getModelIndexByName1 ("punchbagnu", &(objectIndices->miPunchbagnu));
	getModelIndexByName1 ("imy_bbox", &(objectIndices->miImyBbox));
}

// прочитать физические параметры объектов
// параметры хранятся в пакованном виде: для объектов с одинаковыми параметрами используется одна и та же запись 
//	после того, как прочитаны параметры объекта, делается попытка найти такую же запись. 
//	если это удалось, прочитанная запись игнорируется, а вместо нее используется найденная
static void loadObjectDat(char * filename, bool) {	// TODO: CObjectData::initialize
	memset (objectInfo, 0, sizeof (CObjectInfo)*5);	// первые пять объектов, которые заполняются напрямую
	
	// заполним первые пять элементов массива
	objectInfo[0].fMass = 99999.0f;
	objectInfo[0].fTurnMass = 99999.0f;
	objectInfo[0].fAirResistance = 0.99f;
	objectInfo[0].fElasticity = 0.1f;
	objectInfo[0]._f10 = 100.0f/50.0f*99999.0f/125.0f;	// 100% / percentSubmerged * mass / 125
	objectInfo[0].fUprootLimit = 0.0f;
	objectInfo[0].fColDamageMultiplier = 1.0f;
	objectInfo[0].colDamageEffect = 0;
	objectInfo[0].specColResponseCase = 0;
	objectInfo[0].cameraAvoidObject = 2;
	objectInfo[4] = objectInfo[3] = objectInfo[2] = objectInfo[1] = objectInfo[0];

	objectInfo[1].cameraAvoidObject = 0;
	objectInfo[2].specColResponseCase = 4;
	objectInfo[3].specColResponseCase = 4;
	objectInfo[3].cameraAvoidObject = 0;
	objectInfo[4].specColResponseCase = 5;

	// откроем файл
	chdirProgramDir ("");
	FILE * f = fopen (filename, "r");
	if (!f)
		return;

	// debug ("Loading object.dat...\n");

	int i = 5;	// индекс добавляемого объекта
	CObjectInfo * obj = objectInfo+5;	// текущий читаемый объект
	while (char * line = getConfigLine (f)) {
		if (*line == '*')	// end of file
			break;
		if (*line == ';' || *line == '\0')	// comment or empty line
			continue;

		memset (obj, 0, sizeof (CObjectInfo));	// TODO: это, теоретически, можно выкинуть (вернее, заполнять не все)

		char	objectName[256];
		float	_percentSubmerged;
		int		_colDamageEffect, _specialColResponseCase, _cameraAvoid, _causesExplosion, _fxType;
		char	fxName[256];
		sscanf (line, "%s %f %f %f %f %f %f %f %d %d %d %d %d %f %f %f %s %f %f %f %f %f %d %d",	// 24 parameters
			objectName, &(obj->fMass), &(obj->fTurnMass), &(obj->fAirResistance), &(obj->fElasticity),
			&_percentSubmerged, &(obj->fUprootLimit), &(obj->fColDamageMultiplier), &_colDamageEffect, &_specialColResponseCase,
			&_cameraAvoid, &_causesExplosion, &_fxType, &(obj->fxOffset.x), &(obj->fxOffset.y),
			&(obj->fxOffset.z), fxName, &(obj->_f34), &(obj->breakVelocity.x), &(obj->breakVelocity.y),
			&(obj->breakVelocity.z), &(obj->fBreakVelocityRand), &(obj->smashMultiplier), &(obj->sparksOnImpact));

		obj->fxType = _fxType;
		obj->particleData = _fxType > 0 ? particleData->findDataByName (fxName) : NULL;

		obj->specColResponseCase = _specialColResponseCase;
		obj->colDamageEffect = _colDamageEffect;
		obj->causesExplosion = _causesExplosion;
		obj->cameraAvoidObject = _cameraAvoid;
		obj->_f10 = 100.0f/_percentSubmerged*obj->fMass/125.0f;

		CModelBase * model = getModelIndexByName (objectName, NULL);

		if (!model)
			debug ("ERROR: model '%s' not found \n", objectName);
		if (!model)
			continue;

		if (objectInfo[0].fMass == obj->fMass && 
			objectInfo[0].fColDamageMultiplier == obj->fColDamageMultiplier && 
			objectInfo[0].colDamageEffect == obj->colDamageEffect) {
			
			if (objectInfo[0].specColResponseCase == obj->specColResponseCase) {							
				model->objectInfoIndex = obj->cameraAvoidObject ? 0 : 1;
				continue;
			}	
			if (objectInfo[2].specColResponseCase == obj->specColResponseCase) {				
				model->objectInfoIndex = obj->cameraAvoidObject ? 2 : 3;
				continue;
			}
		}
		// >> 5B55F5 проверим, есть ли уже такой объект
		bool found = false;
		for (int j = 0; j < i; j++) 
			if (!memcmp (objectInfo+j, obj, sizeof (CObjectInfo))) {
				model->objectInfoIndex = j;
				found = true;
				break;
			}
		
		if (!found) {	// не найден, используем считанную запись
			model->objectInfoIndex = i++;
			obj++;
		}		
		
		assert (i < 160);	// проверим, хватает ли массива параметров
	}
	fclose (f);
}

static void setupWhiteTexture () {	// TODO: optimize function!
	CTexDictionary * savedTxd = texMan.getCurrentTxd ();	
	texMan.setCurrentTxd ((*txdPool)->getTxd ((*txdPool)->indexByName ("particle")));	
	if (*whiteTexture == NULL) {
		CTexture * wt = texMan.readTexture ("white", NULL); 
		*whiteTexture = wt;
		wt->name[0] = '@';
		wt->filterAddressing &= 0xFFFFFF00;	// TODO: проверить, нужно ли это
		wt->filterAddressing |= 2;
	}
	texMan.setCurrentTxd (savedTxd);
}

// read car color combinations
static void readCarcolsDat () {	// >> 5B6890
	char linebuf[1024];	
	DWORD tag = 0;
	RGBA * col = vehicleColors;
	int colors[32];
	char modelName[64];
	int r, g, b;
	int numcolors;
	CModelCars * model;

	FILE * f = fopen ("data/carcols.dat", "rb");
	while (fgets (linebuf, 1024, f)) {
		char * line = linebuf;
		while (*line <= ' ') {	// trim spaces
			if (*line == 0x0A || *line == '\0')
				break;
			line++;
		}

		if (*line == '#' || *line == '\0' || *line == '\xA' || *line == '\xD')	// skip comment or empty line
			continue;
	
		debug ("reading car colors: %x: %s\n", tag, line);
		for (char * ptr = line; (*ptr != 0x0A) && (*ptr != '\0'); ptr++) 
			if (*ptr == 0x0D || *ptr == ',')
				*ptr = ' ';
		
		if (!tag) {	// секция еще не началась? попробуем получить тэг
			tag = *(DWORD *)line;
			if (tag != 0x34726163)	// 'car4' - единственный четрыехсимвольный тэг, остальные - трехсимвольные
				tag &= 0x00FFFFFF;	
			continue;
		}

		if (((*(DWORD *)line) & 0x00FFFFFF) == 0x646e65) {	// 'end' ?
			tag = 0;	// end of block
			continue;
		}

		switch (tag) {
		case 0x006c6f63:	// 'col'
			sscanf (line, "%d %d %d", &r, &g, &b);
			col->r = (BYTE)r;
			col->g = (BYTE)g; 
			col->b = (BYTE)b;
			col->a = 0xFF;
			col++;
			break;
		case 0x00726163:	// 'car'			 
			numcolors = sscanf (line, "%s %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d", modelName, 
				colors, colors+1, colors+2, colors+3, colors+4, colors+5, colors+6, colors+7,
				colors+8, colors+9, colors+10, colors+11, colors+12, colors+13, colors+14, colors+15);
			model = (CModelCars *)getModelIndexByName (modelName, NULL);
			model->carColorCount = (numcolors-1) >> 1;
			for (int i = 0; i < model->carColorCount; i++) {
				model->primaryColors[i] = colors[i*2];
				model->secondaryColors[i] = colors[i*2+1];
				model->tertiaryColors[i] = model->quaternaryColors[i] = 0;
			}
			debug ("carcol: %s: %d\n", modelName, model->carColorCount);
			break;
		case 0x34726163:	// 'car4'
			numcolors = sscanf (line, "%s %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d", modelName, 
				colors, colors+1, colors+2, colors+3, colors+4, colors+5, colors+6, colors+7,
				colors+8, colors+9, colors+10, colors+11, colors+12, colors+13, colors+14, colors+15,
				colors+16, colors+17, colors+18, colors+19, colors+20, colors+21, colors+22, colors+23,
				colors+24, colors+25, colors+26, colors+27, colors+28, colors+29, colors+30, colors+31);
			model = (CModelCars *)getModelIndexByName (modelName, NULL);
			model->carColorCount = (numcolors-1) >> 2;
			for (int i = 0; i < model->carColorCount; i++) {
				model->primaryColors[i] = colors[i*4];
				model->secondaryColors[i] = colors[i*4+1];
				model->tertiaryColors[i] = colors[i*4+2];
				model->quaternaryColors[i] = colors[i*4+3];
			}
			debug ("carcol: %s (4)\n", modelName, model->carColorCount);
			break;
		default:
			debug ("ERROR: carcols.dat - unrecognized tag %08x\n", tag);
			tag = 0;	// reset tag
			continue;
		}
	}
	fclose (f);
}


static void readCarmodsDat () { // >> 5B65A0
	memset (wheelSetSizes, 0, 4*sizeof (WORD));
	int tag = 0;
	char * delimiter = " \t,";
	FILE * f = fopen ("data/carmods.dat", "rb");
	while (char * line = getConfigLine (f)) {
		if ('#' == *line || '\0' == *line)	// comment
			continue;	// skip comment

		if (tag == 0) {
			if (!strncmp (line, "link", 4))
				tag = 1;
			else if (!strncmp (line, "mods", 4))
				tag = 2;
			else if (!strncmp (line, "wheel", 5))
				tag = 3;
			else 
				debug ("carmods: can't understand '%s'\n", line);
			continue;
		}

		if (((*(DWORD *)line) & 0x00FFFFFF) == 0x646e65) {	// 'end' ?
			tag = 0;	// end of block
			continue;
		}
		
		char * name;
		int index;
		int index2;
		CModelBase * component;
		int componentCount;
		CModelCars * model;
		int wheelNum;

		switch (tag) {
		case 1:	// 'link'
			index = index2 = -1;			
			name = strtok (line, delimiter);
			if (!name)
				continue;
			component = getModelIndexByName (name, &index);
			component->setComponentFlags (name);
			name = strtok (NULL, delimiter);
			component = getModelIndexByName (name, &index2);
			component->setComponentFlags (name);
			componentLinks->setLink (index, index2);
			break;
		case 2:	// 'mods'
			name = strtok (line, delimiter);
			model = (CModelCars *)getModelIndexByName (name, &index);
			componentCount = 0;
			for (name = strtok (NULL, delimiter); name; name = strtok (NULL, delimiter)) {	// process component names
				component = getModelIndexByName (name, &index);
				component->setComponentFlags (name);
				model->carMods[componentCount++] = index;
			}
			component = getModelIndexByName ("hydralics", &index);
			component->setComponentFlags ("hydralics");
			model->carMods[componentCount++] = index;
			component = getModelIndexByName ("stereo", &index);
			component->setComponentFlags ("stereo");
			model->carMods[componentCount++] = index;			
			break;
		case 3:	// 'wheels'
			sscanf (line, "%d", &wheelNum);
			strtok (line, delimiter);	// skip wheel number
			for (name = strtok (NULL, delimiter); name; name = strtok (NULL, delimiter)) {
				component = getModelIndexByName (name, &index);
				component->setComponentFlags (name);
				// inlined >> 4C8700 - add wheel to set
				wheelSets[wheelNum*15+wheelSetSizes[wheelNum]] = index;
				wheelSetSizes[wheelNum]++;
			}
			break;
		}
	}
	fclose (f);
}
static void setupVehicleInfo () {	// >> 5B8F00
	readCarcolsDat ();
	updateLoadingBar ();
	readCarmodsDat ();
	updateLoadingBar ();
	setupWhiteTexture ();
	updateLoadingBar ();

	int vtxd = (*txdPool)->indexByNameAlways ("vehicle");	
	(*txdPool)->load (vtxd, "models/generic/vehicle.txd");
	(*txdPool)->addRef (vtxd);
	*vehicleTxd = (*txdPool)->at (vtxd)->txd;
	*texVehiclelights128 = (*vehicleTxd)->findNamedTexture ("vehiclelights128");
	*texVehiclelightson128 = (*vehicleTxd)->findNamedTexture ("vehiclelightson128");
	updateLoadingBar ();
	*vehicleStructPool = new CPool (50, sizeof (CVehicleStruct));	// TODO: move to pool initialization
	_5D5BC0 ();	// setup texture 'vehiclegrunge256' TODO: inline it!
}

//=============================================================================
//	"IPL" section

BYTE (*_46A760)(CEntity * e) = (BYTE (__cdecl *)(CEntity *))0x46A760;
void (__thiscall * _59B020)(CMatrix * _this, float angle) = (void (__thiscall *)(CMatrix *, float))0x59B020;
void (__thiscall * _54F560)(CEntity * _this) = (void (__thiscall *)(CEntity *))0x54F560;
void (__thiscall * _54F1B0)(Placement * _this, XYZ *) = (void (__thiscall *)(Placement *, XYZ *))0x54F1B0;
void (__thiscall * _59BBF0)(XYZ *, RtQuat *) = (void (__thiscall *)(XYZ *, RtQuat *))0x59BBF0;
void (__thiscall * _404200)(DWORD _this, CRect *) = (void (__thiscall *)(DWORD, CRect *))0x404200;
DWORD (__cdecl * colfilepool_atindex)(int index) = (DWORD (__cdecl *)(int))0x410800;

// ## parse IPL tags ##
static void * readIplInst (char * line) {
	char name[24];
	D3DVECTOR pos;
	RtQuat	rot;
	int id;
	int interior;
	int lod;
	sscanf (line, "%d %s %d %f %f %f %f %f %f %f %d", &id, name, &interior, &pos.x, &pos.y, &pos.z, &rot.imag.x, &rot.imag.y, &rot.imag.z, &rot.real, &lod);

	// addIplInst inlined >> 538090 
	CModelBase * model = modelPtrs[id];
	if (!model)
		return NULL;

	CEntity * e;
	if (model->objectInfoIndex != -1) {	// CDummyObject
		e = new CDummyObject ();
		e->setModelIndexNoCreate (id);
		// TODO: remove call to _46A760; always returns false for CDummy/CDummyObject
		if (_46A760 (e) && (model->flags & 0x7800) != 0x2800)
			e->_f1C &= 0xFFFFFF7F;	// TODO: ~0x80
	} else {
		e = (model->getType () == 5) && (model->flags & 0x100) ? new CTreadable () : new CBuilding ();
		e->setModelIndexNoCreate (id);
		if (model->flags & 0x10)
			e->_f1C |= 0x10000;
		if (model->drawDistance < 2.0f)
			e->_f1C &= 0xFFFFFF7F;	// TODO: ~0x80
	}
	// >> 5381D5
	if (fabsf (rot.imag.x) <= 0.05f && fabsf (rot.imag.y) <= 0.05f && (!(interior & 0x200) || rot.imag.x == 0.0f || rot.imag.y == 0.0f)) {
		if (rot.imag.z < 0.0f)	{
			if (e->xyz) 
				_59B020 (e->xyz, acosf (rot.real)*2.0f);
			else
				e->pl.angle = acosf (rot.real)*2.0f;
		} else {
			if (e->xyz) 
				_59B020 (e->xyz, acosf (rot.real)*-2.0f);
			else
				e->pl.angle = acosf (rot.real)*-2.0f;
		}					
	} else {
		e->allocateXYZ ();
		rot.imag.x = -rot.imag.x;
		rot.imag.y = -rot.imag.y;
		rot.imag.z = -rot.imag.z;
		if (e->xyz) {
			_54F560 (e);
			_54F1B0 (&(e->pl), e->xyz);
		}		
		_59BBF0 (e->xyz, &rot);
	}
	// >> 5382ED
	if (e->xyz) 
		e->xyz->m.pos = pos;
	else
		e->pl.pos = pos;

	if (interior & 0x400) 
		e->_f1C |= 0x100000;
	if (interior & 0x800) 
		e->_f1C |= 0x40000000;
	if (interior & 0x1000) 
		e->_f1C |= 0x80000000;
	if (interior & 0x100) 
		e->_f1C |= 0x20000000;
	e->interior = (BYTE)interior;
	e->_f30 = (BYTE *)lod;

	if (objectIndices->miTraincross2 == id) {
		if (e->xyz) {
			_54F560 (e);
			_54F1B0 (&(e->pl), e->xyz);
		}
		e->allocateXYZ ();	// TODO: а это еще зачем?
		if (e->xyz) {
			_54F560 (e);
			_54F1B0 (&(e->pl), e->xyz);
		}
		_59B020 (e->xyz, 1.3508849f);
	}

	if (!model->colModel)
		return e;

	if (model->colModel->flags & 1) {
		if (model->colModel->_f28) {
			CRect r;
			_404200 (colfilepool_atindex (model->colModel->_f28), e->getBoundRect (&r));
		}
	} else 
		e->_f1C &= ~1;

	D3DVECTOR * p = e->xyz ? &(e->xyz->m.pos) : &(e->pl.pos);
	if (model->colModel->bbox.sup.z + p->z < 0)
		e->_f1C |= 0x100000;
	return e;
}

static void readIplZone (char * line) {	// >> 5B4AB0
	char	_name[24];
	int		type;
	float	x1, y1, z1, x2, y2, z2;
	int		_island;	// town number in SA
	char	_text[12];
	if (10 != sscanf (line, "%s %d %f %f %f %f %f %f %d %s", _name, &type, &x1, &y1, &z1, &x2, &y2, &z2, &_island, _text))
		return;
		
	// addIplZone >> 5728A0
	// TODO: move to CZones::addZone
	if (type < 0)
		return;

	// prepare zone bounds
	float t;
	if (x1 > x2) {
		t = x2; 
		x2 = x1; 
		x1 = t;
	}
	if (y1 > y2) {
		t = y2;
		y2 = y1;
		y1 = t;
	}
	if (z1 > z2) {
		t = z2;
		z2 = z1;
		z1 = t;
	}

	// prepare zone name and text
	char zoneName[8];
	char zoneText[8];
	for (int i = 0; i < 7; i++) 
		zoneName[i] = (_name[i] >= 'a' && _name[i] <= 'z') ? _name[i]-0x20 : _name[i];
	zoneName[7] = '\0'; 
	for (int i = 0; i < 7; i++) 
		zoneText[i] = (_text[i] >= 'a' && _text[i] <= 'z') ? _text[i]-0x20 : _text[i];
	zoneText[7] = '\0'; 
	
	
	if (type <= 1) {
		CZone * zone = infoZones+(*infoZoneCount);

		memcpy (zone->name, zoneName, 8);	// TODO: убрать лишнюю переменную
		memcpy (zone->text, zoneText, 8);	// TODO: убрать лишнюю переменную
		zone->x1 = (short)x1;
		zone->y1 = (short)y1;
		zone->z1 = (short)z1;
		zone->x2 = (short)x2;
		zone->y2 = (short)y2;
		zone->z2 = (short)z2;
		zone->type = (BYTE)type;
		zone->island = _island;
	
		// >> 572180 (inlined)
		bool found = false;
		for (int i = 0; i < *infoZoneCount; i++) 
			if (!memcmp (infoZones[i].name, zone->name, 8)) {
				zone->extraIndexInfo = infoZones[i].extraIndexInfo;
				found = true;
				break;
			}		
		if (!found)
			zone->extraIndexInfo = (*zoneExtraIndexCount)++;	
		//

		(*infoZoneCount)++;
	} else if (type == 3) {
		CZone * zone = mapZones+(*mapZoneCount);
		(*mapZoneCount)++;

		memcpy (zone->name, zoneName, 8);	// TODO: убрать лишнюю переменную
		memcpy (zone->text, zoneText, 8);	// TODO: убрать лишнюю переменную
		zone->x1 = (short)x1;
		zone->y1 = (short)y1;
		zone->z1 = (short)z1;
		zone->x2 = (short)x2;
		zone->y2 = (short)y2;
		zone->z2 = (short)z2;
		zone->type = 3;
		zone->island = _island;
	}
}
static void readIplCull (char * line) {
	float centerx, centery, centerz, _left, ywidth, zbottom, xwidth, _top, ztop, vx, vy, vz, cm;
	DWORD flags, flags2;

	if (14 == sscanf (line, "%f %f %f %f %f %f %f %f %f %d %f %f %f %f", 
		&centerx, &centery, &centerz, &_left, &ywidth, &zbottom, &xwidth, &_top, &ztop, &flags, &vx, &vy, &vz, &cm)) {
		// >> 72DC10 -- addIplCull1 inlined
		CullZoneReflection * cz = cullZonesR+(*cullZoneRCount);
		(*cullZoneRCount)++;
		cz->x = (short)(centerx-_left-xwidth);
		cz->y = (short)(centery-ywidth-_top);
		cz->_f4 = (short)(_left+_left);
		cz->ywidth = (short)(ywidth+ywidth);
		cz->zbottom = (short)zbottom;
		cz->xwidth = (short)(xwidth+xwidth);
		cz->_fA = (short)(_top+_top);
		cz->ztop = (short)ztop;
		cz->cm = cm;
		cz->flags = (BYTE)flags;
		cz->vx = (char)(vx*100.0f);
		cz->vy = (char)(vy*100.0f);
		cz->vz = (char)(vz*100.0f);
		// debug ("CullZone (R): %dx%d, (%d, %d, %d), (%d, %d, %d), %f, (%d, %d, %d),  %04x\n", cz->x, cz->y, cz->_f4, cz->ywidth, cz->zbottom, cz->xwidth, cz->_fA, cz->ztop, cz->cm, cz->vx, cz->vy, cz->vz, flags);
	} else {
		sscanf (line, "%f %f %f %f %f %f %f %f %f %d %d", &centerx, &centery, &centerz, &_left, &ywidth, &zbottom, &xwidth, &_top, &ztop, &flags, &flags2);
		// >> 72DF70 -- addIplCull2 inlined
		if ((flags & 0x80) || (flags&0x800)) {	// TODO: &0x880
			// >> 72DB50 -- addIplCull3 inlined
			CullZone * cz = cullZones3+(*cullZone3Count);
			(*cullZone3Count)++;

			cz->x = (short)(centerx-_left-xwidth);
			cz->y = (short)(centery-ywidth-_top);
			cz->_f4 = (short)(_left+_left);
			cz->ywidth = (short)(ywidth+ywidth);
			cz->zbottom = (short)zbottom;
			cz->xwidth = (short)(xwidth+xwidth);
			cz->_fA = (short)(_top+_top);
			cz->ztop = (short)ztop;
			cz->flags = (WORD)flags;

			// debug ("CullZone (3): %dx%d, (%d, %d, %d), (%d, %d, %d), %04x\n", cz->x, cz->y, cz->_f4, cz->ywidth, cz->zbottom, cz->xwidth, cz->_fA, cz->ztop, flags);

			flags &= 0xF77F;
		}
		
		if (flags) {
			CullZone * cz = cullZones+(*cullZone2Count);
			(*cullZone2Count)++;

			cz->x = (short)(centerx-_left-xwidth);
			cz->y = (short)(centery-ywidth-_top);
			cz->_f4 = (short)(_left+_left);
			cz->ywidth = (short)(ywidth+ywidth);
			cz->zbottom = (short)zbottom;
			cz->xwidth = (short)(xwidth+xwidth);
			cz->_fA = (short)(_top+_top);
			cz->ztop = (short)ztop;
			cz->flags = (WORD)flags;

			// debug ("CullZone: %dx%d, (%d, %d, %d), (%d, %d, %d), %04x\n", cz->x, cz->y, cz->_f4, cz->ywidth, cz->zbottom, cz->xwidth, cz->_fA, cz->ztop, flags);
		}
	}
}

static void readIplOccl (char * line, char * filename) {
	float directMidX, directMidY, bottomZ, widthX, widthY, height, rotation, rotation2, rotation3;
	DWORD flags;
	bool isInterior = strstr(filename, "int.ipl") ? true : false;	// TODO: make parameter instead filename

	sscanf (line, "%f %f %f %f %f %f %f %f %f %d ", &directMidX, &directMidY, &bottomZ, &widthX, &widthY, &height, &rotation, &rotation2, &rotation3, &flags);
	/// debug ("Occl: '%s' (%d)\n", filename, isInterior);
	/// debug ("Occl: %f %f %f, %f %f %f, (%f, %f, %f), %d\n", directMidX, directMidY, bottomZ, widthX, widthY, height, rotation, rotation2, rotation3, flags);

	bottomZ += height/2.0f;

	// >> 71DCD0 -- addOcclusionBox inlined
	widthX = fabsf (widthX);
	widthY = fabsf (widthY);
	height = fabsf (height);

	// TODO: check count
	// для компилятора конфигов, требуются проверки на количество occlusion box (чтобы не выйти за греницы массивов), 
	// и вырожденные случаи (два и больше размеров равны нулю)
	OcclusionBox * ob = isInterior ? occlBoxInt+(*occlBoxIntCount) : occlBoxes+(*occlBoxCount);
	ob->widthX = ((short)widthX)*4;
	ob->widthY = ((short)widthY)*4;
	ob->height = ((short)height)*4;
	
	// int isLine = ob->widthX ? 0 : 1;
	// if (!(ob->widthY))
	//	isLine++;
	// if (!(ob->height))
	//	isLine++;
	// if (isLine > 1)
	//	return;	// указана точка или линия. Не обрабатывается.

	ob->midX = (short)(directMidX*4.0f);
	ob->midY = (short)(directMidY*4.0f);
	ob->midZ = (short)(bottomZ*4.0f);

	// TODO: упростить выражения
	while (rotation < 0.0f)
		rotation += 360.0f;
	while (rotation >= 360.0f)
		rotation -= 360.0f;
	rotation = rotation*(float)M_PI/180.0f;
	ob->rotation = (BYTE)(rotation*128.0f/(float)M_PI);

	while (rotation2 < 0.0f)
		rotation2 += 360.0f;
	while (rotation2 >= 360.0f)
		rotation2 -= 360.0f;
	rotation2 = rotation2*(float)M_PI/180.0f;
	ob->rotation2 = (BYTE)(rotation2*128.0f/(float)M_PI);

	while (rotation3 < 0.0f)
		rotation3 += 360.0f;
	while (rotation3 >= 360.0f)
		rotation3 -= 360.0f;
	rotation3 = rotation3*(float)M_PI/180.0f;
	ob->rotation3 = (BYTE)(rotation3*128.0f/(float)M_PI);

	if (!isInterior) {
		if (flags) 
			ob->flags |= 0x8000;
		else
			ob->flags &= 0x7FFF;
		WORD f = (ob->flags ^ *_8D5D68)&0x7FFF;
		ob->flags ^= f;
		*_8D5D68 = (WORD)(*occlBoxCount);

		(*occlBoxCount)++;
	} else 
		(*occlBoxIntCount)++;
}

void readIplGrge (char * line) {
	float x1, x2, y1, y2, z1, z2, frontX, frontY;
	int type, door;
	char name[8];

	if (11 != sscanf (line, "%f %f %f %f %f %f %f %f %d %d %s", &x1, &y1, &z1, &frontX, &frontY, &x2, &y2, &z2, &door, &type, name))
		return;

	// >> 4471E0 -- addGarage inlined
	float _f = x1 > frontX ? x1 : frontX;	// test ah, 5; jp ... (frontX)
	if (x2 > _f)
		_f = x2;
	float _v4 = frontX+x2-x1;
	
	CGarage * g = garages+(*garageCount);
	g->right = _f > _v4 ? _f : _v4;

	_f = x1 < frontX ? x1 : frontX;
	if (x2 < _f)
		_f = x2;
	g->left = _f < _v4 ? _f : _v4;

	_f = y1 > frontY ? y1 : frontY;
	if (y2 > _f)
		_f = y2;
	_v4 = frontY+y2-y1;
	g->back = _f > _v4 ? _f : _v4;

	_f = y1 < frontY ? y1 : frontY;
	if (y2 < _f)
		_f = y2;
	g->front = _f < _v4 ? _f : _v4;

	g->lowerLeft.x = x1;
	g->lowerLeft.y = y1;
	g->lowerLeft.z = z1;
	g->dir1x = frontX-x1;
	g->dir1y = frontY-y1;
	g->dir2x = x2-x1;
	g->dir2y = y2-y1;
	g->topZ = z2;

	g->normalizedWidth = sqrtf (g->dir1x*g->dir1x + g->dir1y*g->dir1y);
	g->dir1x /= g->normalizedWidth;
	g->dir1y /= g->normalizedWidth;
	g->normalizedHeight = sqrtf (g->dir2x*g->dir2x + g->dir2y*g->dir2y);
	g->dir2x /= g->normalizedHeight;
	g->dir2y /= g->normalizedHeight;
	g->_f4C = g->_f4F = type;
	memcpy (g->name, name, 8);

	// TODO: свернуть конструкцию: _f4E = (_f4E & 0xC7)|((door&7)<<3)
	if (door & 1) 
		g->_f4E |= 8;
	else
		g->_f4E &= ~8;
	if (door & 2)
		g->_f4E |= 0x10;
	else
		g->_f4E &= ~0x10;
	if (door & 4) 
		g->_f4E |= 0x20;
	else
		g->_f4E &= ~0x20;

	(*garageCount)++;
}
//-----------------------------------------------------------------------------
static void loadIplFile (char * filename) {
	FILE * f = fopen (filename, "rb");
	if (!f)
		return ;

	*_iplInstNum = 0;

	DWORD tag = 0;
	while (char * line = getConfigLine (f)) {
		if ('#' == *line || '\0' == *line)
			continue;	// skip comment

		if (tag == 0) {	// tag type undefined. get new tag and skip to the new line
			tag = *(DWORD *)line;	
			continue;
		} 
		
		if (((*(DWORD *)line) & 0x00FFFFFF) == 0x646e65) {	// 'end' ?
			tag = 0;	// end of block
			continue;
		}
		if (0x68746170 == tag)	// 'path' 
			break;

		// TODO: отсортировать по частоте использования
		switch (tag) {			
		case 0x74736e69:	// 'inst' - 2
			_iplInst[(*_iplInstNum)++] = readIplInst (line);			
			break;
		case 0x746c756d:	// 'mult' - 3
			break;
		case 0x656e6f7a:	// 'zone' - 4
			readIplZone (line);	
			break;
		case 0x6c6c7563:	// 'cull' - 5
			readIplCull (line);
			break;
		case 0x6c63636f:	// 'occl' - 6
			readIplOccl	(line, filename);
			break;
		case 0x65677267:	// 'grge' - 8
			readIplGrge (line);
			break;
		case 0x78656e65:	// 'enex' - 9
			readIplEnex (line);
			break;
		case 0x6b636970:	// 'pick' - 10
			readIplPick (line);
			break;
		case 0x73726163:	// 'cars' - 11
			readIplCars (line, 0);
			break;
		case 0x706d756a:	// 'jump' - 12
			readIplJump (line);
			break;
		case 0x63796374:	// 'tcyc' - 13 
			readIplTcyc (line);
			break;
		case 0x6f7a7561:	// 'auzo' - 14
			readIplAuzo (line);
			break;
		default:
			debug ("IPL: ERROR: unknown tag %08x\n", tag);
		}
	}
	int instCount = -1;
	if (*_iplInstNum > 0) {
		// inlined >> 404780 and 4047B0
		_iplInstBlocks[*_iplInstBlockNum] = new void *[*_iplInstNum];
		// end of inlined block
		memcpy (_iplInstBlocks[*_iplInstBlockNum], _iplInst, *_iplInstNum*sizeof (void *));
		instCount = (*_iplInstBlockNum)++;
	}
	_5B51E0 (_404DE0 (filename, instCount, _iplInst+(*_iplInstNum)));
	_405110 (instCount);	//	releaseIplFile
}
//=============================================================================
//	main game config reader
void readGameConfig (char * filename) {
	debug ("readGameConfig: %s\n", filename);

	CTexDictionary * txd = texMan.getCurrentTxd ();
	if (!txd) 
		texMan.setCurrentTxd((txd = new CTexDictionary ()));

	FILE * config = fopen (filename, "r");
	if (!config) {
		debug ("readGameConfig: unable to open file\n");
		return;	
	}

	bool modelsDone = false;	// выставляется после того, как обработаны все .ide

	while (char * line = getConfigLine (config)) {	// for each line ...
		if ('#' == *line || '\0' == *line)	// comment
			continue;	// skip comment

		if (!strncmp (line, "EXIT", 4)) {	// "EXIT"
			debug ("readGameConfig: EXIT \n");
			break;
		}

		if (!strncmp (line, "TEXDICTION", 10)) {	// "TEXDICTION"
			debug ("readGameConfig: TEXDICTION (%s)\n", line+11);
			showProgressMessage (line);
			CTexDictionary * txdFile = loadTxdFile (line+11);
			txd->addAllTexturesFrom (txdFile);	// TODO: если этот кусок будет использоваться, логичнее к CTexDictionary добавить метод loadFromFile, который будет добавлять в словарь все текстуры из файла
			delete txdFile;
			continue;
		}

		if (!strncmp (line, "IMG", 3)) {	// "IMG"
			debug ("readGameConfig: IMG (%s)\n", line+4);
			if (strncmp (line+4, "MODELS\\GTA_INT.IMG", 19))
				_openImgFile (line+4, true);
			else 
				debug ("readGameConfig: GTA_INT.IMG skipped\n");
			continue;
		}

		if (!strncmp (line, "COLFILE", 7)) {
			debug ("readGameConfig: COLFILE (%s)\n", line+10);
			showProgressMessage (line+10);
			loadColFile (line+10);

			continue;
		}

		if (!strncmp (line, "MODELFILE", 9)) {
			debug ("readGameConfig: MODELFILE (%s)\n", line+10);
			showProgressMessage (line+10);
			loadModelFile (line+10);
			continue;
		}

		/*	NOTE: I'd tried to find any HIERFILE, but without success
		if (!strncmp (line, "HIERFILE", 8)) {
			debug ("readGameConfig: HIERFILE (%s)\n", line+9);
			showProgressMessage (line+9);
			loadHierFile (line+9);
			continue;
		}
		*/

		if (!strncmp (line, "IDE", 3)) {
			debug ("readGameConfig: IDE (%s)\n", line+4);
			showProgressMessage (line+4);
			loadIdeFile (line+4);
			continue;
		}

		if (!strncmp (line, "IPL", 3)) {
			debug ("readGameConfig: IPL (%s)\n", line+4);
			char tmpbuf[60];
			strcpy_s (tmpbuf, 60, line+4);

			// setup models before processing IPLs
			if (!modelsDone) {
				setModelIndices ();
				showProgressMessage ("Object data");
				loadObjectDat ("data/object.dat", false);
				showProgressMessage ("Setup vehicle info data");
				setupVehicleInfo ();
				showProgressMessage ("Streaming init");
				setupImgStreaming ();				
				updateLoadingBar ();
				showProgressMessage ("Collision");
				_4113D0 ();
				debug ("... setup models\n");
				for (int i = 0; i < 20000; i++)	// TODO: MODEL_COUNT
					if (modelPtrs[i])
						modelPtrs[i]->initIfp ();	// init ifp indices for all models (if applicable)
				modelsDone = true;
			}

			showProgressMessage (tmpbuf);
			loadIplFile (tmpbuf);
			continue;
		}

		if (!strncmp (line, "SPLASH", 6)) {
			debug ("readGameConfig: SPLASH (?) -- ignored\n");
			continue;
		}
		debug ("readGameConfig: unrecognized line '%s'\n", line);

	}

	fclose (config);
	texMan.setCurrentTxd (txd);

	if (modelsDone) {
		_405780 ();
		_410EC0 ();
		readTrackFiles ();
		_410E00 ();
	}
}
