//----------------------------------------------------------------------------- // Copyright (c) 2012 GarageGames, LLC // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to // deal in the Software without restriction, including without limitation the // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or // sell copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS // IN THE SOFTWARE. //----------------------------------------------------------------------------- #include #include #include "npWebGamePlugin.h" #include "../common/webCommon.h" // Functions exported from the browser to our plugin static NPNetscapeFuncs NPNFuncs; // Set once both the web page and plugin are fully loaded (including any possible Javascript <-> TorqueScript exports/imports) static BOOL gInitialized = false; #ifndef WIN32 // Entry points into our plugin for Safari extern "C" { #pragma GCC visibility push(default) NPError NP_Initialize (NPNetscapeFuncs *browser); NPError NP_GetEntryPoints (NPPluginFuncs *plugin); NPError NP_Shutdown(); #pragma GCC visibility pop } #endif // Converts a NPVARIANT variable to a C string representation const char* MY_NPVARIANT_TO_STRING(const NPVariant& f) { static std::string result; char r[1024] = {0}; if (NPVARIANT_IS_VOID(f) || NPVARIANT_IS_NULL(f)) return ""; if (NPVARIANT_IS_STRING(f)) { NPString str = NPVARIANT_TO_STRING(f); result = std::string(str.UTF8Characters, str.UTF8Length); return result.c_str(); } if (NPVARIANT_IS_BOOLEAN(f)) { if (NPVARIANT_TO_BOOLEAN(f)) return "1"; return "0"; } if (NPVARIANT_IS_INT32(f)) { sprintf(r, "%i", NPVARIANT_TO_INT32(f)); result = r; return result.c_str(); } if (NPVARIANT_IS_DOUBLE(f)) { sprintf(r, "%f", NPVARIANT_TO_DOUBLE(f)); result = r; return result.c_str(); } return ""; } // Javascript -> TorqueScript function calling (with parser) const char* CallScript(const char* code) { std::string scode = code; const char* sig = scode.c_str(); // do not allow large strings which could be used maliciously if (scode.length() > 255 || !gInitialized) { return ""; } // data buffers for laying out data in a Torque 3D console friendly manner char nameSpace[256]; char fname[256]; char argv[256][256]; char* argvv[256]; int argc = 0; int argBegin = 0; memset(nameSpace, 0, 256); memset(fname, 0, 256); memset(argv, 0, 256 * 256); for (int i = 0; i < scode.length(); i++) { if (sig[i] == ')' || sig[i] == ';') { //scan out last arg is any char dummy[256]; memset(dummy, 0, 256); WebCommon::StringCopy(dummy, &sig[argBegin], i - argBegin); if (strlen(dummy)) { strcpy(argv[argc], dummy); argvv[argc] = argv[argc]; argc++; } break; // done } // handle namespace if (sig[i]==':') { if (nameSpace[0] || fname[0]) { return ""; } if (i > 0 && sig[i-1] == ':') { if (i - 2 > 0) WebCommon::StringCopy(nameSpace, sig, i - 1); } continue; } // arguments begin if (sig[i] == '(' ) { if (fname[0] || i < 1) { return ""; } //everything before this is function name, minus nameSpace if (nameSpace[0]) { int nlen = strlen(nameSpace); WebCommon::StringCopy(fname, &sig[nlen + 2], i - nlen - 2); } else { WebCommon::StringCopy(fname, sig, i); } WebCommon::StringCopy(argv[0], fname, strlen(fname)+1); argvv[0] = argv[0]; argc++; argBegin = i + 1; } // argument if (sig[i] == ',' ) { if (argBegin >= i || argc == 255) { return ""; } WebCommon::StringCopy(argv[argc], &sig[argBegin], i - argBegin); argvv[argc] = argv[argc]; argc++; argBegin = i + 1; } } static std::string retVal; if (fname[0]) { // call into the Torque 3D shared library (console system) and get return value retVal = torque_callsecurefunction(nameSpace, fname, argc, (const char **) argvv); return retVal.c_str(); } return ""; } // TorqueScript -> JavaScript const char* CallJavaScriptFunction(const char* name, int numArguments, const char* argv[]) { // our plugin instance NPP pNPInstance = NPWebGamePlugin::sInstance->mInstance; // holds the generated Javascript encoded as a NPString NPString npScript; // retrieve our plugin object from the browser NPObject* pluginObject; if (NPERR_NO_ERROR != NPNFuncs.getvalue(pNPInstance, NPNVPluginElementNPObject, &pluginObject)) { return NULL; } // generate Javascript to be evaluated std::string script = name; script += "("; for (int i = 1; i < numArguments; i++) { script += "\""; script += argv[i]; script += "\""; if ( i + 1 < numArguments) script += ", "; } script += ");"; //encode as a NPString npScript.UTF8Characters = script.c_str(); npScript.UTF8Length = script.length(); // finally, have browser evaluate our script and get the return value NPVariant result; NPNFuncs.evaluate(pNPInstance,pluginObject,&npScript,&result); return MY_NPVARIANT_TO_STRING(result); } // the sole entry point for Torque 3D console system into our browser plugin (handed over as a function pointer) static const char * MyStringCallback(void *obj, int argc, const char* argv[]) { static char ret[4096]; strcpy(ret,CallJavaScriptFunction(argv[0], argc, argv)); return ret; } // these can be added on the page before we're initialized, so we cache until we're ready for them typedef struct { std::string jsCallback; //javascript function name unsigned int numArguments; //the number of arguments it takes } JavasScriptExport; static std::vector gJavaScriptExports; // this actually exports the function to the Torque 3D console system // we do this in two steps as we can't guarantee that Torque 3D is initialized on web page // before JavaScript calls are made void ExportFunctionInternal(const JavasScriptExport& jsexport) { torque_exportstringcallback(MyStringCallback,"JS",jsexport.jsCallback.c_str(),"",jsexport.numArguments,jsexport.numArguments); } // invoked via the Javascript plugin object startup() method once the page/plugin are fully loaded void Startup() { if (gInitialized) return; // actually do the export on any cached functions gInitialized = true; std::vector::iterator i; for (i = gJavaScriptExports.begin(); i != gJavaScriptExports.end();i++) { ExportFunctionInternal(*i); } // setup the secure TorqueScript function calls we can call from Javascript (see webConfig.h) WebCommon::AddSecureFunctions(); } // Export a Javascript function to the Torque 3D console system, possibly caching it void ExportFunction(const char* callback, int numArguments) { JavasScriptExport jsexport; jsexport.jsCallback = callback; jsexport.numArguments = numArguments; if (!gInitialized) { //queue it up gJavaScriptExports.push_back(jsexport); } else { ExportFunctionInternal(jsexport); } } // NP Plugin Interface // Our plugin object structure "inherited" from NPObject typedef struct { // NPObject fields NPClass *_class; uint32_t referenceCount; // Here begins our custom fields (well, field) // Platform specific game plugin class (handles refresh, sizing, initialization of Torque 3D, etc) NPWebGamePlugin* webPlugin; } PluginObject; static PluginObject* gPluginObject = NULL; // interface exports for our plugin that are expected to the browser void pluginInvalidate (); bool pluginHasProperty (NPClass *theClass, NPIdentifier name); bool pluginHasMethod (NPObject *npobj, NPIdentifier name); bool pluginGetProperty (PluginObject *obj, NPIdentifier name, NPVariant *variant); bool pluginSetProperty (PluginObject *obj, NPIdentifier name, const NPVariant *variant); bool pluginInvoke (PluginObject *obj, NPIdentifier name, NPVariant *args, uint32_t argCount, NPVariant *result); bool pluginInvokeDefault (PluginObject *obj, NPVariant *args, uint32_t argCount, NPVariant *result); NPObject *pluginAllocate (NPP npp, NPClass *theClass); void pluginDeallocate (PluginObject *obj); static NPClass _pluginFunctionPtrs = { NP_CLASS_STRUCT_VERSION, (NPAllocateFunctionPtr) pluginAllocate, (NPDeallocateFunctionPtr) pluginDeallocate, (NPInvalidateFunctionPtr) pluginInvalidate, (NPHasMethodFunctionPtr) pluginHasMethod, (NPInvokeFunctionPtr) pluginInvoke, (NPInvokeDefaultFunctionPtr) pluginInvokeDefault, (NPHasPropertyFunctionPtr) pluginHasProperty, (NPGetPropertyFunctionPtr) pluginGetProperty, (NPSetPropertyFunctionPtr) pluginSetProperty, }; // versioning information static bool identifiersInitialized = false; #define ID_VERSION_PROPERTY 0 #define NUM_PROPERTY_IDENTIFIERS 1 static NPIdentifier pluginPropertyIdentifiers[NUM_PROPERTY_IDENTIFIERS]; static const NPUTF8 *pluginPropertyIdentifierNames[NUM_PROPERTY_IDENTIFIERS] = { "version", }; // methods that are callable on the plugin from Javascript #define ID_SETVARIABLE_METHOD 0 #define ID_GETVARIABLE_METHOD 1 #define ID_EXPORTFUNCTION_METHOD 2 #define ID_CALLSCRIPT_METHOD 3 #define ID_STARTUP_METHOD 4 #define NUM_METHOD_IDENTIFIERS 5 static NPIdentifier pluginMethodIdentifiers[NUM_METHOD_IDENTIFIERS]; static const NPUTF8 *pluginMethodIdentifierNames[NUM_METHOD_IDENTIFIERS] = { "setVariable", "getVariable", "exportFunction", "callScript", "startup", }; NPClass *getPluginClass(void) { return &_pluginFunctionPtrs; } // Sets up the property and method identifier arrays used by the browser // via the hasProperty and hasMethod fuction pointers static void initializeIdentifiers() { // fill the property identifier array NPNFuncs.getstringidentifiers(pluginPropertyIdentifierNames, NUM_PROPERTY_IDENTIFIERS, pluginPropertyIdentifiers); // fill the method identifier array NPNFuncs.getstringidentifiers(pluginMethodIdentifierNames, NUM_METHOD_IDENTIFIERS, pluginMethodIdentifiers); }; bool pluginHasProperty (NPClass *theClass, NPIdentifier name) { for (int i = 0; i < NUM_PROPERTY_IDENTIFIERS; i++) { if (name == pluginPropertyIdentifiers[i]) { return true; } } return false; } bool pluginHasMethod(NPObject *npobj, NPIdentifier name) { for (int i = 0; i < NUM_METHOD_IDENTIFIERS; i++) { if (name == pluginMethodIdentifiers[i]) { return true; } } return false; } // utility function that sets up a NPVariant from a std::string void FillString(const std::string& src, NPVariant* variant) { variant->type = NPVariantType_String; variant->value.stringValue.UTF8Length = static_cast(src.length()); variant->value.stringValue.UTF8Characters = reinterpret_cast(NPNFuncs.memalloc(src.size())); memcpy((void*)variant->value.stringValue.UTF8Characters, src.c_str(), src.size()); } bool pluginGetProperty (PluginObject *obj, NPIdentifier name, NPVariant *variant) { VOID_TO_NPVARIANT(*variant); if (name == pluginPropertyIdentifiers[ID_VERSION_PROPERTY]) { FillString(std::string("1.0"), variant); return true; } //unknown property return false; } bool pluginSetProperty (PluginObject *obj, NPIdentifier name, const NPVariant *variant) { return false; } // handle our plugin methods using standard np plugin conventions. bool pluginInvoke (PluginObject *obj, NPIdentifier name, NPVariant *args, unsigned argCount, NPVariant *result) { VOID_TO_NPVARIANT(*result); // sanity check if (argCount > 16) return false; // plugin.startup(); - called once web page is fully loaded and plugin (including Torque 3D) is initialized if (name == pluginMethodIdentifiers[ID_STARTUP_METHOD]) { result->type = NPVariantType_Void; Startup(); return true; } // plugin.setVariable("$MyVariable", 42); - set a Torque 3D console variable else if (name == pluginMethodIdentifiers[ID_SETVARIABLE_METHOD]) { result->type = NPVariantType_Void; if (argCount != 2) return false; std::string arg0(MY_NPVARIANT_TO_STRING(args[0])); std::string arg1(MY_NPVARIANT_TO_STRING(args[1])); WebCommon::SetVariable(arg0.c_str(), arg1.c_str()); return true; } // plugin.getVariable("$MyVariable"); - get a Torque 3D console variable else if (name == pluginMethodIdentifiers[ID_GETVARIABLE_METHOD]) { if (argCount != 1) return false; std::string value; std::string arg0(MY_NPVARIANT_TO_STRING(args[0])); value = WebCommon::GetVariable(arg0.c_str()); FillString(value, result); return true; } // plugin.exportFunction("MyJavascriptFunction",3); - export a Javascript function to the Torque 3D console system via its name and argument count else if (name == pluginMethodIdentifiers[ID_EXPORTFUNCTION_METHOD]) { result->type = NPVariantType_Void; if (argCount != 2) return false; std::string fname(MY_NPVARIANT_TO_STRING(args[0])); int argCount = 0; if (NPVARIANT_IS_DOUBLE(args[1])) argCount = NPVARIANT_TO_DOUBLE(args[1]); else argCount = NPVARIANT_TO_INT32(args[1]); ExportFunction(fname.c_str(), argCount); return true; } // var result = plugin.callScript("mySecureFunction('one', 'two', 'three');"); - call a TorqueScript function marked as secure in webConfig.h with supplied arguments else if (name == pluginMethodIdentifiers[ID_CALLSCRIPT_METHOD]) { if (argCount != 1) return false; std::string value; std::string code(MY_NPVARIANT_TO_STRING(args[0])); value = CallScript(code.c_str()); FillString(value, result); return true; } return false; } bool pluginInvokeDefault (PluginObject *obj, NPVariant *args, unsigned argCount, NPVariant *result) { VOID_TO_NPVARIANT(*result); return false; } void pluginInvalidate () { // Make sure we've released any remaining references to JavaScript // objects. } NPObject *pluginAllocate (NPP npp, NPClass *theClass) { PluginObject *newInstance = new PluginObject; gPluginObject = newInstance; if (!identifiersInitialized) { identifiersInitialized = true; initializeIdentifiers(); } // platform specific NPWebGamePlugin instantiation newInstance->webPlugin = new NPWebGamePlugin(npp); gInitialized = false; gJavaScriptExports.clear(); return (NPObject *)newInstance; } void pluginDeallocate (PluginObject *obj) { delete obj; gPluginObject = NULL; } int32 NPP_Write (NPP instance, NPStream *stream, int32_t offset, int32_t len, void *buffer); NPError OSCALL NP_GetEntryPoints(NPPluginFuncs* pFuncs) { if (pFuncs == NULL) { return NPERR_INVALID_FUNCTABLE_ERROR; } // Safari sets the size field of pFuncs to 0 if (pFuncs->size == 0) pFuncs->size = sizeof(NPPluginFuncs); if (pFuncs->size < sizeof(NPPluginFuncs)) { return NPERR_INVALID_FUNCTABLE_ERROR; } pFuncs->version = (NP_VERSION_MAJOR << 8) | NP_VERSION_MINOR; pFuncs->newp = NPP_New; pFuncs->destroy = NPP_Destroy; pFuncs->setwindow = NPP_SetWindow; pFuncs->newstream = NPP_NewStream; pFuncs->destroystream = NPP_DestroyStream; pFuncs->asfile = NPP_StreamAsFile; pFuncs->writeready = NPP_WriteReady; pFuncs->write = NPP_Write; pFuncs->print = NPP_Print; pFuncs->event = NPP_HandleEvent; pFuncs->urlnotify = NPP_URLNotify; pFuncs->getvalue = NPP_GetValue; pFuncs->setvalue = NPP_SetValue; pFuncs->javaClass = NULL; return NPERR_NO_ERROR; } NPError OSCALL NP_Initialize(NPNetscapeFuncs* pFuncs) { static bool _initialized = false; if (!_initialized) { _initialized = true; } if (pFuncs == NULL) return NPERR_INVALID_FUNCTABLE_ERROR; if ((pFuncs->version >> 8) > NP_VERSION_MAJOR) return NPERR_INCOMPATIBLE_VERSION_ERROR; // Safari sets the pfuncs size to 0 if (pFuncs->size == 0) pFuncs->size = sizeof(NPNetscapeFuncs); if (pFuncs->size < sizeof (NPNetscapeFuncs)) return NPERR_INVALID_FUNCTABLE_ERROR; NPNFuncs = *pFuncs; return NPERR_NO_ERROR; } NPError OSCALL NP_Shutdown() { if (WebCommon::gTorque3DModule) WebCommon::ShutdownTorque3D(); return NPERR_NO_ERROR; } NPError NPP_New(NPMIMEType pluginType, NPP instance, uint16 mode, int16 argc, char *argn[], char *argv[], NPSavedData *saved) { WebCommon::gPluginMIMEType = pluginType; if (gPluginObject) { WebCommon::MessageBox( 0, "This plugin allows only one instance", "Error"); return NPERR_GENERIC_ERROR; } // Get the location we're loading the plugin from (http://, file://) including address // this is used by the domain locking feature to ensure that your plugin is only // being used from your web site NPObject* windowObject = NULL; NPNFuncs.getvalue( instance, NPNVWindowNPObject, &windowObject ); NPVariant variantValue; NPIdentifier identifier = NPNFuncs.getstringidentifier( "location" ); if (!NPNFuncs.getproperty( instance, windowObject, identifier, &variantValue )) return NPERR_GENERIC_ERROR; NPObject *locationObj = variantValue.value.objectValue; identifier = NPNFuncs.getstringidentifier( "href" ); if (!NPNFuncs.getproperty( instance, locationObj, identifier, &variantValue )) return NPERR_GENERIC_ERROR; std::string url = MY_NPVARIANT_TO_STRING(variantValue); if (!WebCommon::CheckDomain(url.c_str())) return NPERR_GENERIC_ERROR; // everything checks out, let's rock if (NPNFuncs.version >= 14) { // this calls pluginAllocate instance->pdata = NPNFuncs.createobject(instance, getPluginClass()); } #ifndef WIN32 // On Mac, make sure we're using CoreGraphics (otherwise 3D rendering fails) NPNFuncs.setvalue(instance, (NPPVariable)NPNVpluginDrawingModel, (void *) NPDrawingModelCoreGraphics); #endif //PluginObject *plugin = (PluginObject*)instance->pdata; return NPERR_NO_ERROR; } // here is the place to clean up and destroy the object NPError NPP_Destroy (NPP instance, NPSavedData** save) { if (instance == NULL) return NPERR_INVALID_INSTANCE_ERROR; if (instance->pdata != NULL) { PluginObject *plugin = (PluginObject *)instance->pdata; delete plugin->webPlugin; NPNFuncs.releaseobject((NPObject*) instance->pdata); instance->pdata = NULL; } return NPERR_NO_ERROR; } NPError NPP_GetValue(NPP instance, NPPVariable variable, void *value) { if (variable == NPPVpluginScriptableNPObject) { if (instance->pdata == NULL) { instance->pdata = NPNFuncs.createobject(instance, getPluginClass()); } NPObject* obj = reinterpret_cast(instance->pdata); NPNFuncs.retainobject(obj); void **v = (void **)value; *v = obj; return NPERR_NO_ERROR; } return NPERR_GENERIC_ERROR; } NPError NPP_SetValue(NPP instance, NPNVariable variable, void *value) { return NPERR_GENERIC_ERROR; } NPError NPP_NewStream(NPP instance, NPMIMEType type, NPStream* stream, NPBool seekable, uint16* stype) { if(instance == NULL) return NPERR_INVALID_INSTANCE_ERROR; return NPERR_NO_ERROR; } int32 NPP_WriteReady (NPP instance, NPStream *stream) { if (instance == NULL) return NPERR_INVALID_INSTANCE_ERROR; return 0x0fffffff; } int32 NPP_Write (NPP instance, NPStream *stream, int32_t offset, int32_t len, void *buffer) { if (instance == NULL) return NPERR_INVALID_INSTANCE_ERROR; return NPERR_NO_ERROR; } NPError NPP_DestroyStream (NPP instance, NPStream *stream, NPError reason) { if(instance == NULL) return NPERR_INVALID_INSTANCE_ERROR; return NPERR_NO_ERROR; } void NPP_URLNotify(NPP instance, const char* url, NPReason reason, void* notifyData) { } void NPP_StreamAsFile (NPP instance, NPStream* stream, const char* fname) { } void NPP_Print (NPP instance, NPPrint* printInfo) { } int16 NPP_HandleEvent(NPP instance, void* event) { return 0; } // browser communicated window changes (creation, etc) here NPError NPP_SetWindow(NPP instance, NPWindow* window) { // strange... if (!window || !window->window) return NPERR_GENERIC_ERROR; // strange... if (!instance) return NPERR_INVALID_INSTANCE_ERROR; // get back the plugin instance object PluginObject *plugin = (PluginObject *)instance->pdata; NPWebGamePlugin* webPlugin = plugin->webPlugin; if (webPlugin) { if (!window->window) { } else { // handle platform specific window and Torque 3D initialization webPlugin->Open(window); } return NPERR_NO_ERROR; } // return an error if no object defined return NPERR_GENERIC_ERROR; }