rewrite of the native and dependency binding/interaction code. this will ease the transition for native overrides, and greatly simplifies most of the logic. all native binding code now takes place almost entirely in ShareSys, and PluginSys supplements this logic where appropriate. extensionsys has been cleaned up
3 * =============================================================================
5 * Copyright (C) 2004-2008 AlliedModders LLC. All rights reserved.
6 * =============================================================================
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.
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
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/>.
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>.
33 #include "sourcemod.h"
34 #include "sourcemm_api.h"
35 #include "systems/LibrarySys.h"
36 #include "vm/sp_vm_engine.h"
37 #include <sh_string.h>
38 #include "PluginSys.h"
40 #include "CoreConfig.h"
42 #include "ExtensionSys.h"
43 #include "AdminCache.h"
44 #include "sm_stringutil.h"
45 #include "PlayerManager.h"
46 #include "Translator.h"
47 #include "ForwardSys.h"
49 #include "GameConfigs.h"
51 SH_DECL_HOOK6(IServerGameDLL, LevelInit, SH_NOATTRIB, false, bool, const char *, const char *, const char *, const char *, bool, bool);
52 SH_DECL_HOOK0_void(IServerGameDLL, LevelShutdown, SH_NOATTRIB, false);
53 SH_DECL_HOOK1_void(IServerGameDLL, GameFrame, SH_NOATTRIB, false, bool);
54 SH_DECL_HOOK1_void(IVEngineServer, ServerCommand, SH_NOATTRIB, false, const char *);
56 SourcePawnEngine g_SourcePawn;
57 SourceModBase g_SourceMod;
59 ILibrary *g_pJIT = NULL;
60 SourceHook::String g_BaseDir;
61 ISourcePawnEngine *g_pSourcePawn = &g_SourcePawn;
62 IVirtualMachine *g_pVM;
63 IdentityToken_t *g_pCoreIdent = NULL;
64 IForward *g_pOnMapEnd = NULL;
65 bool g_Loaded = false;
67 typedef int (*GIVEENGINEPOINTER)(ISourcePawnEngine *);
68 typedef int (*GIVEENGINEPOINTER2)(ISourcePawnEngine *, unsigned int api_version);
69 typedef unsigned int (*GETEXPORTCOUNT)();
70 typedef IVirtualMachine *(*GETEXPORT)(unsigned int);
71 typedef void (*NOTIFYSHUTDOWN)();
75 NOTIFYSHUTDOWN notify = (NOTIFYSHUTDOWN)g_pJIT->GetSymbolAddress("NotifyShutdown");
81 g_pJIT->CloseLibrary();
84 SourceModBase::SourceModBase()
86 m_IsMapLoading = false;
87 m_ExecPluginReload = false;
88 m_GotBasePath = false;
91 ConfigResult SourceModBase::OnSourceModConfigChanged(const char *key,
97 if (strcasecmp(key, "BasePath") == 0)
99 if (source == ConfigSource_Console)
101 UTIL_Format(error, maxlength, "Cannot be set at runtime");
102 return ConfigResult_Reject;
107 g_LibSys.PathFormat(m_SMBaseDir, sizeof(m_SMBaseDir), "%s/%s", g_BaseDir.c_str(), value);
108 g_LibSys.PathFormat(m_SMRelDir, sizeof(m_SMRelDir), value);
110 m_GotBasePath = true;
113 return ConfigResult_Accept;
116 return ConfigResult_Ignore;
119 bool SourceModBase::InitializeSourceMod(char *error, size_t maxlength, bool late)
121 const char *gamepath = g_SMAPI->GetBaseDir();
123 /* Store full path to game */
124 g_BaseDir.assign(gamepath);
126 /* Store name of game directory by itself */
127 size_t len = strlen(gamepath);
128 for (size_t i = len - 1; i >= 0; i--)
130 if (gamepath[i] == PLATFORM_SEP_CHAR)
132 strncopy(m_ModDir, &gamepath[++i], sizeof(m_ModDir));
137 /* Initialize CoreConfig so we can get SourceMod base path properly - this basically parses core.cfg */
138 g_CoreConfig.Initialize();
140 /* This shouldn't happen, but can't hurt to be safe */
141 if (!g_LibSys.PathExists(m_SMBaseDir) || !m_GotBasePath)
143 g_LibSys.PathFormat(m_SMBaseDir, sizeof(m_SMBaseDir), "%s/addons/sourcemod", g_BaseDir.c_str());
144 g_LibSys.PathFormat(m_SMRelDir, sizeof(m_SMRelDir), "addons/sourcemod");
145 m_GotBasePath = true;
148 /* Attempt to load the JIT! */
149 char file[PLATFORM_MAX_PATH];
151 g_SMAPI->PathFormat(file, sizeof(file), "%s/bin/sourcepawn.jit.x86.%s",
156 g_pJIT = g_LibSys.OpenLibrary(file, myerror, sizeof(myerror));
159 if (error && maxlength)
161 snprintf(error, maxlength, "%s (failed to load bin/sourcepawn.jit.x86.%s)",
170 GIVEENGINEPOINTER2 jit_init2 = (GIVEENGINEPOINTER2)g_pJIT->GetSymbolAddress("GiveEnginePointer2");
173 GIVEENGINEPOINTER jit_init = (GIVEENGINEPOINTER)g_pJIT->GetSymbolAddress("GiveEnginePointer");
177 if (error && maxlength)
179 snprintf(error, maxlength, "Failed to find GiveEnginePointer in JIT!");
184 if ((err=jit_init(g_pSourcePawn)) != 0)
187 if (error && maxlength)
189 snprintf(error, maxlength, "GiveEnginePointer returned %d in the JIT", err);
196 /* On version bumps, we should check for older versions as well, if the new version fails.
197 * We can then check the exports to see if any VM versions will be sufficient.
199 if ((err=jit_init2(g_pSourcePawn, SOURCEPAWN_ENGINE_API_VERSION)) != SP_ERROR_NONE)
202 if (error && maxlength)
204 snprintf(error, maxlength, "JIT incompatible with SourceMod version");
210 GETEXPORTCOUNT jit_getnum = (GETEXPORTCOUNT)g_pJIT->GetSymbolAddress("GetExportCount");
211 GETEXPORT jit_get = (GETEXPORT)g_pJIT->GetSymbolAddress("GetExport");
215 if (error && maxlength)
217 snprintf(error, maxlength, "JIT is missing a necessary export!");
222 unsigned int num = jit_getnum();
226 if (error && maxlength)
228 snprintf(error, maxlength, "JIT did not export any virtual machines!");
233 unsigned int api_version;
234 for (unsigned int i=0; i<num; i++)
236 if ((g_pVM=jit_get(i)) == NULL)
238 if (error && maxlength)
240 snprintf(error, maxlength, "JIT did not export any virtual machines!");
244 /* Refuse any API that we might not be able to deal with.
245 * <s>Also refuse anything < 3 because we need fake natives.</s>
246 * Also refuse anything < 7 because we need the new sp_native definition.
248 api_version = g_pVM->GetAPIVersion();
249 if (api_version < 7 || api_version > SOURCEPAWN_VM_API_VERSION)
251 if (error && maxlength)
253 snprintf(error, maxlength, "JIT is not a compatible version");
267 /* Hook this now so we can detect startup without calling StartSourceMod() */
268 SH_ADD_HOOK_MEMFUNC(IServerGameDLL, LevelInit, gamedll, this, &SourceModBase::LevelInit, false);
270 /* Only load if we're not late */
273 StartSourceMod(false);
279 void SourceModBase::StartSourceMod(bool late)
281 SH_ADD_HOOK_MEMFUNC(IServerGameDLL, LevelShutdown, gamedll, this, &SourceModBase::LevelShutdown, false);
282 SH_ADD_HOOK_MEMFUNC(IServerGameDLL, GameFrame, gamedll, &g_Timers, &TimerSystem::GameFrame, false);
284 enginePatch = SH_GET_CALLCLASS(engine);
285 gamedllPatch = SH_GET_CALLCLASS(gamedll);
287 g_ShareSys.Initialize();
289 /* Make the global core identity */
290 g_pCoreIdent = g_ShareSys.CreateCoreIdentity();
293 SMGlobalClass *pBase = SMGlobalClass::head;
296 pBase->OnSourceModStartup(false);
297 pBase = pBase->m_pGlobalClassNext;
301 pBase = SMGlobalClass::head;
304 pBase->OnSourceModAllInitialized();
305 pBase = pBase->m_pGlobalClassNext;
309 pBase = SMGlobalClass::head;
312 pBase->OnSourceModAllInitialized_Post();
313 pBase = pBase->m_pGlobalClassNext;
317 g_ShareSys.AddInterface(NULL, this);
322 /* Initialize VSP stuff */
323 if (vsp_interface != NULL)
325 g_SourceMod_Core.OnVSPListening(vsp_interface);
329 static bool g_LevelEndBarrier = false;
330 bool SourceModBase::LevelInit(char const *pMapName, char const *pMapEntities, char const *pOldLevel, char const *pLandmarkName, bool loadGame, bool background)
332 /* If we're not loaded... */
335 /* Do all global initialization now */
336 StartSourceMod(true);
339 m_IsMapLoading = true;
340 m_ExecPluginReload = true;
343 SMGlobalClass *pBase = SMGlobalClass::head;
346 pBase->OnSourceModLevelChange(pMapName);
347 pBase = pBase->m_pGlobalClassNext;
350 DoGlobalPluginLoads();
352 m_IsMapLoading = false;
355 pBase = SMGlobalClass::head;
358 pBase->OnSourceModPluginsLoaded();
359 pBase = pBase->m_pGlobalClassNext;
364 g_pOnMapEnd = g_Forwards.CreateForward("OnMapEnd", ET_Ignore, 0, NULL);
367 g_LevelEndBarrier = true;
369 RETURN_META_VALUE(MRES_IGNORED, true);
372 void SourceModBase::LevelShutdown()
374 if (g_LevelEndBarrier)
376 SMGlobalClass *next = SMGlobalClass::head;
379 next->OnSourceModLevelEnd();
380 next = next->m_pGlobalClassNext;
383 if (g_pOnMapEnd != NULL)
385 g_pOnMapEnd->Execute(NULL);
388 g_Timers.RemoveMapChangeTimers();
390 g_LevelEndBarrier = false;
393 g_OnMapStarted = false;
395 if (m_ExecPluginReload)
397 g_PluginSys.ReloadOrUnloadPlugins();
398 m_ExecPluginReload = false;
402 bool SourceModBase::IsMapLoading() const
404 return m_IsMapLoading;
407 void SourceModBase::DoGlobalPluginLoads()
409 char config_path[PLATFORM_MAX_PATH];
410 char plugins_path[PLATFORM_MAX_PATH];
412 BuildPath(Path_SM, config_path,
414 "configs/plugin_settings.cfg");
415 BuildPath(Path_SM, plugins_path,
416 sizeof(plugins_path),
419 /* Load any auto extensions */
420 g_Extensions.TryAutoload();
422 /* Fire the extensions ready message */
423 g_SMAPI->MetaFactory(SOURCEMOD_NOTICE_EXTENSIONS, NULL, NULL);
425 /* Load any game extension */
426 const char *game_ext;
427 if ((game_ext = g_pGameConf->GetKeyValue("GameExtension")) != NULL)
429 char path[PLATFORM_MAX_PATH];
430 UTIL_Format(path, sizeof(path), "%s.ext." PLATFORM_LIB_EXT, game_ext);
431 g_Extensions.LoadAutoExtension(path);
434 /* Run the first pass */
435 g_PluginSys.LoadAll_FirstPass(config_path, plugins_path);
437 /* Mark any extensions as loaded */
438 g_Extensions.MarkAllLoaded();
440 /* No modules yet, it's safe to call this from here */
441 g_PluginSys.LoadAll_SecondPass();
443 /* Re-mark any extensions as loaded */
444 g_Extensions.MarkAllLoaded();
446 /* Call OnAllPluginsLoaded */
447 g_PluginSys.AllPluginsLoaded();
450 size_t SourceModBase::BuildPath(PathType type, char *buffer, size_t maxlength, const char *format, ...)
452 char _buffer[PLATFORM_MAX_PATH];
455 va_start(ap, format);
456 vsnprintf(_buffer, PLATFORM_MAX_PATH, format, ap);
459 /* If we get a "file://" notation, strip off the file:// part so we're left
460 * with an absolute path. Note that the absolute path gets returned, so
461 * usage with relative paths here is completely invalid.
463 if (type != Path_SM_Rel && strncmp(_buffer, "file://", 7) == 0)
465 return g_LibSys.PathFormat(buffer, maxlength, "%s", &_buffer[7]);
468 const char *base = NULL;
469 if (type == Path_Game)
471 base = GetGamePath();
473 else if (type == Path_SM)
475 base = GetSourceModPath();
477 else if (type == Path_SM_Rel)
484 return g_LibSys.PathFormat(buffer, maxlength, "%s/%s", base, _buffer);
488 return g_LibSys.PathFormat(buffer, maxlength, "%s", _buffer);
492 void SourceModBase::CloseSourceMod()
494 /* Force a level end */
498 g_PluginSys.Shutdown();
500 /* Unload extensions */
501 g_Extensions.Shutdown();
503 SH_REMOVE_HOOK_MEMFUNC(IServerGameDLL, LevelInit, gamedll, this, &SourceModBase::LevelInit, false);
509 g_Forwards.ReleaseForward(g_pOnMapEnd);
513 SMGlobalClass *pBase = SMGlobalClass::head;
516 pBase->OnSourceModShutdown();
517 pBase = pBase->m_pGlobalClassNext;
520 /* Delete all data packs */
521 CStack<CDataPack *>::iterator iter;
523 for (iter=m_freepacks.begin(); iter!=m_freepacks.end(); iter++)
528 m_freepacks.popall();
531 pBase = SMGlobalClass::head;
534 pBase->OnSourceModAllShutdown();
535 pBase = pBase->m_pGlobalClassNext;
540 SH_RELEASE_CALLCLASS(enginePatch);
546 SH_RELEASE_CALLCLASS(gamedllPatch);
550 SH_REMOVE_HOOK_MEMFUNC(IServerGameDLL, LevelShutdown, gamedll, this, &SourceModBase::LevelShutdown, false);
551 SH_REMOVE_HOOK_MEMFUNC(IServerGameDLL, GameFrame, gamedll, &g_Timers, &TimerSystem::GameFrame, false);
558 void SourceModBase::LogMessage(IExtension *pExt, const char *format, ...)
560 IExtensionInterface *pAPI = pExt->GetAPI();
561 const char *tag = pAPI->GetExtensionTag();
565 va_start(ap, format);
566 vsnprintf(buffer, sizeof(buffer), format, ap);
571 g_Logger.LogMessage("[%s] %s", tag, buffer);
573 g_Logger.LogMessage("%s", buffer);
577 void SourceModBase::LogError(IExtension *pExt, const char *format, ...)
579 IExtensionInterface *pAPI = pExt->GetAPI();
580 const char *tag = pAPI->GetExtensionTag();
584 va_start(ap, format);
585 vsnprintf(buffer, sizeof(buffer), format, ap);
590 g_Logger.LogError("[%s] %s", tag, buffer);
592 g_Logger.LogError("%s", buffer);
596 size_t SourceModBase::FormatString(char *buffer, size_t maxlength, IPluginContext *pContext, const cell_t *params, unsigned int param)
600 pContext->LocalToString(params[param], &fmt);
602 int lparam = ++param;
604 return atcprintf(buffer, maxlength, fmt, pContext, params, &lparam);
607 const char *SourceModBase::GetSourceModPath() const
612 const char *SourceModBase::GetGamePath() const
614 return g_BaseDir.c_str();
617 unsigned int SourceModBase::SetGlobalTarget(unsigned int index)
619 unsigned int old = m_target;
624 unsigned int SourceModBase::GetGlobalTarget() const
629 IDataPack *SourceModBase::CreateDataPack()
632 if (m_freepacks.empty())
634 pack = new CDataPack;
636 pack = m_freepacks.front();
643 void SourceModBase::FreeDataPack(IDataPack *pack)
645 m_freepacks.push(static_cast<CDataPack *>(pack));
648 Handle_t SourceModBase::GetDataPackHandleType(bool readonly)
654 const char *SourceModBase::GetGameFolderName() const
659 ISourcePawnEngine *SourceModBase::GetScriptingEngine()
661 return g_pSourcePawn;
664 IVirtualMachine *SourceModBase::GetScriptingVM()
669 void SourceModBase::AllPluginsLoaded()
671 SMGlobalClass *base = SMGlobalClass::head;
674 base->OnSourceModGameInitialized();
675 base = base->m_pGlobalClassNext;
679 time_t SourceModBase::GetAdjustedTime()
681 return ::GetAdjustedTime();
684 void SourceModBase::AddGameFrameHook(GAME_FRAME_HOOK hook)
686 m_frame_hooks.push_back(hook);
689 void SourceModBase::RemoveGameFrameHook(GAME_FRAME_HOOK hook)
691 for (size_t i = 0; i < m_frame_hooks.size(); i++)
693 if (m_frame_hooks[i] == hook)
695 m_frame_hooks.erase(m_frame_hooks.iterAt(i));
701 void SourceModBase::ProcessGameFrameHooks(bool simulating)
703 if (m_frame_hooks.size() == 0)
708 for (size_t i = 0; i < m_frame_hooks.size(); i++)
710 m_frame_hooks[i](simulating);
714 SMGlobalClass *SMGlobalClass::head = NULL;
716 SMGlobalClass::SMGlobalClass()
718 m_pGlobalClassNext = SMGlobalClass::head;
719 SMGlobalClass::head = this;