Added the ability for extensions to invoke arbitrary natives (bug 3605, r=ds,theY4Kman).

This commit is contained in:
David Anderson 2009-02-01 02:03:03 -05:00
parent c5ec369388
commit a017e4820a
17 changed files with 669 additions and 4 deletions

View File

@ -33,7 +33,8 @@ OBJECTS += ExtensionSys.cpp \
LibrarySys.cpp \
PluginInfoDatabase.cpp \
PluginSys.cpp \
ShareSys.cpp
ShareSys.cpp \
NativeInvoker.cpp
OBJECTS += thread/ThreadWorker.cpp thread/BaseWorker.cpp thread/PosixThreads.cpp ThreadSupport.cpp
##############################################

379
core/NativeInvoker.cpp Normal file
View File

@ -0,0 +1,379 @@
/**
* vim: set ts=4 :
* =============================================================================
* SourcePawn
* Copyright (C) 2004-2009 AlliedModders LLC. All rights reserved.
* =============================================================================
*
* This program is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, version 3.0, as published by the
* Free Software Foundation.
*
* 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, see <http://www.gnu.org/licenses/>.
*
* As a special exception, AlliedModders LLC gives you permission to link the
* code of this program (as well as its derivative works) to "Half-Life 2," the
* "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software
* by the Valve Corporation. You must obey the GNU General Public License in
* all respects for all other code used. Additionally, AlliedModders LLC grants
* this exception to all derivative works. AlliedModders LLC defines further
* exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007),
* or <http://www.sourcemod.net/license.php>.
*
* Version: $Id$
*/
#include "NativeInvoker.h"
#include "ShareSys.h"
NativeInterface g_NInvoke;
NativeInvoker::NativeInvoker()
{
}
NativeInvoker::~NativeInvoker()
{
}
const char *NativeInterface::GetInterfaceName()
{
return SMINTERFACE_NINVOKE_NAME;
}
unsigned int NativeInterface::GetInterfaceVersion()
{
return SMINTERFACE_NINVOKE_VERSION;
}
void NativeInterface::OnSourceModAllInitialized()
{
g_ShareSys.AddInterface(NULL, &g_NInvoke);
}
IPluginRuntime *NativeInterface::CreateRuntime(const char *name, size_t bytes)
{
return g_pSourcePawn2->CreateEmptyRuntime(name, bytes);
}
INativeInvoker *NativeInterface::CreateInvoker()
{
return new NativeInvoker();
}
bool NativeInvoker::Start(IPluginContext *pContext, const char *name)
{
NativeEntry *entry;
entry = g_ShareSys.FindNative(name);
if (entry == NULL)
{
return false;
}
native = NULL;
if (entry->replacement.owner != NULL)
{
native = entry->replacement.func;
}
else if (entry->owner != NULL)
{
native = entry->func;
}
if (native == NULL)
{
return false;
}
this->pContext = pContext;
m_curparam = 0;
m_errorstate = SP_ERROR_NONE;
return true;
}
cell_t NativeInvoker::PushCell(cell_t cell)
{
if (m_curparam >= SP_MAX_EXEC_PARAMS)
{
return SetError(SP_ERROR_PARAMS_MAX);
}
m_info[m_curparam].marked = false;
m_params[m_curparam] = cell;
m_curparam++;
return SP_ERROR_NONE;
}
int NativeInvoker::PushCellByRef(cell_t *cell, int flags)
{
return PushArray(cell, 1, flags);
}
int NativeInvoker::PushFloat(float number)
{
cell_t val = *(cell_t *)&number;
return PushCell(val);
}
int NativeInvoker::PushFloatByRef(float *number, int flags)
{
return PushCellByRef((cell_t *)number, flags);
}
int NativeInvoker::PushArray(cell_t *inarray, unsigned int cells, int copyback)
{
if (m_curparam >= SP_MAX_EXEC_PARAMS)
{
return SetError(SP_ERROR_PARAMS_MAX);
}
ParamInfo *info = &m_info[m_curparam];
info->flags = inarray ? copyback : 0;
info->marked = true;
info->size = cells;
info->str.is_sz = false;
info->orig_addr = inarray;
m_curparam++;
return SP_ERROR_NONE;
}
int NativeInvoker::PushString(const char *string)
{
return _PushString(string, SM_PARAM_STRING_COPY, 0, strlen(string)+1);
}
int NativeInvoker::PushStringEx(char *buffer, size_t length, int sz_flags, int cp_flags)
{
return _PushString(buffer, sz_flags, cp_flags, length);
}
int NativeInvoker::_PushString(const char *string, int sz_flags, int cp_flags, size_t len)
{
if (m_curparam >= SP_MAX_EXEC_PARAMS)
{
return SetError(SP_ERROR_PARAMS_MAX);
}
ParamInfo *info = &m_info[m_curparam];
info->marked = true;
info->orig_addr = (cell_t *)string;
info->flags = cp_flags;
info->size = len;
info->str.sz_flags = sz_flags;
info->str.is_sz = true;
m_curparam++;
return SP_ERROR_NONE;
}
void NativeInvoker::Cancel()
{
if (pContext == NULL)
{
return;
}
m_errorstate = SP_ERROR_NONE;
m_curparam = 0;
pContext = NULL;
native = NULL;
}
int NativeInvoker::SetError(int err)
{
m_errorstate = err;
return err;
}
int NativeInvoker::Invoke(cell_t *result)
{
int err = SP_ERROR_NONE;
if (pContext == NULL)
{
return SP_ERROR_INVALID_NATIVE;
}
if (m_errorstate != SP_ERROR_NONE)
{
err = m_errorstate;
Cancel();
return err;
}
cell_t tresult;
if (result == NULL)
{
result = &tresult;
}
//This is for re-entrancy!
IPluginContext *ctx = pContext;
cell_t _temp_params[SP_MAX_EXEC_PARAMS + 1];
cell_t *temp_params = &_temp_params[1];
ParamInfo temp_info[SP_MAX_EXEC_PARAMS];
unsigned int numparams = m_curparam;
unsigned int i;
bool docopies = true;
if (numparams)
{
//Save the info locally, then reset it for re-entrant calls.
memcpy(temp_info, m_info, numparams * sizeof(ParamInfo));
}
m_curparam = 0;
pContext = NULL;
/* Initialize 0th parameter */
_temp_params[0] = numparams;
/* Browse the parameters and build arrays */
for (i = 0; i < numparams; i++)
{
/* Is this marked as an array? */
if (temp_info[i].marked)
{
if (!temp_info[i].str.is_sz)
{
/* Allocate a normal/generic array */
if ((err = ctx->HeapAlloc(temp_info[i].size,
&temp_info[i].local_addr,
&temp_info[i].phys_addr))
!= SP_ERROR_NONE)
{
break;
}
if (temp_info[i].orig_addr)
{
memcpy(temp_info[i].phys_addr, temp_info[i].orig_addr, sizeof(cell_t) * temp_info[i].size);
}
}
else
{
/* Calculate cells required for the string */
size_t cells = (temp_info[i].size + sizeof(cell_t) - 1) / sizeof(cell_t);
/* Allocate the buffer */
if ((err = ctx->HeapAlloc(cells,
&temp_info[i].local_addr,
&temp_info[i].phys_addr))
!= SP_ERROR_NONE)
{
break;
}
/* Copy original string if necessary */
if ((temp_info[i].str.sz_flags & SM_PARAM_STRING_COPY) && (temp_info[i].orig_addr != NULL))
{
/* Cut off UTF-8 properly */
if (temp_info[i].str.sz_flags & SM_PARAM_STRING_UTF8)
{
if ((err = ctx->StringToLocalUTF8(temp_info[i].local_addr,
temp_info[i].size,
(const char *)temp_info[i].orig_addr,
NULL))
!= SP_ERROR_NONE)
{
break;
}
}
/* Copy a binary blob */
else if (temp_info[i].str.sz_flags & SM_PARAM_STRING_BINARY)
{
memmove(temp_info[i].phys_addr, temp_info[i].orig_addr, temp_info[i].size);
}
/* Copy ASCII characters */
else
{
if ((err = ctx->StringToLocal(temp_info[i].local_addr,
temp_info[i].size,
(const char *)temp_info[i].orig_addr))
!= SP_ERROR_NONE)
{
break;
}
}
}
} /* End array/string calculation */
/* Update the pushed parameter with the byref local address */
temp_params[i] = temp_info[i].local_addr;
}
else
{
/* Just copy the value normally */
temp_params[i] = m_params[i];
}
}
/* Make the call if we can */
if (err == SP_ERROR_NONE)
{
*result = native(ctx, _temp_params);
if (ctx->GetLastNativeError() != SP_ERROR_NONE)
{
docopies = false;
ctx->ClearLastNativeError();
}
}
else
{
docopies = false;
}
/* i should be equal to the last valid parameter + 1 */
while (i--)
{
if (!temp_info[i].marked)
{
continue;
}
if (docopies && (temp_info[i].flags & SM_PARAM_COPYBACK))
{
if (temp_info[i].orig_addr)
{
if (temp_info[i].str.is_sz)
{
memcpy(temp_info[i].orig_addr, temp_info[i].phys_addr, temp_info[i].size);
}
else
{
if (temp_info[i].size == 1)
{
*temp_info[i].orig_addr = *(temp_info[i].phys_addr);
}
else
{
memcpy(temp_info[i].orig_addr,
temp_info[i].phys_addr,
temp_info[i].size * sizeof(cell_t));
}
}
}
}
if ((err = ctx->HeapPop(temp_info[i].local_addr)) != SP_ERROR_NONE)
{
return err;
}
}
return err;
}

