Initial import of replacement Library system - Backwards compat tested!
Updated new SDK (bumped vers number, kept backwards compat) Improved module path resolving
This commit is contained in:
parent
576680eaf9
commit
dad00a2eb6
@ -30,6 +30,7 @@
|
||||
*/
|
||||
|
||||
#include "amxmodx.h"
|
||||
#include "libraries.h"
|
||||
|
||||
#ifndef FAR
|
||||
#define FAR
|
||||
@ -41,6 +42,8 @@ typedef int (FAR *QUERYMOD_NEW)(int * /*ifvers*/, amxx_module_info_s * /*modInfo
|
||||
typedef int (FAR *ATTACHMOD_NEW)(PFN_REQ_FNPTR /*reqFnptrFunc*/);
|
||||
typedef int (FAR *DETACHMOD_NEW)(void);
|
||||
typedef void (FAR *PLUGINSLOADED_NEW)(void);
|
||||
typedef void (*PLUGINSUNLOADED_NEW)(void);
|
||||
typedef void (*PLUGINSUNLOADING_NEW)(void);
|
||||
|
||||
// *****************************************************
|
||||
// class CModule
|
||||
@ -126,7 +129,7 @@ bool CModule::attachModule()
|
||||
{
|
||||
case AMXX_OK:
|
||||
m_Status = MODULE_LOADED;
|
||||
return true;
|
||||
break;
|
||||
case AMXX_PARAM:
|
||||
AMXXLOG_Log("[AMXX] Internal Error: Module \"%s\" (version \"%s\") retured \"Invalid parameter\" from Attach func.", m_Filename.c_str(), getVersion());
|
||||
m_Status = MODULE_INTERROR;
|
||||
@ -144,6 +147,13 @@ bool CModule::attachModule()
|
||||
m_Status = MODULE_BADLOAD;
|
||||
}
|
||||
|
||||
if (m_Status == MODULE_LOADED)
|
||||
{
|
||||
AddLibrariesFromString(m_InfoNew.library, LibType_Library, LibSource_Module, this);
|
||||
AddLibrariesFromString(m_InfoNew.libclass, LibType_Class, LibSource_Module, this);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -184,10 +194,36 @@ bool CModule::queryModule()
|
||||
return false;
|
||||
case AMXX_IFVERS:
|
||||
if (ifVers < AMXX_INTERFACE_VERSION)
|
||||
m_Status = MODULE_OLD;
|
||||
else
|
||||
{
|
||||
//backwards compat for new defs
|
||||
if (ifVers == 3)
|
||||
{
|
||||
g_ModuleCallReason = ModuleCall_Query;
|
||||
g_CurrentlyCalledModule = this;
|
||||
retVal = (*queryFunc_New)(&ifVers, &m_InfoNew);
|
||||
g_CurrentlyCalledModule = NULL;
|
||||
g_ModuleCallReason = ModuleCall_NotCalled;
|
||||
if (retVal == AMXX_OK)
|
||||
{
|
||||
m_InfoNew.library = m_InfoNew.logtag;
|
||||
if (StrCaseStr(m_InfoNew.library, "sql")
|
||||
|| StrCaseStr(m_InfoNew.library, "dbi"))
|
||||
{
|
||||
m_InfoNew.libclass = "DBI";
|
||||
} else {
|
||||
m_InfoNew.libclass = "";
|
||||
}
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
} else {
|
||||
m_Status = MODULE_OLD;
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
m_Status = MODULE_NEWER;
|
||||
return false;
|
||||
return false;
|
||||
}
|
||||
case AMXX_OK:
|
||||
break;
|
||||
default:
|
||||
@ -217,6 +253,8 @@ bool CModule::detachModule()
|
||||
if (m_Status != MODULE_LOADED)
|
||||
return false;
|
||||
|
||||
RemoveLibraries(this);
|
||||
|
||||
if (m_Amxx)
|
||||
{
|
||||
DETACHMOD_NEW detachFunc_New = (DETACHMOD_NEW)DLPROC(m_Handle, "AMXX_Detach");
|
||||
@ -244,6 +282,38 @@ bool CModule::detachModule()
|
||||
return true;
|
||||
}
|
||||
|
||||
void CModule::CallPluginsUnloaded()
|
||||
{
|
||||
if (m_Status != MODULE_LOADED)
|
||||
return;
|
||||
|
||||
if (!m_Handle)
|
||||
return;
|
||||
|
||||
PLUGINSUNLOADED_NEW func = (PLUGINSUNLOADED_NEW)DLPROC(m_Handle, "AMXX_PluginsUnloaded");
|
||||
|
||||
if (!func)
|
||||
return;
|
||||
|
||||
func();
|
||||
}
|
||||
|
||||
void CModule::CallPluginsUnloading()
|
||||
{
|
||||
if (m_Status != MODULE_LOADED)
|
||||
return;
|
||||
|
||||
if (!m_Handle)
|
||||
return;
|
||||
|
||||
PLUGINSUNLOADING_NEW func = (PLUGINSUNLOADING_NEW)DLPROC(m_Handle, "AMXX_PluginsUnloading");
|
||||
|
||||
if (!func)
|
||||
return;
|
||||
|
||||
func();
|
||||
}
|
||||
|
||||
void CModule::CallPluginsLoaded()
|
||||
{
|
||||
if (m_Status != MODULE_LOADED)
|
||||
|
@ -59,6 +59,8 @@ struct amxx_module_info_s
|
||||
const char *version;
|
||||
int reload; // reload on mapchange when nonzero
|
||||
const char *logtag; //added in version 2
|
||||
const char *library; // added in version 4
|
||||
const char *libclass; // added in version 4
|
||||
};
|
||||
|
||||
#define AMXX_OK 0 /* no error */
|
||||
@ -66,7 +68,7 @@ struct amxx_module_info_s
|
||||
#define AMXX_PARAM 2 /* Invalid parameter */
|
||||
#define AMXX_FUNC_NOT_PRESENT 3 /* Function not present */
|
||||
|
||||
#define AMXX_INTERFACE_VERSION 3
|
||||
#define AMXX_INTERFACE_VERSION 4
|
||||
|
||||
class CModule
|
||||
{
|
||||
@ -110,6 +112,8 @@ public:
|
||||
inline bool IsMetamod() { return m_Metamod; }
|
||||
|
||||
void CallPluginsLoaded();
|
||||
void CallPluginsUnloaded();
|
||||
void CallPluginsUnloading();
|
||||
|
||||
CList<AMX_NATIVE_INFO*> m_Natives;
|
||||
};
|
||||
|
@ -34,6 +34,7 @@
|
||||
#include "natives.h"
|
||||
#include "debugger.h"
|
||||
#include "binlog.h"
|
||||
#include "libraries.h"
|
||||
|
||||
static cell AMX_NATIVE_CALL get_xvar_id(AMX *amx, cell *params)
|
||||
{
|
||||
@ -3716,43 +3717,18 @@ static cell AMX_NATIVE_CALL module_exists(AMX *amx, cell *params)
|
||||
int len;
|
||||
char *module = get_amxstring(amx, params[1], 0, len);
|
||||
|
||||
CList<CModule, const char *>::iterator a;
|
||||
if (!FindLibrary(module, LibType_Library))
|
||||
return FindLibrary(module, LibType_Class);
|
||||
|
||||
bool isdbi = false, found = false;
|
||||
const amxx_module_info_s *info;
|
||||
|
||||
if (stricmp(module, "dbi") == 0)
|
||||
isdbi = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
for (a = g_modules.begin(); a; ++a)
|
||||
{
|
||||
if ((*a).getStatusValue() == MODULE_LOADED)
|
||||
{
|
||||
info = (*a).getInfoNew();
|
||||
if (info)
|
||||
{
|
||||
if (isdbi)
|
||||
{
|
||||
if (info->logtag && (StrCaseStr(info->logtag, "sql") || StrCaseStr(info->logtag, "dbi")))
|
||||
{
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
if (info->logtag && (stricmp(info->logtag, module) == 0))
|
||||
{
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!found)
|
||||
found = LibraryExists(module);
|
||||
static cell AMX_NATIVE_CALL LibraryExists(AMX *amx, cell *params)
|
||||
{
|
||||
int len;
|
||||
char *library = get_amxstring(amx, params[1], 0, len);
|
||||
|
||||
return (found ? 1 : 0);
|
||||
return FindLibrary(library, static_cast<LibType>(params[2]));
|
||||
}
|
||||
|
||||
static cell AMX_NATIVE_CALL set_fail_state(AMX *amx, cell *params)
|
||||
@ -4242,5 +4218,6 @@ AMX_NATIVE_INFO amxmodx_Natives[] =
|
||||
{"ExecuteForward", ExecuteForward},
|
||||
{"PrepareArray", PrepareArray},
|
||||
{"ShowSyncHudMsg", ShowSyncHudMsg},
|
||||
{"LibraryExists", LibraryExists},
|
||||
{NULL, NULL}
|
||||
};
|
||||
|
@ -263,6 +263,8 @@ enum CountModulesMode
|
||||
|
||||
int countModules(CountModulesMode mode);
|
||||
void modules_callPluginsLoaded();
|
||||
void modules_callPluginsUnloaded();
|
||||
void modules_callPluginsUnloading();
|
||||
|
||||
cell* get_amxaddr(AMX *amx, cell amx_addr);
|
||||
char* build_pathname(char *fmt, ...);
|
||||
|
@ -506,6 +506,7 @@ void C_ServerDeactivate_Post()
|
||||
g_xvars.clear();
|
||||
g_plugins.clear();
|
||||
ClearPluginLibraries();
|
||||
modules_callPluginsUnloaded();
|
||||
|
||||
for (unsigned int i=0; i<g_hudsync.size(); i++)
|
||||
delete [] g_hudsync[i];
|
||||
@ -1295,12 +1296,17 @@ C_DLLEXPORT int Meta_Detach(PLUG_LOADTIME now, PL_UNLOAD_REASON reason)
|
||||
g_cvars.clear();
|
||||
g_langMngr.Clear();
|
||||
|
||||
modules_callPluginsUnloaded();
|
||||
|
||||
detachModules();
|
||||
|
||||
g_log.CloseFile();
|
||||
|
||||
Module_UncacheFunctions();
|
||||
|
||||
ClearLibraries(LibSource_Plugin);
|
||||
ClearLibraries(LibSource_Module);
|
||||
|
||||
return (TRUE);
|
||||
}
|
||||
|
||||
|
@ -46,6 +46,7 @@
|
||||
#include "debugger.h"
|
||||
#include "optimizer.h"
|
||||
#include "binlog.h"
|
||||
#include "libraries.h"
|
||||
|
||||
CList<CModule, const char*> g_modules;
|
||||
CList<CScript, AMX*> g_loadedscripts;
|
||||
@ -427,68 +428,61 @@ const char *StrCaseStr(const char *as, const char *bs)
|
||||
int CheckModules(AMX *amx, char error[128])
|
||||
{
|
||||
int numLibraries = amx_GetLibraries(amx);
|
||||
char buffer[32];
|
||||
|
||||
bool found = false;
|
||||
bool isdbi = false;
|
||||
|
||||
CList<CModule, const char *>::iterator a;
|
||||
|
||||
const amxx_module_info_s *info;
|
||||
char buffer[64];
|
||||
|
||||
Handler *pHandler = (Handler *)amx->userdata[UD_HANDLER];
|
||||
|
||||
for (int i = 0; i < numLibraries; i++)
|
||||
{
|
||||
amx_GetLibrary(amx, i, buffer, sizeof(buffer) - 1);
|
||||
found = false;
|
||||
|
||||
if (stricmp(buffer, "float") == 0)
|
||||
continue;
|
||||
|
||||
isdbi = false;
|
||||
|
||||
if (stricmp(buffer, "dbi") == 0)
|
||||
isdbi = true;
|
||||
|
||||
for (a = g_modules.begin(); a; ++a)
|
||||
|
||||
LibDecoder dcd;
|
||||
LibType expect;
|
||||
bool found = false;
|
||||
const char *search = NULL;
|
||||
|
||||
DecodeLibCmdString(buffer, dcd);
|
||||
|
||||
switch (dcd.cmd)
|
||||
{
|
||||
if ((*a).getStatusValue() == MODULE_LOADED)
|
||||
{
|
||||
info = (*a).getInfoNew();
|
||||
|
||||
if (info)
|
||||
{
|
||||
if (isdbi)
|
||||
{
|
||||
if (info->logtag && (StrCaseStr(info->logtag, "sql") || StrCaseStr(info->logtag, "dbi")))
|
||||
{
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
if (info->logtag && (stricmp(info->logtag, buffer) == 0))
|
||||
{
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
case LibCmd_ReqLib:
|
||||
search = dcd.param1;
|
||||
expect = LibType_Library;
|
||||
break;
|
||||
case LibCmd_ExpectLib:
|
||||
search = dcd.param2;
|
||||
expect = LibType_Library;
|
||||
break;
|
||||
case LibCmd_ReqClass:
|
||||
search = dcd.param1;
|
||||
expect = LibType_Class;
|
||||
break;
|
||||
case LibCmd_ExpectClass:
|
||||
search = dcd.param2;
|
||||
expect = LibType_Class;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!found)
|
||||
found = LibraryExists(buffer);
|
||||
|
||||
if (!search)
|
||||
continue;
|
||||
|
||||
found = FindLibrary(search, expect);
|
||||
|
||||
if (!found)
|
||||
{
|
||||
if (pHandler->HandleModule(buffer))
|
||||
if (pHandler->HandleModule(search))
|
||||
found = true;
|
||||
}
|
||||
|
||||
if (!found)
|
||||
{
|
||||
sprintf(error, "Module \"%s\" required for plugin. Check modules.ini.", buffer);
|
||||
const char *type = "Module/Library";
|
||||
if (expect == LibType_Class)
|
||||
type = "Module/Library Class";
|
||||
sprintf(error, "%s \"%s\" required for plugin. Check modules.ini.", type, search);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@ -735,130 +729,202 @@ char* build_pathname_addons(char *fmt, ...)
|
||||
return string;
|
||||
}
|
||||
|
||||
bool validFile(const char* file)
|
||||
{
|
||||
const char* a = 0;
|
||||
|
||||
while (*file)
|
||||
if (*file++ == '.')
|
||||
a = file;
|
||||
#ifndef __linux__
|
||||
return (a && !strcmp(a, "dll"));
|
||||
#else
|
||||
return (a && !strcmp(a, "so"));
|
||||
#if defined WIN32
|
||||
#define SEPCHAR '\\'
|
||||
#elif defined __linux__
|
||||
#define SEPCHAR '/'
|
||||
#endif
|
||||
|
||||
bool ConvertModuleName(const char *pathString, String &path)
|
||||
{
|
||||
String local;
|
||||
|
||||
local.assign(pathString);
|
||||
char *tmpname = const_cast<char *>(local.c_str());
|
||||
char *orig_path = tmpname;
|
||||
|
||||
path.clear();
|
||||
|
||||
size_t len = local.size();
|
||||
if (!len)
|
||||
return false;
|
||||
|
||||
/* run to filename instead of dir */
|
||||
char *ptr = tmpname;
|
||||
ptr = tmpname + len - 1;
|
||||
while (ptr >= tmpname && *ptr != SEPCHAR)
|
||||
ptr--;
|
||||
if (ptr >= tmpname)
|
||||
{
|
||||
*ptr++ = '\0';
|
||||
tmpname = ptr;
|
||||
}
|
||||
|
||||
bool foundAmxx = false;
|
||||
int iDigit = '3';
|
||||
ptr = tmpname;
|
||||
while (*ptr)
|
||||
{
|
||||
while (*ptr && *ptr != '_')
|
||||
ptr++;
|
||||
if (strncmp(ptr, "_amxx", 5) == 0)
|
||||
{
|
||||
char *p = ptr + 5;
|
||||
if (strncmp(p, ".dll", 4) == 0)
|
||||
{
|
||||
foundAmxx = true;
|
||||
break;
|
||||
} else if (p[0] == '_') {
|
||||
p++;
|
||||
if (strncmp(p, "amd64.so", 8) == 0)
|
||||
{
|
||||
foundAmxx = true;
|
||||
break;
|
||||
} else if (p[0] == 'i') {
|
||||
p++;
|
||||
if (isdigit(p[0]) && p[1] == '8' && p[2] == '6')
|
||||
{
|
||||
iDigit = p[0];
|
||||
foundAmxx = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if (p[0] == '\0') {
|
||||
foundAmxx = true;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
while (*ptr && *ptr == '_')
|
||||
ptr++;
|
||||
}
|
||||
}
|
||||
|
||||
if (!foundAmxx)
|
||||
{
|
||||
ptr = tmpname + strlen(tmpname); - 1;
|
||||
while (ptr >= tmpname && *ptr != '.')
|
||||
ptr--;
|
||||
if (ptr > tmpname && *ptr == '.')
|
||||
{
|
||||
*ptr = '\0';
|
||||
}
|
||||
} else {
|
||||
*ptr = '\0';
|
||||
}
|
||||
|
||||
path.assign(orig_path);
|
||||
path.append(SEPCHAR);
|
||||
path.append(tmpname);
|
||||
path.append("_amxx");
|
||||
#if defined __linux__
|
||||
#if defined AMD64 || PAWN_CELL_SIZE==64
|
||||
path.append("amd64");
|
||||
#else
|
||||
path.append("i");
|
||||
path.append(iDigit);
|
||||
path.append("86");
|
||||
#endif
|
||||
#endif
|
||||
#if defined WIN32
|
||||
path.append(".dll");
|
||||
#elif defined __linux__
|
||||
path.append(".so");
|
||||
#endif
|
||||
|
||||
FILE *fp = fopen(path.c_str(), "rb");
|
||||
if (!fp)
|
||||
return false;
|
||||
fclose(fp);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void ConvertModuleName(const char *pathString, String &path)
|
||||
bool LoadModule(const char *shortname, PLUG_LOADTIME now)
|
||||
{
|
||||
#if PAWN_CELL_SIZE==64
|
||||
char *ptr = strstr(pathString, "i386");
|
||||
|
||||
if (ptr)
|
||||
{
|
||||
//attempt to fix the binary name
|
||||
*ptr = 0;
|
||||
path.assign(pathString);
|
||||
path.append("amd64.so");
|
||||
} else {
|
||||
ptr = strstr(pathString, ".dll");
|
||||
|
||||
if (ptr)
|
||||
{
|
||||
*ptr = 0;
|
||||
path.assign(pathString);
|
||||
path.append("_amd64.so");
|
||||
} else {
|
||||
ptr = strstr(pathString, ".so");
|
||||
|
||||
if (ptr)
|
||||
{
|
||||
path.assign(pathString);
|
||||
} else {
|
||||
//no extension at all
|
||||
path.assign(pathString);
|
||||
path.append("_amd64.so");
|
||||
}
|
||||
}
|
||||
}
|
||||
#else
|
||||
char pathString[512];
|
||||
String path;
|
||||
|
||||
#ifdef __linux__
|
||||
char *ptr = strstr(pathString, "amd64");
|
||||
|
||||
if (ptr)
|
||||
build_pathname_r(
|
||||
pathString,
|
||||
sizeof(pathString)-1,
|
||||
"%s/%s",
|
||||
get_localinfo("amxx_modulesdir", "addons/amxmodx/modules"),
|
||||
shortname);
|
||||
|
||||
if (!ConvertModuleName(pathString, path))
|
||||
return false;
|
||||
|
||||
CList<CModule, const char *>::iterator a = g_modules.find(path.c_str());
|
||||
|
||||
if (a)
|
||||
return false;
|
||||
|
||||
CModule* cc = new CModule(path.c_str());
|
||||
|
||||
cc->queryModule();
|
||||
|
||||
switch (cc->getStatusValue())
|
||||
{
|
||||
//attempt to fix the binary name
|
||||
*ptr = 0;
|
||||
path.assign(pathString);
|
||||
path.append("i386.so");
|
||||
} else {
|
||||
ptr = strstr(pathString, ".dll");
|
||||
|
||||
if (ptr)
|
||||
{
|
||||
*ptr = 0;
|
||||
path.assign(pathString);
|
||||
path.append("_i386.so");
|
||||
} else {
|
||||
//check to see if this file even has an extension
|
||||
ptr = strstr(pathString, ".so");
|
||||
|
||||
if (ptr)
|
||||
{
|
||||
path.assign(pathString);
|
||||
} else {
|
||||
path.assign(pathString);
|
||||
path.append("_i386.so");
|
||||
}
|
||||
}
|
||||
case MODULE_BADLOAD:
|
||||
report_error(1, "[AMXX] Module is not a valid library (file \"%s\")", path.c_str());
|
||||
break;
|
||||
case MODULE_NOINFO:
|
||||
report_error(1, "[AMXX] Couldn't find info about module (file \"%s\")", path.c_str());
|
||||
break;
|
||||
case MODULE_NOQUERY:
|
||||
report_error(1, "[AMXX] Couldn't find \"AMX_Query\" or \"AMXX_Query\" (file \"%s\")", path.c_str());
|
||||
break;
|
||||
case MODULE_NOATTACH:
|
||||
report_error(1, "[AMXX] Couldn't find \"%s\" (file \"%s\")", cc->isAmxx() ? "AMXX_Attach" : "AMX_Attach", path.c_str());
|
||||
break;
|
||||
case MODULE_OLD:
|
||||
report_error(1, "[AMXX] Module has a different interface version (file \"%s\")", path.c_str());
|
||||
break;
|
||||
case MODULE_NEWER:
|
||||
report_error(1, "[AMXX] Module has a newer interface version (file \"%s\"). Please download a new amxmodx.", path.c_str());
|
||||
break;
|
||||
case MODULE_INTERROR:
|
||||
report_error(1, "[AMXX] Internal error during module load (file \"%s\")", path.c_str());
|
||||
break;
|
||||
case MODULE_NOT64BIT:
|
||||
report_error(1, "[AMXX] Module \"%s\" is not 64 bit compatible.", path.c_str());
|
||||
break;
|
||||
}
|
||||
#else
|
||||
char *ptr = const_cast<char*>(strstr(pathString, ".dll"));
|
||||
|
||||
if (ptr)
|
||||
|
||||
g_modules.put(cc);
|
||||
|
||||
if (cc->IsMetamod())
|
||||
{
|
||||
path.assign(pathString);
|
||||
} else {
|
||||
//prevent this from loading .so too
|
||||
ptr = const_cast<char*>(strstr(pathString, ".so"));
|
||||
|
||||
if (ptr)
|
||||
{
|
||||
int i = 0, len = strlen(pathString), c = -1;
|
||||
|
||||
for (i = len - 1; i >= 0; i--)
|
||||
{
|
||||
//cut off at first _
|
||||
if (pathString[i] == '_')
|
||||
{
|
||||
//make sure this is a valid _
|
||||
if (i == len - 1 || strncmp(&(pathString[i + 1]), "amxx", 4) == 0)
|
||||
break;
|
||||
|
||||
c = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
*ptr = 0;
|
||||
|
||||
if (c == -1)
|
||||
{
|
||||
path.assign(pathString);
|
||||
path.append(".dll");
|
||||
} else {
|
||||
ptr = (char *)&(pathString[c]);
|
||||
*ptr = 0;
|
||||
path.assign(pathString);
|
||||
path.append(".dll");
|
||||
}
|
||||
} else {
|
||||
path.assign(pathString);
|
||||
path.append(".dll");
|
||||
}
|
||||
char *mmpathname = build_pathname_addons(
|
||||
"%s/%s",
|
||||
get_localinfo("amxx_modulesdir", "addons/amxmodx/modules"),
|
||||
shortname);
|
||||
ConvertModuleName(mmpathname, path);
|
||||
cc->attachMetamod(path.c_str(), now);
|
||||
}
|
||||
#endif //__linux__
|
||||
#endif //PAWN_CELL_SIZE==64
|
||||
|
||||
bool retVal = cc->attachModule();
|
||||
|
||||
if (cc->isAmxx() && !retVal)
|
||||
{
|
||||
switch (cc->getStatusValue())
|
||||
{
|
||||
case MODULE_FUNCNOTPRESENT:
|
||||
report_error(1, "[AMXX] Module requested a not exisitng function (file \"%s\")%s%s%s", cc->getFilename(), cc->getMissingFunc() ? " (func \"" : "",
|
||||
cc->getMissingFunc() ? cc->getMissingFunc() : "", cc->getMissingFunc() ? "\")" : "");
|
||||
break;
|
||||
case MODULE_INTERROR:
|
||||
report_error(1, "[AMXX] Internal error during module load (file \"%s\")", cc->getFilename());
|
||||
break;
|
||||
case MODULE_BADLOAD:
|
||||
report_error(1, "[AMXX] Module is not a valid library (file \"%s\")", cc->getFilename());
|
||||
break;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int loadModules(const char* filename, PLUG_LOADTIME now)
|
||||
@ -872,7 +938,6 @@ int loadModules(const char* filename, PLUG_LOADTIME now)
|
||||
}
|
||||
|
||||
char moduleName[256];
|
||||
char pathString[512];
|
||||
String line;
|
||||
int loaded = 0;
|
||||
|
||||
@ -892,91 +957,8 @@ int loadModules(const char* filename, PLUG_LOADTIME now)
|
||||
if (moduleName[0] == ';')
|
||||
continue;
|
||||
|
||||
char* pathname = build_pathname("%s/%s", get_localinfo("amxx_modulesdir", "addons/amxmodx/modules"), moduleName);
|
||||
strcpy(pathString, pathname);
|
||||
|
||||
path.assign("");
|
||||
|
||||
ConvertModuleName(pathString, path);
|
||||
|
||||
if (!validFile(path.c_str()))
|
||||
continue;
|
||||
|
||||
CList<CModule, const char *>::iterator a = g_modules.find(path.c_str());
|
||||
|
||||
if (a)
|
||||
continue; // already loaded
|
||||
|
||||
CModule* cc = new CModule(path.c_str());
|
||||
|
||||
if (cc == 0)
|
||||
{
|
||||
fclose(fp);
|
||||
return loaded;
|
||||
}
|
||||
|
||||
cc->queryModule();
|
||||
|
||||
switch (cc->getStatusValue())
|
||||
{
|
||||
case MODULE_BADLOAD:
|
||||
report_error(1, "[AMXX] Module is not a valid library (file \"%s\")", path.c_str());
|
||||
break;
|
||||
case MODULE_NOINFO:
|
||||
report_error(1, "[AMXX] Couldn't find info. about module (file \"%s\")", path.c_str());
|
||||
break;
|
||||
case MODULE_NOQUERY:
|
||||
report_error(1, "[AMXX] Couldn't find \"AMX_Query\" or \"AMXX_Query\" (file \"%s\")", path.c_str());
|
||||
break;
|
||||
case MODULE_NOATTACH:
|
||||
report_error(1, "[AMXX] Couldn't find \"%s\" (file \"%s\")", cc->isAmxx() ? "AMXX_Attach" : "AMX_Attach", path.c_str());
|
||||
break;
|
||||
case MODULE_OLD:
|
||||
report_error(1, "[AMXX] Module has a different interface version (file \"%s\")", path.c_str());
|
||||
break;
|
||||
case MODULE_NEWER:
|
||||
report_error(1, "[AMXX] Module has a newer interface version (file \"%s\"). Please download a new amxmodx.", path.c_str());
|
||||
break;
|
||||
case MODULE_INTERROR:
|
||||
report_error(1, "[AMXX] Internal error during module load (file \"%s\")", path.c_str());
|
||||
break;
|
||||
case MODULE_NOT64BIT:
|
||||
report_error(1, "[AMXX] Module \"%s\" is not 64 bit compatible.", path.c_str());
|
||||
break;
|
||||
default:
|
||||
++loaded;
|
||||
}
|
||||
|
||||
g_modules.put(cc);
|
||||
|
||||
if (cc->IsMetamod())
|
||||
{
|
||||
char* mmpathname = build_pathname_addons("%s/%s", get_localinfo("amxx_modulesdir", "addons/amxmodx/modules"), line.c_str());
|
||||
|
||||
ConvertModuleName(mmpathname, path);
|
||||
cc->attachMetamod(path.c_str(), now);
|
||||
}
|
||||
|
||||
bool retVal = cc->attachModule();
|
||||
|
||||
if (cc->isAmxx() && !retVal)
|
||||
{
|
||||
switch (cc->getStatusValue())
|
||||
{
|
||||
case MODULE_FUNCNOTPRESENT:
|
||||
report_error(1, "[AMXX] Module requested a not exisitng function (file \"%s\")%s%s%s", cc->getFilename(), cc->getMissingFunc() ? " (func \"" : "",
|
||||
cc->getMissingFunc() ? cc->getMissingFunc() : "", cc->getMissingFunc() ? "\")" : "");
|
||||
break;
|
||||
case MODULE_INTERROR:
|
||||
report_error(1, "[AMXX] Internal error during module load (file \"%s\")", cc->getFilename());
|
||||
break;
|
||||
case MODULE_BADLOAD:
|
||||
report_error(1, "[AMXX] Module is not a valid library (file \"%s\")", cc->getFilename());
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (LoadModule(moduleName, now))
|
||||
loaded++;
|
||||
}
|
||||
|
||||
fclose(fp);
|
||||
@ -1080,6 +1062,29 @@ void modules_callPluginsLoaded()
|
||||
}
|
||||
}
|
||||
|
||||
//same for unloaded
|
||||
void modules_callPluginsUnloaded()
|
||||
{
|
||||
CList<CModule, const char *>::iterator iter = g_modules.begin();
|
||||
|
||||
while (iter)
|
||||
{
|
||||
(*iter).CallPluginsUnloaded();
|
||||
++iter;
|
||||
}
|
||||
}
|
||||
|
||||
void modules_callPluginsUnloading()
|
||||
{
|
||||
CList<CModule, const char *>::iterator iter = g_modules.begin();
|
||||
|
||||
while (iter)
|
||||
{
|
||||
(*iter).CallPluginsUnloading();
|
||||
++iter;
|
||||
}
|
||||
}
|
||||
|
||||
// new functions
|
||||
int MNF_AddNatives(AMX_NATIVE_INFO* natives)
|
||||
{
|
||||
@ -1493,6 +1498,21 @@ void MNF_MergeDefinitionFile(const char *file)
|
||||
g_langMngr.MergeDefinitionFile(file);
|
||||
}
|
||||
|
||||
int MNF_FindLibrary(const char *name, LibType type)
|
||||
{
|
||||
return FindLibrary(name, type) ? 1 : 0;
|
||||
}
|
||||
|
||||
size_t MFN_AddLibraries(const char *name, LibType type, void *parent)
|
||||
{
|
||||
return AddLibrariesFromString(name, type, LibSource_Module, parent) ? 1 : 0;
|
||||
}
|
||||
|
||||
size_t MNF_RemoveLibraries(void *parent)
|
||||
{
|
||||
return RemoveLibraries(parent);
|
||||
}
|
||||
|
||||
edict_t* MNF_GetPlayerEdict(int id)
|
||||
{
|
||||
if (id < 1 || id > gpGlobals->maxClients)
|
||||
@ -1732,6 +1752,10 @@ void Module_CacheFunctions()
|
||||
REGISTER_FUNC("RegAuthFunc", MNF_RegAuthorizeFunc);
|
||||
REGISTER_FUNC("UnregAuthFunc", MNF_UnregAuthorizeFunc);
|
||||
|
||||
REGISTER_FUNC("FindLibrary", MNF_FindLibrary);
|
||||
REGISTER_FUNC("AddLibraries", MFN_AddLibraries);
|
||||
REGISTER_FUNC("RemoveLibraries", MNF_RemoveLibraries);
|
||||
|
||||
#ifdef MEMORY_TEST
|
||||
REGISTER_FUNC("Allocator", m_allocator)
|
||||
REGISTER_FUNC("Deallocator", m_deallocator)
|
||||
|
@ -72,6 +72,7 @@ typedef enum
|
||||
} PlayerProp;
|
||||
|
||||
int CheckModules(AMX *amx, char error[128]);
|
||||
bool LoadModule(const char *shortname, PLUG_LOADTIME now);
|
||||
const char *StrCaseStr(const char *as, const char *bs);
|
||||
|
||||
class Debugger;
|
||||
@ -79,7 +80,6 @@ Debugger *DisableDebugHandler(AMX *amx);
|
||||
void EnableDebugHandler(AMX *amx, Debugger *pd);
|
||||
|
||||
bool DirExists(const char *dir);
|
||||
|
||||
const char* GetFileName(AMX *amx);
|
||||
|
||||
#endif // __MODULES_H__
|
||||
|
@ -415,6 +415,9 @@
|
||||
AssemblerOutput="4"/>
|
||||
</FileConfiguration>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\libraries.cpp">
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\md5.cpp">
|
||||
</File>
|
||||
@ -542,6 +545,9 @@
|
||||
<File
|
||||
RelativePath="..\format.h">
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\libraries.h">
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\md5.h">
|
||||
</File>
|
||||
|
@ -32,6 +32,7 @@
|
||||
#include "sh_stack.h"
|
||||
#include "natives.h"
|
||||
#include "debugger.h"
|
||||
#include "libraries.h"
|
||||
|
||||
#ifdef __linux__
|
||||
#include <malloc.h>
|
||||
@ -47,7 +48,6 @@
|
||||
CStack<int> g_ErrorStk;
|
||||
CVector<regnative *> g_RegNatives;
|
||||
CStack<regnative *> g_NativeStack;
|
||||
CVector<String> g_Libraries;
|
||||
static char g_errorStr[512] = {0};
|
||||
bool g_Initialized = false;
|
||||
|
||||
@ -343,7 +343,7 @@ static cell AMX_NATIVE_CALL register_library(AMX *amx, cell *params)
|
||||
int len;
|
||||
char *lib = get_amxstring(amx, params[1], 0, len);
|
||||
|
||||
AddPluginLibrary(lib);
|
||||
AddLibrary(lib, LibType_Library, LibSource_Plugin, g_plugins.findPluginFast(amx));
|
||||
|
||||
return 1;
|
||||
}
|
||||
@ -396,27 +396,9 @@ static cell AMX_NATIVE_CALL register_native(AMX *amx, cell *params)
|
||||
return 1;
|
||||
}
|
||||
|
||||
bool LibraryExists(const char *name)
|
||||
{
|
||||
for (size_t i=0; i<g_Libraries.size(); i++)
|
||||
{
|
||||
if (stricmp(g_Libraries[i].c_str(), name)==0)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void AddPluginLibrary(const char *name)
|
||||
{
|
||||
String f(name);
|
||||
g_Libraries.push_back(f);
|
||||
}
|
||||
|
||||
void ClearPluginLibraries()
|
||||
{
|
||||
g_Libraries.clear();
|
||||
|
||||
ClearLibraries(LibSource_Plugin);
|
||||
for (size_t i=0; i<g_RegNatives.size(); i++)
|
||||
{
|
||||
delete [] g_RegNatives[i]->pfn;
|
||||
|
@ -61,9 +61,7 @@ extern "C" int amxx_DynaFunc(AMX *amx, cell *params);
|
||||
extern "C" int amxx_DynaCodesize();
|
||||
|
||||
AMX_NATIVE_INFO *BuildNativeTable();
|
||||
void AddPluginLibrary(const char *name);
|
||||
void ClearPluginLibraries();
|
||||
bool LibraryExists(const char *name);
|
||||
|
||||
//I couldn't resist :)
|
||||
extern AMX_NATIVE_INFO g_NativeNatives[];
|
||||
|
@ -2430,7 +2430,9 @@ static amxx_module_info_s g_ModuleInfo =
|
||||
#else // MODULE_RELOAD_ON_MAPCHANGE
|
||||
0,
|
||||
#endif // MODULE_RELOAD_ON_MAPCHANGE
|
||||
MODULE_LOGTAG
|
||||
MODULE_LOGTAG,
|
||||
MODULE_LIBRARY,
|
||||
MODULE_LIBCLASS
|
||||
};
|
||||
|
||||
// Storage for the requested functions
|
||||
@ -2506,6 +2508,9 @@ PFN_SET_TEAM_INFO g_fn_SetTeamInfo;
|
||||
PFN_PLAYER_PROP_ADDR g_fn_PlayerPropAddr;
|
||||
PFN_REG_AUTH_FUNC g_fn_RegAuthFunc;
|
||||
PFN_UNREG_AUTH_FUNC g_fn_UnregAuthFunc;
|
||||
PFN_FINDLIBRARY g_fn_FindLibrary;
|
||||
PFN_ADDLIBRARIES g_fn_AddLibraries;
|
||||
PFN_REMOVELIBRARIES g_fn_RemoveLibraries;
|
||||
|
||||
// *** Exports ***
|
||||
C_DLLEXPORT int AMXX_Query(int *interfaceVersion, amxx_module_info_s *moduleInfo)
|
||||
@ -2620,6 +2625,10 @@ C_DLLEXPORT int AMXX_Attach(PFN_REQ_FNPTR reqFnptrFunc)
|
||||
REQFUNC("RegAuthFunc", g_fn_RegAuthFunc, PFN_REG_AUTH_FUNC);
|
||||
REQFUNC("UnregAuthFunc", g_fn_UnregAuthFunc, PFN_UNREG_AUTH_FUNC);
|
||||
|
||||
REQFUNC("FindLibrary", g_fn_FindLibrary, PFN_FINDLIBRARY);
|
||||
REQFUNC("AddLibraries", g_fn_AddLibraries, PFN_ADDLIBRARIES);
|
||||
REQFUNC("RemoveLibraries", g_fn_RemoveLibraries, PFN_REMOVELIBRARIES);
|
||||
|
||||
#ifdef MEMORY_TEST
|
||||
// Memory
|
||||
REQFUNC_OPT("Allocator", g_fn_Allocator, PFN_ALLOCATOR);
|
||||
@ -2654,6 +2663,20 @@ C_DLLEXPORT int AMXX_PluginsLoaded()
|
||||
return AMXX_OK;
|
||||
}
|
||||
|
||||
C_DLLEXPORT void AMXX_PluginsUnloaded()
|
||||
{
|
||||
#ifdef FN_AMXX_PLUGINSUNLOADED
|
||||
FN_AMXX_PLUGINSUNLOADED();
|
||||
#endif // FN_AMXX_PLUGINSUNLOADED
|
||||
}
|
||||
|
||||
C_DLLEXPORT void AMXX_PluginsUnloading()
|
||||
{
|
||||
#ifdef FN_AMXX_PLUGINSUNLOADING
|
||||
FN_AMXX_PLUGINSUNLOADING();
|
||||
#endif // FN_AMXX_PLUGINSUNLOADING
|
||||
}
|
||||
|
||||
// Advanced MF functions
|
||||
void MF_Log(const char *fmt, ...)
|
||||
{
|
||||
@ -2743,6 +2766,9 @@ void ValidateMacros_DontCallThis_Smiley()
|
||||
MF_PlayerPropAddr(0, 0);
|
||||
MF_RegAuthFunc(NULL);
|
||||
MF_UnregAuthFunc(NULL);
|
||||
MF_FindLibrary(NULL, LibType_Class);
|
||||
MF_AddLibraries(NULL, LibType_Class, NULL);
|
||||
MF_RemoveLibraries(NULL);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
@ -34,7 +34,8 @@
|
||||
// module interface version was 1
|
||||
// 2 - added logtag to struct (amxx1.1-rc1)
|
||||
// 3 - added new tagAMX structure (amxx1.5)
|
||||
#define AMXX_INTERFACE_VERSION 3
|
||||
// 4 - added new 'library' setting for direct loading
|
||||
#define AMXX_INTERFACE_VERSION 4
|
||||
|
||||
// amxx module info
|
||||
struct amxx_module_info_s
|
||||
@ -44,6 +45,8 @@ struct amxx_module_info_s
|
||||
const char *version;
|
||||
int reload; // reload on mapchange when nonzero
|
||||
const char *logtag; // added in version 2
|
||||
const char *library; // added in version 4
|
||||
const char *libclass; // added in version 4
|
||||
};
|
||||
|
||||
// return values from functions called by amxx
|
||||
@ -2032,6 +2035,14 @@ void FN_AMXX_DETACH(void);
|
||||
void FN_AMXX_PLUGINSLOADED(void);
|
||||
#endif // FN_AMXX_PLUGINSLOADED
|
||||
|
||||
#ifdef FN_AMXX_PLUGINSUNLOADING
|
||||
void FN_AMXX_PLUGINSUNLOADING(void);
|
||||
#endif // FN_AMXX_PLUGINSUNLOADING
|
||||
|
||||
#ifdef FN_AMXX_PLUGINSUNLOADED
|
||||
void FN_AMXX_PLUGINSUNLOADED(void);
|
||||
#endif // FN_AMXX_PLUGINSUNLOADED
|
||||
|
||||
// *** Types ***
|
||||
typedef void* (*PFN_REQ_FNPTR)(const char * /*name*/);
|
||||
|
||||
@ -2078,6 +2089,12 @@ enum PlayerProp
|
||||
Player_NewmenuPage, //int
|
||||
};
|
||||
|
||||
enum LibType
|
||||
{
|
||||
LibType_Library,
|
||||
LibType_Class
|
||||
};
|
||||
|
||||
typedef void (*AUTHORIZEFUNC)(int player, const char *authstring);
|
||||
|
||||
typedef int (*PFN_ADD_NATIVES) (const AMX_NATIVE_INFO * /*list*/);
|
||||
@ -2159,6 +2176,9 @@ typedef int (*PFN_AMX_PUSH) (AMX * /*amx*/, cell /*value*/);
|
||||
typedef int (*PFN_SET_TEAM_INFO) (int /*player */, int /*teamid */, const char * /*name */);
|
||||
typedef void (*PFN_REG_AUTH_FUNC) (AUTHORIZEFUNC);
|
||||
typedef void (*PFN_UNREG_AUTH_FUNC) (AUTHORIZEFUNC);
|
||||
typedef int (*PFN_FINDLIBRARY) (const char * /*name*/, LibType /*type*/);
|
||||
typedef size_t (*PFN_ADDLIBRARIES) (const char * /*name*/, LibType /*type*/, void * /*parent*/);
|
||||
typedef size_t (*PFN_REMOVELIBRARIES) (void * /*parent*/);
|
||||
|
||||
extern PFN_ADD_NATIVES g_fn_AddNatives;
|
||||
extern PFN_BUILD_PATHNAME g_fn_BuildPathname;
|
||||
@ -2226,6 +2246,9 @@ extern PFN_SET_TEAM_INFO g_fn_SetTeamInfo;
|
||||
extern PFN_PLAYER_PROP_ADDR g_fn_PlayerPropAddr;
|
||||
extern PFN_REG_AUTH_FUNC g_fn_RegAuthFunc;
|
||||
extern PFN_UNREG_AUTH_FUNC g_fn_UnregAuthFunc;
|
||||
extern PFN_FINDLIBRARY g_fn_FindLibrary;
|
||||
extern PFN_ADDLIBRARIES g_fn_AddLibraries;
|
||||
extern PFN_REMOVELIBRARIES g_fn_RemoveLibraries;
|
||||
|
||||
#ifdef MAY_NEVER_BE_DEFINED
|
||||
// Function prototypes for intellisense and similar systems
|
||||
@ -2290,6 +2313,9 @@ int MF_SetPlayerTeamInfo (int id, int teamid, const char *teamname) { }
|
||||
void * MF_PlayerPropAddr (int id, int prop) { }
|
||||
void MF_RegAuthFunc (AUTHORIZEFUNC fn) { }
|
||||
void MF_UnregAuthFunc (AUTHORIZEFUNC fn) { }
|
||||
int MF_FindLibrary (const char *name, LibType type) { }
|
||||
size_t MF_AddLibraries (const char *name, LibType type, void *parent) { }
|
||||
size_t MF_RemoveLibraries (void *parent) { }
|
||||
#endif // MAY_NEVER_BE_DEFINED
|
||||
|
||||
#define MF_AddNatives g_fn_AddNatives
|
||||
@ -2359,6 +2385,9 @@ void MF_LogError(AMX *amx, int err, const char *fmt, ...);
|
||||
#define MF_PlayerPropAddr g_fn_PlayerPropAddr
|
||||
#define MF_RegAuthFunc g_fn_RegAuthFunc
|
||||
#define MF_UnregAuthFunc g_fn_UnregAuthFunc
|
||||
#define MF_FindLibrary g_fn_FindLibrary;
|
||||
#define MF_AddLibraries g_fn_AddLibraries;
|
||||
#define MF_RemoveLibraries g_fn_RemoveLibraries;
|
||||
|
||||
#ifdef MEMORY_TEST
|
||||
/*** Memory ***/
|
||||
|
@ -3,12 +3,24 @@
|
||||
#ifndef __MODULECONFIG_H__
|
||||
#define __MODULECONFIG_H__
|
||||
|
||||
// Module info
|
||||
/** Module info
|
||||
* -The logtag is the tag that the module's log messages will be
|
||||
* prepended with.
|
||||
* -The library is the name that the #pragma library
|
||||
* message will have prepended.
|
||||
* -The library class is the class of libraries that
|
||||
* a module belongs to (like DBI). Keep it "" to
|
||||
* ignore.
|
||||
* -For both library and library class, you can use a comma
|
||||
* to add multiple entries.
|
||||
*/
|
||||
#define MODULE_NAME "--ENTER NAME HERE--"
|
||||
#define MODULE_VERSION "--ENTER VERSION HERE--"
|
||||
#define MODULE_AUTHOR "--ENTER AUTHOR HERE--"
|
||||
#define MODULE_URL "--ENTER URL HERE--"
|
||||
#define MODULE_LOGTAG "--ENTER LOGTAG HERE--"
|
||||
#define MODULE_LIBRARY "--ENTER LIBRARY HERE--"
|
||||
#define MODULE_LIBCLASS ""
|
||||
// If you want the module not to be reloaded on mapchange, remove / comment out the next line
|
||||
#define MODULE_RELOAD_ON_MAPCHANGE
|
||||
|
||||
@ -34,18 +46,32 @@
|
||||
// Uncomment this if you are using MSVC8 or greater and want to fix some of the compatibility issues yourself
|
||||
// #define NO_MSVC8_AUTO_COMPAT
|
||||
|
||||
// - AMXX Init functions
|
||||
// Also consider using FN_META_*
|
||||
// AMXX query
|
||||
/**
|
||||
* AMXX Init functions
|
||||
* Also consider using FN_META_*
|
||||
*/
|
||||
|
||||
/** AMXX query */
|
||||
//#define FN_AMXX_QUERY OnAmxxQuery
|
||||
// AMXX attach
|
||||
// Do native functions init here (MF_AddNatives)
|
||||
|
||||
/** AMXX attach
|
||||
* Do native functions init here (MF_AddNatives)
|
||||
*/
|
||||
//#define FN_AMXX_ATTACH OnAmxxAttach
|
||||
// AMXX detach
|
||||
|
||||
/** AMXX Detach (unload) */
|
||||
//#define FN_AMXX_DETACH OnAmxxDetach
|
||||
// All plugins loaded
|
||||
// Do forward functions init here (MF_RegisterForward)
|
||||
// #define FN_AMXX_PLUGINSLOADED OnPluginsLoaded
|
||||
|
||||
/** All plugins loaded
|
||||
* Do forward functions init here (MF_RegisterForward)
|
||||
*/
|
||||
//#define FN_AMXX_PLUGINSLOADED OnPluginsLoaded
|
||||
|
||||
/** All plugins are about to be unloaded */
|
||||
//#define FN_AMXX_PLUGINSUNLOADING OnPluginsUnloading
|
||||
|
||||
/** All plguins are now unloaded */
|
||||
//#define FN_AMXX_PLUGINSUNLOADED OnPluginsUnloaded
|
||||
|
||||
/**** METAMOD ****/
|
||||
// If your module doesn't use metamod, you may close the file now :)
|
||||
|
Loading…
Reference in New Issue
Block a user