core/logic/smn_maplists.cpp
author Nicholas Hastings <skamonkey@gmail.com>
Tue Jan 07 10:24:05 2014 -0500 (2014-01-07)
changeset 4196 5346ef2b217e
parent 4066 c055944bdfd6
child 4197 bc5ebfdd07f9
permissions -rw-r--r--
Support all file search paths when finding mapcycle file (bug 5839, r=asherkin).
dvander@2726
     1
/**
dvander@4066
     2
 * vim: set ts=4 sw=4 tw=99 noet :
dvander@2726
     3
 * =============================================================================
dvander@2726
     4
 * SourceMod
dvander@2726
     5
 * Copyright (C) 2004-2008 AlliedModders LLC.  All rights reserved.
dvander@2726
     6
 * =============================================================================
dvander@2726
     7
 *
dvander@2726
     8
 * This program is free software; you can redistribute it and/or modify it under
dvander@2726
     9
 * the terms of the GNU General Public License, version 3.0, as published by the
dvander@2726
    10
 * Free Software Foundation.
dvander@2726
    11
 * 
dvander@2726
    12
 * This program is distributed in the hope that it will be useful, but WITHOUT
dvander@2726
    13
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
dvander@2726
    14
 * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
dvander@2726
    15
 * details.
dvander@2726
    16
 *
dvander@2726
    17
 * You should have received a copy of the GNU General Public License along with
dvander@2726
    18
 * this program.  If not, see <http://www.gnu.org/licenses/>.
dvander@2726
    19
 *
dvander@2726
    20
 * As a special exception, AlliedModders LLC gives you permission to link the
dvander@2726
    21
 * code of this program (as well as its derivative works) to "Half-Life 2," the
dvander@2726
    22
 * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software
dvander@2726
    23
 * by the Valve Corporation.  You must obey the GNU General Public License in
dvander@2726
    24
 * all respects for all other code used.  Additionally, AlliedModders LLC grants
dvander@2726
    25
 * this exception to all derivative works.  AlliedModders LLC defines further
dvander@2726
    26
 * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007),
dvander@2726
    27
 * or <http://www.sourcemod.net/license.php>.
dvander@2726
    28
 *
dvander@2726
    29
 * Version: $Id$
dvander@2726
    30
 */
dvander@2726
    31
dvander@2726
    32
#include <sh_list.h>
dvander@4066
    33
#include <sm_namehashset.h>
dvander@2726
    34
#include "common_logic.h"
dvander@2726
    35
#include "CellArray.h"
skamonkey@3789
    36
#include <IGameHelpers.h>
dvander@2726
    37
#include <ILibrarySys.h>
dvander@2726
    38
#include <ITextParsers.h>
dvander@2726
    39
#include <ISourceMod.h>
voidedweasel@3929
    40
#include "stringutil.h"
dvander@2726
    41
dvander@2726
    42
using namespace SourceHook;
dvander@2726
    43
dvander@2726
    44
struct maplist_info_t
dvander@2726
    45
{
dvander@2726
    46
	bool bIsCompat;
dvander@2726
    47
	bool bIsPath;
dvander@2726
    48
	char name[PLATFORM_MAX_PATH];
dvander@2726
    49
	char path[PLATFORM_MAX_PATH];
dvander@2726
    50
	time_t last_modified_time;
dvander@2726
    51
	CellArray *pArray;
dvander@2726
    52
	int serial;
dvander@4066
    53
dvander@4066
    54
	static inline bool matches(const char *key, const maplist_info_t *value)
dvander@4066
    55
	{
dvander@4066
    56
		return strcmp(value->name, key) == 0;
dvander@4066
    57
	}
dvander@2726
    58
};
dvander@2726
    59
dvander@2726
    60
#define MAPLIST_FLAG_MAPSFOLDER		(1<<0)		/**< On failure, use all maps in the maps folder. */
dvander@2726
    61
#define MAPLIST_FLAG_CLEARARRAY		(1<<1)		/**< If an input array is specified, clear it before adding. */
dvander@2726
    62
#define MAPLIST_FLAG_NO_DEFAULT		(1<<2)		/**< Do not read "default" or "mapcyclefile" on failure. */
dvander@2726
    63
dvander@2726
    64