98
core/NativeInvoker.h Normal file
View File

@ -0,0 +1,98 @@
/**
* vim: set ts=4 :
* =============================================================================
* SourceMod
* Copyright (C) 2004-2009 AlliedModders LLC. All rights reserved.
* =============================================================================
*
* This program is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, version 3.0, as published by the
* Free Software Foundation.
*
* 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, see <http://www.gnu.org/licenses/>.
*
* As a special exception, AlliedModders LLC gives you permission to link the
* code of this program (as well as its derivative works) to "Half-Life 2," the
* "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software
* by the Valve Corporation. You must obey the GNU General Public License in
* all respects for all other code used. Additionally, AlliedModders LLC grants
* this exception to all derivative works. AlliedModders LLC defines further
* exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007),
* or <http://www.sourcemod.net/license.php>.
*
* Version: $Id$
*/
#ifndef _INCLUDE_SOURCEMOD_NATIVE_INVOKER_H_
#define _INCLUDE_SOURCEMOD_NATIVE_INVOKER_H_
#include "sm_globals.h"
#include <INativeInvoker.h>
struct ParamInfo
{
int flags; /* Copy-back flags */
bool marked; /* Whether this is marked as being used */
cell_t local_addr; /* Local address to free */
cell_t *phys_addr; /* Physical address of our copy */
cell_t *orig_addr; /* Original address to copy back to */
ucell_t size; /* Size of array in bytes */
struct
{
bool is_sz; /* is a string */
int sz_flags; /* has sz flags */
} str;
};
class NativeInvoker : public INativeInvoker
{
public:
NativeInvoker();
~NativeInvoker();
public: /* ICallable */
int PushCell(cell_t cell);
int PushCellByRef(cell_t *cell, int flags=SM_PARAM_COPYBACK);
int PushFloat(float number);
int PushFloatByRef(float *number, int flags=SM_PARAM_COPYBACK);
int PushArray(cell_t *inarray, unsigned int cells, int flags=0);
int PushString(const char *string);
int PushStringEx(char *buffer, size_t length, int sz_flags, int cp_flags);
void Cancel();
public: /* INativeInvoker */
bool Start(IPluginContext *pContext, const char *name);
int Invoke(cell_t *result);
private:
int _PushString(const char *string, int sz_flags, int cp_flags, size_t len);
int SetError(int err);
private:
IPluginContext *pContext;
SPVM_NATIVE_FUNC native;
cell_t m_params[SP_MAX_EXEC_PARAMS];
ParamInfo m_info[SP_MAX_EXEC_PARAMS];
unsigned int m_curparam;
int m_errorstate;
};
class NativeInterface :
public INativeInterface,
public SMGlobalClass
{
public: /* SMGlobalClass */
void OnSourceModAllInitialized();
public: /* SMInterface */
unsigned int GetInterfaceVersion();
const char *GetInterfaceName();
public: /* INativeInvoker */
IPluginRuntime *CreateRuntime(const char *name, size_t bytes);
INativeInvoker *CreateInvoker();
};
extern NativeInterface g_NInvoke;
#endif /* _INCLUDE_SOURCEMOD_NATIVE_INVOKER_H_ */

