3 * ===============================================================
4 * SourceMod (C)2004-2007 AlliedModders LLC. All rights reserved.
5 * ===============================================================
7 * This file is not open source and may not be copied without explicit
8 * written permission of AlliedModders LLC. This file may not be redistributed
9 * in whole or significant part.
10 * For information, see LICENSE.txt or http://www.sourcemod.net/license.php
16 #include "sourcemod.h"
17 #include "sourcemm_api.h"
18 #include "systems/LibrarySys.h"
19 #include "vm/sp_vm_engine.h"
20 #include <sh_string.h>
21 #include "PluginSys.h"
23 #include "CoreConfig.h"
25 #include "ExtensionSys.h"
26 #include "AdminCache.h"
27 #include "sm_stringutil.h"
28 #include "PlayerManager.h"
29 #include "Translator.h"
30 #include "ForwardSys.h"
32 #include "MenuStyle_Valve.h"
33 #include "MenuStyle_Radio.h"
36 SH_DECL_HOOK6(IServerGameDLL, LevelInit, SH_NOATTRIB, false, bool, const char *, const char *, const char *, const char *, bool, bool);
37 SH_DECL_HOOK0_void(IServerGameDLL, LevelShutdown, SH_NOATTRIB, false);
38 SH_DECL_HOOK1_void(IServerGameDLL, GameFrame, SH_NOATTRIB, false, bool);
39 SH_DECL_HOOK1_void(IVEngineServer, ServerCommand, SH_NOATTRIB, false, const char *);
41 SourcePawnEngine g_SourcePawn;
42 SourceModBase g_SourceMod;
44 ILibrary *g_pJIT = NULL;
45 SourceHook::String g_BaseDir;
46 ISourcePawnEngine *g_pSourcePawn = &g_SourcePawn;
47 IVirtualMachine *g_pVM;
48 IdentityToken_t *g_pCoreIdent = NULL;
49 float g_LastTime = 0.0f;
50 float g_LastMenuTime = 0.0f;
51 float g_LastAuthCheck = 0.0f;
52 IForward *g_pOnGameFrame = NULL;
53 IForward *g_pOnMapEnd = NULL;
54 bool g_Loaded = false;
55 int g_StillFrames = 0;
56 float g_StillTime = 0.0f;
58 typedef int (*GIVEENGINEPOINTER)(ISourcePawnEngine *);
59 typedef unsigned int (*GETEXPORTCOUNT)();
60 typedef IVirtualMachine *(*GETEXPORT)(unsigned int);
61 typedef void (*NOTIFYSHUTDOWN)();
65 NOTIFYSHUTDOWN notify = (NOTIFYSHUTDOWN)g_pJIT->GetSymbolAddress("NotifyShutdown");
71 g_pJIT->CloseLibrary();
74 SourceModBase::SourceModBase()
76 m_IsMapLoading = false;
77 m_ExecPluginReload = false;
78 m_ExecOnMapEnd = false;
79 m_GotBasePath = false;
82 ConfigResult SourceModBase::OnSourceModConfigChanged(const char *key,
88 if (strcasecmp(key, "BasePath") == 0)
90 if (source == ConfigSource_Console)
92 UTIL_Format(error, maxlength, "Cannot be set at runtime");
93 return ConfigResult_Reject;
98 g_LibSys.PathFormat(m_SMBaseDir, sizeof(m_SMBaseDir), "%s/%s", g_BaseDir.c_str(), value);
99 g_LibSys.PathFormat(m_SMRelDir, sizeof(m_SMRelDir), value);
101 m_GotBasePath = true;
104 return ConfigResult_Accept;
107 return ConfigResult_Ignore;
110 bool SourceModBase::InitializeSourceMod(char *error, size_t maxlength, bool late)
112 const char *gamepath = g_SMAPI->GetBaseDir();
114 /* Store full path to game */
115 g_BaseDir.assign(gamepath);
117 /* Store name of game directory by itself */
118 size_t len = strlen(gamepath);
119 for (size_t i = len - 1; i >= 0; i--)
121 if (gamepath[i] == PLATFORM_SEP_CHAR)
123 strncopy(m_ModDir, &gamepath[++i], sizeof(m_ModDir));
128 /* Initialize CoreConfig so we can get SourceMod base path properly - this basically parses core.cfg */
129 g_CoreConfig.Initialize();
131 /* This shouldn't happen, but can't hurt to be safe */
132 if (!g_LibSys.PathExists(m_SMBaseDir) || !m_GotBasePath)
134 g_LibSys.PathFormat(m_SMBaseDir, sizeof(m_SMBaseDir), "%s/addons/sourcemod", g_BaseDir.c_str());
135 g_LibSys.PathFormat(m_SMRelDir, sizeof(m_SMRelDir), "addons/sourcemod");
136 m_GotBasePath = true;
139 /* Attempt to load the JIT! */
140 char file[PLATFORM_MAX_PATH];
142 g_SMAPI->PathFormat(file, sizeof(file), "%s/bin/sourcepawn.jit.x86.%s",
147 g_pJIT = g_LibSys.OpenLibrary(file, myerror, sizeof(myerror));
150 if (error && maxlength)
152 snprintf(error, maxlength, "%s (failed to load bin/sourcepawn.jit.x86.%s)",
160 GIVEENGINEPOINTER jit_init = (GIVEENGINEPOINTER)g_pJIT->GetSymbolAddress("GiveEnginePointer");
164 if (error && maxlength)
166 snprintf(error, maxlength, "Failed to find GiveEnginePointer in JIT!");
171 if ((err=jit_init(g_pSourcePawn)) != 0)
174 if (error && maxlength)
176 snprintf(error, maxlength, "GiveEnginePointer returned %d in the JIT", err);
181 GETEXPORTCOUNT jit_getnum = (GETEXPORTCOUNT)g_pJIT->GetSymbolAddress("GetExportCount");
182 GETEXPORT jit_get = (GETEXPORT)g_pJIT->GetSymbolAddress("GetExport");
186 if (error && maxlength)
188 snprintf(error, maxlength, "JIT is missing a necessary export!");
193 unsigned int num = jit_getnum();
194 if (!num || ((g_pVM=jit_get(0)) == NULL))
197 if (error && maxlength)
199 snprintf(error, maxlength, "JIT did not export any virtual machines!");
204 unsigned int api = g_pVM->GetAPIVersion();
205 /* :TODO: clean this up (see amb398) */
206 if (api > SOURCEPAWN_VM_API_VERSION || api < 2)
209 if (error && maxlength)
211 snprintf(error, maxlength, "JIT is not a compatible version");
216 /* Hook this now so we can detect startup without calling StartSourceMod() */
217 SH_ADD_HOOK_MEMFUNC(IServerGameDLL, LevelInit, gamedll, this, &SourceModBase::LevelInit, false);
219 /* Only load if we're not late */
222 StartSourceMod(false);
228 void SourceModBase::StartSourceMod(bool late)
230 SH_ADD_HOOK_MEMFUNC(IServerGameDLL, LevelShutdown, gamedll, this, &SourceModBase::LevelShutdown, false);
231 SH_ADD_HOOK_MEMFUNC(IServerGameDLL, GameFrame, gamedll, this, &SourceModBase::GameFrame, false);
233 enginePatch = SH_GET_CALLCLASS(engine);
234 gamedllPatch = SH_GET_CALLCLASS(gamedll);
237 SMGlobalClass *pBase = SMGlobalClass::head;
240 pBase->OnSourceModStartup(false);
241 pBase = pBase->m_pGlobalClassNext;
244 /* Make the global core identity */
245 g_pCoreIdent = g_ShareSys.CreateCoreIdentity();
248 pBase = SMGlobalClass::head;
251 pBase->OnSourceModAllInitialized();
252 pBase = pBase->m_pGlobalClassNext;
256 g_ShareSys.AddInterface(NULL, this);
262 static bool g_LevelEndBarrier = false;
263 bool SourceModBase::LevelInit(char const *pMapName, char const *pMapEntities, char const *pOldLevel, char const *pLandmarkName, bool loadGame, bool background)
265 /* If we're not loaded... */
268 /* Do all global initialization now */
269 StartSourceMod(true);
272 m_IsMapLoading = true;
273 m_ExecPluginReload = true;
274 m_ExecOnMapEnd = true;
276 g_LastMenuTime = 0.0f;
277 g_LastAuthCheck = 0.0f;
278 g_SimTicks.ticking = true;
279 g_SimTicks.tickcount = 0;
284 SMGlobalClass *pBase = SMGlobalClass::head;
287 pBase->OnSourceModLevelChange(pMapName);
288 pBase = pBase->m_pGlobalClassNext;
291 DoGlobalPluginLoads();
293 m_IsMapLoading = false;
296 pBase = SMGlobalClass::head;
299 pBase->OnSourceModPluginsLoaded();
300 pBase = pBase->m_pGlobalClassNext;
305 g_pOnGameFrame = g_Forwards.CreateForward("OnGameFrame", ET_Ignore, 0, NULL);
310 g_pOnMapEnd = g_Forwards.CreateForward("OnMapEnd", ET_Ignore, 0, NULL);
313 g_LevelEndBarrier = true;
315 RETURN_META_VALUE(MRES_IGNORED, true);
318 void StartTickSimulation()
320 g_SimTicks.ticking = false;
321 g_SimTicks.tickcount = 0;
322 g_SimTicks.ticktime = gpGlobals->curtime;
325 void StopTickSimulation()
327 g_SimTicks.ticking = true;
328 g_Timers.MapChange();
330 g_LastTime = gpGlobals->curtime;
335 g_SimTicks.tickcount++;
336 g_SimTicks.ticktime = g_StillTime + (g_SimTicks.tickcount * gpGlobals->interval_per_tick);
339 void SourceModBase::GameFrame(bool simulating)
344 * Note: This is all hardcoded rather than delegated to save
345 * precious CPU cycles.
347 float curtime = gpGlobals->curtime;
348 int framecount = gpGlobals->framecount;
350 /* Verify that we're still ticking */
351 if (g_SimTicks.ticking)
353 if (g_StillFrames == 0)
355 g_StillFrames = framecount;
356 g_StillTime = curtime;
358 /* Try to detect when we've stopped ticking.
359 * We do this once 10 frames pass and there have been no ticks.
361 if (g_StillTime == curtime)
363 if (framecount - g_StillFrames >= 5)
365 StartTickSimulation();
369 /* We're definitely ticking we get here,
370 * but update everything as a precaution */
371 g_StillFrames = framecount;
372 g_StillTime = curtime;
376 /* We need to make sure we should still be simulating. */
377 if (g_StillTime != curtime)
379 /* Wow, we're ticking again! It's time to revert. */
380 StopTickSimulation();
383 /* Nope, not ticking. Simulate! */
385 curtime = g_SimTicks.ticktime;
388 if (curtime - g_LastTime >= 0.1f)
391 && (gpGlobals->curtime - g_LastAuthCheck > 0.7f))
393 g_LastAuthCheck = gpGlobals->curtime;
394 g_Players.RunAuthChecks();
398 g_LastTime = curtime;
401 if (g_SimTicks.ticking && curtime - g_LastMenuTime >= 1.0f)
403 g_ValveMenuStyle.ProcessWatchList();
404 g_RadioMenuStyle.ProcessWatchList();
405 g_LastMenuTime = curtime;
408 if (g_pOnGameFrame && g_pOnGameFrame->GetFunctionCount())
410 g_pOnGameFrame->Execute(NULL);
414 void SourceModBase::LevelShutdown()
416 if (g_LevelEndBarrier)
418 SMGlobalClass *next = SMGlobalClass::head;
421 next->OnSourceModLevelEnd();
422 next = next->m_pGlobalClassNext;
424 g_LevelEndBarrier = false;
427 if (g_pOnMapEnd && m_ExecOnMapEnd)
429 g_pOnMapEnd->Execute(NULL);
430 m_ExecOnMapEnd = false;
433 g_OnMapStarted = false;
435 if (m_ExecPluginReload)
437 g_PluginSys.ReloadOrUnloadPlugins();
438 m_ExecPluginReload = false;
442 bool SourceModBase::IsMapLoading() const
444 return m_IsMapLoading;
447 void SourceModBase::DoGlobalPluginLoads()
449 char config_path[PLATFORM_MAX_PATH];
450 char plugins_path[PLATFORM_MAX_PATH];
452 BuildPath(Path_SM, config_path,
454 "configs/plugin_settings.cfg");
455 BuildPath(Path_SM, plugins_path,
456 sizeof(plugins_path),
459 /* Load any auto extensions */
460 g_Extensions.TryAutoload();
462 /* Run the first pass */
463 g_PluginSys.LoadAll_FirstPass(config_path, plugins_path);
465 /* Mark any extensions as loaded */
466 g_Extensions.MarkAllLoaded();
468 /* No modules yet, it's safe to call this from here */
469 g_PluginSys.LoadAll_SecondPass();
471 /* Re-mark any extensions as loaded */
472 g_Extensions.MarkAllLoaded();
475 size_t SourceModBase::BuildPath(PathType type, char *buffer, size_t maxlength, char *format, ...)
477 char _buffer[PLATFORM_MAX_PATH];
480 va_start(ap, format);
481 vsnprintf(_buffer, PLATFORM_MAX_PATH, format, ap);
484 const char *base = NULL;
485 if (type == Path_Game)
487 base = GetGamePath();
488 } else if (type == Path_SM) {
489 base = GetSourceModPath();
490 } else if (type == Path_SM_Rel) {
496 return g_LibSys.PathFormat(buffer, maxlength, "%s/%s", base, _buffer);
498 return g_LibSys.PathFormat(buffer, maxlength, "%s", _buffer);
502 void SourceModBase::CloseSourceMod()
504 /* Force a level end */
508 g_PluginSys.Shutdown();
510 /* Unload extensions */
511 g_Extensions.Shutdown();
513 SH_REMOVE_HOOK_MEMFUNC(IServerGameDLL, LevelInit, gamedll, this, &SourceModBase::LevelInit, false);
519 g_Forwards.ReleaseForward(g_pOnGameFrame);
524 g_Forwards.ReleaseForward(g_pOnMapEnd);
528 SMGlobalClass *pBase = SMGlobalClass::head;
531 pBase->OnSourceModShutdown();
532 pBase = pBase->m_pGlobalClassNext;
535 /* Delete all data packs */
536 CStack<CDataPack *>::iterator iter;
538 for (iter=m_freepacks.begin(); iter!=m_freepacks.end(); iter++)
543 m_freepacks.popall();
546 pBase = SMGlobalClass::head;
549 pBase->OnSourceModAllShutdown();
550 pBase = pBase->m_pGlobalClassNext;
555 SH_RELEASE_CALLCLASS(enginePatch);
561 SH_RELEASE_CALLCLASS(gamedllPatch);
565 SH_REMOVE_HOOK_MEMFUNC(IServerGameDLL, LevelShutdown, gamedll, this, &SourceModBase::LevelShutdown, false);
566 SH_REMOVE_HOOK_MEMFUNC(IServerGameDLL, GameFrame, gamedll, this, &SourceModBase::GameFrame, false);
573 void SourceModBase::LogMessage(IExtension *pExt, const char *format, ...)
575 IExtensionInterface *pAPI = pExt->GetAPI();
576 const char *tag = pAPI->GetExtensionTag();
580 va_start(ap, format);
581 vsnprintf(buffer, sizeof(buffer), format, ap);
586 g_Logger.LogMessage("[%s] %s", tag, buffer);
588 g_Logger.LogMessage("%s", buffer);
592 void SourceModBase::LogError(IExtension *pExt, const char *format, ...)
594 IExtensionInterface *pAPI = pExt->GetAPI();
595 const char *tag = pAPI->GetExtensionTag();
599 va_start(ap, format);
600 vsnprintf(buffer, sizeof(buffer), format, ap);
605 g_Logger.LogError("[%s] %s", tag, buffer);
607 g_Logger.LogError("%s", buffer);
611 size_t SourceModBase::FormatString(char *buffer, size_t maxlength, IPluginContext *pContext, const cell_t *params, unsigned int param)
615 pContext->LocalToString(params[param], &fmt);
617 int lparam = ++param;
619 return atcprintf(buffer, maxlength, fmt, pContext, params, &lparam);
622 const char *SourceModBase::GetSourceModPath() const
627 const char *SourceModBase::GetGamePath() const
629 return g_BaseDir.c_str();
632 void SourceModBase::SetGlobalTarget(unsigned int index)
637 void SourceModBase::SetAuthChecking(bool set)
639 m_CheckingAuth = set;
642 unsigned int SourceModBase::GetGlobalTarget() const
647 IDataPack *SourceModBase::CreateDataPack()
650 if (m_freepacks.empty())
652 pack = new CDataPack;
654 pack = m_freepacks.front();
661 void SourceModBase::FreeDataPack(IDataPack *pack)
663 m_freepacks.push(static_cast<CDataPack *>(pack));
666 Handle_t SourceModBase::GetDataPackHandleType(bool readonly)
672 const char *SourceModBase::GetGameFolderName() const
677 ISourcePawnEngine *SourceModBase::GetScriptingEngine()
679 return g_pSourcePawn;
682 IVirtualMachine *SourceModBase::GetScriptingVM()
687 void SourceModBase::AllPluginsLoaded()
689 SMGlobalClass *base = SMGlobalClass::head;
692 base->OnSourceModGameInitialized();
693 base = base->m_pGlobalClassNext;
697 SMGlobalClass *SMGlobalClass::head = NULL;
699 SMGlobalClass::SMGlobalClass()
701 m_pGlobalClassNext = SMGlobalClass::head;
702 SMGlobalClass::head = this;