class MapLists : public SMGlobalClass, public ITextListener_SMC
dvander@2726
    65
{
dvander@2726
    66
public:
dvander@2726
    67
	enum MapListState
dvander@2726
    68
	{
dvander@2726
    69
		MPS_NONE,
dvander@2726
    70
		MPS_GLOBAL,
dvander@2726
    71
		MPS_MAPLIST,
dvander@2726
    72
	};
dvander@2726
    73
public:
dvander@2726
    74
	MapLists()
dvander@2726
    75
	{
dvander@2726
    76
		m_pMapCycleFile = NULL;
dvander@2726
    77
		m_ConfigLastChanged = 0;
dvander@2726
    78
		m_nSerialChange = 0;
dvander@2726
    79
	}
dvander@2726
    80
	void OnSourceModAllInitialized()
dvander@2726
    81
	{
dvander@2726
    82
		g_pSM->BuildPath(Path_SM, m_ConfigFile, sizeof(m_ConfigFile), "configs/maplists.cfg");
dvander@2726
    83
	}
dvander@2726
    84
	void OnSourceModShutdown()
dvander@2726
    85
	{
dvander@2726
    86
		DumpCache(NULL);
dvander@2726
    87
	}
skamonkey@3973
    88
	void GetMapCycleFilePath(char *pBuffer, int maxlen)
skamonkey@3973
    89
	{
skamonkey@3973
    90
		const char *pEngineName = smcore.GetSourceEngineName();
skamonkey@3973
    91
		const char *pMapCycleFileName = m_pMapCycleFile ? smcore.GetCvarString(m_pMapCycleFile) : "mapcycle.txt";
skamonkey@3973
    92
skamonkey@3973
    93
		if (strcmp(pEngineName, "tf2") == 0 || strcmp(pEngineName, "css") == 0
skamonkey@3973
    94
			|| strcmp(pEngineName, "dods") == 0 || strcmp(pEngineName, "hl2dm") == 0)
skamonkey@3973
    95
		{
skamonkey@3973
    96
			// These four games and Source SDK 2013 do a lookup in this order; so shall we.
skamonkey@4196
    97
			g_pSM->Format(pBuffer, maxlen, "cfg/%s", pMapCycleFileName);
skamonkey@3973
    98
skamonkey@4196
    99
			if (!smcore.filesystem->FileExists(pBuffer, "GAME"))
skamonkey@3973
   100
			{
skamonkey@4196
   101
				g_pSM->Format(pBuffer, maxlen, "%s", pMapCycleFileName);
skamonkey@3973
   102
skamonkey@4196
   103
				if (!smcore.filesystem->FileExists(pBuffer, "GAME"))
skamonkey@3973
   104
				{
skamonkey@4196
   105
					g_pSM->Format(pBuffer, maxlen, "cfg/mapcycle_default.txt");
skamonkey@3973
   106
				}
skamonkey@3973
   107
			}
skamonkey@3973
   108
		}
skamonkey@3973
   109
		else
skamonkey@3973
   110
		{
skamonkey@4196
   111
			g_pSM->Format(pBuffer, maxlen, "%s", pMapCycleFileName);
skamonkey@3973
   112
		}
skamonkey@3973
   113
	}
dvander@2726
   114
	void AddOrUpdateDefault(const char *name, const char *file)
dvander@2726
   115
	{
dvander@2726
   116
		char path[PLATFORM_MAX_PATH];
dvander@4066
   117
		maplist_info_t *pMapList;
dvander@2726
   118
dvander@4066
   119
		if (!m_ListLookup.retrieve(name, &pMapList))
dvander@2726
   120
		{
dvander@2726
   121
			pMapList = new maplist_info_t;
dvander@2726
   122
			pMapList->bIsCompat = true;
dvander@2726
   123
			pMapList->bIsPath = true;
dvander@2726
   124
			pMapList->last_modified_time = 0;
dvander@2726
   125
			smcore.strncopy(pMapList->name, name, sizeof(pMapList->name));
dvander@2726
   126
			pMapList->pArray = NULL;
skamonkey@4196
   127
			smcore.strncopy(pMapList->path, file, sizeof(pMapList->path));
dvander@2726
   128
			pMapList->serial = 0;
dvander@2726
   129
			m_ListLookup.insert(name, pMapList);
dvander@2726
   130
			m_MapLists.push_back(pMapList);
dvander@2726
   131
			return;
dvander@2726
   132
		}
dvander@2726
   133
dvander@2726
   134
		/* Don't modify if it's from the config file */
dvander@2726
   135
		if (!pMapList->bIsCompat)
dvander@2726
   136
			return;
dvander@2726
   137
skamonkey@4196
   138
		smcore.strncopy(path, file, sizeof(path));
dvander@2726
   139
dvander@2726
   140
		/* If the path matches, don't reset the serial/time */
dvander@2726
   141
		if (strcmp(path, pMapList->path) == 0)
dvander@2726
   142
			return;
dvander@2726
   143
dvander@2726
   144
		smcore.strncopy(pMapList->path, path, sizeof(pMapList->path));
dvander@2726
   145
		pMapList->bIsPath = true;
dvander@2726
   146
		pMapList->last_modified_time = 0;
dvander@2726
   147
		pMapList->serial = 0;
dvander@2726
   148
	}
dvander@2726
   149
	void UpdateCache()
dvander@2726
   150
	{
dvander@2726
   151
		bool fileFound;
dvander@2726
   152
		SMCError error;
dvander@2726
   153
		time_t fileTime;
dvander@2726
   154
		SMCStates states = {0, 0};
dvander@2726
   155
		
dvander@2726
   156
		fileFound = libsys->FileTime(m_ConfigFile, FileTime_LastChange, &fileTime);
dvander@2726
   157
dvander@2726
   158
		/* If the file is found and hasn't changed, bail out now. */
dvander@2726
   159
		if (fileFound && fileTime == m_ConfigLastChanged)
dvander@2726
   160
		{
dvander@2726
   161
			return;
dvander@2726
   162
		}
dvander@2726
   163
dvander@2726
   164
		/* If the file wasn't found, and we already have entries, we bail out too. 
dvander@2726
   165
		 * This case lets us optimize when a user deletes the config file, so we 
dvander@2726
   166
		 * don't reparse every single time the function is called.
dvander@2726
   167
		 */
dvander@2726
   168
		if (!fileFound && m_MapLists.size() > 0)
dvander@2726
   169
		{
dvander@2726
   170
			return;
dvander@2726
   171
		}
dvander@2726
   172
dvander@2726
   173
		m_pMapCycleFile = smcore.FindConVar("mapcyclefile");
dvander@2726
   174
dvander@2726
   175
		/* Dump everything we know about. */
dvander@2726
   176
		List<maplist_info_t *> compat;
dvander@2726
   177
		DumpCache(&compat);
dvander@2726
   178
dvander@2726
   179
		/* All this is to add the default entry back in. */
dvander@2726
   180
		maplist_info_t *pDefList = new maplist_info_t;
dvander@2726
   181
dvander@2726
   182
		pDefList->bIsPath = true;
dvander@2726
   183
		smcore.strncopy(pDefList->name, "mapcyclefile", sizeof(pDefList->name));
skamonkey@3966
   184
skamonkey@3973
   185
		GetMapCycleFilePath(pDefList->path, sizeof(pDefList->path));
skamonkey@3966
   186
		
dvander@2726
   187
		pDefList->last_modified_time = 0;
dvander@2726
   188
		pDefList->pArray = NULL;
dvander@2726
   189
		pDefList->serial = 0;
dvander@2726
   190
dvander@2726
   191
		m_ListLookup.insert("mapcyclefile", pDefList);
dvander@2726
   192
		m_MapLists.push_back(pDefList);
dvander@2726
   193
dvander@2726
   194
		/* Now parse the config file even if we don't know about it.
dvander@2726
   195
		 * This will give us a nice error message.
dvander@2726
   196
		 */
dvander@2726
   197
		if ((error = textparser->ParseFile_SMC(m_ConfigFile, this, &states))
dvander@2726
   198
			!= SMCError_Okay)
dvander@2726
   199
		{
dvander@2726
   200
			const char *errmsg = textparser->GetSMCErrorString(error);
dvander@2726
   201
			if (errmsg == NULL)
dvander@2726
   202
			{
dvander@2726
   203
				errmsg = "Unknown error";
dvander@2726
   204
			}
dvander@2726
   205
			smcore.LogError("[SM] Could not parse file \"%s\"", m_ConfigFile);
dvander@2726
   206
			smcore.LogError("[SM] Error on line %d (col %d): %s", states.line, states.col, errmsg);
dvander@2726
   207
		}
dvander@2726
   208
		else
dvander@2726
   209
		{
dvander@2726
   210
			m_ConfigLastChanged = fileTime;
dvander@2726
   211
		}
dvander@2726
   212
dvander@2726
   213
		/* Now, re-add compat stuff back in if we can. */
dvander@2726
   214
		List<maplist_info_t *>::iterator iter = compat.begin();
dvander@2726
   215
		while (iter != compat.end())
dvander@2726
   216
		{
dvander@4066
   217
			if (m_ListLookup.contains((*iter)->name))
dvander@2726
   218
			{
dvander@2726
   219
				/* The compatibility shim is no longer needed. */
dvander@2726
   220
				delete (*iter)->pArray;
dvander@2726
   221
				delete (*iter);
dvander@2726
   222
			}
dvander@2726
   223
			else
dvander@2726
   224
			{
dvander@2726
   225
				m_ListLookup.insert((*iter)->name, (*iter));
dvander@2726
   226
				m_MapLists.push_back((*iter));
dvander@2726
   227
			}
dvander@2726
   228
			iter = compat.erase(iter);
dvander@2726
   229
		}
dvander@2726
   230
	}
dvander@2726
   231
	void ReadSMC_ParseStart()
dvander@2726
   232
	{
dvander@2726
   233
		m_CurState = MPS_NONE;
dvander@2726
   234
		m_IgnoreLevel = 0;
dvander@2726
   235
		m_pCurMapList = NULL;
dvander@2726
   236
	}
dvander@2726
   237
	SMCResult ReadSMC_NewSection(const SMCStates *states, const char *name)
dvander@2726
   238
	{
dvander@2726
   239
		if (m_IgnoreLevel)
dvander@2726
   240
		{
dvander@2726
   241
			m_IgnoreLevel++;
dvander@2726
   242
			return SMCResult_Continue;
dvander@2726
   243
		}
dvander@2726
   244
dvander@2726
   245
		if (m_CurState == MPS_NONE)
dvander@2726
   246
		{
dvander@2726
   247
			if (strcmp(name, "MapLists") == 0)
dvander@2726
   248
			{
dvander@2726
   249
				m_CurState = MPS_GLOBAL;
dvander@2726
   250
			}
dvander@2726
   251
			else
dvander@2726
   252
			{
dvander@2726
   253
				m_IgnoreLevel = 1;
dvander@2726
   254
			}
dvander@2726
   255
		}
dvander@2726
   256
		else if (m_CurState == MPS_GLOBAL)
dvander@2726
   257
		{
dvander@2726
   258
			m_pCurMapList = new maplist_info_t;
dvander@2726
   259
dvander@2726
   260
			memset(m_pCurMapList, 0, sizeof(maplist_info_t));
dvander@2726
   261
			smcore.strncopy(m_pCurMapList->name, name, sizeof(m_pCurMapList->name));
dvander@2726
   262
dvander@2726
   263
			m_CurState = MPS_MAPLIST;
dvander@2726
   264
		}
dvander@2726
   265
		else if (m_CurState == MPS_MAPLIST)
dvander@2726
   266
		{
dvander@2726
   267
			m_IgnoreLevel++;
dvander@2726
   268
		}
dvander@2726
   269
dvander@2726
   270
		return SMCResult_Continue;
dvander@2726
   271
	}
dvander@2726
   272
	SMCResult ReadSMC_KeyValue(const SMCStates *states, const char *key, const char *value)
dvander@2726
   273
	{
dvander@2726
   274
		if (m_IgnoreLevel || m_pCurMapList == NULL)
dvander@2726
   275
		{
dvander@2726
   276
			return SMCResult_Continue;
dvander@2726
   277
		}
dvander@2726
   278
dvander@2726
   279
		if (strcmp(key, "file") == 0)
dvander@2726
   280
		{
skamonkey@4196
   281
			smcore.strncopy(m_pCurMapList->path, value, sizeof(m_pCurMapList->path));
dvander@2726
   282
			m_pCurMapList->bIsPath = true;
dvander@2726
   283
		}
dvander@2726
   284
		else if (strcmp(key, "target") == 0)
dvander@2726
   285
		{
dvander@2726
   286
			smcore.strncopy(m_pCurMapList->path, value, sizeof(m_pCurMapList->path));
dvander@2726
   287
			m_pCurMapList->bIsPath = false;
dvander@2726
   288
		}
dvander@2726
   289
dvander@2726
   290
		return SMCResult_Continue;
dvander@2726
   291
	}
dvander@2726
   292
	SMCResult ReadSMC_LeavingSection(const SMCStates *states)
dvander@2726
   293
	{
dvander@2726
   294
		if (m_IgnoreLevel)
dvander@2726
   295
		{
dvander@2726
   296
			m_IgnoreLevel--;
dvander@2726
   297
			return SMCResult_Continue;
dvander@2726
   298
		}
dvander@2726
   299
dvander@2726
   300
		if (m_CurState == MPS_MAPLIST)
dvander@2726
   301
		{
dvander@2726
   302
			if (m_pCurMapList != NULL
dvander@2726
   303
				&& m_pCurMapList->path[0] != '\0'
dvander@4066
   304
				&& !m_ListLookup.contains(m_pCurMapList->name))
dvander@2726
   305
			{
dvander@2726
   306
				m_ListLookup.insert(m_pCurMapList->name, m_pCurMapList);
dvander@2726
   307
				m_MapLists.push_back(m_pCurMapList);
dvander@2726
   308
				m_pCurMapList = NULL;
dvander@2726
   309
			}
dvander@2726
   310
			else
dvander@2726
   311
			{
dvander@2726
   312
				delete m_pCurMapList;
dvander@2726
   313
				m_pCurMapList = NULL;
dvander@2726
   314
			}
dvander@2726
   315
			m_CurState = MPS_GLOBAL;
dvander@2726
   316
		}
dvander@2726
   317
		else if (m_CurState == MPS_GLOBAL)
dvander@2726
   318
		{
dvander@2726
   319
			m_CurState = MPS_NONE;
dvander@2726
   320
		}
dvander@2726
   321
dvander@2726
   322
		return SMCResult_Continue;
dvander@2726
   323
	}
dvander@2726
   324
	void ReadSMC_ParseEnd(bool halted, bool failed)
dvander@2726
   325
	{
dvander@2726
   326
		delete m_pCurMapList;
dvander@2726
   327
		m_pCurMapList = NULL;
dvander@2726
   328
	}
dvander@2726
   329
	static int sort_maps_in_adt_array(const void *str1, const void *str2)
dvander@2726
   330
	{
dvander@2726
   331
		return strcmp((char *)str1, (char *)str2);
dvander@2726
   332
	}
dvander@2726
   333
	CellArray *UpdateMapList(CellArray *pUseArray, const char *name, int *pSerial, unsigned int flags)
dvander@2726
   334
	{
dvander@2726
   335
		int change_serial;
dvander@2726
   336
		CellArray *pNewArray = NULL;
dvander@2726
   337
		bool success, free_new_array;
dvander@2726
   338
dvander@2726
   339
		free_new_array = false;
dvander@2726
   340
		
dvander@2726
   341
		if ((success = GetMapList(&pNewArray, name, &change_serial)) == false)
dvander@2726
   342
		{
dvander@2726
   343
			if ((flags & MAPLIST_FLAG_NO_DEFAULT) != MAPLIST_FLAG_NO_DEFAULT)
dvander@2726
   344
			{
dvander@2726
   345
				/* If this list failed, and it's not the default, try the default. 
dvander@2726
   346
				 */
dvander@2726
   347
				if (strcmp(name, "default") != 0)
dvander@2726
   348
				{
dvander@2726
   349
					success = GetMapList(&pNewArray, name, &change_serial);
dvander@2726
   350
				}
dvander@2726
   351
				/* If either of the last two conditions failed, try again if we can. */
dvander@2726
   352
				if (!success && strcmp(name, "mapcyclefile") != 0)
dvander@2726
   353
				{
dvander@2726
   354
					success = GetMapList(&pNewArray, "mapcyclefile", &change_serial);
dvander@2726
   355
				}
dvander@2726
   356
			}
dvander@2726
   357
		}
dvander@2726
   358
dvander@2726
   359
		/* If there was a success, and the serial has not changed, bail out. */
dvander@2726
   360
		if (success && *pSerial == change_serial)
dvander@2726
   361
		{
dvander@2726
   362
			return NULL;
dvander@2726
   363
		}
dvander@2726
   364
dvander@2726
   365
		/**
dvander@2726
   366
		 * If there was a success but no map list, we need to look in the maps folder.
dvander@2726
   367
		 * If there was a failure and the flag is specified, we need to look in the maps folder.
dvander@2726
   368
		 */
dvander@2726
   369
		if ((success && pNewArray == NULL)
dvander@2726
   370
			|| (!success && ((flags & MAPLIST_FLAG_MAPSFOLDER) == MAPLIST_FLAG_MAPSFOLDER)))
dvander@2726
   371
		{
dvander@2726
   372
			pNewArray = new CellArray(64);
dvander@2726
   373
			free_new_array = true;
voidedweasel@3929
   374
voidedweasel@3929
   375
			cell_t *blk;
dvander@2726
   376
voidedweasel@3929
   377
			FileFindHandle_t findHandle;
voidedweasel@3929
   378
			const char *fileName = smcore.filesystem->FindFirstEx("maps/*.bsp", "GAME", &findHandle);
voidedweasel@3929
   379
voidedweasel@3929
   380
			while (fileName)
dvander@2726
   381
			{
dvander@2726
   382
				char buffer[PLATFORM_MAX_PATH];
dvander@2726
   383
voidedweasel@3929
   384
				UTIL_StripExtension(fileName, buffer, sizeof(buffer));
voidedweasel@3929
   385
voidedweasel@3929
   386
				if (!engine->IsMapValid(buffer))
voidedweasel@3929
   387
				{
voidedweasel@3929
   388
					fileName = smcore.filesystem->FindNext(findHandle);
voidedweasel@3929
   389
					continue;
voidedweasel@3929
   390
				}
voidedweasel@3929
   391
voidedweasel@3929
   392
				if ((blk = pNewArray->push()) == NULL)
dvander@2726
   393
				{
voidedweasel@3929
   394
					fileName = smcore.filesystem->FindNext(findHandle);
voidedweasel@3929
   395
					continue;
dvander@2726
   396
				}
voidedweasel@3929
   397
voidedweasel@3929
   398
				smcore.strncopy((char *)blk, buffer, 255);
voidedweasel@3929
   399
voidedweasel@3929
   400
				fileName = smcore.filesystem->FindNext(findHandle);
dvander@2726
   401
			}
dvander@2726
   402
voidedweasel@3929
   403
			smcore.filesystem->FindClose(findHandle);
voidedweasel@3929
   404
dvander@2726
   405
			/* Remove the array if there were no items. */
dvander@2726
   406
			if (pNewArray->size() == 0)
dvander@2726
   407
			{
dvander@2726
   408
				delete pNewArray;
dvander@2726
   409
				pNewArray = NULL;
dvander@2726
   410
			}
dvander@2726
   411
			else
dvander@2726
   412
			{
dvander@2726
   413
				qsort(pNewArray->base(), 
dvander@2726
   414
					pNewArray->size(), 
dvander@2726
   415
					pNewArray->blocksize() * sizeof(cell_t), 
dvander@2726
   416
					sort_maps_in_adt_array);
dvander@2726
   417
			}
dvander@2726
   418
dvander@2726
   419
			change_serial = -1;
dvander@2726
   420
		}
dvander@2726
   421
dvander@2726
   422
		/* If there is still no array by this point, bail out. */
dvander@2726
   423
		if (pNewArray == NULL)
dvander@2726
   424
		{
dvander@2726
   425
			*pSerial = -1;
dvander@2726
   426
			return NULL;
dvander@2726
   427
		}
dvander@2726
   428
dvander@2726
   429
		*pSerial = change_serial;
dvander@2726
   430
dvander@2726
   431
		/* If there is no input array, return something temporary. */
dvander@2726
   432
		if (pUseArray == NULL)
dvander@2726
   433
		{
dvander@2726
   434
			if (free_new_array)
dvander@2726
   435
			{
dvander@2726
   436
				return pNewArray;
dvander@2726
   437
			}
dvander@2726
   438
			else
dvander@2726
   439
			{
dvander@2726
   440
				return pNewArray->clone();
dvander@2726
   441
			}
dvander@2726
   442
		}
dvander@2726
   443
dvander@2726
   444
		/* Clear the input array if necessary. */
dvander@2726
   445
		if ((flags & MAPLIST_FLAG_CLEARARRAY) == MAPLIST_FLAG_CLEARARRAY)
dvander@2726
   446
		{
dvander@2726
   447
			pUseArray->clear();
dvander@2726
   448
		}
dvander@2726
   449
dvander@2726
   450
		/* Copy. */
dvander@2726
   451
		cell_t *blk_dst;
dvander@2726
   452
		cell_t *blk_src;
dvander@2726
   453
		for (size_t i = 0; i < pNewArray->size(); i++)
dvander@2726
   454
		{
dvander@2726
   455
			blk_dst = pUseArray->push();
dvander@2726
   456
			blk_src = pNewArray->at(i);
dvander@2726
   457
			smcore.strncopy((char *)blk_dst, (char *)blk_src, pUseArray->blocksize() * sizeof(cell_t));
dvander@2726
   458
		}
dvander@2726
   459
dvander@2726
   460
		/* Free resources if necessary. */
dvander@2726
   461
		if (free_new_array)
dvander@2726
   462
		{
dvander@2726
   463
			delete pNewArray;
dvander@2726
   464
		}
dvander@2726
   465
dvander@2726
   466
		/* Return the array we were given. */
dvander@2726
   467
		return pUseArray;
dvander@2726
   468
	}
dvander@2726
   469
private:
dvander@2726
   470
	bool GetMapList(CellArray **ppArray, const char *name, int *pSerial)
dvander@2726
   471
	{
dvander@2726
   472
		time_t last_time;
dvander@4066
   473
		maplist_info_t *pMapList;
dvander@2726
   474
dvander@4066
   475
		if (!m_ListLookup.retrieve(name, &pMapList))
dvander@2726
   476
			return false;
dvander@2726
   477
dvander@2726
   478
		if (!pMapList->bIsPath)
dvander@2726
   479
			return GetMapList(ppArray, pMapList->path, pSerial);
dvander@2726
   480
dvander@2726
   481
		/* If it is a path, and the path is "*", assume all files must be used. */
dvander@2726
   482
		if (strcmp(pMapList->path, "*") == 0)
dvander@2726
   483
		{
dvander@2726
   484
			*ppArray = NULL;
dvander@2726
   485
			return true;
dvander@2726
   486
		}
dvander@2726
   487
dvander@2726
   488
		if (m_pMapCycleFile != NULL && strcmp(name, "mapcyclefile") == 0)
dvander@2726
   489
		{
dvander@2726
   490
			char path[PLATFORM_MAX_PATH];
skamonkey@3973
   491
			GetMapCycleFilePath(path, sizeof(path));
dvander@2726
   492
dvander@2726
   493
			if (strcmp(path, pMapList->path) != 0)
dvander@2726
   494
			{
dvander@2726
   495
				smcore.strncopy(pMapList->path, path, sizeof(pMapList->path));
dvander@2726
   496
				pMapList->last_modified_time = 0;
dvander@2726
   497
			}
dvander@2726
   498
		}
dvander@2726
   499
dvander@2726
   500
		if (!libsys->FileTime(pMapList->path, FileTime_LastChange, &last_time)
dvander@2726
   501
			|| last_time > pMapList->last_modified_time)
dvander@2726
   502
		{
dvander@2726
   503
			/* Reparse */
skamonkey@4196
   504
			FileHandle_t fp;
dvander@2726
   505
			cell_t *blk;
dvander@2726
   506
			char buffer[255];
dvander@2726
   507
skamonkey@4196
   508
			if ((fp = smcore.filesystem->Open(pMapList->path, "rt", "GAME")) == NULL)
dvander@2726
   509
			{
dvander@2726
   510
				return false;
dvander@2726
   511
			}
dvander@2726
   512
dvander@2726
   513
			delete pMapList->pArray;
dvander@2726
   514
			pMapList->pArray = new CellArray(64);
dvander@2726
   515
skamonkey@4196
   516
			while (!smcore.filesystem->EndOfFile(fp) && smcore.filesystem->ReadLine(buffer, sizeof(buffer), fp) != NULL)
dvander@2726
   517
			{
dvander@2726
   518
				size_t len = strlen(buffer);
dvander@2726
   519
				char *ptr = smcore.TrimWhitespace(buffer, len);
dvander@2726
   520
				if (*ptr == '\0'
dvander@2726
   521
					|| *ptr == ';'
dvander@2726
   522
					|| strncmp(ptr, "//", 2) == 0)
dvander@2726
   523
				{
dvander@2726
   524
					continue;
dvander@2726
   525
				}
skamonkey@3789
   526
				if (!gamehelpers->IsMapValid(ptr))
dvander@2726
   527
				{
dvander@2726
   528
					continue;
dvander@2726
   529
				}
dvander@2726
   530
				if ((blk = pMapList->pArray->push()) != NULL)
dvander@2726
   531
				{
dvander@2726
   532
					smcore.strncopy((char *)blk, ptr, 255);
dvander@2726
   533
				}
dvander@2726
   534
			}
dvander@2726
   535
skamonkey@4196
   536
			smcore.filesystem->Close(fp);
dvander@2726
   537
dvander@2726
   538
			pMapList->last_modified_time = last_time;
dvander@2726
   539
			pMapList->serial = ++m_nSerialChange;
dvander@2726
   540
		}
dvander@2726
   541
dvander@2726
   542
		if (pMapList->pArray == NULL || pMapList->pArray->size() == 0)
dvander@2726
   543
		{
dvander@2726
   544
			return false;
dvander@2726
   545
		}
dvander@2726
   546
dvander@2726
   547
		*pSerial = pMapList->serial;
dvander@2726
   548
		*ppArray = pMapList->pArray;
dvander@2726
   549
dvander@2726
   550
		return true;
dvander@2726
   551
	}
dvander@2726
   552
	void DumpCache(List<maplist_info_t *> *compat_list)
dvander@2726
   553
	{
dvander@2726
   554
		m_ListLookup.clear();
dvander@2726
   555
dvander@2726
   556
		List<maplist_info_t *>::iterator iter = m_MapLists.begin();
dvander@2726
   557
		while (iter != m_MapLists.end())
dvander@2726
   558
		{
dvander@2726
   559
			if (compat_list != NULL && (*iter)->bIsCompat)
dvander@2726
   560
			{
dvander@2726
   561
				compat_list->push_back((*iter));
dvander@2726
   562
			}
dvander@2726
   563
			else
dvander@2726
   564
			{
dvander@2726
   565
				delete (*iter)->pArray;
dvander@2726
   566
				delete (*iter);
dvander@2726
   567
			}
dvander@2726
   568
			iter = m_MapLists.erase(iter);
dvander@2726
   569
		}
dvander@2726
   570
	}
dvander@2726
   571
private:
dvander@2726
   572
	char m_ConfigFile[PLATFORM_MAX_PATH];
dvander@2726
   573
	time_t m_ConfigLastChanged;
dvander@2726
   574
	ConVar *m_pMapCycleFile;
dvander@4066
   575
	NameHashSet<maplist_info_t *> m_ListLookup;
dvander@2726
   576
	List<maplist_info_t *> m_MapLists;
dvander@2726
   577
	MapListState m_CurState;
dvander@2726
   578
	unsigned int m_IgnoreLevel;
dvander@2726
   579
	maplist_info_t *m_pCurMapList;
dvander@2726
   580
	int m_nSerialChange;
dvander@2726
   581
} s_MapLists;
dvander@2726
   582