View File

@ -128,10 +128,10 @@ public:
void BindNativesToPlugin(CPlugin *pPlugin, bool bCoreOnly);
void BindNativeToPlugin(CPlugin *pPlugin, NativeEntry *pEntry);
NativeEntry *AddFakeNative(IPluginFunction *pFunc, const char *name, SPVM_FAKENATIVE_FUNC func);
NativeEntry *FindNative(const char *name);
private:
NativeEntry *AddNativeToCache(CNativeOwner *pOwner, const sp_nativeinfo_t *ntv);
void ClearNativeFromCache(CNativeOwner *pOwner, const char *name);
NativeEntry *FindNative(const char *name);
void BindNativeToPlugin(CPlugin *pPlugin,
sp_native_t *ntv,
uint32_t index,

View File

@ -1185,6 +1185,10 @@
RelativePath="..\MenuVoting.cpp"
>
</File>
<File
RelativePath="..\NativeInvoker.cpp"
>
</File>
<File
RelativePath="..\NativeOwner.cpp"
>
@ -1391,6 +1395,10 @@
RelativePath="..\MenuVoting.h"
>
</File>
<File
RelativePath="..\NativeInvoker.h"
>
</File>
<File
RelativePath="..\NativeOwner.h"
>
@ -1556,6 +1564,10 @@
RelativePath="..\..\public\IMenuManager.h"
>
</File>
<File
RelativePath="..\..\public\INativeInvoker.h"
>
</File>
<File
RelativePath="..\..\public\IPlayerHelpers.h"
>

View File

@ -200,7 +200,7 @@ bool SourceModBase::InitializeSourceMod(char *error, size_t maxlength, bool late
g_pSourcePawn = getv1();
g_pSourcePawn2 = getv2();
if (g_pSourcePawn2->GetAPIVersion() < 2)
if (g_pSourcePawn2->GetAPIVersion() < 3)
{
g_pSourcePawn2 = NULL;
if (error && maxlength)

106
public/INativeInvoker.h Normal file
View File

@ -0,0 +1,106 @@
/**
* vim: set ts=4 :
* =============================================================================
* SourceMod
* Copyright (C) 2004-2009 AlliedModders LLC. All rights reserved.
* =============================================================================
*
* This program is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, version 3.0, as published by the
* Free Software Foundation.
*
* 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, see <http://www.gnu.org/licenses/>.
*
* As a special exception, AlliedModders LLC gives you permission to link the
* code of this program (as well as its derivative works) to "Half-Life 2," the
* "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software
* by the Valve Corporation. You must obey the GNU General Public License in
* all respects for all other code used. Additionally, AlliedModders LLC grants
* this exception to all derivative works. AlliedModders LLC defines further
* exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007),
* or <http://www.sourcemod.net/license.php>.
*
* Version: $Id$
*/
#ifndef _INCLUDE_SOURCEMOD_INATIVEINVOKER_H_
#define _INCLUDE_SOURCEMOD_INATIVEINVOKER_H_
/**
* @file INativeInvoker.h
* @brief Interface for invoking natives.
*/
#include <IShareSys.h>
#define SMINTERFACE_NINVOKE_NAME "INativeInterface"
#define SMINTERFACE_NINVOKE_VERSION 1
#define NINVOKE_DEFAULT_MEMORY 16384
namespace SourceMod
{
class INativeInvoker : public SourcePawn::ICallable
{
public:
/**
* @brief Virtual destructor - use delete to free this.
*/
virtual ~INativeInvoker()
{
}
public:
/**
* @brief Begins a native call.
*
* During a call's preparation, no new calls may be started.
*
* @param pContext Context to invoke native under.
* @param name Name of native.
* @return True if native was found, false otherwise.
*/
virtual bool Start(IPluginContext *pContext, const char *name) = 0;
/**
* @brief Invokes the native. The preparation state is cleared immediately, meaning that
* this object can be re-used after or even from inside the native being called.
*
* @param result Optional pointer to retrieve a result.
* @return SP_ERROR return code.
*/
virtual int Invoke(cell_t *result) = 0;
};
/**
* @brief Factory for dealing with native invocations.
*/
class INativeInterface : public SMInterface
{
public:
/**
* @brief Creates a virtual plugin. This can be used as an environment to invoke natives.
*
* IPluginRuntime objects must be freed with the delete operator.
*
* @param name Name, or NULL for anonymous.
* @param bytes Number of bytes for memory (NINVOKE_DEFAULT_MEMORY recommended).
* @return New runtime, or NULL on failure.
*/
virtual IPluginRuntime *CreateRuntime(const char *name, size_t bytes) = 0;
/**
* @brief Creates an object that can be used to invoke a single native code.
*
* @return New native invoker (free with delete).
*/
virtual INativeInvoker *CreateInvoker() = 0;
};
}
#endif /* _INCLUDE_SOURCEMOD_INATIVEINVOKER_H_ */

View File

@ -76,5 +76,6 @@
//#define SMEXT_ENABLE_TEXTPARSERS
//#define SMEXT_ENABLE_USERMSGS
//#define SMEXT_ENABLE_TRANSLATOR
//#define SMEXT_ENABLE_NINVOKE
#endif // _INCLUDE_SOURCEMOD_EXTENSION_CONFIG_H_

View File

@ -97,6 +97,9 @@ IUserMessages *usermsgs = NULL;
#if defined SMEXT_ENABLE_TRANSLATOR
ITranslator *translator = NULL;
#endif
#if defined SMEXT_ENABLE_NINVOKE
INativeInterface *ninvoke = NULL;
#endif
/** Exports the main interface */
PLATFORM_EXTERN_C IExtensionInterface *GetSMExtAPI()

View File

@ -91,6 +91,9 @@
#if defined SMEXT_ENABLE_TRANSLATOR
#include <ITranslator.h>
#endif
#if defined SMEXT_ENABLE_NINVOKE
#include <INativeInvoker.h>
#endif
#if defined SMEXT_CONF_METAMOD
#include <ISmmPlugin.h>
@ -289,6 +292,9 @@ extern IUserMessages *usermsgs;
#if defined SMEXT_ENABLE_TRANSLATOR
extern ITranslator *translator;
#endif
#if defined SMEXT_ENABLE_NINVOKE
extern INativeInterface *ninvoke;
#endif
#if defined SMEXT_CONF_METAMOD
PLUGIN_GLOBALVARS();

View File

@ -42,7 +42,7 @@
/** SourcePawn Engine API Version */
#define SOURCEPAWN_ENGINE_API_VERSION 4
#define SOURCEPAWN_ENGINE2_API_VERSION 2
#define SOURCEPAWN_ENGINE2_API_VERSION 3
#if !defined SOURCEMOD_BUILD
#define SOURCEMOD_BUILD
@ -876,6 +876,11 @@ namespace SourcePawn
* @return True on success, false on failure.
*/
virtual bool GetKey(int k, void **value) =0;
/**
* @brief Clears the last native error.
*/
virtual void ClearLastNativeError() =0;
};
@ -1252,6 +1257,15 @@ namespace SourcePawn
* Initialize() succeeded.
*/
virtual void Shutdown() =0;
/**
* @brief Creates an empty plugin with a blob of memory.
*
* @param name Name, for debugging (NULL for anonymous).
* @param bytes Number of bytes of memory (hea+stk).
* @return New runtime, or NULL if not enough memory.
*/
virtual IPluginRuntime *CreateEmptyRuntime(const char *name, uint32_t memory) =0;
};
};

