/* AMX Mod X
*
* by the AMX Mod X Development Team
*  originally developed by OLO
*
*
*  This program is free software; you can redistribute it and/or modify it
*  under the terms of the GNU General Public License as published by the
*  Free Software Foundation; either version 2 of the License, or (at
*  your option) any later version.
*
*  This program is distributed in the hope that it will be useful, but
*  WITHOUT ANY WARRANTY; without even the implied warranty of
*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
*  General Public License for more details.
*
*  You should have received a copy of the GNU General Public License
*  along with this program; if not, write to the Free Software Foundation,
*  Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*  In addition, as a special exception, the author gives permission to
*  link the code of this program with the Half-Life Game Engine ("HL
*  Engine") and Modified Game Libraries ("MODs") developed by Valve,
*  L.L.C ("Valve"). You must obey the GNU General Public License in all
*  respects for all of the code used other than the HL Engine and MODs
*  from Valve. If you modify this file, you may extend this exception
*  to your version of the file, but you are not obligated to do so. If
*  you do not wish to do so, delete this exception statement from your
*  version.
*/
#include <time.h>
#if defined WIN32
#include <direct.h>
#else
#include <dirent.h>
#endif
#include "amxmodx.h"
#include "fakemeta.h"
#include "CMenu.h"
#include "newmenus.h"
#include "natives.h"
#include "binlog.h"
#include "optimizer.h"
#include "libraries.h"
#include "messages.h"
#include "amxmod_compat.h"
#include "datastructs.h"
#include "CFlagManager.h"
#include "svn_version.h"
#include "trie_natives.h"
plugin_info_t Plugin_info = 
{
	META_INTERFACE_VERSION,		// ifvers
	"AMX Mod X",				// name
	SVN_VERSION_STRING,			// version
	__DATE__,					// date
	"AMX Mod X Dev Team",		// author
	"http://www.amxmodx.org",	// url
	"AMXX",						// logtag
	PT_STARTUP,					// (when) loadable
	PT_ANYTIME,					// (when) unloadable
};
meta_globals_t *gpMetaGlobals;
gamedll_funcs_t *gpGamedllFuncs;
mutil_funcs_t *gpMetaUtilFuncs;
enginefuncs_t g_engfuncs;
globalvars_t *gpGlobals;
funEventCall modMsgsEnd[MAX_REG_MSGS];
funEventCall modMsgs[MAX_REG_MSGS];
void (*function)(void*);
void (*endfunction)(void*);
extern List<AUTHORIZEFUNC> g_auth_funcs;
extern CVector<CAdminData *> DynamicAdmins;
CLog g_log;
CForwardMngr g_forwards;
CList<CPlayer*> g_auth;
CList<CCVar> g_cvars;
CList<ForceObject> g_forcemodels;
CList<ForceObject> g_forcesounds;
CList<ForceObject> g_forcegeneric;
CPlayer g_players[33];
CPlayer* mPlayer;
CPluginMngr g_plugins;
CTaskMngr g_tasksMngr;
CmdMngr g_commands;
CFlagManager FlagMan;
EventsMngr g_events;
Grenades g_grenades;
LogEventsMngr g_logevents;
MenuMngr g_menucmds;
CLangMngr g_langMngr;
String g_log_dir;
String g_mod_name;
XVars g_xvars;
bool g_bmod_tfc;
bool g_bmod_cstrike;
bool g_bmod_dod;
bool g_dontprecache;
bool g_forcedmodules;
bool g_forcedsounds;
fakecmd_t g_fakecmd;
float g_game_restarting;
float g_game_timeleft;
float g_task_time;
float g_auth_time;
bool g_initialized = false;
bool g_coloredmenus;
bool g_activated = false;
bool g_NewDLL_Available = false;
#ifdef MEMORY_TEST
	float g_next_memreport_time;
	unsigned int g_memreport_count;
	String g_memreport_dir;
	bool g_memreport_enabled;
	#define MEMREPORT_INTERVAL 300.0f	/* 5 mins */