dvander@2726
   583
static cell_t LoadMapList(IPluginContext *pContext, const cell_t *params)
dvander@2726
   584
{
dvander@2726
   585
	char *str;
dvander@2726
   586
	Handle_t hndl;
dvander@2726
   587
	cell_t *addr, flags;
dvander@2726
   588
	CellArray *pArray, *pNewArray;
dvander@2726
   589
dvander@2726
   590
	hndl = params[1];
dvander@2726
   591
	pContext->LocalToPhysAddr(params[2], &addr);
dvander@2726
   592
	pContext->LocalToString(params[3], &str);
dvander@2726
   593
	flags = params[4];
dvander@2726
   594
dvander@2726
   595
	/* Make sure the input Handle is valid */
dvander@2726
   596
	pArray = NULL;
dvander@2726
   597
	if (hndl != BAD_HANDLE)
dvander@2726
   598
	{
dvander@2726
   599
		HandleError err;
dvander@2726
   600
		HandleSecurity sec(pContext->GetIdentity(), g_pCoreIdent);
dvander@2726
   601
dvander@2726
   602
		if ((err = handlesys->ReadHandle(hndl, htCellArray, &sec, (void **)&pArray))
dvander@2726
   603
			!= HandleError_None)
dvander@2726
   604
		{
dvander@2726
   605
			return pContext->ThrowNativeError("Invalid Handle %x (error %d)", hndl, err);
dvander@2726
   606
		}
dvander@2726
   607
	}
dvander@2726
   608
dvander@2726
   609
	/* Make sure the map list cache is up to date at the root */
dvander@2726
   610
	s_MapLists.UpdateCache();
dvander@2726
   611
dvander@2726
   612
	/* Try to get the map list. */
dvander@2726
   613
	if ((pNewArray = s_MapLists.UpdateMapList(pArray, str, addr, flags)) == NULL)
dvander@2726
   614
	{
dvander@2726
   615
		return BAD_HANDLE;
dvander@2726
   616
	}
dvander@2726
   617
dvander@2726
   618
	/* If the user wanted a new array, create it now. */
dvander@2726
   619
	if (hndl == BAD_HANDLE)
dvander@2726
   620
	{
dvander@2726
   621
		if ((hndl = handlesys->CreateHandle(htCellArray, pNewArray, pContext->GetIdentity(), g_pCoreIdent, NULL))
dvander@2726
   622
			== BAD_HANDLE)
dvander@2726
   623
		{
dvander@2726
   624
			*addr = -1;
dvander@2726
   625
			delete pNewArray;
dvander@2726
   626
			return BAD_HANDLE;
dvander@2726
   627
		}
dvander@2726
   628
	}
dvander@2726
   629
dvander@2726
   630
	return hndl;
dvander@2726
   631
}
dvander@2726
   632
dvander@2726
   633
static cell_t SetMapListCompatBind(IPluginContext *pContext, const cell_t *params)
dvander@2726
   634
{
dvander@2726
   635
	char *name, *file;
dvander@2726
   636
dvander@2726
   637
	pContext->LocalToString(params[1], &name);
dvander@2726
   638
	pContext->LocalToString(params[2], &file);
dvander@2726
   639
dvander@2726
   640
	s_MapLists.UpdateCache();
dvander@2726
   641
	s_MapLists.AddOrUpdateDefault(name, file);
dvander@2726
   642
dvander@2726
   643
	return 1;
dvander@2726
   644
}
dvander@2726
   645
dvander@2726
   646
REGISTER_NATIVES(mapListNatives)
dvander@2726
   647
{
dvander@2726
   648
	{"ReadMapList",				LoadMapList},
dvander@2726
   649
	{"SetMapListCompatBind",	SetMapListCompatBind},
dvander@2726
   650
	{NULL,						NULL},
dvander@2726
   651
};