PageRenderTime 92ms CodeModel.GetById 16ms app.highlight 68ms RepoModel.GetById 1ms app.codeStats 0ms

/ghost/map.cpp

http://ghostcb.googlecode.com/
C++ | 1044 lines | 721 code | 191 blank | 132 comment | 240 complexity | e76a315888bfb9759d89c62ae2b51c51 MD5 | raw file
   1/*
   2
   3   Copyright [2008] [Trevor Hogan]
   4
   5   Licensed under the Apache License, Version 2.0 (the "License");
   6   you may not use this file except in compliance with the License.
   7   You may obtain a copy of the License at
   8
   9       http://www.apache.org/licenses/LICENSE-2.0
  10
  11   Unless required by applicable law or agreed to in writing, software
  12   distributed under the License is distributed on an "AS IS" BASIS,
  13   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14   See the License for the specific language governing permissions and
  15   limitations under the License.
  16
  17   CODE PORTED FROM THE ORIGINAL GHOST PROJECT: http://ghost.pwner.org/
  18
  19*/
  20
  21#include "ghost.h"
  22#include "util.h"
  23#include "crc32.h"
  24#include "sha1.h"
  25#include "config.h"
  26#include "map.h"
  27
  28#define __STORMLIB_SELF__
  29#include <stormlib/StormLib.h>
  30
  31#define ROTL(x,n) ((x)<<(n))|((x)>>(32-(n)))	// this won't work with signed types
  32#define ROTR(x,n) ((x)>>(n))|((x)<<(32-(n)))	// this won't work with signed types
  33
  34//
  35// CMap
  36//
  37
  38CMap :: CMap( CGHost *nGHost ) : m_GHost( nGHost ), m_Valid( true ), m_MapPath( "Maps\\FrozenThrone\\(12)EmeraldGardens.w3x" ), m_MapSpeed( MAPSPEED_FAST ), m_MapVisibility( MAPVIS_DEFAULT ), m_MapObservers( MAPOBS_NONE ), m_MapFlags( MAPFLAG_TEAMSTOGETHER | MAPFLAG_FIXEDTEAMS ), m_MapFilterMaker( MAPFILTER_MAKER_BLIZZARD ), m_MapFilterType( MAPFILTER_TYPE_MELEE ), m_MapFilterSize( MAPFILTER_SIZE_LARGE ), m_MapFilterObs( MAPFILTER_OBS_NONE ), m_MapOptions( MAPOPT_MELEE ), m_MapLoadInGame( false ), m_MapNumPlayers( 12 ), m_MapNumTeams( 12 )
  39{
  40	CONSOLE_Print( "[MAP] using hardcoded Emerald Gardens map data for Warcraft 3 version 1.24 & 1.24b" );
  41	m_MapSize = UTIL_ExtractNumbers( "174 221 4 0", 4 );
  42	m_MapInfo = UTIL_ExtractNumbers( "251 57 68 98", 4 );
  43	m_MapCRC = UTIL_ExtractNumbers( "108 250 204 59", 4 );
  44	m_MapSHA1 = UTIL_ExtractNumbers( "35 81 104 182 223 63 204 215 1 17 87 234 220 66 3 185 82 99 6 13", 20 );
  45	m_MapWidth = UTIL_ExtractNumbers( "172 0", 2 );
  46	m_MapHeight = UTIL_ExtractNumbers( "172 0", 2 );
  47	m_Slots.push_back( CGameSlot( 0, 255, SLOTSTATUS_OPEN, 0, 0, 0, SLOTRACE_RANDOM | SLOTRACE_SELECTABLE ) );
  48	m_Slots.push_back( CGameSlot( 0, 255, SLOTSTATUS_OPEN, 0, 1, 1, SLOTRACE_RANDOM | SLOTRACE_SELECTABLE ) );
  49	m_Slots.push_back( CGameSlot( 0, 255, SLOTSTATUS_OPEN, 0, 2, 2, SLOTRACE_RANDOM | SLOTRACE_SELECTABLE ) );
  50	m_Slots.push_back( CGameSlot( 0, 255, SLOTSTATUS_OPEN, 0, 3, 3, SLOTRACE_RANDOM | SLOTRACE_SELECTABLE ) );
  51	m_Slots.push_back( CGameSlot( 0, 255, SLOTSTATUS_OPEN, 0, 4, 4, SLOTRACE_RANDOM | SLOTRACE_SELECTABLE ) );
  52	m_Slots.push_back( CGameSlot( 0, 255, SLOTSTATUS_OPEN, 0, 5, 5, SLOTRACE_RANDOM | SLOTRACE_SELECTABLE ) );
  53	m_Slots.push_back( CGameSlot( 0, 255, SLOTSTATUS_OPEN, 0, 6, 6, SLOTRACE_RANDOM | SLOTRACE_SELECTABLE ) );
  54	m_Slots.push_back( CGameSlot( 0, 255, SLOTSTATUS_OPEN, 0, 7, 7, SLOTRACE_RANDOM | SLOTRACE_SELECTABLE ) );
  55	m_Slots.push_back( CGameSlot( 0, 255, SLOTSTATUS_OPEN, 0, 8, 8, SLOTRACE_RANDOM | SLOTRACE_SELECTABLE ) );
  56	m_Slots.push_back( CGameSlot( 0, 255, SLOTSTATUS_OPEN, 0, 9, 9, SLOTRACE_RANDOM | SLOTRACE_SELECTABLE ) );
  57	m_Slots.push_back( CGameSlot( 0, 255, SLOTSTATUS_OPEN, 0, 10, 10, SLOTRACE_RANDOM | SLOTRACE_SELECTABLE ) );
  58	m_Slots.push_back( CGameSlot( 0, 255, SLOTSTATUS_OPEN, 0, 11, 11, SLOTRACE_RANDOM | SLOTRACE_SELECTABLE ) );
  59}
  60
  61CMap :: CMap( CGHost *nGHost, CConfig *CFG, string nCFGFile ) : m_GHost( nGHost )
  62{
  63	Load( CFG, nCFGFile );
  64}
  65
  66CMap :: ~CMap( )
  67{
  68
  69}
  70
  71BYTEARRAY CMap :: GetMapGameFlags( )
  72{
  73	/*
  74
  75	Speed: (mask 0x00000003) cannot be combined
  76		0x00000000 - Slow game speed
  77		0x00000001 - Normal game speed
  78		0x00000002 - Fast game speed
  79	Visibility: (mask 0x00000F00) cannot be combined
  80		0x00000100 - Hide terrain
  81		0x00000200 - Map explored
  82		0x00000400 - Always visible (no fog of war)
  83		0x00000800 - Default
  84	Observers/Referees: (mask 0x40003000) cannot be combined
  85		0x00000000 - No Observers
  86		0x00002000 - Observers on Defeat
  87		0x00003000 - Additional players as observer allowed
  88		0x40000000 - Referees
  89	Teams/Units/Hero/Race: (mask 0x07064000) can be combined
  90		0x00004000 - Teams Together (team members are placed at neighbored starting locations)
  91		0x00060000 - Fixed teams
  92		0x01000000 - Unit share
  93		0x02000000 - Random hero
  94		0x04000000 - Random races
  95
  96	*/
  97
  98	uint32_t GameFlags = 0;
  99
 100	// speed
 101
 102	if( m_MapSpeed == MAPSPEED_SLOW )
 103		GameFlags = 0x00000000;
 104	else if( m_MapSpeed == MAPSPEED_NORMAL )
 105		GameFlags = 0x00000001;
 106	else
 107		GameFlags = 0x00000002;
 108
 109	// visibility
 110
 111	if( m_MapVisibility == MAPVIS_HIDETERRAIN )
 112		GameFlags |= 0x00000100;
 113	else if( m_MapVisibility == MAPVIS_EXPLORED )
 114		GameFlags |= 0x00000200;
 115	else if( m_MapVisibility == MAPVIS_ALWAYSVISIBLE )
 116		GameFlags |= 0x00000400;
 117	else
 118		GameFlags |= 0x00000800;
 119
 120	// observers
 121
 122	if( m_MapObservers == MAPOBS_ONDEFEAT )
 123		GameFlags |= 0x00002000;
 124	else if( m_MapObservers == MAPOBS_ALLOWED )
 125		GameFlags |= 0x00003000;
 126	else if( m_MapObservers == MAPOBS_REFEREES )
 127		GameFlags |= 0x40000000;
 128
 129	// teams/units/hero/race
 130
 131	if( m_MapFlags & MAPFLAG_TEAMSTOGETHER )
 132		GameFlags |= 0x00004000;
 133	if( m_MapFlags & MAPFLAG_FIXEDTEAMS )
 134		GameFlags |= 0x00060000;
 135	if( m_MapFlags & MAPFLAG_UNITSHARE )
 136		GameFlags |= 0x01000000;
 137	if( m_MapFlags & MAPFLAG_RANDOMHERO )
 138		GameFlags |= 0x02000000;
 139	if( m_MapFlags & MAPFLAG_RANDOMRACES )
 140		GameFlags |= 0x04000000;
 141
 142	return UTIL_CreateByteArray( GameFlags, false );
 143}
 144
 145uint32_t CMap :: GetMapGameType( )
 146{
 147	/* spec stolen from Strilanc as follows:
 148
 149    Public Enum GameTypes As UInteger
 150        None = 0
 151        Unknown0 = 1 << 0 '[always seems to be set?]
 152
 153        '''<summary>Setting this bit causes wc3 to check the map and disc if it is not signed by Blizzard</summary>
 154        AuthenticatedMakerBlizzard = 1 << 3
 155        OfficialMeleeGame = 1 << 5
 156
 157		SavedGame = 1 << 9
 158        PrivateGame = 1 << 11
 159
 160        MakerUser = 1 << 13
 161        MakerBlizzard = 1 << 14
 162        TypeMelee = 1 << 15
 163        TypeScenario = 1 << 16
 164        SizeSmall = 1 << 17
 165        SizeMedium = 1 << 18
 166        SizeLarge = 1 << 19
 167        ObsFull = 1 << 20
 168        ObsOnDeath = 1 << 21
 169        ObsNone = 1 << 22
 170
 171        MaskObs = ObsFull Or ObsOnDeath Or ObsNone
 172        MaskMaker = MakerBlizzard Or MakerUser
 173        MaskType = TypeMelee Or TypeScenario
 174        MaskSize = SizeLarge Or SizeMedium Or SizeSmall
 175        MaskFilterable = MaskObs Or MaskMaker Or MaskType Or MaskSize
 176    End Enum
 177
 178	*/
 179
 180	// note: we allow "conflicting" flags to be set at the same time (who knows if this is a good idea)
 181	// we also don't set any flags this class is unaware of such as Unknown0, SavedGame, and PrivateGame
 182
 183	uint32_t GameType = 0;
 184
 185	// maker
 186
 187	if( m_MapFilterMaker & MAPFILTER_MAKER_USER )
 188		GameType |= MAPGAMETYPE_MAKERUSER;
 189	if( m_MapFilterMaker & MAPFILTER_MAKER_BLIZZARD )
 190		GameType |= MAPGAMETYPE_MAKERBLIZZARD;
 191
 192	// type
 193
 194	if( m_MapFilterType & MAPFILTER_TYPE_MELEE )
 195		GameType |= MAPGAMETYPE_TYPEMELEE;
 196	if( m_MapFilterType & MAPFILTER_TYPE_SCENARIO )
 197		GameType |= MAPGAMETYPE_TYPESCENARIO;
 198
 199	// size
 200
 201	if( m_MapFilterSize & MAPFILTER_SIZE_SMALL )
 202		GameType |= MAPGAMETYPE_SIZESMALL;
 203	if( m_MapFilterSize & MAPFILTER_SIZE_MEDIUM )
 204		GameType |= MAPGAMETYPE_SIZEMEDIUM;
 205	if( m_MapFilterSize & MAPFILTER_SIZE_LARGE )
 206		GameType |= MAPGAMETYPE_SIZELARGE;
 207
 208	// obs
 209
 210	if( m_MapFilterObs & MAPFILTER_OBS_FULL )
 211		GameType |= MAPGAMETYPE_OBSFULL;
 212	if( m_MapFilterObs & MAPFILTER_OBS_ONDEATH )
 213		GameType |= MAPGAMETYPE_OBSONDEATH;
 214	if( m_MapFilterObs & MAPFILTER_OBS_NONE )
 215		GameType |= MAPGAMETYPE_OBSNONE;
 216
 217	return GameType;
 218}
 219
 220unsigned char CMap :: GetMapLayoutStyle( )
 221{
 222	// 0 = melee
 223	// 1 = custom forces
 224	// 2 = fixed player settings (not possible with the Warcraft III map editor)
 225	// 3 = custom forces + fixed player settings
 226
 227	if( !( m_MapOptions & MAPOPT_CUSTOMFORCES ) )
 228		return 0;
 229
 230	if( !( m_MapOptions & MAPOPT_FIXEDPLAYERSETTINGS ) )
 231		return 1;
 232
 233	return 3;
 234}
 235
 236void CMap :: Load( CConfig *CFG, string nCFGFile )
 237{
 238	m_Valid = true;
 239	m_CFGFile = nCFGFile;
 240
 241	// load the map data
 242
 243	m_MapLocalPath = CFG->GetString( "map_localpath", string( ) );
 244	m_MapData.clear( );
 245
 246	if( !m_MapLocalPath.empty( ) )
 247		m_MapData = UTIL_FileRead( m_GHost->m_MapPath + m_MapLocalPath );
 248
 249	// load the map MPQ
 250
 251	string MapMPQFileName = m_GHost->m_MapPath + m_MapLocalPath;
 252	HANDLE MapMPQ;
 253	bool MapMPQReady = false;
 254
 255	if( SFileOpenArchive( MapMPQFileName.c_str( ), 0, MPQ_OPEN_FORCE_MPQ_V1, &MapMPQ ) )
 256	{
 257		CONSOLE_Print( "[MAP] loading MPQ file [" + MapMPQFileName + "]" );
 258		MapMPQReady = true;
 259	}
 260	else
 261		CONSOLE_Print( "[MAP] warning - unable to load MPQ file [" + MapMPQFileName + "]" );
 262
 263	// try to calculate map_size, map_info, map_crc, map_sha1
 264
 265	BYTEARRAY MapSize;
 266	BYTEARRAY MapInfo;
 267	BYTEARRAY MapCRC;
 268	BYTEARRAY MapSHA1;
 269
 270	if( !m_MapData.empty( ) )
 271	{
 272		m_GHost->m_SHA->Reset( );
 273
 274		// calculate map_size
 275
 276		MapSize = UTIL_CreateByteArray( (uint32_t)m_MapData.size( ), false );
 277		CONSOLE_Print( "[MAP] calculated map_size = " + UTIL_ByteArrayToDecString( MapSize ) );
 278
 279		// calculate map_info (this is actually the CRC)
 280
 281		MapInfo = UTIL_CreateByteArray( (uint32_t)m_GHost->m_CRC->FullCRC( (unsigned char *)m_MapData.c_str( ), m_MapData.size( ) ), false );
 282		CONSOLE_Print( "[MAP] calculated map_info = " + UTIL_ByteArrayToDecString( MapInfo ) );
 283
 284		// calculate map_crc (this is not the CRC) and map_sha1
 285		// a big thank you to Strilanc for figuring the map_crc algorithm out
 286
 287		string CommonJ = UTIL_FileRead( m_GHost->m_MapCFGPath + "common.j" );
 288
 289		if( CommonJ.empty( ) )
 290			CONSOLE_Print( "[MAP] unable to calculate map_crc/sha1 - unable to read file [" + m_GHost->m_MapCFGPath + "common.j]" );
 291		else
 292		{
 293			string BlizzardJ = UTIL_FileRead( m_GHost->m_MapCFGPath + "blizzard.j" );
 294
 295			if( BlizzardJ.empty( ) )
 296				CONSOLE_Print( "[MAP] unable to calculate map_crc/sha1 - unable to read file [" + m_GHost->m_MapCFGPath + "blizzard.j]" );
 297			else
 298			{
 299				uint32_t Val = 0;
 300
 301				// update: it's possible for maps to include their own copies of common.j and/or blizzard.j
 302				// this code now overrides the default copies if required
 303
 304				bool OverrodeCommonJ = false;
 305				bool OverrodeBlizzardJ = false;
 306
 307				if( MapMPQReady )
 308				{
 309					HANDLE SubFile;
 310
 311					// override common.j
 312
 313					if( SFileOpenFileEx( MapMPQ, "Scripts\\common.j", 0, &SubFile ) )
 314					{
 315						uint32_t FileLength = SFileGetFileSize( SubFile, NULL );
 316
 317						if( FileLength > 0 && FileLength != 0xFFFFFFFF )
 318						{
 319							char *SubFileData = new char[FileLength];
 320							DWORD BytesRead = 0;
 321
 322							if( SFileReadFile( SubFile, SubFileData, FileLength, &BytesRead ) )
 323							{
 324								CONSOLE_Print( "[MAP] overriding default common.j with map copy while calculating map_crc/sha1" );
 325								OverrodeCommonJ = true;
 326								Val = Val ^ XORRotateLeft( (unsigned char *)SubFileData, BytesRead );
 327								m_GHost->m_SHA->Update( (unsigned char *)SubFileData, BytesRead );
 328							}
 329
 330							delete [] SubFileData;
 331						}
 332
 333						SFileCloseFile( SubFile );
 334					}
 335				}
 336
 337				if( !OverrodeCommonJ )
 338				{
 339					Val = Val ^ XORRotateLeft( (unsigned char *)CommonJ.c_str( ), CommonJ.size( ) );
 340					m_GHost->m_SHA->Update( (unsigned char *)CommonJ.c_str( ), CommonJ.size( ) );
 341				}
 342
 343				if( MapMPQReady )
 344				{
 345					HANDLE SubFile;
 346
 347					// override blizzard.j
 348
 349					if( SFileOpenFileEx( MapMPQ, "Scripts\\blizzard.j", 0, &SubFile ) )
 350					{
 351						uint32_t FileLength = SFileGetFileSize( SubFile, NULL );
 352
 353						if( FileLength > 0 && FileLength != 0xFFFFFFFF )
 354						{
 355							char *SubFileData = new char[FileLength];
 356							DWORD BytesRead = 0;
 357
 358							if( SFileReadFile( SubFile, SubFileData, FileLength, &BytesRead ) )
 359							{
 360								CONSOLE_Print( "[MAP] overriding default blizzard.j with map copy while calculating map_crc/sha1" );
 361								OverrodeBlizzardJ = true;
 362								Val = Val ^ XORRotateLeft( (unsigned char *)SubFileData, BytesRead );
 363								m_GHost->m_SHA->Update( (unsigned char *)SubFileData, BytesRead );
 364							}
 365
 366							delete [] SubFileData;
 367						}
 368
 369						SFileCloseFile( SubFile );
 370					}
 371				}
 372
 373				if( !OverrodeBlizzardJ )
 374				{
 375					Val = Val ^ XORRotateLeft( (unsigned char *)BlizzardJ.c_str( ), BlizzardJ.size( ) );
 376					m_GHost->m_SHA->Update( (unsigned char *)BlizzardJ.c_str( ), BlizzardJ.size( ) );
 377				}
 378
 379				Val = ROTL( Val, 3 );
 380				Val = ROTL( Val ^ 0x03F1379E, 3 );
 381				m_GHost->m_SHA->Update( (unsigned char *)"\x9E\x37\xF1\x03", 4 );
 382
 383				if( MapMPQReady )
 384				{
 385					vector<string> FileList;
 386					FileList.push_back( "war3map.j" );
 387					FileList.push_back( "scripts\\war3map.j" );
 388					FileList.push_back( "war3map.w3e" );
 389					FileList.push_back( "war3map.wpm" );
 390					FileList.push_back( "war3map.doo" );
 391					FileList.push_back( "war3map.w3u" );
 392					FileList.push_back( "war3map.w3b" );
 393					FileList.push_back( "war3map.w3d" );
 394					FileList.push_back( "war3map.w3a" );
 395					FileList.push_back( "war3map.w3q" );
 396					bool FoundScript = false;
 397
 398                                        for( vector<string> :: iterator i = FileList.begin( ); i != FileList.end( ); ++i )
 399					{
 400						// don't use scripts\war3map.j if we've already used war3map.j (yes, some maps have both but only war3map.j is used)
 401
 402						if( FoundScript && *i == "scripts\\war3map.j" )
 403							continue;
 404
 405						HANDLE SubFile;
 406
 407						if( SFileOpenFileEx( MapMPQ, (*i).c_str( ), 0, &SubFile ) )
 408						{
 409							uint32_t FileLength = SFileGetFileSize( SubFile, NULL );
 410
 411							if( FileLength > 0 && FileLength != 0xFFFFFFFF )
 412							{
 413								char *SubFileData = new char[FileLength];
 414								DWORD BytesRead = 0;
 415
 416								if( SFileReadFile( SubFile, SubFileData, FileLength, &BytesRead ) )
 417								{
 418									if( *i == "war3map.j" || *i == "scripts\\war3map.j" )
 419										FoundScript = true;
 420
 421									Val = ROTL( Val ^ XORRotateLeft( (unsigned char *)SubFileData, BytesRead ), 3 );
 422									m_GHost->m_SHA->Update( (unsigned char *)SubFileData, BytesRead );
 423									// DEBUG_Print( "*** found: " + *i );
 424								}
 425
 426								delete [] SubFileData;
 427							}
 428
 429							SFileCloseFile( SubFile );
 430						}
 431						else
 432						{
 433							// DEBUG_Print( "*** not found: " + *i );
 434						}
 435					}
 436
 437					if( !FoundScript )
 438						CONSOLE_Print( "[MAP] couldn't find war3map.j or scripts\\war3map.j in MPQ file, calculated map_crc/sha1 is probably wrong" );
 439
 440					MapCRC = UTIL_CreateByteArray( Val, false );
 441					CONSOLE_Print( "[MAP] calculated map_crc = " + UTIL_ByteArrayToDecString( MapCRC ) );
 442
 443					m_GHost->m_SHA->Final( );
 444					unsigned char SHA1[20];
 445					memset( SHA1, 0, sizeof( unsigned char ) * 20 );
 446					m_GHost->m_SHA->GetHash( SHA1 );
 447					MapSHA1 = UTIL_CreateByteArray( SHA1, 20 );
 448					CONSOLE_Print( "[MAP] calculated map_sha1 = " + UTIL_ByteArrayToDecString( MapSHA1 ) );
 449				}
 450				else
 451					CONSOLE_Print( "[MAP] unable to calculate map_crc/sha1 - map MPQ file not loaded" );
 452			}
 453		}
 454	}
 455	else
 456		CONSOLE_Print( "[MAP] no map data available, using config file for map_size, map_info, map_crc, map_sha1" );
 457
 458	// try to calculate map_width, map_height, map_slot<x>, map_numplayers, map_numteams
 459
 460	uint32_t MapOptions = 0;
 461	BYTEARRAY MapWidth;
 462	BYTEARRAY MapHeight;
 463	uint32_t MapNumPlayers = 0;
 464	uint32_t MapNumTeams = 0;
 465	vector<CGameSlot> Slots;
 466
 467	if( !m_MapData.empty( ) )
 468	{
 469		if( MapMPQReady )
 470		{
 471			HANDLE SubFile;
 472
 473			if( SFileOpenFileEx( MapMPQ, "war3map.w3i", 0, &SubFile ) )
 474			{
 475				uint32_t FileLength = SFileGetFileSize( SubFile, NULL );
 476
 477				if( FileLength > 0 && FileLength != 0xFFFFFFFF )
 478				{
 479					char *SubFileData = new char[FileLength];
 480					DWORD BytesRead = 0;
 481
 482					if( SFileReadFile( SubFile, SubFileData, FileLength, &BytesRead ) )
 483					{
 484						istringstream ISS( string( SubFileData, BytesRead ) );
 485
 486						// war3map.w3i format found at http://www.wc3campaigns.net/tools/specs/index.html by Zepir/PitzerMike
 487
 488						string GarbageString;
 489						uint32_t FileFormat;
 490						uint32_t RawMapWidth;
 491						uint32_t RawMapHeight;
 492						uint32_t RawMapFlags;
 493						uint32_t RawMapNumPlayers;
 494						uint32_t RawMapNumTeams;
 495
 496						ISS.read( (char *)&FileFormat, 4 );				// file format (18 = ROC, 25 = TFT)
 497
 498						if( FileFormat == 18 || FileFormat == 25 )
 499						{
 500							ISS.seekg( 4, ios :: cur );					// number of saves
 501							ISS.seekg( 4, ios :: cur );					// editor version
 502							getline( ISS, GarbageString, '\0' );		// map name
 503							getline( ISS, GarbageString, '\0' );		// map author
 504							getline( ISS, GarbageString, '\0' );		// map description
 505							getline( ISS, GarbageString, '\0' );		// players recommended
 506							ISS.seekg( 32, ios :: cur );				// camera bounds
 507							ISS.seekg( 16, ios :: cur );				// camera bounds complements
 508							ISS.read( (char *)&RawMapWidth, 4 );		// map width
 509							ISS.read( (char *)&RawMapHeight, 4 );		// map height
 510							ISS.read( (char *)&RawMapFlags, 4 );		// flags
 511							ISS.seekg( 1, ios :: cur );					// map main ground type
 512
 513							if( FileFormat == 18 )
 514								ISS.seekg( 4, ios :: cur );				// campaign background number
 515							else if( FileFormat == 25 )
 516							{
 517								ISS.seekg( 4, ios :: cur );				// loading screen background number
 518								getline( ISS, GarbageString, '\0' );	// path of custom loading screen model
 519							}
 520
 521							getline( ISS, GarbageString, '\0' );		// map loading screen text
 522							getline( ISS, GarbageString, '\0' );		// map loading screen title
 523							getline( ISS, GarbageString, '\0' );		// map loading screen subtitle
 524
 525							if( FileFormat == 18 )
 526								ISS.seekg( 4, ios :: cur );				// map loading screen number
 527							else if( FileFormat == 25 )
 528							{
 529								ISS.seekg( 4, ios :: cur );				// used game data set
 530								getline( ISS, GarbageString, '\0' );	// prologue screen path
 531							}
 532
 533							getline( ISS, GarbageString, '\0' );		// prologue screen text
 534							getline( ISS, GarbageString, '\0' );		// prologue screen title
 535							getline( ISS, GarbageString, '\0' );		// prologue screen subtitle
 536
 537							if( FileFormat == 25 )
 538							{
 539								ISS.seekg( 4, ios :: cur );				// uses terrain fog
 540								ISS.seekg( 4, ios :: cur );				// fog start z height
 541								ISS.seekg( 4, ios :: cur );				// fog end z height
 542								ISS.seekg( 4, ios :: cur );				// fog density
 543								ISS.seekg( 1, ios :: cur );				// fog red value
 544								ISS.seekg( 1, ios :: cur );				// fog green value
 545								ISS.seekg( 1, ios :: cur );				// fog blue value
 546								ISS.seekg( 1, ios :: cur );				// fog alpha value
 547								ISS.seekg( 4, ios :: cur );				// global weather id
 548								getline( ISS, GarbageString, '\0' );	// custom sound environment
 549								ISS.seekg( 1, ios :: cur );				// tileset id of the used custom light environment
 550								ISS.seekg( 1, ios :: cur );				// custom water tinting red value
 551								ISS.seekg( 1, ios :: cur );				// custom water tinting green value
 552								ISS.seekg( 1, ios :: cur );				// custom water tinting blue value
 553								ISS.seekg( 1, ios :: cur );				// custom water tinting alpha value
 554							}
 555
 556							ISS.read( (char *)&RawMapNumPlayers, 4 );	// number of players
 557							uint32_t ClosedSlots = 0;
 558
 559                                                        for( uint32_t i = 0; i < RawMapNumPlayers; ++i )
 560							{
 561								CGameSlot Slot( 0, 255, SLOTSTATUS_OPEN, 0, 0, 1, SLOTRACE_RANDOM );
 562								uint32_t Colour;
 563								uint32_t Status;
 564								uint32_t Race;
 565
 566								ISS.read( (char *)&Colour, 4 );			// colour
 567								Slot.SetColour( Colour );
 568								ISS.read( (char *)&Status, 4 );			// status
 569
 570								if( Status == 1 )
 571									Slot.SetSlotStatus( SLOTSTATUS_OPEN );
 572								else if( Status == 2 )
 573								{
 574									Slot.SetSlotStatus( SLOTSTATUS_OCCUPIED );
 575									Slot.SetComputer( 1 );
 576									Slot.SetComputerType( SLOTCOMP_NORMAL );
 577								}
 578								else
 579								{
 580									Slot.SetSlotStatus( SLOTSTATUS_CLOSED );
 581                                                                        ++ClosedSlots;
 582								}
 583
 584								ISS.read( (char *)&Race, 4 );			// race
 585
 586								if( Race == 1 )
 587									Slot.SetRace( SLOTRACE_HUMAN );
 588								else if( Race == 2 )
 589									Slot.SetRace( SLOTRACE_ORC );
 590								else if( Race == 3 )
 591									Slot.SetRace( SLOTRACE_UNDEAD );
 592								else if( Race == 4 )
 593									Slot.SetRace( SLOTRACE_NIGHTELF );
 594								else
 595									Slot.SetRace( SLOTRACE_RANDOM );
 596
 597								ISS.seekg( 4, ios :: cur );				// fixed start position
 598								getline( ISS, GarbageString, '\0' );	// player name
 599								ISS.seekg( 4, ios :: cur );				// start position x
 600								ISS.seekg( 4, ios :: cur );				// start position y
 601								ISS.seekg( 4, ios :: cur );				// ally low priorities
 602								ISS.seekg( 4, ios :: cur );				// ally high priorities
 603
 604								if( Slot.GetSlotStatus( ) != SLOTSTATUS_CLOSED )
 605									Slots.push_back( Slot );
 606							}
 607
 608							ISS.read( (char *)&RawMapNumTeams, 4 );		// number of teams
 609
 610                                                        for( uint32_t i = 0; i < RawMapNumTeams; ++i )
 611							{
 612								uint32_t Flags;
 613								uint32_t PlayerMask;
 614
 615								ISS.read( (char *)&Flags, 4 );			// flags
 616								ISS.read( (char *)&PlayerMask, 4 );		// player mask
 617
 618                                                                for( unsigned char j = 0; j < 12; ++j )
 619								{
 620									if( PlayerMask & 1 )
 621									{
 622                                                                                for( vector<CGameSlot> :: iterator k = Slots.begin( ); k != Slots.end( ); ++k )
 623										{
 624											if( (*k).GetColour( ) == j )
 625												(*k).SetTeam( i );
 626										}
 627									}
 628
 629									PlayerMask >>= 1;
 630								}
 631
 632								getline( ISS, GarbageString, '\0' );	// team name
 633							}
 634
 635							// the bot only cares about the following options: melee, fixed player settings, custom forces
 636							// let's not confuse the user by displaying erroneous map options so zero them out now
 637
 638							MapOptions = RawMapFlags & ( MAPOPT_MELEE | MAPOPT_FIXEDPLAYERSETTINGS | MAPOPT_CUSTOMFORCES );
 639							CONSOLE_Print( "[MAP] calculated map_options = " + UTIL_ToString( MapOptions ) );
 640							MapWidth = UTIL_CreateByteArray( (uint16_t)RawMapWidth, false );
 641							CONSOLE_Print( "[MAP] calculated map_width = " + UTIL_ByteArrayToDecString( MapWidth ) );
 642							MapHeight = UTIL_CreateByteArray( (uint16_t)RawMapHeight, false );
 643							CONSOLE_Print( "[MAP] calculated map_height = " + UTIL_ByteArrayToDecString( MapHeight ) );
 644							MapNumPlayers = RawMapNumPlayers - ClosedSlots;
 645							CONSOLE_Print( "[MAP] calculated map_numplayers = " + UTIL_ToString( MapNumPlayers ) );
 646							MapNumTeams = RawMapNumTeams;
 647							CONSOLE_Print( "[MAP] calculated map_numteams = " + UTIL_ToString( MapNumTeams ) );
 648
 649							uint32_t SlotNum = 1;
 650
 651                                                        for( vector<CGameSlot> :: iterator i = Slots.begin( ); i != Slots.end( ); ++i )
 652							{
 653								CONSOLE_Print( "[MAP] calculated map_slot" + UTIL_ToString( SlotNum ) + " = " + UTIL_ByteArrayToDecString( (*i).GetByteArray( ) ) );
 654                                                                ++SlotNum;
 655							}
 656
 657							if( MapOptions & MAPOPT_MELEE )
 658							{
 659								CONSOLE_Print( "[MAP] found melee map, initializing slots" );
 660
 661								// give each slot a different team and set the race to random
 662
 663								unsigned char Team = 0;
 664
 665                                                                for( vector<CGameSlot> :: iterator i = Slots.begin( ); i != Slots.end( ); ++i )
 666								{
 667									(*i).SetTeam( Team++ );
 668									(*i).SetRace( SLOTRACE_RANDOM );
 669								}
 670							}
 671
 672							if( !( MapOptions & MAPOPT_FIXEDPLAYERSETTINGS ) )
 673							{
 674								// make races selectable
 675
 676                                                                for( vector<CGameSlot> :: iterator i = Slots.begin( ); i != Slots.end( ); ++i )
 677									(*i).SetRace( (*i).GetRace( ) | SLOTRACE_SELECTABLE );
 678							}
 679						}
 680					}
 681					else
 682						CONSOLE_Print( "[MAP] unable to calculate map_options, map_width, map_height, map_slot<x>, map_numplayers, map_numteams - unable to extract war3map.w3i from MPQ file" );
 683
 684					delete [] SubFileData;
 685				}
 686
 687				SFileCloseFile( SubFile );
 688			}
 689			else
 690				CONSOLE_Print( "[MAP] unable to calculate map_options, map_width, map_height, map_slot<x>, map_numplayers, map_numteams - couldn't find war3map.w3i in MPQ file" );
 691		}
 692		else
 693			CONSOLE_Print( "[MAP] unable to calculate map_options, map_width, map_height, map_slot<x>, map_numplayers, map_numteams - map MPQ file not loaded" );
 694	}
 695	else
 696		CONSOLE_Print( "[MAP] no map data available, using config file for map_options, map_width, map_height, map_slot<x>, map_numplayers, map_numteams" );
 697
 698	// close the map MPQ
 699
 700	if( MapMPQReady )
 701		SFileCloseArchive( MapMPQ );
 702
 703	m_MapPath = CFG->GetString( "map_path", string( ) );
 704
 705	if( MapSize.empty( ) )
 706		MapSize = UTIL_ExtractNumbers( CFG->GetString( "map_size", string( ) ), 4 );
 707	else if( CFG->Exists( "map_size" ) )
 708	{
 709		CONSOLE_Print( "[MAP] overriding calculated map_size with config value map_size = " + CFG->GetString( "map_size", string( ) ) );
 710		MapSize = UTIL_ExtractNumbers( CFG->GetString( "map_size", string( ) ), 4 );
 711	}
 712
 713	m_MapSize = MapSize;
 714
 715	if( MapInfo.empty( ) )
 716		MapInfo = UTIL_ExtractNumbers( CFG->GetString( "map_info", string( ) ), 4 );
 717	else if( CFG->Exists( "map_info" ) )
 718	{
 719		CONSOLE_Print( "[MAP] overriding calculated map_info with config value map_info = " + CFG->GetString( "map_info", string( ) ) );
 720		MapInfo = UTIL_ExtractNumbers( CFG->GetString( "map_info", string( ) ), 4 );
 721	}
 722
 723	m_MapInfo = MapInfo;
 724
 725	if( MapCRC.empty( ) )
 726		MapCRC = UTIL_ExtractNumbers( CFG->GetString( "map_crc", string( ) ), 4 );
 727	else if( CFG->Exists( "map_crc" ) )
 728	{
 729		CONSOLE_Print( "[MAP] overriding calculated map_crc with config value map_crc = " + CFG->GetString( "map_crc", string( ) ) );
 730		MapCRC = UTIL_ExtractNumbers( CFG->GetString( "map_crc", string( ) ), 4 );
 731	}
 732
 733	m_MapCRC = MapCRC;
 734
 735	if( MapSHA1.empty( ) )
 736		MapSHA1 = UTIL_ExtractNumbers( CFG->GetString( "map_sha1", string( ) ), 20 );
 737	else if( CFG->Exists( "map_sha1" ) )
 738	{
 739		CONSOLE_Print( "[MAP] overriding calculated map_sha1 with config value map_sha1 = " + CFG->GetString( "map_sha1", string( ) ) );
 740		MapSHA1 = UTIL_ExtractNumbers( CFG->GetString( "map_sha1", string( ) ), 20 );
 741	}
 742
 743	m_MapSHA1 = MapSHA1;
 744	m_MapSpeed = CFG->GetInt( "map_speed", MAPSPEED_FAST );
 745	m_MapVisibility = CFG->GetInt( "map_visibility", MAPVIS_DEFAULT );
 746	m_MapObservers = CFG->GetInt( "map_observers", MAPOBS_NONE );
 747	m_MapFlags = CFG->GetInt( "map_flags", MAPFLAG_TEAMSTOGETHER | MAPFLAG_FIXEDTEAMS );
 748	m_MapFilterMaker = CFG->GetInt( "map_filter_maker", MAPFILTER_MAKER_USER );
 749	m_MapFilterType = CFG->GetInt( "map_filter_type", 0 );
 750	m_MapFilterSize = CFG->GetInt( "map_filter_size", MAPFILTER_SIZE_LARGE );
 751	m_MapFilterObs = CFG->GetInt( "map_filter_obs", MAPFILTER_OBS_NONE );
 752
 753	// todotodo: it might be possible for MapOptions to legitimately be zero so this is not a valid way of checking if it wasn't parsed out earlier
 754
 755	if( MapOptions == 0 )
 756		MapOptions = CFG->GetInt( "map_options", 0 );
 757	else if( CFG->Exists( "map_options" ) )
 758	{
 759		CONSOLE_Print( "[MAP] overriding calculated map_options with config value map_options = " + CFG->GetString( "map_options", string( ) ) );
 760		MapOptions = CFG->GetInt( "map_options", 0 );
 761	}
 762
 763	m_MapOptions = MapOptions;
 764
 765	if( MapWidth.empty( ) )
 766		MapWidth = UTIL_ExtractNumbers( CFG->GetString( "map_width", string( ) ), 2 );
 767	else if( CFG->Exists( "map_width" ) )
 768	{
 769		CONSOLE_Print( "[MAP] overriding calculated map_width with config value map_width = " + CFG->GetString( "map_width", string( ) ) );
 770		MapWidth = UTIL_ExtractNumbers( CFG->GetString( "map_width", string( ) ), 2 );
 771	}
 772
 773	m_MapWidth = MapWidth;
 774
 775	if( MapHeight.empty( ) )
 776		MapHeight = UTIL_ExtractNumbers( CFG->GetString( "map_height", string( ) ), 2 );
 777	else if( CFG->Exists( "map_height" ) )
 778	{
 779		CONSOLE_Print( "[MAP] overriding calculated map_height with config value map_height = " + CFG->GetString( "map_height", string( ) ) );
 780		MapHeight = UTIL_ExtractNumbers( CFG->GetString( "map_height", string( ) ), 2 );
 781	}
 782
 783	m_MapHeight = MapHeight;
 784	if (m_MapLocalPath.substr(0,4)=="DotA") 
 785	{
 786		m_MapType= "dota";
 787		m_MapMatchMakingCategory = "dota_elo";
 788	}
 789	else
 790	{
 791		m_MapType = CFG->GetString( "map_type", string( ) );
 792		m_MapMatchMakingCategory = CFG->GetString( "map_matchmakingcategory", string( ) );
 793	}
 794	m_MapStatsW3MMDCategory = CFG->GetString( "map_statsw3mmdcategory", string( ) );
 795	m_MapDefaultHCL = CFG->GetString( "map_defaulthcl", string( ) );
 796	//ghost custom build additions
 797	// @disturbed_oc
 798	m_HCLCommandFromGameName = CFG->GetInt( "map_hclfromgamename", 0 ) == 0 ? false : true;
 799    if ( m_GHost->m_HCLCommandFromGameName )
 800        m_HCLCommandFromGameName = true;
 801	m_HCLValidModes = CFG->GetString( "map_validmodes", string( ) );
 802	m_MapGameNameWithMode = CFG->GetString( "map_gamenamewithmode", string( ) );
 803	// @end
 804	m_MapDefaultPlayerScore = CFG->GetInt( "map_defaultplayerscore", 1000 );
 805	m_MapLoadInGame = CFG->GetInt( "map_loadingame", 0 ) == 0 ? false : true;
 806    if (m_GHost->m_ForceLoadInGame)
 807		m_MapLoadInGame = true;
 808    
 809	if( MapNumPlayers == 0 )
 810		MapNumPlayers = CFG->GetInt( "map_numplayers", 0 );
 811	else if( CFG->Exists( "map_numplayers" ) )
 812	{
 813		CONSOLE_Print( "[MAP] overriding calculated map_numplayers with config value map_numplayers = " + CFG->GetString( "map_numplayers", string( ) ) );
 814		MapNumPlayers = CFG->GetInt( "map_numplayers", 0 );
 815	}
 816
 817	m_MapNumPlayers = MapNumPlayers;
 818
 819	if( MapNumTeams == 0 )
 820		MapNumTeams = CFG->GetInt( "map_numteams", 0 );
 821	else if( CFG->Exists( "map_numteams" ) )
 822	{
 823		CONSOLE_Print( "[MAP] overriding calculated map_numteams with config value map_numteams = " + CFG->GetString( "map_numteams", string( ) ) );
 824		MapNumTeams = CFG->GetInt( "map_numteams", 0 );
 825	}
 826
 827	m_MapNumTeams = MapNumTeams;
 828
 829	if( Slots.empty( ) )
 830	{
 831                for( uint32_t Slot = 1; Slot <= 12; ++Slot )
 832		{
 833			string SlotString = CFG->GetString( "map_slot" + UTIL_ToString( Slot ), string( ) );
 834
 835			if( SlotString.empty( ) )
 836				break;
 837
 838			BYTEARRAY SlotData = UTIL_ExtractNumbers( SlotString, 9 );
 839			Slots.push_back( CGameSlot( SlotData ) );
 840		}
 841	}
 842	else if( CFG->Exists( "map_slot1" ) )
 843	{
 844		CONSOLE_Print( "[MAP] overriding slots" );
 845		Slots.clear( );
 846
 847                for( uint32_t Slot = 1; Slot <= 12; ++Slot )
 848		{
 849			string SlotString = CFG->GetString( "map_slot" + UTIL_ToString( Slot ), string( ) );
 850
 851			if( SlotString.empty( ) )
 852				break;
 853
 854			BYTEARRAY SlotData = UTIL_ExtractNumbers( SlotString, 9 );
 855			Slots.push_back( CGameSlot( SlotData ) );
 856		}
 857	}
 858
 859	m_Slots = Slots;
 860
 861	// if random races is set force every slot's race to random
 862
 863	if( m_MapFlags & MAPFLAG_RANDOMRACES )
 864	{
 865		CONSOLE_Print( "[MAP] forcing races to random" );
 866
 867                for( vector<CGameSlot> :: iterator i = m_Slots.begin( ); i != m_Slots.end( ); ++i )
 868			(*i).SetRace( SLOTRACE_RANDOM );
 869	}
 870
 871	// add observer slots
 872
 873	if( m_MapObservers == MAPOBS_ALLOWED || m_MapObservers == MAPOBS_REFEREES )
 874	{
 875		CONSOLE_Print( "[MAP] adding " + UTIL_ToString( 12 - m_Slots.size( ) ) + " observer slots" );
 876
 877		while( m_Slots.size( ) < 12 )
 878			m_Slots.push_back( CGameSlot( 0, 255, SLOTSTATUS_OPEN, 0, 12, 12, SLOTRACE_RANDOM ) );
 879	}
 880
 881	CheckValid( );
 882}
 883
 884// @disturbed_oc
 885// string CMAP :: GetMapGameNameWithRandomMode()
 886// Returns a gamename based on cfg value 'map_gamenamewitmode' with a random mode picked from cfg value 'map_validhcl', which is later encoded to hcl string at game start.
 887// map_gamenamewithmode = DotA $mode$ GHost++
 888// map_validhcl = -apso -arso -sdso -rdso
 889// Will result in for example DotA -arso GHost++ and HCL command will be set accordingly.
 890
 891string CMap::GetMapGameNameWithRandomMode()
 892{
 893	string GameName;
 894	string find, replace;
 895	string::size_type loc;
 896	vector<string> modes;
 897
 898	if (m_MapGameNameWithMode.empty() || !m_HCLCommandFromGameName || m_HCLValidModes.empty())
 899		return "";
 900
 901	GameName = m_MapGameNameWithMode;
 902
 903	find = "$mode$";
 904	loc = GameName.find( find );
 905
 906	if ( loc != string::npos )
 907	{
 908		modes = UTIL_Tokenize(m_HCLValidModes, ' ');
 909		//CONSOLE_Print( "[MAPMODE] Gamename with random mode [" + GameName + "] Found [" + UTIL_ToString(modes.size()) + "] Valid modes [" + m_HCLValidModes + "]" );
 910		replace = modes[rand() % modes.size()];
 911		//CONSOLE_Print( "[MAPMODE] HCL Command will be [" + replace.substr(1) + "]" );
 912		GameName.replace(loc, find.size(), replace);
 913
 914		return GameName;
 915	}
 916	else // no $mode$ found stick to autohost name
 917	{
 918		m_HCLCommandFromGameName = false;
 919		return "";
 920	}
 921}
 922
 923// @end
 924
 925void CMap :: CheckValid( )
 926{
 927	// todotodo: should this code fix any errors it sees rather than just warning the user?
 928
 929	if( m_MapPath.empty( ) || m_MapPath.length( ) >= 50 )
 930	{
 931		m_Valid = false;
 932		CONSOLE_Print( "[MAP] invalid map_path detected" );
 933	}
 934	else if( m_MapPath[0] == '\\' )
 935		CONSOLE_Print( "[MAP] warning - map_path starts with '\\', any replays saved by GHost++ will not be playable in Warcraft III" );
 936
 937	if( m_MapPath.find( '/' ) != string :: npos )
 938		CONSOLE_Print( "[MAP] warning - map_path contains forward slashes '/' but it must use Windows style back slashes '\\'" );
 939
 940	if( m_MapSize.size( ) != 4 )
 941	{
 942		m_Valid = false;
 943		CONSOLE_Print( "[MAP] invalid map_size detected" );
 944	}
 945	else if( !m_MapData.empty( ) && m_MapData.size( ) != UTIL_ByteArrayToUInt32( m_MapSize, false ) )
 946	{
 947		m_Valid = false;
 948		CONSOLE_Print( "[MAP] invalid map_size detected - size mismatch with actual map data" );
 949	}
 950
 951	if( m_MapInfo.size( ) != 4 )
 952	{
 953		m_Valid = false;
 954		CONSOLE_Print( "[MAP] invalid map_info detected" );
 955	}
 956
 957	if( m_MapCRC.size( ) != 4 )
 958	{
 959		m_Valid = false;
 960		CONSOLE_Print( "[MAP] invalid map_crc detected" );
 961	}
 962
 963	if( m_MapSHA1.size( ) != 20 )
 964	{
 965		m_Valid = false;
 966		CONSOLE_Print( "[MAP] invalid map_sha1 detected" );
 967	}
 968
 969	if( m_MapSpeed != MAPSPEED_SLOW && m_MapSpeed != MAPSPEED_NORMAL && m_MapSpeed != MAPSPEED_FAST )
 970	{
 971		m_Valid = false;
 972		CONSOLE_Print( "[MAP] invalid map_speed detected" );
 973	}
 974
 975	if( m_MapVisibility != MAPVIS_HIDETERRAIN && m_MapVisibility != MAPVIS_EXPLORED && m_MapVisibility != MAPVIS_ALWAYSVISIBLE && m_MapVisibility != MAPVIS_DEFAULT )
 976	{
 977		m_Valid = false;
 978		CONSOLE_Print( "[MAP] invalid map_visibility detected" );
 979	}
 980
 981	if( m_MapObservers != MAPOBS_NONE && m_MapObservers != MAPOBS_ONDEFEAT && m_MapObservers != MAPOBS_ALLOWED && m_MapObservers != MAPOBS_REFEREES )
 982	{
 983		m_Valid = false;
 984		CONSOLE_Print( "[MAP] invalid map_observers detected" );
 985	}
 986
 987	// todotodo: m_MapFlags
 988	// todotodo: m_MapFilterMaker, m_MapFilterType, m_MapFilterSize, m_MapFilterObs
 989
 990	if( m_MapWidth.size( ) != 2 )
 991	{
 992		m_Valid = false;
 993		CONSOLE_Print( "[MAP] invalid map_width detected" );
 994	}
 995
 996	if( m_MapHeight.size( ) != 2 )
 997	{
 998		m_Valid = false;
 999		CONSOLE_Print( "[MAP] invalid map_height detected" );
1000	}
1001
1002	if( m_MapNumPlayers == 0 || m_MapNumPlayers > 12 )
1003	{
1004		m_Valid = false;
1005		CONSOLE_Print( "[MAP] invalid map_numplayers detected" );
1006	}
1007
1008	if( m_MapNumTeams == 0 || m_MapNumTeams > 12 )
1009	{
1010		m_Valid = false;
1011		CONSOLE_Print( "[MAP] invalid map_numteams detected" );
1012	}
1013
1014	if( m_Slots.empty( ) || m_Slots.size( ) > 12 )
1015	{
1016		m_Valid = false;
1017		CONSOLE_Print( "[MAP] invalid map_slot<x> detected" );
1018	}
1019}
1020
1021uint32_t CMap :: XORRotateLeft( unsigned char *data, uint32_t length )
1022{
1023	// a big thank you to Strilanc for figuring this out
1024
1025	uint32_t i = 0;
1026	uint32_t Val = 0;
1027
1028	if( length > 3 )
1029	{
1030		while( i < length - 3 )
1031		{
1032			Val = ROTL( Val ^ ( (uint32_t)data[i] + (uint32_t)( data[i + 1] << 8 ) + (uint32_t)( data[i + 2] << 16 ) + (uint32_t)( data[i + 3] << 24 ) ), 3 );
1033			i += 4;
1034		}
1035	}
1036
1037	while( i < length )
1038	{
1039		Val = ROTL( Val ^ data[i], 3 );
1040                ++i;
1041	}
1042
1043	return Val;
1044}