#endif // MEMORY_TEST
hudtextparms_t g_hudset;
//int g_edict_point;
int g_players_num;
int mPlayerIndex;
int mState;
int g_srvindex;
cvar_t init_amxmodx_version = {"amxmodx_version", "", FCVAR_SERVER | FCVAR_SPONLY};
cvar_t init_amxmodx_modules = {"amxmodx_modules", "", FCVAR_SPONLY};
cvar_t init_amxmodx_debug = {"amx_debug", "1", FCVAR_SPONLY};
cvar_t init_amxmodx_mldebug = {"amx_mldebug", "", FCVAR_SPONLY};
cvar_t init_amxmodx_cl_langs = {"amx_client_languages", "", FCVAR_SERVER};
cvar_t* amxmodx_version = NULL;
cvar_t* amxmodx_modules = NULL;
cvar_t* hostname = NULL;
cvar_t* mp_timelimit = NULL;
// main forwards
int FF_ClientCommand = -1;
int FF_ClientConnect = -1;
int FF_ClientDisconnect = -1;
int FF_ClientInfoChanged = -1;
int FF_ClientPutInServer = -1;
int FF_PluginInit = -1;
int FF_PluginCfg = -1;
int FF_PluginPrecache = -1;
int FF_PluginLog = -1;
int FF_PluginEnd = -1;
int FF_InconsistentFile = -1;
int FF_ClientAuthorized = -1;
int FF_ChangeLevel = -1;
void ParseAndOrAdd(CStack<String *> & files, const char *name)
{
	if (strncmp(name, "plugins-", 8) == 0)
	{
#if !defined WIN32
		size_t len = strlen(name);
		if (strcmp(&name[len-4], ".ini") == 0)
		{
#endif
			String *pString = new String(name);
			files.push(pString);
#if !defined WIN32
		}
#endif
	}
}
void BuildPluginFileList(const char *initialdir, CStack<String *> & files)
{
	char path[255];
#if defined WIN32
	build_pathname_r(path, sizeof(path)-1, "%s/*.ini", initialdir);
	_finddata_t fd;
	intptr_t handle = _findfirst(path, &fd);
	if (handle < 0)
	{
		return;
	}
	while (!_findnext(handle, &fd))
	{
		ParseAndOrAdd(files, fd.name);
	}
	_findclose(handle);
#elif defined __linux__
	build_pathname_r(path, sizeof(path)-1, "%s/", initialdir);
	struct dirent *ep;
	DIR *dp;
	if ((dp = opendir(path)) == NULL)
	{
		return;
	}
	while ( (ep=readdir(dp)) != NULL )
	{
		ParseAndOrAdd(files, ep->d_name);
	}
	closedir (dp);
#endif
}
//Loads a plugin list into the Plugin Cache and Load Modules cache
void LoadExtraPluginsToPCALM(const char *initialdir)
{
	CStack<String *> files;
	BuildPluginFileList(initialdir, files);
	char path[255];
	while (!files.empty())
	{
		String *pString = files.front();
		snprintf(path, sizeof(path)-1, "%s/%s", 
			initialdir,
			pString->c_str());
		g_plugins.CALMFromFile(path);
		delete pString;
		files.pop();
	}
}
void LoadExtraPluginsFromDir(const char *initialdir)
{
	CStack<String *> files;
	char path[255];
	BuildPluginFileList(initialdir, files);
	while (!files.empty())
	{
		String *pString = files.front();
		snprintf(path, sizeof(path)-1, "%s/%s", 
			initialdir,
			pString->c_str());
		g_plugins.loadPluginsFromFile(path);
		delete pString;
		files.pop();
	}
}
// Precache	stuff from force consistency calls
// or check	for	pointed	files won't	be done
int	C_PrecacheModel(char *s)
{
	if (!g_forcedmodules)
	{
		g_forcedmodules	= true;
		for (CList<ForceObject>::iterator a = g_forcemodels.begin(); a; ++a)
		{
			PRECACHE_MODEL((char*)(*a).getFilename());
			ENGINE_FORCE_UNMODIFIED((*a).getForceType(), (*a).getMin(), (*a).getMax(), (*a).getFilename());
		}
	}
	RETURN_META_VALUE(MRES_IGNORED, 0);
}
int	C_PrecacheSound(char *s)
{
	if (!g_forcedsounds)
	{
		g_forcedsounds = true;
		for (CList<ForceObject>::iterator a = g_forcesounds.begin(); a; ++a)
		{
			PRECACHE_SOUND((char*)(*a).getFilename());
			ENGINE_FORCE_UNMODIFIED((*a).getForceType(), (*a).getMin(), (*a).getMax(), (*a).getFilename());
		}
	
		if (!g_bmod_cstrike)
		{
			PRECACHE_SOUND("weapons/cbar_hitbod1.wav");
			PRECACHE_SOUND("weapons/cbar_hitbod2.wav");
			PRECACHE_SOUND("weapons/cbar_hitbod3.wav");
		}
	}
	RETURN_META_VALUE(MRES_IGNORED, 0);
}
// On InconsistentFile call	forward	function from plugins
int	C_InconsistentFile(const edict_t *player, const char *filename, char *disconnect_message)
{
	if (FF_InconsistentFile < 0)
		RETURN_META_VALUE(MRES_IGNORED,	FALSE);
	if (MDLL_InconsistentFile(player, filename, disconnect_message))
	{
		CPlayer	*pPlayer = GET_PLAYER_POINTER((edict_t *)player);
		if (executeForwards(FF_InconsistentFile, static_cast<cell>(pPlayer->index),
			filename, disconnect_message) == 1)
			RETURN_META_VALUE(MRES_SUPERCEDE, FALSE);
		
		RETURN_META_VALUE(MRES_SUPERCEDE, TRUE);
	}
	RETURN_META_VALUE(MRES_IGNORED, FALSE);
}
const char*	get_localinfo(const char* name, const char* def)
{
	const char* b = LOCALINFO((char*)name);
	if (b == 0 || *b == 0)
	{
		SET_LOCALINFO((char*)name, (char*)(b = def));
	}
	return b;
}
const char*	get_localinfo_r(const char *name, const char *def, char buffer[], size_t maxlength)
{
	const char* b = LOCALINFO((char*)name);
	if (b == 0 || *b == 0)
	{
		SET_LOCALINFO((char*)name, (char*)(b = def));
	}
	snprintf(buffer, maxlength, "%s", b);
	return buffer;
}
// Very	first point	at map load
// Load	AMX	modules	for	new	native functions
// Initialize AMX stuff	and	load it's plugins from plugins.ini list
// Call	precache forward function from plugins
int	C_Spawn(edict_t *pent)
{
	if (g_initialized)
	{
		RETURN_META_VALUE(MRES_IGNORED, 0);
	}
	g_activated = false;
	g_initialized = true;
	g_forcedmodules = false;
	g_forcedsounds = false;
	g_srvindex = IS_DEDICATED_SERVER() ? 0 : 1;
	hostname = CVAR_GET_POINTER("hostname");
	mp_timelimit = CVAR_GET_POINTER("mp_timelimit");
	// Fix for crashing on mods that do not have mp_timelimit
	if (mp_timelimit == NULL)
	{
		static cvar_t timelimit_holder;
		timelimit_holder.name = "mp_timelimit";
		timelimit_holder.string = "0";
		timelimit_holder.flags = 0;
		timelimit_holder.value = 0.0;
		CVAR_REGISTER(&timelimit_holder);
		mp_timelimit = &timelimit_holder;
	}
	g_forwards.clear();
	g_log.MapChange();
	// ###### Initialize task manager
	g_tasksMngr.registerTimers(&gpGlobals->time, &mp_timelimit->value, &g_game_timeleft);
	// ###### Initialize commands prefixes
	g_commands.registerPrefix("amx");
	g_commands.registerPrefix("amxx");
	g_commands.registerPrefix("say");
	g_commands.registerPrefix("admin_");
	g_commands.registerPrefix("sm_");
	g_commands.registerPrefix("cm_");
	// make sure localinfos are set
	get_localinfo("amxx_basedir", "addons/amxmodx");
	get_localinfo("amxx_pluginsdir", "addons/amxmodx/plugins");
	get_localinfo("amxx_modulesdir", "addons/amxmodx/modules");
	get_localinfo("amxx_configsdir", "addons/amxmodx/configs");
	get_localinfo("amxx_customdir", "addons/amxmodx/custom");
	// make sure bcompat localinfos are set
	get_localinfo("amx_basedir", "addons/amxmodx");
	get_localinfo("amx_configdir", "addons/amxmodx/configs");
	get_localinfo("amx_langdir", "addons/amxmodx/data/amxmod-lang");
	get_localinfo("amx_modulesdir", "addons/amxmodx/modules");
	get_localinfo("amx_pluginsdir", "addons/amxmodx/plugins");
	get_localinfo("amx_logdir", "addons/amxmodx/logs");
	FlagMan.LoadFile();
	for (unsigned int i=0; i<VectorHolder.size(); i++)
	{
		delete VectorHolder[i];
	};
	VectorHolder.clear();
	g_TrieHandles.clear();
	char map_pluginsfile_path[256];
	char prefixed_map_pluginsfile[256];
	char configs_dir[256];
	// ###### Load modules
	loadModules(get_localinfo("amxx_modules", "addons/amxmodx/configs/modules.ini"), PT_ANYTIME);
	get_localinfo_r("amxx_configsdir", "addons/amxmodx/configs", configs_dir, sizeof(configs_dir)-1);
	g_plugins.CALMFromFile(get_localinfo("amxx_plugins", "addons/amxmodx/configs/plugins.ini"));
	LoadExtraPluginsToPCALM(configs_dir);
	char temporaryMap[64], *tmap_ptr;
	snprintf(temporaryMap, sizeof(temporaryMap), "%s", STRING(gpGlobals->mapname));
	prefixed_map_pluginsfile[0] = '\0';
	if ((tmap_ptr = strchr(temporaryMap, '_')) != NULL)
	{
		// this map has a prefix
		*tmap_ptr = '\0';
		snprintf(prefixed_map_pluginsfile, 
			sizeof(prefixed_map_pluginsfile),
			"%s/maps/plugins-%s.ini",
			configs_dir,
			temporaryMap);
		g_plugins.CALMFromFile(prefixed_map_pluginsfile);
	}
	snprintf(map_pluginsfile_path, 
		sizeof(map_pluginsfile_path),
		"%s/maps/plugins-%s.ini",
		configs_dir,
		STRING(gpGlobals->mapname));
	g_plugins.CALMFromFile(map_pluginsfile_path);
	int loaded = countModules(CountModules_Running); // Call after attachModules so all modules don't have pending stat
	
	// Set some info about amx version and modules
	CVAR_SET_STRING(init_amxmodx_version.name, SVN_VERSION_STRING);
	char buffer[32];
	sprintf(buffer, "%d", loaded);
	CVAR_SET_STRING(init_amxmodx_modules.name, buffer);
	// ###### Load Vault
	char file[255];
	g_vault.setSource(build_pathname_r(file, sizeof(file) - 1, "%s", get_localinfo("amxx_vault", "addons/amxmodx/configs/vault.ini")));
	g_vault.loadVault();
	
	if (strlen(g_vault.get("server_language")) < 1)
	{
		g_vault.put("server_language", "en");
		g_vault.saveVault();
	}
	// ###### Init time and freeze tasks
	g_game_timeleft = g_bmod_dod ? 1.0f : 0.0f;
	g_task_time = gpGlobals->time + 99999.0f;
	g_auth_time = gpGlobals->time + 99999.0f;
#ifdef MEMORY_TEST
	g_next_memreport_time = gpGlobals->time + 99999.0f;
#endif
	g_players_num = 0;
	// Set server flags
	memset(g_players[0].flags, -1, sizeof(g_players[0].flags));
	g_opt_level = atoi(get_localinfo("optimizer", "7"));
	if (!g_opt_level)
		g_opt_level = 7;
	// ###### Load AMX Mod X plugins
	g_plugins.loadPluginsFromFile(get_localinfo("amxx_plugins", "addons/amxmodx/configs/plugins.ini"));
	LoadExtraPluginsFromDir(configs_dir);
	g_plugins.loadPluginsFromFile(map_pluginsfile_path, false);
	if (prefixed_map_pluginsfile[0] != '\0')
	{
		g_plugins.loadPluginsFromFile(prefixed_map_pluginsfile, false);
	}
	g_plugins.Finalize();
	g_plugins.InvalidateCache();
	// Register forwards
	FF_PluginInit = registerForward("plugin_init", ET_IGNORE, FP_DONE);
	FF_ClientCommand = registerForward("client_command", ET_STOP, FP_CELL, FP_DONE);
	FF_ClientConnect = registerForward("client_connect", ET_IGNORE, FP_CELL, FP_DONE);
	FF_ClientDisconnect = registerForward("client_disconnect", ET_IGNORE, FP_CELL, FP_DONE);
	FF_ClientInfoChanged = registerForward("client_infochanged", ET_IGNORE, FP_CELL, FP_DONE);
	FF_ClientPutInServer = registerForward("client_putinserver", ET_IGNORE, FP_CELL, FP_DONE);
	FF_PluginCfg = registerForward("plugin_cfg", ET_IGNORE, FP_DONE);
	FF_PluginPrecache = registerForward("plugin_precache", ET_IGNORE, FP_DONE);
	FF_PluginLog = registerForward("plugin_log", ET_STOP, FP_DONE);
	FF_PluginEnd = registerForward("plugin_end", ET_IGNORE, FP_DONE);
	FF_InconsistentFile = registerForward("inconsistent_file", ET_STOP, FP_CELL, FP_STRING, FP_STRINGEX, FP_DONE);
	FF_ClientAuthorized = registerForward("client_authorized", ET_IGNORE, FP_CELL, FP_DONE);
	FF_ChangeLevel = registerForward("server_changelevel", ET_STOP, FP_STRING, FP_DONE);
#if defined BINLOG_ENABLED
	if (!g_BinLog.Open())
	{
		LOG_ERROR(PLID, "Binary log failed to open.");
	}
	g_binlog_level = atoi(get_localinfo("bin_logging", "17"));
	g_binlog_maxsize = atoi(get_localinfo("max_binlog_size", "20"));
#endif
	modules_callPluginsLoaded();
	// ###### Call precache forward function
	g_dontprecache = false;
	executeForwards(FF_PluginPrecache);
	g_dontprecache = true;
	for (CList<ForceObject>::iterator a = g_forcegeneric.begin(); a; ++a)
	{
		PRECACHE_GENERIC((char*)(*a).getFilename());
		ENGINE_FORCE_UNMODIFIED((*a).getForceType(),
		(*a).getMin(), (*a).getMax(), (*a).getFilename());
	}
	RETURN_META_VALUE(MRES_IGNORED, 0);
}
struct sUserMsg
{
	const char* name;
	int* id;
	funEventCall func;
	bool endmsg;
	bool cstrike;
} g_user_msg[] = 
{
	{"CurWeapon", &gmsgCurWeapon, Client_CurWeapon, false, false},
	{"Damage", &gmsgDamage, Client_DamageEnd, true, true},
	{"DeathMsg", &gmsgDeathMsg,	Client_DeathMsg, false, true},
	{"TextMsg", &gmsgTextMsg, Client_TextMsg, false, false},
	{"TeamInfo", &gmsgTeamInfo, Client_TeamInfo, false, false},
	{"WeaponList", &gmsgWeaponList, Client_WeaponList, false, false},
	{"MOTD", &gmsgMOTD,	0, false, false},
	{"ServerName", &gmsgServerName,	0, false, false},
	{"Health", &gmsgHealth,	0, false, false},
	{"Battery", &gmsgBattery, 0, false, false},
	{"ShowMenu", &gmsgShowMenu, Client_ShowMenu, false, false},
	{"SendAudio", &gmsgSendAudio, 0, false, false},
	{"AmmoX", &gmsgAmmoX, Client_AmmoX, false, false},
	{"ScoreInfo", &gmsgScoreInfo, Client_ScoreInfo,	false, false},
	{"VGUIMenu", &gmsgVGUIMenu, Client_VGUIMenu, false, false},
	{"AmmoPickup", &gmsgAmmoPickup,	Client_AmmoPickup, false, false},
	{"WeapPickup", &gmsgWeapPickup, 0, false, false},
	{"ResetHUD", &gmsgResetHUD, 0, false, false},
	{"RoundTime", &gmsgRoundTime, 0, false, false},
	{0, 0, 0, false, false}
};
int	C_RegUserMsg_Post(const char *pszName, int iSize)
{
	for (int i = 0; g_user_msg[i].name;	++i)
	{
		if (strcmp(g_user_msg[i].name, pszName) == 0)
		{
			int id = META_RESULT_ORIG_RET(int);
			*g_user_msg[i].id =	id;
			if (!g_user_msg[i].cstrike || g_bmod_cstrike)
			{
				if (g_user_msg[i].endmsg)
					modMsgsEnd[id] = g_user_msg[i].func;
				else
					modMsgs[id] = g_user_msg[i].func;
			}
			break;
		}
	 }
	RETURN_META_VALUE(MRES_IGNORED, 0);
}
/*
Much more later	after precache.	All	is precached, server
will be	flaged as ready	to use so call
plugin_init	forward	function from plugins
*/
void C_ServerActivate(edict_t *pEdictList, int edictCount, int clientMax)
{
	int id;
	
	for (int i = 0; g_user_msg[i].name;	++i)
	{
		if ((*g_user_msg[i].id == 0) && (id = GET_USER_MSG_ID(PLID, g_user_msg[i].name, NULL)) != 0)
		{
			*g_user_msg[i].id =	id;
			
			if (!g_user_msg[i].cstrike || g_bmod_cstrike)
			{
				if (g_user_msg[i].endmsg)
					modMsgsEnd[id] = g_user_msg[i].func;
				else
					modMsgs[id] = g_user_msg[i].func;
			}
		}
	}
	RETURN_META(MRES_IGNORED);
}
void C_ServerActivate_Post(edict_t *pEdictList, int edictCount, int clientMax)
{
	if (g_activated)
		RETURN_META(MRES_IGNORED);
	for (int i = 1; i <= gpGlobals->maxClients; ++i)
	{
		CPlayer	*pPlayer = GET_PLAYER_POINTER_I(i);
		pPlayer->Init(pEdictList + i, i);
	}
	executeForwards(FF_PluginInit);
	executeForwards(FF_PluginCfg);
	// Correct time in Counter-Strike and other mods (except DOD)
	if (!g_bmod_dod)
		g_game_timeleft = 0;
	g_task_time = gpGlobals->time;
	g_auth_time = gpGlobals->time;
#ifdef MEMORY_TEST
	g_next_memreport_time = gpGlobals->time + MEMREPORT_INTERVAL;
	g_memreport_count = 0;
	g_memreport_enabled = true;
#endif
	g_activated = true;
	RETURN_META(MRES_IGNORED);
}
// Call	plugin_end forward function	from plugins.
void C_ServerDeactivate()
{
	if (!g_activated)
		RETURN_META(MRES_IGNORED);
	
	for (int i = 1; i <= gpGlobals->maxClients; ++i)
	{
		CPlayer	*pPlayer = GET_PLAYER_POINTER_I(i);
		if (pPlayer->initialized)
			executeForwards(FF_ClientDisconnect, static_cast<cell>(pPlayer->index));
		if (pPlayer->ingame)
		{
			pPlayer->Disconnect();
			--g_players_num;
		}
	}
	g_players_num	= 0;
	executeForwards(FF_PluginEnd);
	RETURN_META(MRES_IGNORED);
}
extern CVector<cell *> g_hudsync;
// After all clear whole AMX configuration
// However leave AMX modules which are loaded only once
void C_ServerDeactivate_Post()
{
	if (!g_initialized)
		RETURN_META(MRES_IGNORED);
	modules_callPluginsUnloading();
	
	detachReloadModules();
	g_auth.clear();
	g_commands.clear();
	g_forcemodels.clear();
	g_forcesounds.clear();
	g_forcegeneric.clear();
	g_grenades.clear();
	g_tasksMngr.clear();
	g_forwards.clear();
	g_logevents.clearLogEvents();
	g_events.clearEvents();
	g_menucmds.clear();
	ClearMenus();
	g_vault.clear();
	g_xvars.clear();
	g_plugins.clear();
	ClearPluginLibraries();
	modules_callPluginsUnloaded();
	ClearMessages();
	
	// Flush the dynamic admins list
	for (size_t iter=DynamicAdmins.size();iter--; )
	{
		delete DynamicAdmins[iter];
	}
	DynamicAdmins.clear();
	for (unsigned int i=0; i<g_hudsync.size(); i++)
		delete [] g_hudsync[i];
	g_hudsync.clear();
	FlagMan.WriteCommands();
	// last memreport
#ifdef MEMORY_TEST
	if (g_memreport_enabled)
	{
		if (g_memreport_count == 0)
		{
			// make new directory
			time_t td;
			time(&td);
			tm *curTime = localtime(&td);
			int i = 0;
#ifdef __linux__
			mkdir(build_pathname("%s/memreports", get_localinfo("amxx_basedir", "addons/amxmodx")), 0700);
#else
			mkdir(build_pathname("%s/memreports", get_localinfo("amxx_basedir", "addons/amxmodx")));
#endif
			while (true)
			{
				char buffer[256];
				sprintf(buffer, "%s/memreports/D%02d%02d%03d", get_localinfo("amxx_basedir", "addons/amxmodx"), curTime->tm_mon + 1, curTime->tm_mday, i);
#ifdef __linux__
				mkdir(build_pathname("%s", g_log_dir.c_str()), 0700);
				if (mkdir(build_pathname(buffer), 0700) < 0)
#else
				mkdir(build_pathname("%s", g_log_dir.c_str()));
				if (mkdir(build_pathname(buffer)) < 0)
#endif
				{
					if (errno == EEXIST)
					{
						// good
						++i;
						continue;
					} else {
						// bad
						g_memreport_enabled = false;
						AMXXLOG_Log("[AMXX] Fatal error: Can't create directory for memreport files (%s)", buffer);
						break;
					}
				}
				g_memreport_dir.assign(buffer);
				
				// g_memreport_dir should be valid now
				break;
			}
		}
		
		m_dumpMemoryReport(build_pathname("%s/r%03d.txt", g_memreport_dir.c_str(), g_memreport_count));
		AMXXLOG_Log("Memreport #%d created (file \"%s/r%03d.txt\") (interval %f)", g_memreport_count + 1, g_memreport_dir.c_str(), g_memreport_count, MEMREPORT_INTERVAL);
		
		g_memreport_count++;
	}
#endif // MEMORY_TEST
#if defined BINLOG_ENABLED
	g_BinLog.Close();
#endif
	g_initialized = false;
	RETURN_META(MRES_IGNORED);
}
BOOL C_ClientConnect_Post(edict_t *pEntity, const char *pszName, const char *pszAddress, char szRejectReason[128])
{
	CPlayer* pPlayer = GET_PLAYER_POINTER(pEntity);
	if (!pPlayer->IsBot())
	{
		bool a = pPlayer->Connect(pszName, pszAddress);
		executeForwards(FF_ClientConnect, static_cast<cell>(pPlayer->index));
		
		if (a)
		{
			CPlayer** aa = new CPlayer*(pPlayer);
			if (aa) 
				g_auth.put(aa);
		} else {
			pPlayer->Authorize();
			if (g_auth_funcs.size())
			{
				List<AUTHORIZEFUNC>::iterator iter, end=g_auth_funcs.end();
				AUTHORIZEFUNC fn;
				const char* authid = GETPLAYERAUTHID(pEntity);
				for (iter=g_auth_funcs.begin(); iter!=end; iter++)
				{
					fn = (*iter);
					fn(pPlayer->index, authid);
				}
			}
			executeForwards(FF_ClientAuthorized, static_cast<cell>(pPlayer->index));
		}
	}
	
	RETURN_META_VALUE(MRES_IGNORED, TRUE);
}
void C_ClientDisconnect(edict_t *pEntity)
{
	CPlayer *pPlayer = GET_PLAYER_POINTER(pEntity);
	if (pPlayer->initialized)
		executeForwards(FF_ClientDisconnect, static_cast<cell>(pPlayer->index));
	if (pPlayer->ingame)
	{
		--g_players_num;
	}
	pPlayer->Disconnect();
	RETURN_META(MRES_IGNORED);
}
void C_ClientPutInServer_Post(edict_t *pEntity)
{
	CPlayer *pPlayer = GET_PLAYER_POINTER(pEntity);
	if (!pPlayer->IsBot())
	{
		pPlayer->PutInServer();
		++g_players_num;
		executeForwards(FF_ClientPutInServer, static_cast<cell>(pPlayer->index));
	}
	
	RETURN_META(MRES_IGNORED);
}
void C_ClientUserInfoChanged_Post(edict_t *pEntity, char *infobuffer)
{
	CPlayer *pPlayer = GET_PLAYER_POINTER(pEntity);
	executeForwards(FF_ClientInfoChanged, static_cast<cell>(pPlayer->index));
	const char* name = INFOKEY_VALUE(infobuffer, "name");
	// Emulate bot connection and putinserver
	if (pPlayer->ingame)
	{
		pPlayer->name.assign(name);			//	Make sure player have name up to date
	} else if (pPlayer->IsBot()) {
		pPlayer->Connect(name, "127.0.0.1"/*CVAR_GET_STRING("net_address")*/);
		executeForwards(FF_ClientConnect, static_cast<cell>(pPlayer->index));
		pPlayer->Authorize();
		if (g_auth_funcs.size())
		{
			List<AUTHORIZEFUNC>::iterator iter, end=g_auth_funcs.end();
			AUTHORIZEFUNC fn;
			const char* authid = GETPLAYERAUTHID(pEntity);
			for (iter=g_auth_funcs.begin(); iter!=end; iter++)
			{
				fn = (*iter);
				fn(pPlayer->index, authid);
			}
		}
		executeForwards(FF_ClientAuthorized, static_cast<cell>(pPlayer->index));
		pPlayer->PutInServer();
		++g_players_num;
		executeForwards(FF_ClientPutInServer, static_cast<cell>(pPlayer->index));
	}
	RETURN_META(MRES_IGNORED);
}
void C_ClientCommand(edict_t *pEntity)
{
	CPlayer *pPlayer = GET_PLAYER_POINTER(pEntity);
	
	META_RES result = MRES_IGNORED;
	cell ret = 0;
	
	const char* cmd = CMD_ARGV(0);
	const char* arg = CMD_ARGV(1);
	// Handle "amxx" if not on listenserver
	if (IS_DEDICATED_SERVER())
	{
		if (cmd && stricmp(cmd, "amxx") == 0)
		{
			// Print version
			static char buf[1024];
			size_t len = 0;
			
			sprintf(buf, "%s %s\n", Plugin_info.name, Plugin_info.version);
			CLIENT_PRINT(pEntity, print_console, buf);
			len = sprintf(buf, "Authors: \n         David \"BAILOPAN\" Anderson, Pavol \"PM OnoTo\" Marko, Felix \"SniperBeamer\" Geyer\n");
			len += sprintf(&buf[len], "         Jonny \"Got His Gun\" Bergstrom, Lukasz \"SidLuke\" Wlasinski\n");
			CLIENT_PRINT(pEntity, print_console, buf);
			len = sprintf(buf, "         Christian \"Basic-Master\" Hammacher, Borja \"faluco\" Ferrer\n");
			len += sprintf(&buf[len], "         Scott \"Damaged Soul\" Ehlert\n");
			len += sprintf(&buf[len], "Compiled: %s\nURL:http://www.amxmodx.org/\n", __DATE__ ", " __TIME__);
			CLIENT_PRINT(pEntity, print_console, buf);
#ifdef JIT
			sprintf(buf, "Core mode: JIT\n");
#else
#ifdef ASM32
			sprintf(buf, "Core mode: ASM\n");
#else
			sprintf(buf, "Core mode: Normal\n");
#endif
#endif
			CLIENT_PRINT(pEntity, print_console, buf);
			RETURN_META(MRES_SUPERCEDE);
		}
	}
	if (executeForwards(FF_ClientCommand, static_cast<cell>(pPlayer->index)) > 0)
		RETURN_META(MRES_SUPERCEDE);
	/* check for command and if needed also for first argument and call proper function */
	CmdMngr::iterator aa = g_commands.clcmdprefixbegin(cmd);
	
	if (!aa)
		aa = g_commands.clcmdbegin();
	while (aa)
	{
		if ((*aa).matchCommandLine(cmd, arg) && (*aa).getPlugin()->isExecutable((*aa).getFunction()))
		{
			ret = executeForwards((*aa).getFunction(), static_cast<cell>(pPlayer->index),
				static_cast<cell>((*aa).getFlags()), static_cast<cell>((*aa).getId()));
			if (ret & 2) result = MRES_SUPERCEDE;
			if (ret & 1) RETURN_META(MRES_SUPERCEDE);
		}
		++aa;
	}
	/* check menu commands */
	if (!strcmp(cmd, "menuselect"))
	{
		int	pressed_key	= atoi(arg) - 1;
		int	bit_key	= (1<<pressed_key);
		if (pPlayer->keys &	bit_key)
		{
			if ((pPlayer->menu > 0 && !pPlayer->vgui) && (gpGlobals->time > pPlayer->menuexpire))
			{
				pPlayer->menu = 0;
				pPlayer->keys = 0;
				RETURN_META(MRES_SUPERCEDE);
			}
			
			int menuid = pPlayer->menu;
			pPlayer->menu = 0;
			/* First, do new menus */
			int func_was_executed = -1;
			if (pPlayer->newmenu != -1)
			{
				int menu = pPlayer->newmenu;
				pPlayer->newmenu = -1;
				if (menu >= 0 && menu < (int)g_NewMenus.size() && g_NewMenus[menu])
				{
					Menu *pMenu = g_NewMenus[menu];
					int item = pMenu->PagekeyToItem(pPlayer->page, pressed_key+1);
					if (item == MENU_BACK)
					{
						pMenu->Display(pPlayer->index, pPlayer->page - 1);
					} else if (item == MENU_MORE) {
						pMenu->Display(pPlayer->index, pPlayer->page + 1);
					} else {
						ret = executeForwards(pMenu->func, static_cast<cell>(pPlayer->index), static_cast<cell>(menu), static_cast<cell>(item));
						if (ret & 2)
						{
							result = MRES_SUPERCEDE;
						} else if (ret & 1) {
							RETURN_META(MRES_SUPERCEDE);
						}
					}
					/**
					 * No matter what we marked it as executed, since the callback styles are 
					 * entirely different.  After all, this is a backwards compat shim.
					 */
					func_was_executed = pMenu->func;
				}
			}		
			/* Now, do old menus */
			MenuMngr::iterator a = g_menucmds.begin();
			while (a)
			{
				g_menucmds.SetWatchIter(a);
				if ((*a).matchCommand(menuid, bit_key) 
					&& (*a).getPlugin()->isExecutable((*a).getFunction())
					&& (func_was_executed == -1 
						|| !g_forwards.isSameSPForward(func_was_executed, (*a).getFunction()))
					)
				{
					ret = executeForwards((*a).getFunction(), static_cast<cell>(pPlayer->index),
						static_cast<cell>(pressed_key), 0);
					
					if (ret & 2) result = MRES_SUPERCEDE;
					if (ret & 1) RETURN_META(MRES_SUPERCEDE);
				}
				if (g_menucmds.GetWatchIter() != a)
				{
					a = g_menucmds.GetWatchIter();
				} else {
					++a;
				}
			}
		}
	}
	/* check for PLUGIN_HANDLED_MAIN and block hl call if needed */
	RETURN_META(result);
}
void C_StartFrame_Post(void)
{
	if (g_auth_time < gpGlobals->time)
	{
		g_auth_time = gpGlobals->time + 0.7f;
		CList<CPlayer*>::iterator a = g_auth.begin();
		while (a)
		{
			const char*	auth = GETPLAYERAUTHID((*a)->pEdict);
			if ((auth == 0) || (*auth == 0))
			{
				a.remove();
				continue;
			}
			if (strcmp(auth, "STEAM_ID_PENDING"))
			{
				(*a)->Authorize();
				if (g_auth_funcs.size())
				{
					List<AUTHORIZEFUNC>::iterator iter, end=g_auth_funcs.end();
					AUTHORIZEFUNC fn;
					for (iter=g_auth_funcs.begin(); iter!=end; iter++)
					{
						fn = (*iter);
						fn((*a)->index, auth);
					}
				}
				executeForwards(FF_ClientAuthorized, static_cast<cell>((*a)->index));
				a.remove();
				
				continue;
			}
			++a;
		}
	}
#ifdef MEMORY_TEST
	if (g_memreport_enabled && g_next_memreport_time <= gpGlobals->time)
	{
		g_next_memreport_time = gpGlobals->time + MEMREPORT_INTERVAL;
		
		if (g_memreport_count == 0)
		{
			// make new directory
			time_t td;
			time(&td);
			tm *curTime = localtime(&td);
			
			int i = 0;
#ifdef __linux__
			mkdir(build_pathname("%s/memreports", get_localinfo("amxx_basedir", "addons/amxmodx")), 0700);
#else
			mkdir(build_pathname("%s/memreports", get_localinfo("amxx_basedir", "addons/amxmodx")));
#endif
			while (true)
			{
				char buffer[256];
				sprintf(buffer, "%s/memreports/D%02d%02d%03d", get_localinfo("amxx_basedir", "addons/amxmodx"), curTime->tm_mon + 1, curTime->tm_mday, i);
#ifdef __linux__
				mkdir(build_pathname("%s", g_log_dir.c_str()), 0700);
				if (mkdir(build_pathname(buffer), 0700) < 0)
#else
				mkdir(build_pathname("%s", g_log_dir.c_str()));
				if (mkdir(build_pathname(buffer)) < 0)
#endif
				{
					if (errno == EEXIST)
					{
						// good
						++i;
						continue;
					} else {
						// bad
						g_memreport_enabled = false;
						AMXXLOG_Log("[AMXX] Fatal error: Can't create directory for memreport files (%s)", buffer);
						break;
					}
				}
				g_memreport_dir.assign(buffer);
				// g_memreport_dir should be valid now
				break;
			}
		}
		
		m_dumpMemoryReport(build_pathname("%s/r%03d.txt", g_memreport_dir.c_str(), g_memreport_count));
		AMXXLOG_Log("Memreport #%d created (file \"%s/r%03d.txt\") (interval %f)", g_memreport_count + 1, g_memreport_dir.c_str(), g_memreport_count, MEMREPORT_INTERVAL);
		
		g_memreport_count++;
	}
#endif // MEMORY_TEST
	if (g_task_time > gpGlobals->time)
		RETURN_META(MRES_IGNORED);
	g_task_time = gpGlobals->time + 0.1f;
	g_tasksMngr.startFrame();
	RETURN_META(MRES_IGNORED);
}
void C_MessageBegin_Post(int msg_dest, int msg_type, const float *pOrigin, edict_t *ed)
{
	if (ed)
	{
		mPlayerIndex = ENTINDEX(ed);
		mPlayer	= GET_PLAYER_POINTER_I(mPlayerIndex);
	} else {
		mPlayerIndex = 0;
		mPlayer	= 0;
	}
	
	if (msg_type < 0 || msg_type >= MAX_REG_MSGS)
		msg_type = 0;
	mState = 0;
	function = modMsgs[msg_type];
	endfunction = modMsgsEnd[msg_type];
	
	g_events.parserInit(msg_type, &gpGlobals->time, mPlayer, mPlayerIndex);
	RETURN_META(MRES_IGNORED);
}
void C_WriteByte_Post(int iValue)
{
	g_events.parseValue(iValue);
	if (function) (*function)((void *)&iValue);
	
	RETURN_META(MRES_IGNORED);
}
void C_WriteChar_Post(int iValue)
{
	g_events.parseValue(iValue);
	if (function) (*function)((void *)&iValue);
	RETURN_META(MRES_IGNORED);
}
void C_WriteShort_Post(int iValue)
{
	g_events.parseValue(iValue);
	if (function) (*function)((void *)&iValue);
	
	RETURN_META(MRES_IGNORED);
}
void C_WriteLong_Post(int iValue)
{
	g_events.parseValue(iValue);
	if (function) (*function)((void *)&iValue);
	
	RETURN_META(MRES_IGNORED);
}
void C_WriteAngle_Post(float flValue)
{
	g_events.parseValue(flValue);
	if (function) (*function)((void *)&flValue);
	
	RETURN_META(MRES_IGNORED);
}
void C_WriteCoord_Post(float flValue)
{
	g_events.parseValue(flValue);
	if (function) (*function)((void *)&flValue);
	RETURN_META(MRES_IGNORED);
}
void C_WriteString_Post(const char *sz)
{
	g_events.parseValue(sz);
	if (function) (*function)((void *)sz);
	RETURN_META(MRES_IGNORED);
}
void C_WriteEntity_Post(int iValue)
{
	g_events.parseValue(iValue);
	if (function) (*function)((void *)&iValue);
	
	RETURN_META(MRES_IGNORED);
}
void C_MessageEnd_Post(void)
{
	g_events.executeEvents();
	if (endfunction) (*endfunction)(NULL);
	
	RETURN_META(MRES_IGNORED);
}
const char *C_Cmd_Args(void)
{
	// if the global "fake" flag is set, which means that engclient_cmd was used, supercede the function
	if (g_fakecmd.fake)
		RETURN_META_VALUE(MRES_SUPERCEDE, (g_fakecmd.argc > 1) ? g_fakecmd.args : g_fakecmd.argv[0]);
	
	// otherwise ignore it
	RETURN_META_VALUE(MRES_IGNORED, NULL);
}
const char *C_Cmd_Argv(int argc)
{
	// if the global "fake" flag is set, which means that engclient_cmd was used, supercede the function
	if (g_fakecmd.fake)
		RETURN_META_VALUE(MRES_SUPERCEDE, (argc < 3) ? g_fakecmd.argv[argc] : "");
	
	// otherwise ignore it
	RETURN_META_VALUE(MRES_IGNORED, NULL);
}
int	C_Cmd_Argc(void)
{
	// if the global "fake" flag is set, which means that engclient_cmd was used, supercede the function
	if (g_fakecmd.fake)
		RETURN_META_VALUE(MRES_SUPERCEDE, g_fakecmd.argc);
	
	// otherwise ignore it
	RETURN_META_VALUE(MRES_IGNORED, 0);
}
// Grenade has been	thrown.
// Only	here we	may	find out who is	an owner.
void C_SetModel(edict_t *e, const char *m)
{
	if (e->v.owner && m[7]=='w' && m[8]=='_' && m[9]=='h')
		g_grenades.put(e, 1.75, 4, GET_PLAYER_POINTER(e->v.owner));
	
	RETURN_META(MRES_IGNORED);
}
// Save	at what	part of	body a player is aiming
void C_TraceLine_Post(const float *v1, const float *v2, int fNoMonsters, edict_t *e, TraceResult *ptr)
{
	if (e && (e->v.flags & (FL_CLIENT | FL_FAKECLIENT)))
	{
		CPlayer* pPlayer = GET_PLAYER_POINTER(e);
		
		if (ptr->pHit && (ptr->pHit->v.flags & (FL_CLIENT | FL_FAKECLIENT)))
			pPlayer->aiming = ptr->iHitgroup;
			
		pPlayer->lastTrace = ptr->vecEndPos;
	}
	RETURN_META(MRES_IGNORED);
}
void C_AlertMessage(ALERT_TYPE atype, char *szFmt, ...)
{
	if (atype != at_logged)
	{
		RETURN_META(MRES_IGNORED);
	}
	/* There are also more messages but we want only logs
	at_notice,
	at_console,		// same	as at_notice, but forces a ConPrintf, not a	message	box
	at_aiconsole,	// same	as at_console, but only	shown if developer level is	2!
	at_warning,
	at_error,
	at_logged		// Server print to console ( only in multiplayer games ).
	*/
	cell retVal = 0;
	// execute logevents and plugin_log forward
	if (g_logevents.logEventsExist()
		|| g_forwards.getFuncsNum(FF_PluginLog))
	{
		va_list	logArgPtr;
		va_start(logArgPtr, szFmt);
		g_logevents.setLogString(szFmt, logArgPtr);
		va_end(logArgPtr);
		g_logevents.parseLogString();
		if (g_logevents.logEventsExist())
		{
			g_logevents.executeLogEvents();
		}
		retVal = executeForwards(FF_PluginLog);
	}
	if (retVal)
	{
		RETURN_META(MRES_SUPERCEDE);
	}
    RETURN_META(MRES_IGNORED);
}
void C_ChangeLevel(char *map, char *what)
{
	int ret = executeForwards(FF_ChangeLevel,  map);
	if (ret)
		RETURN_META(MRES_SUPERCEDE);
	RETURN_META(MRES_IGNORED);
}
void C_CvarValue2(const edict_t *pEdict, int requestId, const char *cvar, const char *value)
{
	CPlayer *pPlayer = GET_PLAYER_POINTER(pEdict);
	if (pPlayer->queries.empty())
		RETURN_META(MRES_IGNORED);
	List<ClientCvarQuery_Info *>::iterator iter, end=pPlayer->queries.end();
	ClientCvarQuery_Info *info;
	for (iter=pPlayer->queries.begin(); iter!=end; iter++)
	{
		info = (*iter);
		if ( info->requestId == requestId )
		{
			if (info->paramLen)
			{
				cell arr = prepareCellArray(info->params, info->paramLen);
				executeForwards(info->resultFwd, static_cast<cell>(ENTINDEX(pEdict)),
					cvar, value, arr);
			} else {
				executeForwards(info->resultFwd, static_cast<cell>(ENTINDEX(pEdict)),
					cvar, value);
			}
			unregisterSPForward(info->resultFwd);
			pPlayer->queries.erase(iter);
			delete [] info->params;
			delete info;
			break;
		}
	}
	RETURN_META(MRES_HANDLED);
}
C_DLLEXPORT	int	Meta_Query(char	*ifvers, plugin_info_t **pPlugInfo,	mutil_funcs_t *pMetaUtilFuncs)
{
	gpMetaUtilFuncs = pMetaUtilFuncs;
	*pPlugInfo = &Plugin_info;
	int	mmajor = 0, mminor = 0,	pmajor = 0, pminor = 0;
		
	sscanf(ifvers, "%d:%d",	&mmajor, &mminor);
	sscanf(Plugin_info.ifvers, "%d:%d",	&pmajor, &pminor);
	if (strcmp(ifvers, Plugin_info.ifvers))
	{
		LOG_MESSAGE(PLID, "warning: ifvers mismatch (pl \"%s\") (mm \"%s\")", Plugin_info.ifvers, ifvers);
		if (pmajor > mmajor)
		{
			LOG_ERROR(PLID, "metamod version is too old for this plugin; update metamod");
			return (FALSE);
		} else if (pmajor < mmajor) {
			LOG_ERROR(PLID, "metamod version is incompatible with this plugin; please find a newer version of this plugin");
			return (FALSE);
		} else if (pmajor == mmajor) {
			if (pminor > mminor) 
			{
				LOG_ERROR(PLID, "metamod version is incompatible with this plugin; please find a newer version of this plugin");
				return FALSE;
			} else if (pminor < mminor) {
				LOG_MESSAGE(PLID, "warning: there may be a newer version of metamod available");
			}
		}
	}
	// :NOTE: Don't call modules query here (g_FakeMeta.Meta_Query), because we don't know modules yet. Do it in Meta_Attach
	return (TRUE);
}
static META_FUNCTIONS gMetaFunctionTable;
C_DLLEXPORT	int	Meta_Attach(PLUG_LOADTIME now, META_FUNCTIONS *pFunctionTable, meta_globals_t *pMGlobals, gamedll_funcs_t *pGamedllFuncs)
{
	if (now > Plugin_info.loadable)
	{
		LOG_ERROR(PLID,	"Can't load	plugin right now");
		return (FALSE);
	}
	gpMetaGlobals = pMGlobals;
	gMetaFunctionTable.pfnGetEntityAPI2 = GetEntityAPI2;
	gMetaFunctionTable.pfnGetEntityAPI2_Post = GetEntityAPI2_Post;
	gMetaFunctionTable.pfnGetEngineFunctions = GetEngineFunctions;
	gMetaFunctionTable.pfnGetEngineFunctions_Post = GetEngineFunctions_Post;
#if !defined AMD64
	gMetaFunctionTable.pfnGetNewDLLFunctions = GetNewDLLFunctions;
#endif
	memcpy(pFunctionTable, &gMetaFunctionTable, sizeof(META_FUNCTIONS));
	gpGamedllFuncs=pGamedllFuncs;
	Module_CacheFunctions();
	CVAR_REGISTER(&init_amxmodx_version);
	CVAR_REGISTER(&init_amxmodx_modules);
	CVAR_REGISTER(&init_amxmodx_debug);
	CVAR_REGISTER(&init_amxmodx_mldebug);
	CVAR_REGISTER(&init_amxmodx_cl_langs);
	
	amxmodx_version = CVAR_GET_POINTER(init_amxmodx_version.name);
	
	REG_SVR_COMMAND("amxx", amx_command);
	char gameDir[512];
	GET_GAME_DIR(gameDir);
	char *a = gameDir;
	int i = 0;
	
	while (gameDir[i])
		if (gameDir[i++] ==	'/')
			a = &gameDir[i];
	
	g_mod_name.assign(a);
	if (g_mod_name.compare("cstrike") == 0 || g_mod_name.compare("czero") == 0 || g_mod_name.compare("dod") == 0)
		g_coloredmenus = true;
	else
		g_coloredmenus = false;
	// ###### Print short GPL
	print_srvconsole("\n   AMX Mod X version %s Copyright (c) 2004-2006 AMX Mod X Development Team \n"
					 "   AMX Mod X comes with ABSOLUTELY NO WARRANTY; for details type `amxx gpl'.\n", SVN_VERSION_STRING);
	print_srvconsole("   This is free software and you are welcome to redistribute it under \n"
					 "   certain conditions; type 'amxx gpl' for details.\n  \n");
	// ###### Load custom path configuration
	Vault amx_config;
	amx_config.setSource(build_pathname("%s", get_localinfo("amxx_cfg", "addons/amxmodx/configs/core.ini")));
	if (amx_config.loadVault())
	{
		Vault::iterator	a =	amx_config.begin();
		
		while (a != amx_config.end())
		{
			SET_LOCALINFO((char*)a.key().c_str(), (char*)a.value().c_str());
			++a;
		}
		amx_config.clear();
	}
	// ###### Initialize logging here
	g_log_dir.assign(get_localinfo("amxx_logs", "addons/amxmodx/logs"));
	// ###### Now attach metamod modules
	// This will also call modules Meta_Query and Meta_Attach functions
	loadModules(get_localinfo("amxx_modules", "addons/amxmodx/configs/modules.ini"), now);
	GET_HOOK_TABLES(PLID, &g_pEngTable, NULL, NULL);
	FlagMan.SetFile("cmdaccess.ini");
	
	return (TRUE);
}
C_DLLEXPORT	int	Meta_Detach(PLUG_LOADTIME now, PL_UNLOAD_REASON	reason)
{
	if (now > Plugin_info.unloadable && reason != PNL_CMD_FORCED)
	{
		LOG_ERROR(PLID,	"Can't unload plugin right now");
		return (FALSE);
	}
	modules_callPluginsUnloading();
	g_auth.clear();
	g_forwards.clear();
	g_commands.clear();
	g_forcemodels.clear();
	g_forcesounds.clear();
	g_forcegeneric.clear();
	g_grenades.clear();
	g_tasksMngr.clear();
	g_logevents.clearLogEvents();
	g_events.clearEvents();
	g_menucmds.clear();
	ClearMenus();
	g_vault.clear();
	g_xvars.clear();
	g_plugins.clear();
	g_cvars.clear();
	g_langMngr.Clear();
	ClearMessages();
	modules_callPluginsUnloaded();
	detachModules();
	g_log.CloseFile();
	Module_UncacheFunctions();
	ClearLibraries(LibSource_Plugin);
	ClearLibraries(LibSource_Module);
	return (TRUE);
}
#ifdef __linux__
// linux prototype
C_DLLEXPORT void GiveFnptrsToDll(enginefuncs_t* pengfuncsFromEngine, globalvars_t *pGlobals)
{
#else
#ifdef _MSC_VER
// MSVC: Simulate __stdcall calling convention
C_DLLEXPORT __declspec(naked) void GiveFnptrsToDll(enginefuncs_t* pengfuncsFromEngine, globalvars_t *pGlobals)
{
	__asm			// Prolog
	{
		// Save ebp
		push		ebp
		// Set stack frame pointer
		mov			ebp, esp
		// Allocate space for local variables
		// The MSVC compiler gives us the needed size in __LOCAL_SIZE.
		sub			esp, __LOCAL_SIZE
		// Push registers
		push		ebx
		push		esi
		push		edi
	}
#else	// _MSC_VER
#ifdef __GNUC__
// GCC can also work with this
C_DLLEXPORT void __stdcall GiveFnptrsToDll(enginefuncs_t* pengfuncsFromEngine, globalvars_t *pGlobals)
{
#else	// __GNUC__
// compiler not known
#error There is no support (yet) for your compiler. Please use MSVC or GCC compilers or contact the AMX Mod X dev team.
#endif	// __GNUC__
#endif // _MSC_VER
#endif // __linux__
	// ** Function core <--
	memcpy(&g_engfuncs, pengfuncsFromEngine, sizeof(enginefuncs_t));
	gpGlobals = pGlobals;
	// --> ** Function core
#ifdef _MSC_VER
	// Epilog
	if (sizeof(int*) == 8)
	{	// 64 bit
		__asm
		{
			// Pop registers
			pop	edi
			pop	esi
			pop	ebx
			// Restore stack frame pointer
			mov	esp, ebp
			// Restore ebp
			pop	ebp
			// 2 * sizeof(int*) = 16 on 64 bit
			ret 16
		}
	}
	else
	{	// 32 bit
		__asm
		{
			// Pop registers
			pop	edi
			pop	esi
			pop	ebx
			// Restore stack frame pointer
			mov	esp, ebp
			// Restore ebp
			pop	ebp
			// 2 * sizeof(int*) = 8 on 32 bit
			ret 8
		}
	}
#endif // #ifdef _MSC_VER
}
DLL_FUNCTIONS gFunctionTable;
C_DLLEXPORT	int	GetEntityAPI2(DLL_FUNCTIONS *pFunctionTable, int *interfaceVersion)
{
	memset(&gFunctionTable, 0, sizeof(DLL_FUNCTIONS));
	gFunctionTable.pfnSpawn = C_Spawn;
	gFunctionTable.pfnClientCommand = C_ClientCommand;
	gFunctionTable.pfnServerDeactivate = C_ServerDeactivate;
	gFunctionTable.pfnClientDisconnect = C_ClientDisconnect;
	gFunctionTable.pfnInconsistentFile = C_InconsistentFile;
	gFunctionTable.pfnServerActivate = C_ServerActivate;
	memcpy(pFunctionTable, &gFunctionTable, sizeof(DLL_FUNCTIONS));
	return 1;
}
DLL_FUNCTIONS gFunctionTable_Post;
C_DLLEXPORT	int	GetEntityAPI2_Post(DLL_FUNCTIONS *pFunctionTable, int *interfaceVersion)
{
	memset(&gFunctionTable_Post, 0, sizeof(DLL_FUNCTIONS));
	gFunctionTable_Post.pfnClientPutInServer = C_ClientPutInServer_Post;
	gFunctionTable_Post.pfnClientUserInfoChanged = C_ClientUserInfoChanged_Post;
	gFunctionTable_Post.pfnServerActivate = C_ServerActivate_Post;
	gFunctionTable_Post.pfnClientConnect = C_ClientConnect_Post;
	gFunctionTable_Post.pfnStartFrame = C_StartFrame_Post;
	gFunctionTable_Post.pfnServerDeactivate = C_ServerDeactivate_Post;
	memcpy(pFunctionTable, &gFunctionTable_Post, sizeof(DLL_FUNCTIONS));
	return 1;
}
enginefuncs_t meta_engfuncs;
C_DLLEXPORT	int	GetEngineFunctions(enginefuncs_t *pengfuncsFromEngine, int *interfaceVersion)
{
	memset(&meta_engfuncs, 0, sizeof(enginefuncs_t));
	if (stricmp(g_mod_name.c_str(), "cstrike") == 0 || stricmp(g_mod_name.c_str(), "czero") == 0)
	{
		meta_engfuncs.pfnSetModel =	C_SetModel;
		g_bmod_cstrike = true;
	} else {
		g_bmod_cstrike = false;
		g_bmod_dod = !stricmp(g_mod_name.c_str(), "dod");
		g_bmod_tfc = !stricmp(g_mod_name.c_str(), "tfc");
	}
	meta_engfuncs.pfnCmd_Argc = C_Cmd_Argc;
	meta_engfuncs.pfnCmd_Argv = C_Cmd_Argv;
	meta_engfuncs.pfnCmd_Args = C_Cmd_Args;
	meta_engfuncs.pfnPrecacheModel = C_PrecacheModel;
	meta_engfuncs.pfnPrecacheSound = C_PrecacheSound;
	meta_engfuncs.pfnChangeLevel = C_ChangeLevel;
	/* message stuff from messages.h/cpp */
	meta_engfuncs.pfnMessageBegin = C_MessageBegin;
	meta_engfuncs.pfnMessageEnd = C_MessageEnd;
	meta_engfuncs.pfnWriteAngle = C_WriteAngle;
	meta_engfuncs.pfnWriteByte = C_WriteByte;
	meta_engfuncs.pfnWriteChar = C_WriteChar;
	meta_engfuncs.pfnWriteCoord = C_WriteCoord;
	meta_engfuncs.pfnWriteEntity = C_WriteEntity;
	meta_engfuncs.pfnWriteLong = C_WriteLong;
	meta_engfuncs.pfnWriteShort = C_WriteShort;
	meta_engfuncs.pfnWriteString = C_WriteString;
	meta_engfuncs.pfnAlertMessage = C_AlertMessage;
	memcpy(pengfuncsFromEngine, &meta_engfuncs, sizeof(enginefuncs_t));
	return 1;
}
enginefuncs_t meta_engfuncs_post;
C_DLLEXPORT	int	GetEngineFunctions_Post(enginefuncs_t *pengfuncsFromEngine,	int	*interfaceVersion)
{
	memset(&meta_engfuncs_post, 0, sizeof(enginefuncs_t));
	meta_engfuncs_post.pfnTraceLine = C_TraceLine_Post;
	meta_engfuncs_post.pfnMessageBegin = C_MessageBegin_Post;
	meta_engfuncs_post.pfnMessageEnd = C_MessageEnd_Post;
	meta_engfuncs_post.pfnWriteByte = C_WriteByte_Post;
	meta_engfuncs_post.pfnWriteChar = C_WriteChar_Post;
	meta_engfuncs_post.pfnWriteShort = C_WriteShort_Post;
	meta_engfuncs_post.pfnWriteLong = C_WriteLong_Post;	
	meta_engfuncs_post.pfnWriteAngle = C_WriteAngle_Post;
	meta_engfuncs_post.pfnWriteCoord = C_WriteCoord_Post;
	meta_engfuncs_post.pfnWriteString = C_WriteString_Post;
	meta_engfuncs_post.pfnWriteEntity = C_WriteEntity_Post;
	meta_engfuncs_post.pfnRegUserMsg = C_RegUserMsg_Post;
	memcpy(pengfuncsFromEngine, &meta_engfuncs_post, sizeof(enginefuncs_t));
	return 1;
}
//quick hack - disable all newdll stuff for AMD64
// until VALVe gets their act together!
#if !defined AMD64
NEW_DLL_FUNCTIONS gNewDLLFunctionTable;
C_DLLEXPORT int GetNewDLLFunctions(NEW_DLL_FUNCTIONS *pNewFunctionTable, int *interfaceVersion)
{
	memset(&gNewDLLFunctionTable, 0, sizeof(NEW_DLL_FUNCTIONS));
	
	// default metamod does not call this if the gamedll doesn't provide it
	if (g_engfuncs.pfnQueryClientCvarValue2)
	{
		gNewDLLFunctionTable.pfnCvarValue2 = C_CvarValue2;
		g_NewDLL_Available = true;
	}
	memcpy(pNewFunctionTable, &gNewDLLFunctionTable, sizeof(NEW_DLL_FUNCTIONS));
	return 1;
}
#endif
