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