View File

@ -535,3 +535,21 @@ uint32_t BaseRuntime::AddJittedFunction(JitFunction *fn)
return m_NumFuncs;
}
int BaseRuntime::CreateBlank(uint32_t heastk)
{
memset(m_pPlugin, 0, sizeof(sp_plugin_t));
/* Align to cell_t bytes */
heastk += sizeof(cell_t);
heastk -= heastk % sizeof(cell_t);
m_pPlugin->mem_size = heastk;
m_pPlugin->memory = new uint8_t[heastk];
m_pPlugin->profiler = g_engine2.GetProfiler();
m_pCtx = new BaseContext(this);
m_pCo = g_Jit.StartCompilation(this);
return SP_ERROR_NONE;
}

View File

@ -27,6 +27,7 @@ public:
BaseRuntime();
~BaseRuntime();
public:
virtual int CreateBlank(uint32_t heastk);
virtual int CreateFromMemory(sp_file_hdr_t *hdr, uint8_t *base);
virtual bool IsDebugging();
virtual IPluginDebugInfo *GetDebugInfo();

View File

@ -191,3 +191,22 @@ void SourcePawnEngine2::Shutdown()
{
g_Jit.ShutdownJIT();
}
IPluginRuntime *SourcePawnEngine2::CreateEmptyRuntime(const char *name, uint32_t memory)
{
int err;
BaseRuntime *rt;
rt = new BaseRuntime();
if ((err = rt->CreateBlank(memory)) != SP_ERROR_NONE)
{
delete rt;
return NULL;
}
rt->m_pPlugin->name = strdup(name != NULL ? name : "<anonymous>");
rt->ApplyCompilationOptions(NULL);
return rt;
}

View File

@ -25,6 +25,7 @@ namespace SourcePawn
const char *GetErrorString(int err);
bool Initialize();
void Shutdown();
IPluginRuntime *CreateEmptyRuntime(const char *name, uint32_t memory);
public:
IProfiler *GetProfiler();
private:

View File

@ -873,3 +873,8 @@ bool BaseContext::GetKey(int k, void **value)
return true;
}
void BaseContext::ClearLastNativeError()
{
m_ctx.n_err = SP_ERROR_NONE;
}

View File

@ -93,6 +93,7 @@ public: //IPluginContext
void SetKey(int k, void *value);
bool GetKey(int k, void **value);
void Refresh();
void ClearLastNativeError();
public:
bool IsInExec();
private: