{"name":"Extensions","title":"Extensions","type":"topic","url":"https://community.bistudio.com/wiki/Extensions","revid":379582,"updated":"2026-11-03T14:36:25Z","aliases":[],"markdown":"> [!WARNING]\n> If you came here because you have been told to install an extension, be warned! Extensions are just like executables. They may contain viruses etc. More on that, in the FAQ section.\n\n## Historical\n\n- Arma 2 v1.61 The first stable version of this functionality arrived.\n- Arma 3 v1.18 Linux (server) support was introduced.\n- Arma 3 v1.68 The ability to pass multiple arguments to callExtension and the interface `RVExtensionArgs` got added.\n- Arma 3 v1.70 `RVExtensionVersion` became available. It is called when the extension loads. Will be printed into the RPT log.\n- Arma 3 v1.96 Callbacks became available.\n- Arma 3 v2.12 Context became available.\n- Arma 3 v2.18 `RVExtensionFeatureFlags` and Game Functions became available.\n- Arma 3 v2.20 `Extension UI` became available.\n- Arma 3 v2.22 `Extension Texture Source` became available.\n\n## FAQ\n\n##### How to use Extensions?\n\nExtensions can be accessed using callExtension.\n\n> [!NOTE]\n> The exact way about how to use an extension is up to the developer of said extension.\n\n##### Where to put Extensions?\n\nArma 3 will look at the following places to find your extension files:\n\n- In all loaded mods root folders (for example: `..\\arma3\\@ModXYZ\\yourextension.dll`).\n- In your Arma 3 installation folder (for example: `..\\arma3\\yourextension.dll`).\n\nAt best, you place your extensions inside some mod folder and load it.\nAt worst, you just throw it where the Arma 3 executable is located.\n\n*Extensions may require other libraries and runtimes to be installed on the computer it is to run on.*\nConsult the authors installation manual if it exists.\n\n##### What are Extensions?\n\nExtensions are *Dynamic Link Library* (DLL) files written in, for example, C.\n\nLinux systems use a different file type: *Shared Object* (SO).\n\n##### What do I have to look out for?\n\nExtensions fall under the same rules as executable files.\nThey may require additional runtime libraries or cause the game to crash.\n\n> [!WARNING]\n> Extensions can contain malicious code!\n> Be sure to only use extensions from reliable developers, obtained through reliable sources!\n\n##### How to know which extensions can be trusted?\n\nTheoretically, never trust anything! Including Arma and your OS itself.\nTechnically however, this would make a boring experience, as all you would be left with is an expensive heater.\n\nThus a better rule is: If the are sources freely available, you probably can trust the extension.\nEven if you cannot read the source code, chances are that somebody out there can and would find something odd.\nThat will not protect you!\nDevelopers might add malicious code that is not exposed in the available source files, they not even need to be aware of this.\nThey may be infected already and the virus is just writing itself into the compiled extension.\n\n##### When I call the extension method, Arma freezes.\n\nThis is most likely caused by the extension taking too much time.\nDue to the low-level nature of extensions, an extension cannot be suspended like scheduled scripts can be.\nYou should notify the developer of said extension about this freezing issue or use the extension less extensively.\n\n##### Can I join BattlEye protected servers with my extension?\n\nIf the extension is whitelisted by BattlEye, it will be allowed to load - see Getting your extension whitelisted by BattlEye.\nIf it is not, it just will be blocked causing any callExtension attempt to fail.\n\nGenerally speaking, joining BattlEye-protected servers should be no problem with or without extensions.\n\nIf in doubt, ask the extension's author.\n\n##### Can I use extensions in my mission?\n\nYes, you can use extensions in your missions, but you should consider if it is worth it.\nUsing extensions requires that all hosts (servers and clients) which use the extension have it installed. Extensions are not packed into the mission pbo.\n\n> [!NOTE]\n> If only the server is using the extension, the clients do not need it.\n\n##### What do i have to look out for when deploying my extension?\n\nExtensions have to be provided for 32bit and 64bit Arma.\nWhen you deploy your extension, you thus have to build it twice.\nOnce for the 32bit support and once for the 64bit support.\n\nAfter you are done, copy both output files to some location and add a `_x64.dll` suffix to the 64bit file.\n\nExample:\n\n- Windows:\n  - `MyExtension.dll` (32bit)\n  - `MyExtension_x64.dll` (64bit)\n- Linux:\n  - `MyExtension.so` (32bit)\n  - `MyExtension_x64.so` (64bit)\n\n## Creating Extensions\n\n### Preamble\n\nBefore we start, ask yourself the following questions:\n\n- is SQF unable to fulfill your needs?\n- Making an extension requires knowledge of a programming language. Are you capable of performing that programming task?\n- Why should a user trust you and use your extension?\n- Is it worth adding an extension for the feature you want to implement?\n\nIf the answer to any question was no, you probably should not proceed to create an extension but rather use plain SQF.\n\n### Getting Started\n\nAt first, we have to choose what language we want to use.\n\nThis wiki currently covers the following: **C**, **C++**, **C#**\n\n#### Preparations\n\n##### C/C++\n\nCreate a new library project in the IDE of your choice.\n\n##### C\\#\n\nC# requires you to install additional dependencies to work out of the box.\nThe snippets in here all are using [DllExport](https://www.nuget.org/packages/UnmanagedExports) ([sources](https://github.com/3F/DllExport)) which can be installed using NuGet.\n\nCreate a new Class Library project in the IDE of your choice.\n\n### Available Interfaces\n\nThe Extension is required to contain at least the entry point in a form of a method named RVExtension or RVExtensionArgs.\nEverything else is optional.\n\nThe actual exports need to look like this (Windows only):\n\n**32-bit requires usage of [decorated names](https://docs.microsoft.com/en-us/cpp/build/reference/decorated-names?view=vs-2019) following the `__stdcall` convention**\n\n- `_RVExtension@12`\n- `_RVExtensionArgs@20`\n- `_RVExtensionVersion@8`\n\n**64-bit**\n\n- `RVExtension`\n- `RVExtensionArgs`\n- `RVExtensionVersion`\n\nIf you use the Visual C++ compiler, this should work out of the box. But other compilers might require some manual work.\n\n#### C/C++\n\n##### Visual C++\n\n```c\n    //--- Called by Engine on extension load\n    __declspec (dllexport) void __stdcall RVExtensionVersion(char *output, unsigned int outputSize);\n    //--- STRING callExtension STRING\n    __declspec (dllexport) void __stdcall RVExtension(char *output, unsigned int outputSize, const char *function);\n    //--- STRING callExtension ARRAY\n    __declspec (dllexport) int __stdcall RVExtensionArgs(char *output, unsigned int outputSize, const char *function, const char **argv, unsigned int argc);\n    //--- Extension Callback\n    __declspec (dllexport) void __stdcall RVExtensionRegisterCallback(int(*callbackProc)(char const *name, char const *function, char const *data));\n    //--- Engine passed context [steamID, fileSource, missionSourceName, serverName] since Arma 3 v2.12\n    __declspec (dllexport) void __stdcall RVExtensionContext(const char **argv, unsigned int argc);\n    //--- Request creation of UI element\n    __declspec (dllexport) bool __stdcall RVExtensionRequestUI(const char *uiClass, void *interfaceStruct);\n    //--- Request filling in a Extension Texture Source\n    __declspec (dllexport) void __stdcall RVExtensionFillTextureSource(const char* uniqueName, uint16_t width, uint16_t height, uint16_t pitch, uint8_t mipLevel, uint32_t format, void* buffer);\n    //--- Check if a animated Extension Texture Source should be updated\n    __declspec (dllexport) bool __stdcall RVExtensionHasTextureSourceUpdate(const char* uniqueName);\n```\n\n##### GCC\n\n```c\n    //--- Called by Engine on extension load\n    __attribute__((dllexport)) void RVExtensionVersion(char *output, unsigned int outputSize);\n    //--- STRING callExtension STRING\n    __attribute__((dllexport)) void RVExtension(char *output, unsigned int outputSize, const char *function);\n    //--- STRING callExtension ARRAY\n    __attribute__((dllexport)) int RVExtensionArgs(char *output, unsigned int outputSize, const char *function, const char **argv, unsigned int argc);\n    //--- Extension Callback\n    __attribute__((dllexport)) void RVExtensionRegisterCallback(int(*callbackProc)(char const *name, char const *function, char const *data));\n    //--- Engine passed context [steamID, fileSource, missionSourceName, serverName] since Arma 3 v2.12\n    __attribute__((dllexport)) void RVExtensionContext(const char **argv, unsigned int argc);\n```\n\nSee GCC documentation on [\"How to use the new C++ visibility support\"](https://gcc.gnu.org/wiki/Visibility)\n\n#### C\\#\n\n> [!NOTE]\n> You might need to install [UnmanagedExports](https://www.nuget.org/packages/UnmanagedExports). You might also need to additionally set `WIN64` and `WIN32` in project properties.\n>\n> Go to: `Project->Properties->Build tab->Conditional compilation symbols`, add `WIN32` for your `x86` platform and `WIN64` for your `x64` platform. Save.\n>\n> Rename resulting `WIN64` `dllname.dll` to `dllname_x64.dll`.\n\n> [!NOTE]\n> If you intend to use .NET instead of .NET Framework, the **UnmanagedExports** package above might not work properly. Use [DllExport](https://github.com/3F/DllExport) instead\n\n```c#\n        /// <summary>\n        /// Gets called when Arma starts up and loads all extension.\n        /// It's perfect to load in static objects in a separate thread so that the extension doesn't needs any separate initalization\n        /// </summary>\n        /// <param name=\"output\">The string builder object that contains the result of the function</param>\n        /// <param name=\"outputSize\">The maximum size of bytes that can be returned</param>\n#if WIN64\n        [DllExport(\"RVExtensionVersion\", CallingConvention = CallingConvention.Winapi)]\n#else\n        [DllExport(\"_RVExtensionVersion@8\", CallingConvention = CallingConvention.Winapi)]\n#endif\n        public static void RvExtensionVersion(StringBuilder output, unsigned int outputSize) { }\n\n        /// <summary>\n        /// The entry point for the default callExtension command.\n        /// </summary>\n        /// <param name=\"output\">The string builder object that contains the result of the function</param>\n        /// <param name=\"outputSize\">The maximum size of bytes that can be returned</param>\n        /// <param name=\"function\">The string argument that is used along with callExtension</param>\n#if WIN64\n        [DllExport(\"RVExtension\", CallingConvention = CallingConvention.Winapi)]\n#else\n        [DllExport(\"_RVExtension@12\", CallingConvention = CallingConvention.Winapi)]\n#endif\n        public static void RvExtension(StringBuilder output, unsigned int outputSize,\n            [MarshalAs(UnmanagedType.LPStr)] string function) { }\n\n        /// <summary>\n        /// The entry point for the callExtensionArgs command.\n        /// </summary>\n        /// <param name=\"output\">The string builder object that contains the result of the function</param>\n        /// <param name=\"outputSize\">The maximum size of bytes that can be returned</param>\n        /// <param name=\"function\">The string argument that is used along with callExtension</param>\n        /// <param name=\"args\">The args passed to callExtension as a string array</param>\n        /// <param name=\"argsCount\">The size of the string array args</param>\n        /// <returns>The result code</returns>\n#if WIN64\n        [DllExport(\"RVExtensionArgs\", CallingConvention = CallingConvention.Winapi)]\n#else\n        [DllExport(\"_RVExtensionArgs@20\", CallingConvention = CallingConvention.Winapi)]\n#endif\n        public static int RvExtensionArgs(StringBuilder output, unsigned int outputSize,\n            [MarshalAs(UnmanagedType.LPStr)] string function,\n            [MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.LPStr, SizeParamIndex = 4)] string[] args, unsigned int argCount) { }\n```\n\n### Getting your extension whitelisted by BattlEye (Arma 3 v1.46)\n\nSince Arma 3 v1.46, client extensions need to be whitelisted in order to get loaded.\n\nThe [SITREP #00109](https://dev.arma3.com/post/sitrep-00109) said the following on that topic:\n\n```\nThe enhanced BattlEye protection in 1.46 will require extensions to be white-listed, or their access to the game's processes will be blocked.\nIn order to get your extension white-listed, please contact BattlEye support via its web form or support[at]battleye.com.\nBe sure to include the file name, a description and possibly a download link.\n```\n\nThis boils down to the following tasks to get your extension whitelisted:\n\n1. Upload the extension in question somewhere.\n1. Open the [BattlEye Contact Page](http://www.battleye.com/contact/).\n1. Pick the topic `Other requests` (may change in future, select the most appropriate).\n1. Fill in your Name and E-Mail.\n1. Set the subject to something like `Arma extension whitelisting`.\n1. Add link to your extension from step 1 (you may include other information too) into the Message.\n\nAfter a certain time, your extension should be whitelisted.\n\n### Deploying extensions\n\nTo correctly deploy your extensions, you have to make sure to have a 32bit and a 64bit variant of your extension.\nThe reason for this is because 64bit Arma cannot load 32bit extensions and vice versa.\n\nYou also should create an addon package (even if you only deploy the actual extension binaries) + add the documentation for your extension.\n\nAn example might be structured like this:\n\n- `/@MyExtension/`\n  - `/MyExtension.dll`\n  - `/MyExtension_x64.dll`\n  - `/docs/`\n    - `/MyExtension_Documentation.txt`\n\nYou may also want to provide convenience SQF functions to access your extension.\n\n### Minimum Viable Example\n\nThe following examples will copy the input into the output. They are provided to show some practical example.\n\n**C (Visual C++)**\n\n```c\n__declspec(dllexport) void __stdcall RVExtension(char *output, unsigned int outputSize, const char *function);\n__declspec(dllexport) int __stdcall RVExtensionArgs(char *output, unsigned int outputSize, const char *function, const char **argv, unsigned int argc);\n__declspec(dllexport) void __stdcall RVExtensionVersion(char *output, unsigned int outputSize);\n\nint strncpy_safe(char *output, const char *src, int size)\n{\n    int i;\n    size--;\n    for (i = 0; i < size && src[i] != '\\0'; i++)\n    {\n        output[i] = src[i];\n    }\n    output[i] = '\\0';\n    return i;\n}\n\nvoid __stdcall RVExtension(char *output, unsigned int outputSize, const char *function)\n{\n    strncpy_safe(output, function, outputSize);\n}\n\nint __stdcall RVExtensionArgs(char *output, unsigned int outputSize, const char *function, const char **argv, unsigned int argc)\n{\n    int index = 0;\n    for (int i = 0; i < argc && index < outputSize; i++)\n    {\n        index += strncpy_safe(output + index, argv[i], outputSize - 1 - index);\n    }\n    return 0;\n}\n\nvoid __stdcall RVExtensionVersion(char *output, unsigned int outputSize)\n{\n    strncpy_safe(output, \"Test-Extension v1.0\", outputSize);\n}\n```\n\n**C (GCC)**\n\n```c\n__attribute__((dllexport)) void RVExtension(char *output, unsigned int outputSize, const char *function);\n__attribute__((dllexport)) int RVExtensionArgs(char *output, unsigned int outputSize, const char *function, const char **argv, unsigned int argc);\n__attribute__((dllexport)) void RVExtensionVersion(char *output, unsigned int outputSize);\n\nint strncpy_safe(char *output, const char *src, int size)\n{\n    int i;\n    size--;\n    for (i = 0; i < size && src[i] != '\\0'; i++)\n    {\n        output[i] = src[i];\n    }\n    output[i] = '\\0';\n    return i;\n}\n\nvoid RVExtension(char *output, unsigned int outputSize, const char *function)\n{\n    strncpy_safe(output, function, outputSize);\n}\n\nint RVExtensionArgs(char *output, unsigned int outputSize, const char *function, const char **argv, unsigned int argc)\n{\n    int index = 0;\n    for (int i = 0; i < argc && index < outputSize; i++)\n    {\n        index += strncpy_safe(output + index, argv[i], outputSize - 1 - index);\n    }\n    return 0;\n}\n\nvoid RVExtensionVersion(char *output, unsigned int outputSize)\n{\n    strncpy_safe(output, \"Test-Extension v1.0\", outputSize);\n}\n```\n\n**C++ (Visual C++)**\n\n```cpp\n#include <string>\n#include <cstring>\n#include <sstream>\nextern \"C\"\n{\n    __declspec(dllexport) void __stdcall RVExtension(char *output, unsigned int outputSize, const char *function);\n    __declspec(dllexport) int __stdcall RVExtensionArgs(char *output, unsigned int outputSize, const char *function, const char **argv, unsigned int argc);\n    __declspec(dllexport) void __stdcall RVExtensionVersion(char *output, unsigned int outputSize);\n}\nvoid __stdcall RVExtension(char *output, unsigned int outputSize, const char *function)\n{\n    std::strncpy(output, function, outputSize - 1);\n}\n\nint __stdcall RVExtensionArgs(char *output, unsigned int outputSize, const char *function, const char **argv, unsigned int argc)\n{\n    std::stringstream sstream;\n    for (int i = 0; i < argc; i++)\n    {\n        sstream << argv[i];\n    }\n    std::strncpy(output, sstream.str().c_str(), outputSize - 1);\n    return 0;\n}\n\nvoid __stdcall RVExtensionVersion(char *output, unsigned int outputSize)\n{\n    std::strncpy(output, \"Test-Extension v1.0\", outputSize - 1);\n}\n```\n\n**C++ (GCC)**\n\n```cpp\n#include <string>\n#include <cstring>\n#include <sstream>\nextern \"C\"\n{\n    __attribute__((dllexport)) void RVExtension(char *output, unsigned int outputSize, const char *function);\n    __attribute__((dllexport)) int RVExtensionArgs(char *output, unsigned int outputSize, const char *function, const char **argv, unsigned int argc);\n    __attribute__((dllexport)) void RVExtensionVersion(char *output, unsigned int outputSize);\n}\nvoid RVExtension(char *output, unsigned int outputSize, const char *function)\n{\n    std::strncpy(output, function, outputSize - 1);\n}\n\nint RVExtensionArgs(char *output, unsigned int outputSize, const char *function, const char **argv, unsigned int argc)\n{\n    std::stringstream sstream;\n    for (int i = 0; i < argc; i++)\n    {\n        sstream << argv[i];\n    }\n    std::strncpy(output, sstream.str().c_str(), outputSize - 1);\n    return 0;\n}\n\nvoid RVExtensionVersion(char *output, unsigned int outputSize)\n{\n    std::strncpy(output, \"Test-Extension v1.0\", outputSize - 1);\n}\n```\n\n**C#**\n\n```c#\nusing System.Text;\nusing System.Runtime.InteropServices;\nusing RGiesecke.DllExport;\n\nclass MyExtension {\n\n#if WIN64\n        [DllExport(\"RVExtensionVersion\", CallingConvention = CallingConvention.Winapi)]\n#else\n        [DllExport(\"_RVExtensionVersion@8\", CallingConvention = CallingConvention.Winapi)]\n#endif\n        public static void RvExtensionVersion(StringBuilder output, unsigned int outputSize)\n        {\n            output.Append(\"Test-Extension v1.0\");\n        }\n\n#if WIN64\n        [DllExport(\"RVExtension\", CallingConvention = CallingConvention.Winapi)]\n#else\n        [DllExport(\"_RVExtension@12\", CallingConvention = CallingConvention.Winapi)]\n#endif\n        public static void RvExtension(StringBuilder output, unsigned int outputSize,\n            [MarshalAs(UnmanagedType.LPStr)] string function)\n        {\n            output.Append(function);\n        }\n\n#if WIN64\n        [DllExport(\"RVExtensionArgs\", CallingConvention = CallingConvention.Winapi)]\n#else\n        [DllExport(\"_RVExtensionArgs@20\", CallingConvention = CallingConvention.Winapi)]\n#endif\n        public static int RvExtensionArgs(StringBuilder output, unsigned int outputSize,\n            [MarshalAs(UnmanagedType.LPStr)] string function,\n            [MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.LPStr, SizeParamIndex = 4)] string[] args, unsigned int argCount)\n        {\n            foreach(var arg in args)\n            {\n                output.Append(arg);\n            }\n            return 0;\n        }\n}\n```\n\n**C# (w/ callback)**\n\n```c#\nusing System.Text;\nusing System.Runtime.InteropServices;\nusing RGiesecke.DllExport;\n\nclass MyExtension {\n\n    public static ExtensionCallback callback;\n    public delegate int ExtensionCallback([MarshalAs(UnmanagedType.LPStr)] string name, [MarshalAs(UnmanagedType.LPStr)] string function, [MarshalAs(UnmanagedType.LPStr)] string data);\n\n#if WIN64\n        [DllExport(\"RVExtensionRegisterCallback\", CallingConvention = CallingConvention.Winapi)]\n#else\n        [DllExport(\"_RVExtensionRegisterCallback@4\", CallingConvention = CallingConvention.Winapi)]\n#endif\n        public static void RVExtensionRegisterCallback([MarshalAs(UnmanagedType.FunctionPtr)] ExtensionCallback func)\n        {\n            callback = func;\n        }\n\n#if WIN64\n        [DllExport(\"RVExtensionVersion\", CallingConvention = CallingConvention.Winapi)]\n#else\n        [DllExport(\"_RVExtensionVersion@8\", CallingConvention = CallingConvention.Winapi)]\n#endif\n        public static void RvExtensionVersion(StringBuilder output, unsigned int outputSize)\n        {\n            output.Append(\"Test-Extension v1.0\");\n        }\n\n#if WIN64\n        [DllExport(\"RVExtension\", CallingConvention = CallingConvention.Winapi)]\n#else\n        [DllExport(\"_RVExtension@12\", CallingConvention = CallingConvention.Winapi)]\n#endif\n        public static void RvExtension(StringBuilder output, unsigned int outputSize,\n            [MarshalAs(UnmanagedType.LPStr)] string function)\n        {\n            output.Append(function);\n        }\n\n#if WIN64\n        [DllExport(\"RVExtensionArgs\", CallingConvention = CallingConvention.Winapi)]\n#else\n        [DllExport(\"_RVExtensionArgs@20\", CallingConvention = CallingConvention.Winapi)]\n#endif\n        public static int RvExtensionArgs(StringBuilder output, unsigned int outputSize,\n            [MarshalAs(UnmanagedType.LPStr)] string function,\n            [MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.LPStr, SizeParamIndex = 4)] string[] args, unsigned int argCount)\n        {\n            foreach(var arg in args)\n            {\n                output.Append(arg);\n            }\n            return 0;\n        }\n}\n```\n\n### Other Languages\n\nThis section contains other languages which may or may not work. Finding out requirements etc. is up to you.\n\n> [!NOTE]\n> Help Wanted!\n\nIf you know any of the following languages, consider editing this page to add the language to the upper list properly.\n\n**Delphi/Pascal**\n\n```delphi\nlibrary dllTest;\n\nuses\n  SysUtils;\n\n{Return value is not used.}\nprocedure RVExtension(toArma: PAnsiChar; outputSize: Integer; fromArma: PAnsiChar); stdcall; export;\nbegin\n  StrCopy(toArma, fromArma);\nend;\n\nexports\n  { 32-bit }\n  RVExtension name '_RVExtension@12';\n  { 64-bit }\n  RVExtension name 'RVExtension';\nbegin\nend;\n```\n\n**D**\n\n```d\nimport std.c.windows.windows;\nimport core.sys.windows.dll;\n\n__gshared HINSTANCE g_hInst;\n\nextern (Windows) BOOL DllMain(HINSTANCE hInstance, ULONG ulReason, LPVOID pvReserved) {\n    final switch (ulReason) {\n        case DLL_PROCESS_ATTACH:\n            g_hInst = hInstance;\n            dll_process_attach(hInstance, true);\n            break;\n\n        case DLL_PROCESS_DETACH:\n            dll_process_detach(hInstance, true);\n            break;\n\n        case DLL_THREAD_ATTACH:\n            dll_thread_attach(true, true);\n            break;\n\n        case DLL_THREAD_DETACH:\n            dll_thread_detach(true, true);\n            break;\n    }\n\n    return true;\n}\n\nimport std.conv;\nimport std.exception;\n\nexport extern (Windows) void RVExtension(char* output, int output_size, const char* cinput) {\n    auto dinput = to!string(cinput);\n    auto doutput = output[0 .. output_size];\n    string result;\n\n    // ...\n\n    enforce(result.length <= output_size, \"Output length too long\");\n    doutput[0 .. result.length] = result[];\n    doutput[result.length] = '\\0';\n}\n```\n\n### Utility Header\n\nThis C++ header file can be used to define most of the Extension interface. It works on Windows and Linux.\nIt will be used in the following examples to keep them shorter. It also contains useful definitions for Feature Flags and Context -\nIts name is RVExtensionUtils.hpp\n\n```cpp\n#pragma once\n#include <cstdint>\n#ifdef _WIN32\n#include <windows.h> // GetModuleHandle, GetProcAddress\n#else // Linux\n#include <dlfcn.h> // dlopen, dlclose, dlsym\n#endif\n\n// bitset\nenum DllExtensionFeatureFlags : uint64_t\n{\n    RVFeature_ContextArgumentsVoidPtr = 1 << 0,\n    RVFeature_ContextStackTrace = 1 << 1,\n    RVFeature_ContextNoDefaultCall = 1 << 2,\n    RVFeature_ArgumentNoEscapeString = 1 << 3,\n};\n\nstruct RVContext_StackTrace\n{\n    struct StackTraceLine\n    {\n        // Line number in file (before preprocessing if preprocessed with line numbers)\n        uint32_t lineNumber;\n        // File offset in bytes from the start of the file (after preprocessing)\n        uint32_t fileOffset;\n        // Filepath to the source file\n        const char* sourceFile;\n        // scopeName set on that level\n        const char* scopeName;\n        // Complete fileContent of the sourceFile (after preprocessing, can be combined with fileOffset to find exact location)\n        const char* fileContent;\n    };\n\n    StackTraceLine* lines = nullptr;\n    uint32_t lineCount = 0;\n};\n\nstruct ProjectionViewTransform\n{\n    typedef float M4x4[4][4];\n\n    M4x4 projection;\n    M4x4 view;\n};\n\nstruct CExtensionControlInterface\n{\n    CExtensionControlInterface()\n    {\n        memset(this, 0, sizeof(CExtensionControlInterface));\n    }\n\n    uint32_t size = sizeof(CExtensionControlInterface);\n\n    enum Flags : uint32_t\n    {\n        None = 0,\n        DynamicTexture = 1 << 0, // If set we do not set render target, and instead pass a texture that can be mapped with D3D11_MAP_WRITE_DISCARD\n        TextureFormatBGRA = 1 << 1 // If set the texture is BGRA, if not set its ARGB\n    };\n    Flags flags = Flags::None;\n\n    // This is passed to every callback\n    void* context;\n\n    // [required]\n    // Render into currently active target\n    // target is a ID3D11RenderTargetView if Flags::DynamicTexture is not set\n    // target is a ID3D11Texture2D if Flags::DynamicTexture is set\n    void (*OnDraw)(void* context, float alpha, void* target);\n    // [required]\n    // Called after \"Unload\" display EH and \"Destroy\" control EH\n    // This is the last call before the control is destroyed. Release all your resources\n    void (*OnDestroy)(void* context, int code);\n\n    // optional\n    // Called before OnDraw, if the control (including backing render target) size has changed\n    void (*OnSizeChanged)(void* context, unsigned int width, unsigned int height);\n\n    // Interactions\n    // Optional all of these\n\n    // Return if you accept the focus\n    bool (*OnSetFocus)(void* context, bool focus);\n\n    void (*OnLButtonDown)(void* context, float x, float y);\n    void (*OnLButtonUp)(void* context, float x, float y);\n    void (*OnLButtonClick)(void* context, float x, float y);\n    void (*OnLButtonDblClick)(void* context, float x, float y);\n    void (*OnRButtonDown)(void* context, float x, float y);\n    void (*OnRButtonUp)(void* context, float x, float y);\n    void (*OnRButtonClick)(void* context, float x, float y);\n    void (*OnMouseMove)(void* context, float x, float y);\n    // Return if the input was handled, if not it will be passed to the parent controlsgroup/display\n    bool (*OnMouseZChanged)(void* context, float dz);\n\n    void (*OnMouseEnter)(void* context, float x, float y);\n    void (*OnMouseExit)(void* context, float x, float y);\n\n    // Return if the key was handled, if not it will be passed to the parent controlsgroup/display\n    bool (*OnKeyDown)(void* context, int dikCode);\n    bool (*OnKeyUp)(void* context, int dikCode);\n    bool (*OnChar)(void* context, unsigned nChar, unsigned nRepCnt, unsigned nFlags);\n};\n\n#ifdef _D3D11_CONSTANTS // Only available with d3d11.h header\nstruct RVExtensionRenderInfo\n{\n    ID3D11Device*            d3dDevice;\n    ID3D11DeviceContext*     d3dDeviceContext;\n};\n\nstruct RVExtensionGraphicsLockGuard\n{\n    //! Call this when done with the lock, forgetting to call this will freeze the game.\n    virtual void ReleaseLock() const = 0;\n    virtual ~RVExtensionGraphicsLockGuard() = default;\n};\n#endif\n\nenum TextureFormat : uint32_t\n{\n    PacAI88 = 1,\n    PacARGB8888 = 5\n};\n\n#ifdef _WIN32\n#define DLLEXPORT __declspec (dllexport)\n#define CALL_CONVENTION __stdcall\n#else // Linux\n#if __GNUC__ >= 4\n#define DLLEXPORT __attribute__ ((visibility (\"default\")))\n#else\n#define DLLEXPORT __attribute__((dllexport))\n#endif\n\n#define CALL_CONVENTION\n#endif\n\nextern \"C\"\n{\n    typedef int (CALL_CONVENTION RVExtensionCallbackProc)(char const* name, char const* function, char const* data);\n    typedef void CALL_CONVENTION RVExtensionRequestContextProc();\n    typedef void (CALL_CONVENTION RVGetProjectionViewTransformProc)(ProjectionViewTransform& pvTransform);\n    typedef uint64_t (CALL_CONVENTION RVExtensionLoadFile)(const char* path, void* buffer, uint64_t bufferSize);\n\n#ifdef _D3D11_CONSTANTS // Only available with d3d11.h header\n    typedef RVExtensionGraphicsLockGuard* (CALL_CONVENTION RVExtensionGLockProc)();\n    typedef void (CALL_CONVENTION RVExtensionGSetWHkProc)(bool (CALL_CONVENTION *newHook)(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam));\n    typedef void (CALL_CONVENTION RVExtensionGSetResetHook)(void (CALL_CONVENTION *newHook)());\n    typedef void (CALL_CONVENTION RVExtensionGGetR2TTexture)(const char* name, void (CALLCONVENTION *onResult)(ID3D11Texture2D* texture, ID3D11ShaderResourceView* resView, ID3D11RenderTargetView* rtView, uint32_t width, uint32_t height, uint32_t lastSeenFrameID, const char* name));\n#endif\n\n    //--- Called by Engine on extension load\n    DLLEXPORT void CALL_CONVENTION RVExtensionVersion(char* output, unsigned int outputSize);\n    //--- STRING callExtension STRING\n    DLLEXPORT void CALL_CONVENTION RVExtension(char* output, unsigned int outputSize, const char* function);\n    //--- STRING callExtension ARRAY\n    DLLEXPORT int CALL_CONVENTION RVExtensionArgs(char* output, unsigned int outputSize, const char* function, const char** argv, unsigned int argc);\n    //--- Called by Engine on extension load to pass RVExtensionCallbackProc to it\n    DLLEXPORT void CALL_CONVENTION RVExtensionRegisterCallback(RVExtensionCallbackProc* callbackProc);\n    //--- Request creation of UI element\n    DLLEXPORT bool CALL_CONVENTION RVExtensionRequestUI(const char* uiClass, CExtensionControlInterface* interfaceStruct);\n    //--- Request filling in a Extension Texture Source\n    DLLEXPORT void CALL_CONVENTION RVExtensionFillTextureSource(const char* uniqueName, uint16_t width, uint16_t height, uint16_t pitch, uint8_t mipLevel, TextureFormat format, void* buffer);\n    DLLEXPORT bool CALL_CONVENTION RVExtensionHasTextureSourceUpdate(const char* uniqueName);\n}\n\n//--- Finds a game exported function by its name\ninline const void* FindRVFunction(const char* name)\n{\n#ifdef _WIN32\n    return reinterpret_cast<void*>(GetProcAddress(GetModuleHandle(nullptr), name));\n#else // Linux\n    auto armaHandle = dlopen(nullptr, RTLD_LAZY | RTLD_NOLOAD);\n    auto result = dlsym(armaHandle, name);\n    dlclose(armaHandle);\n    return result;\n#endif\n}\n```\n\n### Feature Flags (Arma 3 v2.18)\n\nFeature flags can change the behavior of the Extension.\nTheir main purpose is to only enable certain features when they are needed, so that Extensions that don't need them don't have to pay the cost for it.\nMultiple flags can be combined.\nTo set no flags, either 0 must be specified or the RVExtensionFeatureFlags variable must be omitted.\n\nFlags are checked before every RVExtension/RVExtensionArgs execution. They can be changed at runtime between executions, but make sure that the effects of changed flags are handled correctly.\n\nExisting flags\n\n- `RVFeature_ContextArgumentsVoidPtr` - RVExtensionContext takes `const void**` as argument, instead of the default `const char**`, and arguments will be passed in their custom types - see Context Arguments.\n- `RVFeature_ContextStackTrace` - RVExtensionContext will retrieve a full Stacktrace - see Context.\n- `RVFeature_ContextNoDefaultCall` - RVExtensionContext will not be called automatically. It must be manually requested via RVExtensionRequestContext (This improves performance when context is not needed).\n- Arma 3 v2.22 `RVFeature_ArgumentNoEscapeString` - RVExtensionArgs string arguments will not be escaped (double-quoted) and instead passed directly. Provides significant performance advantage, and removes the need to un-escape the strings after receiving them.\n\nSetting Flags\n\nExample Visual C++\n\n```cpp\n#include <cstdint>\n#include \"RVExtensionUtil.hpp\"\n\nextern \"C\"\n{\n    __declspec(dllexport) uint64_t RVExtensionFeatureFlags = RVFeature_ContextStackTrace | RVFeature_ContextNoDefaultCall;\n}\n```\n\n### Game Functions (Arma 3 v2.18)\n\nThe Game exports some functions that can be retrieved, in order to call back into Game functionality.\nThe Utility Header defines a function called `FindRVFunction` that can be used to retrieve their addresses.\n\n**Available Game Functions**\n\n| Name | Type | Description |\n|---|---|---|\n| RVExtensionCallback | `typedef int (CALL_CONVENTION RVExtensionCallbackProc)(char const* name, char const* function, char const* data);` | Adds a callback, see #TODO |\n| RVExtensionRequestContext | `typedef void CALL_CONVENTION RVExtensionRequestContextProc();` | Requests execution of `RVExtensionContext` |\n| RVExtensionGData | ```cpp<br>struct RVExtensionRenderInfo<br>{<br>    ID3D11Device*            d3dDevice;<br>    ID3D11DeviceContext*     d3dDeviceContext;<br>};<br>const RVExtensionRenderInfo* const *<br>``` | Acquires pointers to the DirectX 11 device and device context. |\n| RVExtensionGLock | ```cpp<br>struct RVExtensionGraphicsLockGuard<br>{<br>    //! Call this when done with the lock, forgetting to call this will freeze the game.<br>    virtual void ReleaseLock() const = 0;<br>    virtual ~RVExtensionGraphicsLockGuard() = default;<br>};<br>typedef RVExtensionGraphicsLockGuard* (CALL_CONVENTION RVExtensionGLockProc)();<br>``` | Acquires a DirectX lock, required to access DirectX functions. |\n| RVGetProjectionViewTransform | ```cpp<br>struct ProjectionViewTransform<br>{<br>    typedef float M4x4[4][4];<br>    M4x4 projection;<br>    M4x4 view;<br>};<br>typedef void (CALL_CONVENTION RVGetProjectionViewTransformProc)(ProjectionViewTransform& pvTransform);<br>``` | Returns the projection and camera view (i.e. \"look at\") matrices for drawing 3D content. The matrices are row-major. The camera view matrix is left-handed. |\n| RVExtensionGSetWHk | `typedef void (CALL_CONVENTION RVExtensionGSetWHkProc)(bool (CALL_CONVENTION *newHook)(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam));` | Sets a WNDPROC hook to handle mouse/key inputs when rendering external UI. |\n| RVExtensionGSetResetHook | `typedef void (CALL_CONVENTION RVExtensionGSetResetHook)(void (CALL_CONVENTION *newHook)());` | Sets a hook that gets called when the game 3D engine is shut down. At this point the extension must destroy its D3D resources and release the pointers to the D3D device/context. |\n| RVExtensionGGetR2TTexture Arma 3 v2.22 | `typedef void (CALL_CONVENTION RVExtensionGGetR2TTexture)(const char* name, void (CALLCONVENTION *onResult)(ID3D11Texture2D* texture, ID3D11ShaderResourceView* resView, ID3D11RenderTargetView* rtView, uint32_t width, uint32_t height, uint32_t lastSeenFrameID, const char* name));` | Retrieves R2T texture's by name, name can be nullptr, in which case all textures are returned via the callback. |\n| RVExtensionLoadFile Arma 3 v2.24 | `typedef uint64_t (CALL_CONVENTION RVExtensionLoadFile)(const char* path, void* buffer, uint64_t bufferSize);` | Load file from PBO (Similar to loadFile script command). Pass buffer as nullptr to retrieve actual size of the file (0 if not found). If buffer is too small, it will be fully written and return value will be bufferSize. |\n\n### Callback (Arma 3 v1.96)\n\nIt is possible to call the game directly from the extension via function pointer provided when extension is called for the first time.\nSince Arma 3 v2.18 it can also be requested when needed, see Game Functions.\nThe function pointer passed over to `RVExtensionRegisterCallback` method is of the following signature:\n\n```cpp\nint(*callbackProc)(char const *name, char const *function, char const *data)\n```\n\nCalling this function pointer from extension will trigger \"ExtensionCallback\" mission event handler with three user-supplied\nparams. The params are:\n\n- `name` - make it unique name, for example the extension name, so that other modders can quickly filter out calls from own extensions\n- `function` - make it name of the function the extension sends the result to. (Note: The returned function is just a STRING! So compile is needed, before using call or spawn, to execute it)\n- `data` - make it the actual result. You can also format it as an array so it could be parsed by parseSimpleArray\n\nCalling function pointer returns an `int`. This is the number of available slots in the input buffer left for this frame after your call and can range from 99 to -1.\nThe buffer is processed and cleared every frame and the maximum number of slots that can be filled per frame is 100.\nIf you are planning to call back with more than 100 results per frame, make sure your extension retries if it receives negative `int`, which means the buffer was full and your call did not succeed.\nIdeally suited for callbacks from different threads, but if callback is initiated from the calling thread, the EH will fire on the next frame of game simulation.\nThe \"ExtensionCallback\" event handler needs to exist before any callbacks, otherwise the data will just stay in the buffer. Removing all \"ExtensionCallback\" EHs clears the buffer as well as mission restart.\n\nUsage Example:\n\n```sqf\nfncToExecute_1 = { hint format [\"Extension Result 1: %1\", _this] };\nfncToExecute_2 = { hint format [\"Extension Result 2: %1\", _this] };\nfncToExecute_3 = { hint format [\"Extension Result 3: %1\", _this] };\n\naddMissionEventHandler [\"ExtensionCallback\",\n{\n    params [\"_name\", \"_function\", \"_data\"];\n    if (_name isEqualTo \"test_callback\") then\n    {\n        parseSimpleArray _data call (missionNamespace getVariable [_function,\n        {\n            hint \"Function does not exist!\";\n        }]);\n    };\n}];\n\n\"test_callback\" callExtension str \"test data\";\n```\n\nHere is a minimal example of an extension using extension callback (do not actually do it like this for a real extension).\n*fncToExecute_X* function is called from \"ExtensionCallback\" event handler when it is triggered after 2 seconds of the extension call.\n\n```cpp\n#include <thread>\n#include <string>\n#include <chrono>\n\n#include \"RVExtensionUtil.hpp\"\n\nRVExtensionCallbackProc* callbackPtr = nullptr;\n\nvoid CALL_CONVENTION RVExtensionRegisterCallback(RVExtensionCallbackProc* callbackProc)\n{\n    callbackPtr = callbackProc;\n}\n\nvoid CALL_CONVENTION RVExtension(char *output, unsigned int outputSize, const char *function)\n{\n    if (!callbackPtr)\n        return;\n\n    std::thread ([](std::string fnc)\n    {\n        using namespace std::chrono_literals;\n        fnc = \"[1,2,3,\" + fnc + \"]\";\n\n        for (int i = 1; i < 4; ++i) // run 3 times\n        {\n            std::this_thread::sleep_for(2s); // sleep for 2 seconds\n            callbackPtr(\"test_callback\", (\"fncToExecute_\" + std::to_string(i)).c_str(), fnc.c_str());\n        }\n\n    }, function).detach();\n}\n```\n\n### Context (Arma 3 v2.12)\n\nRVExtensionContext is called before every RVExtension/RVExtensionArgs execution, providing context about where the call came from.\nUsing Feature Flags can change the number of elements passed, but it does not change their order.\n\n#### Context Arguments\n\nBy default (due to backwards compatability) context receives an array of char*. Every value will be converted to a string.\nHowever with the `RVFeature_ContextArgumentsVoidPtr` Feature Flags, it can be switched to an array of void*, where the specific datatype per entry is defined per context element.\n\nIt will pass the following data in this order:\n\n**Caption text**\n\n| Name | Description | String content | Custom datatype with `RVFeature_ContextArgumentsVoidPtr` |\n|---|---|---|---|\n| steamId | Steam 64bit userID of the local machine, same as getPlayerUID | \"0\", number as string, no extra quotes | `const uint64_t*` the UID |\n| fileSource | file from which the extension was executed or \"\" if unavailable | X | `const char*` |\n| missionName | missionNameSource | X | `const char*` |\n| serverName | serverName | X | `const char*` |\n| remoteExecutedOwner | remoteExecutedOwner | \"0\", number as string, no extra quotes | `const int16_t*` |\n| stackTrace | Stacktrace of the script that executed callExtension. diag_stacktrace | In format `LineNumber;SourceFile;ScopeName\\n` for each stack level. | `const RVContext_StackTrace*`<br>```cpp<br>struct RVContext_StackTrace<br>{<br>    struct StackTraceLine<br>    {<br>        // Line number in file (before preprocessing if preprocessed with line numbers)<br>        uint32_t lineNumber;<br>        // File offset in bytes from the start of the file (after preprocessing)<br>        uint32_t fileOffset;<br>        // Filepath to the source file<br>        const char* sourceFile;<br>        // scopeName set on that level<br>        const char* scopeName;<br>        // Complete fileContent of the sourceFile (after preprocessing, can be combined with fileOffset to find exact location)<br>        const char* fileContent;<br>    };<br>    StackTraceLine* lines = nullptr;<br>    uint32_t lineCount = 0;<br>};<br>``` |\n\nHere is a minimal example:\n\n```cpp\n#include <string>\n#include <vector>\n#include <iterator>\n#include <sstream>\n#include <iomanip>\n#include \"RVExtensionUtil.hpp\"\n\nstd::vector<std::string> contextInfo;\n\nextern \"C\"\n{\n    //--- Engine passed context\n    __declspec (dllexport) void __stdcall RVExtensionContext(const char **args, unsigned int argsCnt);\n}\n\n//--- name callExtension function\nvoid CALL_CONVENTION RVExtension(char *output, unsigned int outputSize, const char *function)\n{\n    //--- Not used here\n    (void)function;\n\n    if (!contextInfo.empty())\n    {\n        std::ostringstream oss;\n        const char qt = '\"';\n\n        for (auto it = contextInfo.begin(); it != contextInfo.end() - 1; ++it)\n            oss << std::quoted(*it, qt, qt) << \",\";\n        oss << std::quoted(contextInfo.back(), qt, qt);\n\n        //--- Send context info back\n        strncpy_s(output, outputSize, (\"[\" + oss.str() + \"]\").c_str(), _TRUNCATE);\n    }\n}\n\n//--- Context is executed first, copy it\nvoid CALL_CONVENTION RVExtensionContext(const char **args, unsigned int argsCnt)\n{\n    contextInfo.assign(args, std::next(args, argsCnt));\n}\n```\n\nHere is a example using `RVFeature_ContextArgumentsVoidPtr` combined with `RVFeature_ContextStackTrace`:\n\n```cpp\n#include <string>\n#include <vector>\n#include <iterator>\n#include <sstream>\n#include <iomanip>\n\n#include \"RVExtensionUtil.hpp\"\n\nstruct CallContext\n{\n    uint64_t steamId;\n    std::string fileSource;\n    std::string missionName;\n    std::string serverName;\n    int16_t remoteExecutedOwner;\n\n    struct StackTraceLine\n    {\n        // Line number in file (before preprocessing if preprocessed with line numbers)\n        uint32_t lineNumber;\n        // File offset in bytes from the start of the file (after preprocessing)\n        uint32_t fileOffset;\n        // Filepath to the source file\n        std::string sourceFile;\n        // scopeName set on that level\n        std::string scopeName;\n        // Complete fileContent of the sourceFile (after preprocessing, can be combined with fileOffset to find exact location)\n        std::string fileContent;\n    };\n\n    std::vector<StackTraceLine> stackTrace;\n} contextInfo;\n\nextern \"C\"\n{\n    //--- Engine passed context\n    __declspec (dllexport) void __stdcall RVExtensionContext(const void **args, unsigned int argsCnt);\n\n    __declspec(dllexport) uint64_t RVExtensionFeatureFlags = RVFeature_ContextArgumentsVoidPtr | RVFeature_ContextStackTrace;\n}\n\n//--- name callExtension function\nvoid CALL_CONVENTION RVExtension(char *output, unsigned int outputSize, const char *function)\n{\n    //--- Not used here\n    (void)function;\n\n    {\n        std::ostringstream oss;\n        const char qt = '\"';\n\n        oss << qt << contextInfo.steamId << qt << \",\";\n        oss << std::quoted(contextInfo.fileSource, qt, qt) << \",\";\n        oss << std::quoted(contextInfo.missionName, qt, qt) << \",\";\n        oss << std::quoted(contextInfo.serverName, qt, qt) << \",\";\n        oss << contextInfo.remoteExecutedOwner << \",\";\n\n        //--- Send context info back\n        strncpy_s(output, outputSize, (\"[\" + oss.str() + \"]\").c_str(), _TRUNCATE);\n    }\n}\n\n//--- Context is executed before RVExtension is called, and its values are only valid while inside RVExtensionContext function, so we need to copy them to use them later\nvoid CALL_CONVENTION RVExtensionContext(const void **args, unsigned int argsCnt)\n{\n    size_t index = 0;\n    contextInfo.steamId = *static_cast<const uint64_t*>(args[index++]);\n    contextInfo.fileSource = static_cast<const char*>(args[index++]);\n    contextInfo.missionName = static_cast<const char*>(args[index++]);\n    contextInfo.serverName = static_cast<const char*>(args[index++]);\n    contextInfo.remoteExecutedOwner = *static_cast<const int16_t*>(args[index++]);\n\n    if (RVExtensionFeatureFlags & RVFeature_ContextStackTrace) {\n        auto stackTrace = static_cast<const RVContext_StackTrace*>(argv[index++]);\n\n        std::transform(stackTrace->lines, stackTrace->lines + stackTrace->lineCount, std::back_inserter(contextInfo.stackTrace),\n            [](const RVContext_StackTrace::StackTraceLine& line)\n            {\n                    return CallContext::StackTraceLine\n                    {\n                        line.lineNumber,\n                        line.fileOffset,\n                        line.sourceFile,\n                        line.scopeName,\n                        line.fileContent // File content is large and expensive to copy\n                    };\n            });\n    }\n}\n```\n\n#### Manually Request Context\n\nHaving RVExtensionContext be called every time before every extension execution can be a waste of performance if the context is not required every time.\nThis automatic call can be disabled with the `RVFeature_ContextNoDefaultCall` Feature Flag. When the flag is set, the context must be requested.\n\nThe Game is exporting a function called `RVExtensionRequestContext` that can be used to request it, it will then call `RVExtensionContext`.\nThe `RVExtensionContext` execution will be completed when `RVExtensionRequestContext` returns.\nIf the `RVExtensionContext` exported function is not defined, requesting context will have no effect.\nThe `RVExtensionContext` call will respect the currently set `RVExtensionFeatureFlags`, `RVExtensionContext` can also be called multiple times, with different feature flags.\n\nHere is a example on how to fetch the RequestContext function, and how to use it:\n\n```cpp\n#include <string>\n#include <vector>\n#include <iterator>\n#include <sstream>\n#include <iomanip>\n\n#include \"RVExtensionUtil.hpp\"\n\nstruct CallContext\n{\n    uint64_t steamId;\n    std::string fileSource;\n    std::string missionName;\n    std::string serverName;\n    int16_t remoteExecutedOwner;\n} contextInfo;\n\nextern \"C\"\n{\n    //--- Engine passed context\n    __declspec (dllexport) void __stdcall RVExtensionContext(const void **args, unsigned int argsCnt);\n\n    __declspec(dllexport) uint64_t RVExtensionFeatureFlags = RVFeature_ContextArgumentsVoidPtr | RVFeature_ContextStackTrace | RVFeature_ContextNoDefaultCall;\n}\n\nRVExtensionRequestContextProc* requestContext;\n\nvoid CALL_CONVENTION RVExtensionVersion(char *output, unsigned int outputSize)\n{\n    requestContext = reinterpret_cast<RVExtensionRequestContextProc*>(FindRVFunction(\"RVExtensionRequestContext\"));\n    std::strncpy(output, \"Test-Extension v1.0\", outputSize - 1);\n}\n\n//--- name callExtension function\nvoid CALL_CONVENTION RVExtension(char *output, unsigned int outputSize, const char *function)\n{\n    //--- Not used here\n    (void)function;\n\n    requestContext(); // Request the context dynamically when we need it\n\n    {\n        std::ostringstream oss;\n        const char qt = '\"';\n\n        oss << qt << contextInfo.steamId << qt << \",\";\n        oss << std::quoted(contextInfo.fileSource, qt, qt) << \",\";\n        oss << std::quoted(contextInfo.missionName, qt, qt) << \",\";\n        oss << std::quoted(contextInfo.serverName, qt, qt) << \",\";\n        oss << contextInfo.remoteExecutedOwner << \",\";\n\n        //--- Send context info back\n        strncpy_s(output, outputSize, (\"[\" + oss.str() + \"]\").c_str(), _TRUNCATE);\n    }\n}\n\n//--- Its values are only valid while inside RVExtensionContext function, so we need to copy them to use them later\nvoid CALL_CONVENTION RVExtensionContext(const void **args, unsigned int argsCnt)\n{\n    size_t index = 0;\n    contextInfo.steamId = *static_cast<const uint64_t*>(args[index++]);\n    contextInfo.fileSource = static_cast<const char*>(args[index++]);\n    contextInfo.missionName = static_cast<const char*>(args[index++]);\n    contextInfo.serverName = static_cast<const char*>(args[index++]);\n    contextInfo.remoteExecutedOwner = *static_cast<const int16_t*>(args[index++]);\n}\n```\n\n### Extension UI (Arma 3 v2.20)\n\n> [!WARNING]\n> This is a experimental feature currently available for testing on Development-Branch. It is not yet decided whether this will become available in Stable branch.\n\nUsing RVExtensionRequestUI and CT_EXTENSION, Extensions can implement their own UI Controls.\n\n`RVExtensionRequestUI` receives a \"uiClass\" name that is defined in the UI Control's config, based on that the Extension should decide what UI to create internally.\nIt then has to fill out the passed `CExtensionControlInterface` structure.\nVerify that the `size` member of the passed structure, matches the expected size of the structure. If it does not, then a Game Update has changed the structure and additional code for backwards compatibility might be needed.\n\nA sample is available at https://github.com/arma3/RVExtensionImGui/blob/main/dllmain.cpp#L38\n\n`OnSizeChanged` will be called before the first `OnDraw` call. And every time that the control's size is changed via ctrlSetScale, ctrlSetPosition or others.\nThe passed size, will also be the size of the RenderTarget/Texture, used in `OnDraw`.\n\nInside OnDraw, DirectX functions must be used to draw into the RenderTarget/Texture. DirectX device and context are available via \"RVExtensionGData\", see Game Functions.\n\nThere are two methods for drawing.\n\nThe default is a RenderTarget, the Game will set the active RenderTarget before the call to \"OnDraw\", and every DirectX render command will render into it.\nThe alternative is setting `CExtensionControlInterface::Flags::DynamicTexture` into the \"flags\" member of the interface structure. Then the Game will provide a texture allocated with `D3D11_USAGE_DYNAMIC`, which can be mapped using `d3dDeviceContext->Map` and the `D3D11_MAP_WRITE_DISCARD` MapType, and the CPU can then write data into the mapped memory.\n\n### Extension Texture Source (Arma 3 v2.22)\n\nProcedural texture source (#TODO link to the wiki page, and also document it there)\n\n1. (rgb,width,height,nMips)extension(\"RVExtensionName\",\"UniqueName\", isAnimated, textureType)\n\nRVExtensionFillTextureSource will be called whenever the engine needs to fill the texture.\nThe Texture will be stored in video memory. It may be deleted from Video memory at any time, and when its visible on screen again it will be re-fetched from extension.\nThe extension fetch is done on a separate texture loading thread.\n\nIn video/animated mode, the texture is re-loaded/re-requested from the extension, every frame that the texture is visible on-screen. Be mindful of performance.\n\nWhen using animated textures that do not have to update every frame, it is recommended to implement RVExtensionHasTextureSourceUpdate.\nThat function will be called every frame to check if an update is required, and provides a performance advantage over updating the texture anyway even though it didn't change.\nTextures are only checked for updates, if it is visible on screen and considered for rendering.\n\n### Extension Signatures (Arma 3 v2.22)\n\nDLLs can be signed on windows with SignTool [\"How to sign an app package using SignTool\"](https://learn.microsoft.com/en-us/windows/win32/appxpkg/how-to-sign-a-package-using-signtool)\nSelf signed certificates are possible!\n\nallExtensions and \"ExtensionLoaded\" eventhandler provide signature information.\nImportant is the \"status\" field.\n\"badDigest\" means the signature does not match the file, likely the file was modified after it was signed, the signature cannot be trusted.\n\"expired\" means the signature is now expired, but it might still have been valid when the signature was created.\n\"revoked\" means the signature was revoked after the file was signed, likely because the certificate is compromised, it might not be trustworthy.\n\"partialChain\" means the full certificate chain could not be verified, while the known certificate is valid, it might have been created by a invalid authority, it might not be trustworthy.\n\"ok\" is set if there were no errors with the validation (Certificate is valid and matches the file, but we ignore expiry date, ignore self signed certificates ), it is better to check individual status values than just relying on this.\n\nThe recommended method to check is to make sure that \"badDigest\" is not set (The file was actually really signed by the certificate, and not modified afterwards), and that the certificate's public key matches the expected one (It was signed by the intended certificate, and not a self-signed certificate someone else created).\n\nSignature verification script can be set in config\n\n```cpp\nclass CfgExtensions {\n  class MyModName {\n    extensions[] = {\"extension1\", \"extension2\"}; // Whitelist, which extension names to run this check on (so its not ran on unrelated extensions we don't care about)\n    onCheckLoad = \"call MyMod_fnc_checkExtensionLoad\";\n    publicKey = \"308201...E4EA81730203010001\"; // The handler has access to the config class, so we can store some properties here)\n  }\n}\n```\n\n```sqf\nMyMod_fnc_checkExtensionLoad = {\n\n  if !(\"signatures\" in _this) exitWith { true; }; // This must be linux, it doesn't have signature reading. We'll just skip validation here\n\n  // must have signature, so signatures can't be empty\n  if ((_this get \"signatures\") isNotEqualTo []) exitWith { false; };\n\n  // Find a signature that is..\n  _validSignatureFound = (_this get \"signatures\"]) findIf {\n    (_x get \"publicKey\") == getText((_this get \"config\") >> \"publicKey\") // Signed by my key (Easy access to config that added the handler, so can store the key there)\n    &&\n    !(\"expired\" in (_x get \"status\")) // Not expired\n    &&\n    !(\"badDigest\" in (_x get \"status\")) // The signature actually matches the binary (it was not edited after signing)\n  };\n\n  _validSignatureFound != -1 // If signature found we load, otherwise we block\n}\n```\n\n## External References\n\nThe [Community-Lead Examples Repository](https://github.com/arma3/RV-Extension-Examples) can be used for further examples which go more into detail.\nTo get your example on there, just fork the repository and create a Pull-Request afterwards.\n\n##### C/C++\n\nKillzone_Kid's \"How To Make an Arma Extension\" tutorials:\n\n[Part 1](http://killzonekid.com/arma-scripting-tutorials-how-to-make-arma-extension-part-1/) [Part 2](http://killzonekid.com/arma-scripting-tutorials-how-to-make-arma-extension-part-2/) [Part 3](http://killzonekid.com/arma-scripting-tutorials-how-to-make-arma-extension-part-3/) [Part 4](http://killzonekid.com/arma-scripting-tutorials-how-to-make-arma-extension-part-4/)\n\n##### C\\#\n\n- [Write An Arma Extension In C#/.NET](http://maca134.co.uk/tutorial/write-an-arma-extension-in-c-sharp-dot-net/)\n\n##### Python\n\n- [Pythia - an Arma 3 extension that lets you write Python extensions for the game](https://github.com/overfl0/Pythia)\n\n##### Rust\n\n- [arma-rs - Crate for easily and quickly building powerful extensions](https://github.com/BrettMayson/arma-rs)\n\n##### Golang\n\n- [a3go](https://github.com/indig0fox/a3go), an Arma Go library by IndigoFox - for Go **1.20**\n- [Armago](https://github.com/code34/armago_x64/), an Arma template extension - for Go ***1.16***\n\n##### External Applications\n\n- [KK's Arma 3 Extension Tester](http://killzonekid.com/arma-3-extension-tester-callextension-exe-callextension_x64-exe/)\n- [KK's Arma 3 Extension Tester v2.0 with callback](http://killzonekid.com/callextension-v2-0/)\n- [SQF-VM - Community implementation of SQF](https://github.com/SQFvm/vm/releases)","sections":[{"heading":null,"level":1,"markdown":"> [!WARNING]\n> If you came here because you have been told to install an extension, be warned! Extensions are just like executables. They may contain viruses etc. More on that, in the FAQ section."},{"heading":"Historical","level":2,"markdown":"- Arma 2 v1.61 The first stable version of this functionality arrived.\n- Arma 3 v1.18 Linux (server) support was introduced.\n- Arma 3 v1.68 The ability to pass multiple arguments to callExtension and the interface `RVExtensionArgs` got added.\n- Arma 3 v1.70 `RVExtensionVersion` became available. It is called when the extension loads. Will be printed into the RPT log.\n- Arma 3 v1.96 Callbacks became available.\n- Arma 3 v2.12 Context became available.\n- Arma 3 v2.18 `RVExtensionFeatureFlags` and Game Functions became available.\n- Arma 3 v2.20 `Extension UI` became available.\n- Arma 3 v2.22 `Extension Texture Source` became available."},{"heading":"FAQ","level":2,"markdown":""},{"heading":"How to use Extensions?","level":5,"markdown":"Extensions can be accessed using callExtension.\n\n> [!NOTE]\n> The exact way about how to use an extension is up to the developer of said extension."},{"heading":"Where to put Extensions?","level":5,"markdown":"Arma 3 will look at the following places to find your extension files:\n\n- In all loaded mods root folders (for example: `..\\arma3\\@ModXYZ\\yourextension.dll`).\n- In your Arma 3 installation folder (for example: `..\\arma3\\yourextension.dll`).\n\nAt best, you place your extensions inside some mod folder and load it.\nAt worst, you just throw it where the Arma 3 executable is located.\n\n*Extensions may require other libraries and runtimes to be installed on the computer it is to run on.*\nConsult the authors installation manual if it exists."},{"heading":"What are Extensions?","level":5,"markdown":"Extensions are *Dynamic Link Library* (DLL) files written in, for example, C.\n\nLinux systems use a different file type: *Shared Object* (SO)."},{"heading":"What do I have to look out for?","level":5,"markdown":"Extensions fall under the same rules as executable files.\nThey may require additional runtime libraries or cause the game to crash.\n\n> [!WARNING]\n> Extensions can contain malicious code!\n> Be sure to only use extensions from reliable developers, obtained through reliable sources!"},{"heading":"How to know which extensions can be trusted?","level":5,"markdown":"Theoretically, never trust anything! Including Arma and your OS itself.\nTechnically however, this would make a boring experience, as all you would be left with is an expensive heater.\n\nThus a better rule is: If the are sources freely available, you probably can trust the extension.\nEven if you cannot read the source code, chances are that somebody out there can and would find something odd.\nThat will not protect you!\nDevelopers might add malicious code that is not exposed in the available source files, they not even need to be aware of this.\nThey may be infected already and the virus is just writing itself into the compiled extension."},{"heading":"When I call the extension method, Arma freezes.","level":5,"markdown":"This is most likely caused by the extension taking too much time.\nDue to the low-level nature of extensions, an extension cannot be suspended like scheduled scripts can be.\nYou should notify the developer of said extension about this freezing issue or use the extension less extensively."},{"heading":"Can I join BattlEye protected servers with my extension?","level":5,"markdown":"If the extension is whitelisted by BattlEye, it will be allowed to load - see Getting your extension whitelisted by BattlEye.\nIf it is not, it just will be blocked causing any callExtension attempt to fail.\n\nGenerally speaking, joining BattlEye-protected servers should be no problem with or without extensions.\n\nIf in doubt, ask the extension's author."},{"heading":"Can I use extensions in my mission?","level":5,"markdown":"Yes, you can use extensions in your missions, but you should consider if it is worth it.\nUsing extensions requires that all hosts (servers and clients) which use the extension have it installed. Extensions are not packed into the mission pbo.\n\n> [!NOTE]\n> If only the server is using the extension, the clients do not need it."},{"heading":"What do i have to look out for when deploying my extension?","level":5,"markdown":"Extensions have to be provided for 32bit and 64bit Arma.\nWhen you deploy your extension, you thus have to build it twice.\nOnce for the 32bit support and once for the 64bit support.\n\nAfter you are done, copy both output files to some location and add a `_x64.dll` suffix to the 64bit file.\n\nExample:\n\n- Windows:\n  - `MyExtension.dll` (32bit)\n  - `MyExtension_x64.dll` (64bit)\n- Linux:\n  - `MyExtension.so` (32bit)\n  - `MyExtension_x64.so` (64bit)"},{"heading":"Creating Extensions","level":2,"markdown":""},{"heading":"Preamble","level":3,"markdown":"Before we start, ask yourself the following questions:\n\n- is SQF unable to fulfill your needs?\n- Making an extension requires knowledge of a programming language. Are you capable of performing that programming task?\n- Why should a user trust you and use your extension?\n- Is it worth adding an extension for the feature you want to implement?\n\nIf the answer to any question was no, you probably should not proceed to create an extension but rather use plain SQF."},{"heading":"Getting Started","level":3,"markdown":"At first, we have to choose what language we want to use.\n\nThis wiki currently covers the following: **C**, **C++**, **C#**"},{"heading":"Preparations","level":4,"markdown":""},{"heading":"C/C++","level":5,"markdown":"Create a new library project in the IDE of your choice."},{"heading":"C\\#","level":5,"markdown":"C# requires you to install additional dependencies to work out of the box.\nThe snippets in here all are using [DllExport](https://www.nuget.org/packages/UnmanagedExports) ([sources](https://github.com/3F/DllExport)) which can be installed using NuGet.\n\nCreate a new Class Library project in the IDE of your choice."},{"heading":"Available Interfaces","level":3,"markdown":"The Extension is required to contain at least the entry point in a form of a method named RVExtension or RVExtensionArgs.\nEverything else is optional.\n\nThe actual exports need to look like this (Windows only):\n\n**32-bit requires usage of [decorated names](https://docs.microsoft.com/en-us/cpp/build/reference/decorated-names?view=vs-2019) following the `__stdcall` convention**\n\n- `_RVExtension@12`\n- `_RVExtensionArgs@20`\n- `_RVExtensionVersion@8`\n\n**64-bit**\n\n- `RVExtension`\n- `RVExtensionArgs`\n- `RVExtensionVersion`\n\nIf you use the Visual C++ compiler, this should work out of the box. But other compilers might require some manual work."},{"heading":"C/C++","level":4,"markdown":""},{"heading":"Visual C++","level":5,"markdown":"```c\n    //--- Called by Engine on extension load\n    __declspec (dllexport) void __stdcall RVExtensionVersion(char *output, unsigned int outputSize);\n    //--- STRING callExtension STRING\n    __declspec (dllexport) void __stdcall RVExtension(char *output, unsigned int outputSize, const char *function);\n    //--- STRING callExtension ARRAY\n    __declspec (dllexport) int __stdcall RVExtensionArgs(char *output, unsigned int outputSize, const char *function, const char **argv, unsigned int argc);\n    //--- Extension Callback\n    __declspec (dllexport) void __stdcall RVExtensionRegisterCallback(int(*callbackProc)(char const *name, char const *function, char const *data));\n    //--- Engine passed context [steamID, fileSource, missionSourceName, serverName] since Arma 3 v2.12\n    __declspec (dllexport) void __stdcall RVExtensionContext(const char **argv, unsigned int argc);\n    //--- Request creation of UI element\n    __declspec (dllexport) bool __stdcall RVExtensionRequestUI(const char *uiClass, void *interfaceStruct);\n    //--- Request filling in a Extension Texture Source\n    __declspec (dllexport) void __stdcall RVExtensionFillTextureSource(const char* uniqueName, uint16_t width, uint16_t height, uint16_t pitch, uint8_t mipLevel, uint32_t format, void* buffer);\n    //--- Check if a animated Extension Texture Source should be updated\n    __declspec (dllexport) bool __stdcall RVExtensionHasTextureSourceUpdate(const char* uniqueName);\n```"},{"heading":"GCC","level":5,"markdown":"```c\n    //--- Called by Engine on extension load\n    __attribute__((dllexport)) void RVExtensionVersion(char *output, unsigned int outputSize);\n    //--- STRING callExtension STRING\n    __attribute__((dllexport)) void RVExtension(char *output, unsigned int outputSize, const char *function);\n    //--- STRING callExtension ARRAY\n    __attribute__((dllexport)) int RVExtensionArgs(char *output, unsigned int outputSize, const char *function, const char **argv, unsigned int argc);\n    //--- Extension Callback\n    __attribute__((dllexport)) void RVExtensionRegisterCallback(int(*callbackProc)(char const *name, char const *function, char const *data));\n    //--- Engine passed context [steamID, fileSource, missionSourceName, serverName] since Arma 3 v2.12\n    __attribute__((dllexport)) void RVExtensionContext(const char **argv, unsigned int argc);\n```\n\nSee GCC documentation on [\"How to use the new C++ visibility support\"](https://gcc.gnu.org/wiki/Visibility)"},{"heading":"C\\#","level":4,"markdown":"> [!NOTE]\n> You might need to install [UnmanagedExports](https://www.nuget.org/packages/UnmanagedExports). You might also need to additionally set `WIN64` and `WIN32` in project properties.\n>\n> Go to: `Project->Properties->Build tab->Conditional compilation symbols`, add `WIN32` for your `x86` platform and `WIN64` for your `x64` platform. Save.\n>\n> Rename resulting `WIN64` `dllname.dll` to `dllname_x64.dll`.\n\n> [!NOTE]\n> If you intend to use .NET instead of .NET Framework, the **UnmanagedExports** package above might not work properly. Use [DllExport](https://github.com/3F/DllExport) instead\n\n```c#\n        /// <summary>\n        /// Gets called when Arma starts up and loads all extension.\n        /// It's perfect to load in static objects in a separate thread so that the extension doesn't needs any separate initalization\n        /// </summary>\n        /// <param name=\"output\">The string builder object that contains the result of the function</param>\n        /// <param name=\"outputSize\">The maximum size of bytes that can be returned</param>\n#if WIN64\n        [DllExport(\"RVExtensionVersion\", CallingConvention = CallingConvention.Winapi)]\n#else\n        [DllExport(\"_RVExtensionVersion@8\", CallingConvention = CallingConvention.Winapi)]\n#endif\n        public static void RvExtensionVersion(StringBuilder output, unsigned int outputSize) { }\n\n        /// <summary>\n        /// The entry point for the default callExtension command.\n        /// </summary>\n        /// <param name=\"output\">The string builder object that contains the result of the function</param>\n        /// <param name=\"outputSize\">The maximum size of bytes that can be returned</param>\n        /// <param name=\"function\">The string argument that is used along with callExtension</param>\n#if WIN64\n        [DllExport(\"RVExtension\", CallingConvention = CallingConvention.Winapi)]\n#else\n        [DllExport(\"_RVExtension@12\", CallingConvention = CallingConvention.Winapi)]\n#endif\n        public static void RvExtension(StringBuilder output, unsigned int outputSize,\n            [MarshalAs(UnmanagedType.LPStr)] string function) { }\n\n        /// <summary>\n        /// The entry point for the callExtensionArgs command.\n        /// </summary>\n        /// <param name=\"output\">The string builder object that contains the result of the function</param>\n        /// <param name=\"outputSize\">The maximum size of bytes that can be returned</param>\n        /// <param name=\"function\">The string argument that is used along with callExtension</param>\n        /// <param name=\"args\">The args passed to callExtension as a string array</param>\n        /// <param name=\"argsCount\">The size of the string array args</param>\n        /// <returns>The result code</returns>\n#if WIN64\n        [DllExport(\"RVExtensionArgs\", CallingConvention = CallingConvention.Winapi)]\n#else\n        [DllExport(\"_RVExtensionArgs@20\", CallingConvention = CallingConvention.Winapi)]\n#endif\n        public static int RvExtensionArgs(StringBuilder output, unsigned int outputSize,\n            [MarshalAs(UnmanagedType.LPStr)] string function,\n            [MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.LPStr, SizeParamIndex = 4)] string[] args, unsigned int argCount) { }\n```"},{"heading":"Getting your extension whitelisted by BattlEye (Arma 3 v1.46)","level":3,"markdown":"Since Arma 3 v1.46, client extensions need to be whitelisted in order to get loaded.\n\nThe [SITREP #00109](https://dev.arma3.com/post/sitrep-00109) said the following on that topic:\n\n```\nThe enhanced BattlEye protection in 1.46 will require extensions to be white-listed, or their access to the game's processes will be blocked.\nIn order to get your extension white-listed, please contact BattlEye support via its web form or support[at]battleye.com.\nBe sure to include the file name, a description and possibly a download link.\n```\n\nThis boils down to the following tasks to get your extension whitelisted:\n\n1. Upload the extension in question somewhere.\n1. Open the [BattlEye Contact Page](http://www.battleye.com/contact/).\n1. Pick the topic `Other requests` (may change in future, select the most appropriate).\n1. Fill in your Name and E-Mail.\n1. Set the subject to something like `Arma extension whitelisting`.\n1. Add link to your extension from step 1 (you may include other information too) into the Message.\n\nAfter a certain time, your extension should be whitelisted."},{"heading":"Deploying extensions","level":3,"markdown":"To correctly deploy your extensions, you have to make sure to have a 32bit and a 64bit variant of your extension.\nThe reason for this is because 64bit Arma cannot load 32bit extensions and vice versa.\n\nYou also should create an addon package (even if you only deploy the actual extension binaries) + add the documentation for your extension.\n\nAn example might be structured like this:\n\n- `/@MyExtension/`\n  - `/MyExtension.dll`\n  - `/MyExtension_x64.dll`\n  - `/docs/`\n    - `/MyExtension_Documentation.txt`\n\nYou may also want to provide convenience SQF functions to access your extension."},{"heading":"Minimum Viable Example","level":3,"markdown":"The following examples will copy the input into the output. They are provided to show some practical example.\n\n**C (Visual C++)**\n\n```c\n__declspec(dllexport) void __stdcall RVExtension(char *output, unsigned int outputSize, const char *function);\n__declspec(dllexport) int __stdcall RVExtensionArgs(char *output, unsigned int outputSize, const char *function, const char **argv, unsigned int argc);\n__declspec(dllexport) void __stdcall RVExtensionVersion(char *output, unsigned int outputSize);\n\nint strncpy_safe(char *output, const char *src, int size)\n{\n    int i;\n    size--;\n    for (i = 0; i < size && src[i] != '\\0'; i++)\n    {\n        output[i] = src[i];\n    }\n    output[i] = '\\0';\n    return i;\n}\n\nvoid __stdcall RVExtension(char *output, unsigned int outputSize, const char *function)\n{\n    strncpy_safe(output, function, outputSize);\n}\n\nint __stdcall RVExtensionArgs(char *output, unsigned int outputSize, const char *function, const char **argv, unsigned int argc)\n{\n    int index = 0;\n    for (int i = 0; i < argc && index < outputSize; i++)\n    {\n        index += strncpy_safe(output + index, argv[i], outputSize - 1 - index);\n    }\n    return 0;\n}\n\nvoid __stdcall RVExtensionVersion(char *output, unsigned int outputSize)\n{\n    strncpy_safe(output, \"Test-Extension v1.0\", outputSize);\n}\n```\n\n**C (GCC)**\n\n```c\n__attribute__((dllexport)) void RVExtension(char *output, unsigned int outputSize, const char *function);\n__attribute__((dllexport)) int RVExtensionArgs(char *output, unsigned int outputSize, const char *function, const char **argv, unsigned int argc);\n__attribute__((dllexport)) void RVExtensionVersion(char *output, unsigned int outputSize);\n\nint strncpy_safe(char *output, const char *src, int size)\n{\n    int i;\n    size--;\n    for (i = 0; i < size && src[i] != '\\0'; i++)\n    {\n        output[i] = src[i];\n    }\n    output[i] = '\\0';\n    return i;\n}\n\nvoid RVExtension(char *output, unsigned int outputSize, const char *function)\n{\n    strncpy_safe(output, function, outputSize);\n}\n\nint RVExtensionArgs(char *output, unsigned int outputSize, const char *function, const char **argv, unsigned int argc)\n{\n    int index = 0;\n    for (int i = 0; i < argc && index < outputSize; i++)\n    {\n        index += strncpy_safe(output + index, argv[i], outputSize - 1 - index);\n    }\n    return 0;\n}\n\nvoid RVExtensionVersion(char *output, unsigned int outputSize)\n{\n    strncpy_safe(output, \"Test-Extension v1.0\", outputSize);\n}\n```\n\n**C++ (Visual C++)**\n\n```cpp\n#include <string>\n#include <cstring>\n#include <sstream>\nextern \"C\"\n{\n    __declspec(dllexport) void __stdcall RVExtension(char *output, unsigned int outputSize, const char *function);\n    __declspec(dllexport) int __stdcall RVExtensionArgs(char *output, unsigned int outputSize, const char *function, const char **argv, unsigned int argc);\n    __declspec(dllexport) void __stdcall RVExtensionVersion(char *output, unsigned int outputSize);\n}\nvoid __stdcall RVExtension(char *output, unsigned int outputSize, const char *function)\n{\n    std::strncpy(output, function, outputSize - 1);\n}\n\nint __stdcall RVExtensionArgs(char *output, unsigned int outputSize, const char *function, const char **argv, unsigned int argc)\n{\n    std::stringstream sstream;\n    for (int i = 0; i < argc; i++)\n    {\n        sstream << argv[i];\n    }\n    std::strncpy(output, sstream.str().c_str(), outputSize - 1);\n    return 0;\n}\n\nvoid __stdcall RVExtensionVersion(char *output, unsigned int outputSize)\n{\n    std::strncpy(output, \"Test-Extension v1.0\", outputSize - 1);\n}\n```\n\n**C++ (GCC)**\n\n```cpp\n#include <string>\n#include <cstring>\n#include <sstream>\nextern \"C\"\n{\n    __attribute__((dllexport)) void RVExtension(char *output, unsigned int outputSize, const char *function);\n    __attribute__((dllexport)) int RVExtensionArgs(char *output, unsigned int outputSize, const char *function, const char **argv, unsigned int argc);\n    __attribute__((dllexport)) void RVExtensionVersion(char *output, unsigned int outputSize);\n}\nvoid RVExtension(char *output, unsigned int outputSize, const char *function)\n{\n    std::strncpy(output, function, outputSize - 1);\n}\n\nint RVExtensionArgs(char *output, unsigned int outputSize, const char *function, const char **argv, unsigned int argc)\n{\n    std::stringstream sstream;\n    for (int i = 0; i < argc; i++)\n    {\n        sstream << argv[i];\n    }\n    std::strncpy(output, sstream.str().c_str(), outputSize - 1);\n    return 0;\n}\n\nvoid RVExtensionVersion(char *output, unsigned int outputSize)\n{\n    std::strncpy(output, \"Test-Extension v1.0\", outputSize - 1);\n}\n```\n\n**C#**\n\n```c#\nusing System.Text;\nusing System.Runtime.InteropServices;\nusing RGiesecke.DllExport;\n\nclass MyExtension {\n\n#if WIN64\n        [DllExport(\"RVExtensionVersion\", CallingConvention = CallingConvention.Winapi)]\n#else\n        [DllExport(\"_RVExtensionVersion@8\", CallingConvention = CallingConvention.Winapi)]\n#endif\n        public static void RvExtensionVersion(StringBuilder output, unsigned int outputSize)\n        {\n            output.Append(\"Test-Extension v1.0\");\n        }\n\n#if WIN64\n        [DllExport(\"RVExtension\", CallingConvention = CallingConvention.Winapi)]\n#else\n        [DllExport(\"_RVExtension@12\", CallingConvention = CallingConvention.Winapi)]\n#endif\n        public static void RvExtension(StringBuilder output, unsigned int outputSize,\n            [MarshalAs(UnmanagedType.LPStr)] string function)\n        {\n            output.Append(function);\n        }\n\n#if WIN64\n        [DllExport(\"RVExtensionArgs\", CallingConvention = CallingConvention.Winapi)]\n#else\n        [DllExport(\"_RVExtensionArgs@20\", CallingConvention = CallingConvention.Winapi)]\n#endif\n        public static int RvExtensionArgs(StringBuilder output, unsigned int outputSize,\n            [MarshalAs(UnmanagedType.LPStr)] string function,\n            [MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.LPStr, SizeParamIndex = 4)] string[] args, unsigned int argCount)\n        {\n            foreach(var arg in args)\n            {\n                output.Append(arg);\n            }\n            return 0;\n        }\n}\n```\n\n**C# (w/ callback)**\n\n```c#\nusing System.Text;\nusing System.Runtime.InteropServices;\nusing RGiesecke.DllExport;\n\nclass MyExtension {\n\n    public static ExtensionCallback callback;\n    public delegate int ExtensionCallback([MarshalAs(UnmanagedType.LPStr)] string name, [MarshalAs(UnmanagedType.LPStr)] string function, [MarshalAs(UnmanagedType.LPStr)] string data);\n\n#if WIN64\n        [DllExport(\"RVExtensionRegisterCallback\", CallingConvention = CallingConvention.Winapi)]\n#else\n        [DllExport(\"_RVExtensionRegisterCallback@4\", CallingConvention = CallingConvention.Winapi)]\n#endif\n        public static void RVExtensionRegisterCallback([MarshalAs(UnmanagedType.FunctionPtr)] ExtensionCallback func)\n        {\n            callback = func;\n        }\n\n#if WIN64\n        [DllExport(\"RVExtensionVersion\", CallingConvention = CallingConvention.Winapi)]\n#else\n        [DllExport(\"_RVExtensionVersion@8\", CallingConvention = CallingConvention.Winapi)]\n#endif\n        public static void RvExtensionVersion(StringBuilder output, unsigned int outputSize)\n        {\n            output.Append(\"Test-Extension v1.0\");\n        }\n\n#if WIN64\n        [DllExport(\"RVExtension\", CallingConvention = CallingConvention.Winapi)]\n#else\n        [DllExport(\"_RVExtension@12\", CallingConvention = CallingConvention.Winapi)]\n#endif\n        public static void RvExtension(StringBuilder output, unsigned int outputSize,\n            [MarshalAs(UnmanagedType.LPStr)] string function)\n        {\n            output.Append(function);\n        }\n\n#if WIN64\n        [DllExport(\"RVExtensionArgs\", CallingConvention = CallingConvention.Winapi)]\n#else\n        [DllExport(\"_RVExtensionArgs@20\", CallingConvention = CallingConvention.Winapi)]\n#endif\n        public static int RvExtensionArgs(StringBuilder output, unsigned int outputSize,\n            [MarshalAs(UnmanagedType.LPStr)] string function,\n            [MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.LPStr, SizeParamIndex = 4)] string[] args, unsigned int argCount)\n        {\n            foreach(var arg in args)\n            {\n                output.Append(arg);\n            }\n            return 0;\n        }\n}\n```"},{"heading":"Other Languages","level":3,"markdown":"This section contains other languages which may or may not work. Finding out requirements etc. is up to you.\n\n> [!NOTE]\n> Help Wanted!\n\nIf you know any of the following languages, consider editing this page to add the language to the upper list properly.\n\n**Delphi/Pascal**\n\n```delphi\nlibrary dllTest;\n\nuses\n  SysUtils;\n\n{Return value is not used.}\nprocedure RVExtension(toArma: PAnsiChar; outputSize: Integer; fromArma: PAnsiChar); stdcall; export;\nbegin\n  StrCopy(toArma, fromArma);\nend;\n\nexports\n  { 32-bit }\n  RVExtension name '_RVExtension@12';\n  { 64-bit }\n  RVExtension name 'RVExtension';\nbegin\nend;\n```\n\n**D**\n\n```d\nimport std.c.windows.windows;\nimport core.sys.windows.dll;\n\n__gshared HINSTANCE g_hInst;\n\nextern (Windows) BOOL DllMain(HINSTANCE hInstance, ULONG ulReason, LPVOID pvReserved) {\n    final switch (ulReason) {\n        case DLL_PROCESS_ATTACH:\n            g_hInst = hInstance;\n            dll_process_attach(hInstance, true);\n            break;\n\n        case DLL_PROCESS_DETACH:\n            dll_process_detach(hInstance, true);\n            break;\n\n        case DLL_THREAD_ATTACH:\n            dll_thread_attach(true, true);\n            break;\n\n        case DLL_THREAD_DETACH:\n            dll_thread_detach(true, true);\n            break;\n    }\n\n    return true;\n}\n\nimport std.conv;\nimport std.exception;\n\nexport extern (Windows) void RVExtension(char* output, int output_size, const char* cinput) {\n    auto dinput = to!string(cinput);\n    auto doutput = output[0 .. output_size];\n    string result;\n\n    // ...\n\n    enforce(result.length <= output_size, \"Output length too long\");\n    doutput[0 .. result.length] = result[];\n    doutput[result.length] = '\\0';\n}\n```"},{"heading":"Utility Header","level":3,"markdown":"This C++ header file can be used to define most of the Extension interface. It works on Windows and Linux.\nIt will be used in the following examples to keep them shorter. It also contains useful definitions for Feature Flags and Context -\nIts name is RVExtensionUtils.hpp\n\n```cpp\n#pragma once\n#include <cstdint>\n#ifdef _WIN32\n#include <windows.h> // GetModuleHandle, GetProcAddress\n#else // Linux\n#include <dlfcn.h> // dlopen, dlclose, dlsym\n#endif\n\n// bitset\nenum DllExtensionFeatureFlags : uint64_t\n{\n    RVFeature_ContextArgumentsVoidPtr = 1 << 0,\n    RVFeature_ContextStackTrace = 1 << 1,\n    RVFeature_ContextNoDefaultCall = 1 << 2,\n    RVFeature_ArgumentNoEscapeString = 1 << 3,\n};\n\nstruct RVContext_StackTrace\n{\n    struct StackTraceLine\n    {\n        // Line number in file (before preprocessing if preprocessed with line numbers)\n        uint32_t lineNumber;\n        // File offset in bytes from the start of the file (after preprocessing)\n        uint32_t fileOffset;\n        // Filepath to the source file\n        const char* sourceFile;\n        // scopeName set on that level\n        const char* scopeName;\n        // Complete fileContent of the sourceFile (after preprocessing, can be combined with fileOffset to find exact location)\n        const char* fileContent;\n    };\n\n    StackTraceLine* lines = nullptr;\n    uint32_t lineCount = 0;\n};\n\nstruct ProjectionViewTransform\n{\n    typedef float M4x4[4][4];\n\n    M4x4 projection;\n    M4x4 view;\n};\n\nstruct CExtensionControlInterface\n{\n    CExtensionControlInterface()\n    {\n        memset(this, 0, sizeof(CExtensionControlInterface));\n    }\n\n    uint32_t size = sizeof(CExtensionControlInterface);\n\n    enum Flags : uint32_t\n    {\n        None = 0,\n        DynamicTexture = 1 << 0, // If set we do not set render target, and instead pass a texture that can be mapped with D3D11_MAP_WRITE_DISCARD\n        TextureFormatBGRA = 1 << 1 // If set the texture is BGRA, if not set its ARGB\n    };\n    Flags flags = Flags::None;\n\n    // This is passed to every callback\n    void* context;\n\n    // [required]\n    // Render into currently active target\n    // target is a ID3D11RenderTargetView if Flags::DynamicTexture is not set\n    // target is a ID3D11Texture2D if Flags::DynamicTexture is set\n    void (*OnDraw)(void* context, float alpha, void* target);\n    // [required]\n    // Called after \"Unload\" display EH and \"Destroy\" control EH\n    // This is the last call before the control is destroyed. Release all your resources\n    void (*OnDestroy)(void* context, int code);\n\n    // optional\n    // Called before OnDraw, if the control (including backing render target) size has changed\n    void (*OnSizeChanged)(void* context, unsigned int width, unsigned int height);\n\n    // Interactions\n    // Optional all of these\n\n    // Return if you accept the focus\n    bool (*OnSetFocus)(void* context, bool focus);\n\n    void (*OnLButtonDown)(void* context, float x, float y);\n    void (*OnLButtonUp)(void* context, float x, float y);\n    void (*OnLButtonClick)(void* context, float x, float y);\n    void (*OnLButtonDblClick)(void* context, float x, float y);\n    void (*OnRButtonDown)(void* context, float x, float y);\n    void (*OnRButtonUp)(void* context, float x, float y);\n    void (*OnRButtonClick)(void* context, float x, float y);\n    void (*OnMouseMove)(void* context, float x, float y);\n    // Return if the input was handled, if not it will be passed to the parent controlsgroup/display\n    bool (*OnMouseZChanged)(void* context, float dz);\n\n    void (*OnMouseEnter)(void* context, float x, float y);\n    void (*OnMouseExit)(void* context, float x, float y);\n\n    // Return if the key was handled, if not it will be passed to the parent controlsgroup/display\n    bool (*OnKeyDown)(void* context, int dikCode);\n    bool (*OnKeyUp)(void* context, int dikCode);\n    bool (*OnChar)(void* context, unsigned nChar, unsigned nRepCnt, unsigned nFlags);\n};\n\n#ifdef _D3D11_CONSTANTS // Only available with d3d11.h header\nstruct RVExtensionRenderInfo\n{\n    ID3D11Device*            d3dDevice;\n    ID3D11DeviceContext*     d3dDeviceContext;\n};\n\nstruct RVExtensionGraphicsLockGuard\n{\n    //! Call this when done with the lock, forgetting to call this will freeze the game.\n    virtual void ReleaseLock() const = 0;\n    virtual ~RVExtensionGraphicsLockGuard() = default;\n};\n#endif\n\nenum TextureFormat : uint32_t\n{\n    PacAI88 = 1,\n    PacARGB8888 = 5\n};\n\n#ifdef _WIN32\n#define DLLEXPORT __declspec (dllexport)\n#define CALL_CONVENTION __stdcall\n#else // Linux\n#if __GNUC__ >= 4\n#define DLLEXPORT __attribute__ ((visibility (\"default\")))\n#else\n#define DLLEXPORT __attribute__((dllexport))\n#endif\n\n#define CALL_CONVENTION\n#endif\n\nextern \"C\"\n{\n    typedef int (CALL_CONVENTION RVExtensionCallbackProc)(char const* name, char const* function, char const* data);\n    typedef void CALL_CONVENTION RVExtensionRequestContextProc();\n    typedef void (CALL_CONVENTION RVGetProjectionViewTransformProc)(ProjectionViewTransform& pvTransform);\n    typedef uint64_t (CALL_CONVENTION RVExtensionLoadFile)(const char* path, void* buffer, uint64_t bufferSize);\n\n#ifdef _D3D11_CONSTANTS // Only available with d3d11.h header\n    typedef RVExtensionGraphicsLockGuard* (CALL_CONVENTION RVExtensionGLockProc)();\n    typedef void (CALL_CONVENTION RVExtensionGSetWHkProc)(bool (CALL_CONVENTION *newHook)(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam));\n    typedef void (CALL_CONVENTION RVExtensionGSetResetHook)(void (CALL_CONVENTION *newHook)());\n    typedef void (CALL_CONVENTION RVExtensionGGetR2TTexture)(const char* name, void (CALLCONVENTION *onResult)(ID3D11Texture2D* texture, ID3D11ShaderResourceView* resView, ID3D11RenderTargetView* rtView, uint32_t width, uint32_t height, uint32_t lastSeenFrameID, const char* name));\n#endif\n\n    //--- Called by Engine on extension load\n    DLLEXPORT void CALL_CONVENTION RVExtensionVersion(char* output, unsigned int outputSize);\n    //--- STRING callExtension STRING\n    DLLEXPORT void CALL_CONVENTION RVExtension(char* output, unsigned int outputSize, const char* function);\n    //--- STRING callExtension ARRAY\n    DLLEXPORT int CALL_CONVENTION RVExtensionArgs(char* output, unsigned int outputSize, const char* function, const char** argv, unsigned int argc);\n    //--- Called by Engine on extension load to pass RVExtensionCallbackProc to it\n    DLLEXPORT void CALL_CONVENTION RVExtensionRegisterCallback(RVExtensionCallbackProc* callbackProc);\n    //--- Request creation of UI element\n    DLLEXPORT bool CALL_CONVENTION RVExtensionRequestUI(const char* uiClass, CExtensionControlInterface* interfaceStruct);\n    //--- Request filling in a Extension Texture Source\n    DLLEXPORT void CALL_CONVENTION RVExtensionFillTextureSource(const char* uniqueName, uint16_t width, uint16_t height, uint16_t pitch, uint8_t mipLevel, TextureFormat format, void* buffer);\n    DLLEXPORT bool CALL_CONVENTION RVExtensionHasTextureSourceUpdate(const char* uniqueName);\n}\n\n//--- Finds a game exported function by its name\ninline const void* FindRVFunction(const char* name)\n{\n#ifdef _WIN32\n    return reinterpret_cast<void*>(GetProcAddress(GetModuleHandle(nullptr), name));\n#else // Linux\n    auto armaHandle = dlopen(nullptr, RTLD_LAZY | RTLD_NOLOAD);\n    auto result = dlsym(armaHandle, name);\n    dlclose(armaHandle);\n    return result;\n#endif\n}\n```"},{"heading":"Feature Flags (Arma 3 v2.18)","level":3,"markdown":"Feature flags can change the behavior of the Extension.\nTheir main purpose is to only enable certain features when they are needed, so that Extensions that don't need them don't have to pay the cost for it.\nMultiple flags can be combined.\nTo set no flags, either 0 must be specified or the RVExtensionFeatureFlags variable must be omitted.\n\nFlags are checked before every RVExtension/RVExtensionArgs execution. They can be changed at runtime between executions, but make sure that the effects of changed flags are handled correctly.\n\nExisting flags\n\n- `RVFeature_ContextArgumentsVoidPtr` - RVExtensionContext takes `const void**` as argument, instead of the default `const char**`, and arguments will be passed in their custom types - see Context Arguments.\n- `RVFeature_ContextStackTrace` - RVExtensionContext will retrieve a full Stacktrace - see Context.\n- `RVFeature_ContextNoDefaultCall` - RVExtensionContext will not be called automatically. It must be manually requested via RVExtensionRequestContext (This improves performance when context is not needed).\n- Arma 3 v2.22 `RVFeature_ArgumentNoEscapeString` - RVExtensionArgs string arguments will not be escaped (double-quoted) and instead passed directly. Provides significant performance advantage, and removes the need to un-escape the strings after receiving them.\n\nSetting Flags\n\nExample Visual C++\n\n```cpp\n#include <cstdint>\n#include \"RVExtensionUtil.hpp\"\n\nextern \"C\"\n{\n    __declspec(dllexport) uint64_t RVExtensionFeatureFlags = RVFeature_ContextStackTrace | RVFeature_ContextNoDefaultCall;\n}\n```"},{"heading":"Game Functions (Arma 3 v2.18)","level":3,"markdown":"The Game exports some functions that can be retrieved, in order to call back into Game functionality.\nThe Utility Header defines a function called `FindRVFunction` that can be used to retrieve their addresses.\n\n**Available Game Functions**\n\n| Name | Type | Description |\n|---|---|---|\n| RVExtensionCallback | `typedef int (CALL_CONVENTION RVExtensionCallbackProc)(char const* name, char const* function, char const* data);` | Adds a callback, see #TODO |\n| RVExtensionRequestContext | `typedef void CALL_CONVENTION RVExtensionRequestContextProc();` | Requests execution of `RVExtensionContext` |\n| RVExtensionGData | ```cpp<br>struct RVExtensionRenderInfo<br>{<br>    ID3D11Device*            d3dDevice;<br>    ID3D11DeviceContext*     d3dDeviceContext;<br>};<br>const RVExtensionRenderInfo* const *<br>``` | Acquires pointers to the DirectX 11 device and device context. |\n| RVExtensionGLock | ```cpp<br>struct RVExtensionGraphicsLockGuard<br>{<br>    //! Call this when done with the lock, forgetting to call this will freeze the game.<br>    virtual void ReleaseLock() const = 0;<br>    virtual ~RVExtensionGraphicsLockGuard() = default;<br>};<br>typedef RVExtensionGraphicsLockGuard* (CALL_CONVENTION RVExtensionGLockProc)();<br>``` | Acquires a DirectX lock, required to access DirectX functions. |\n| RVGetProjectionViewTransform | ```cpp<br>struct ProjectionViewTransform<br>{<br>    typedef float M4x4[4][4];<br>    M4x4 projection;<br>    M4x4 view;<br>};<br>typedef void (CALL_CONVENTION RVGetProjectionViewTransformProc)(ProjectionViewTransform& pvTransform);<br>``` | Returns the projection and camera view (i.e. \"look at\") matrices for drawing 3D content. The matrices are row-major. The camera view matrix is left-handed. |\n| RVExtensionGSetWHk | `typedef void (CALL_CONVENTION RVExtensionGSetWHkProc)(bool (CALL_CONVENTION *newHook)(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam));` | Sets a WNDPROC hook to handle mouse/key inputs when rendering external UI. |\n| RVExtensionGSetResetHook | `typedef void (CALL_CONVENTION RVExtensionGSetResetHook)(void (CALL_CONVENTION *newHook)());` | Sets a hook that gets called when the game 3D engine is shut down. At this point the extension must destroy its D3D resources and release the pointers to the D3D device/context. |\n| RVExtensionGGetR2TTexture Arma 3 v2.22 | `typedef void (CALL_CONVENTION RVExtensionGGetR2TTexture)(const char* name, void (CALLCONVENTION *onResult)(ID3D11Texture2D* texture, ID3D11ShaderResourceView* resView, ID3D11RenderTargetView* rtView, uint32_t width, uint32_t height, uint32_t lastSeenFrameID, const char* name));` | Retrieves R2T texture's by name, name can be nullptr, in which case all textures are returned via the callback. |\n| RVExtensionLoadFile Arma 3 v2.24 | `typedef uint64_t (CALL_CONVENTION RVExtensionLoadFile)(const char* path, void* buffer, uint64_t bufferSize);` | Load file from PBO (Similar to loadFile script command). Pass buffer as nullptr to retrieve actual size of the file (0 if not found). If buffer is too small, it will be fully written and return value will be bufferSize. |"},{"heading":"Callback (Arma 3 v1.96)","level":3,"markdown":"It is possible to call the game directly from the extension via function pointer provided when extension is called for the first time.\nSince Arma 3 v2.18 it can also be requested when needed, see Game Functions.\nThe function pointer passed over to `RVExtensionRegisterCallback` method is of the following signature:\n\n```cpp\nint(*callbackProc)(char const *name, char const *function, char const *data)\n```\n\nCalling this function pointer from extension will trigger \"ExtensionCallback\" mission event handler with three user-supplied\nparams. The params are:\n\n- `name` - make it unique name, for example the extension name, so that other modders can quickly filter out calls from own extensions\n- `function` - make it name of the function the extension sends the result to. (Note: The returned function is just a STRING! So compile is needed, before using call or spawn, to execute it)\n- `data` - make it the actual result. You can also format it as an array so it could be parsed by parseSimpleArray\n\nCalling function pointer returns an `int`. This is the number of available slots in the input buffer left for this frame after your call and can range from 99 to -1.\nThe buffer is processed and cleared every frame and the maximum number of slots that can be filled per frame is 100.\nIf you are planning to call back with more than 100 results per frame, make sure your extension retries if it receives negative `int`, which means the buffer was full and your call did not succeed.\nIdeally suited for callbacks from different threads, but if callback is initiated from the calling thread, the EH will fire on the next frame of game simulation.\nThe \"ExtensionCallback\" event handler needs to exist before any callbacks, otherwise the data will just stay in the buffer. Removing all \"ExtensionCallback\" EHs clears the buffer as well as mission restart.\n\nUsage Example:\n\n```sqf\nfncToExecute_1 = { hint format [\"Extension Result 1: %1\", _this] };\nfncToExecute_2 = { hint format [\"Extension Result 2: %1\", _this] };\nfncToExecute_3 = { hint format [\"Extension Result 3: %1\", _this] };\n\naddMissionEventHandler [\"ExtensionCallback\",\n{\n    params [\"_name\", \"_function\", \"_data\"];\n    if (_name isEqualTo \"test_callback\") then\n    {\n        parseSimpleArray _data call (missionNamespace getVariable [_function,\n        {\n            hint \"Function does not exist!\";\n        }]);\n    };\n}];\n\n\"test_callback\" callExtension str \"test data\";\n```\n\nHere is a minimal example of an extension using extension callback (do not actually do it like this for a real extension).\n*fncToExecute_X* function is called from \"ExtensionCallback\" event handler when it is triggered after 2 seconds of the extension call.\n\n```cpp\n#include <thread>\n#include <string>\n#include <chrono>\n\n#include \"RVExtensionUtil.hpp\"\n\nRVExtensionCallbackProc* callbackPtr = nullptr;\n\nvoid CALL_CONVENTION RVExtensionRegisterCallback(RVExtensionCallbackProc* callbackProc)\n{\n    callbackPtr = callbackProc;\n}\n\nvoid CALL_CONVENTION RVExtension(char *output, unsigned int outputSize, const char *function)\n{\n    if (!callbackPtr)\n        return;\n\n    std::thread ([](std::string fnc)\n    {\n        using namespace std::chrono_literals;\n        fnc = \"[1,2,3,\" + fnc + \"]\";\n\n        for (int i = 1; i < 4; ++i) // run 3 times\n        {\n            std::this_thread::sleep_for(2s); // sleep for 2 seconds\n            callbackPtr(\"test_callback\", (\"fncToExecute_\" + std::to_string(i)).c_str(), fnc.c_str());\n        }\n\n    }, function).detach();\n}\n```"},{"heading":"Context (Arma 3 v2.12)","level":3,"markdown":"RVExtensionContext is called before every RVExtension/RVExtensionArgs execution, providing context about where the call came from.\nUsing Feature Flags can change the number of elements passed, but it does not change their order."},{"heading":"Context Arguments","level":4,"markdown":"By default (due to backwards compatability) context receives an array of char*. Every value will be converted to a string.\nHowever with the `RVFeature_ContextArgumentsVoidPtr` Feature Flags, it can be switched to an array of void*, where the specific datatype per entry is defined per context element.\n\nIt will pass the following data in this order:\n\n**Caption text**\n\n| Name | Description | String content | Custom datatype with `RVFeature_ContextArgumentsVoidPtr` |\n|---|---|---|---|\n| steamId | Steam 64bit userID of the local machine, same as getPlayerUID | \"0\", number as string, no extra quotes | `const uint64_t*` the UID |\n| fileSource | file from which the extension was executed or \"\" if unavailable | X | `const char*` |\n| missionName | missionNameSource | X | `const char*` |\n| serverName | serverName | X | `const char*` |\n| remoteExecutedOwner | remoteExecutedOwner | \"0\", number as string, no extra quotes | `const int16_t*` |\n| stackTrace | Stacktrace of the script that executed callExtension. diag_stacktrace | In format `LineNumber;SourceFile;ScopeName\\n` for each stack level. | `const RVContext_StackTrace*`<br>```cpp<br>struct RVContext_StackTrace<br>{<br>    struct StackTraceLine<br>    {<br>        // Line number in file (before preprocessing if preprocessed with line numbers)<br>        uint32_t lineNumber;<br>        // File offset in bytes from the start of the file (after preprocessing)<br>        uint32_t fileOffset;<br>        // Filepath to the source file<br>        const char* sourceFile;<br>        // scopeName set on that level<br>        const char* scopeName;<br>        // Complete fileContent of the sourceFile (after preprocessing, can be combined with fileOffset to find exact location)<br>        const char* fileContent;<br>    };<br>    StackTraceLine* lines = nullptr;<br>    uint32_t lineCount = 0;<br>};<br>``` |\n\nHere is a minimal example:\n\n```cpp\n#include <string>\n#include <vector>\n#include <iterator>\n#include <sstream>\n#include <iomanip>\n#include \"RVExtensionUtil.hpp\"\n\nstd::vector<std::string> contextInfo;\n\nextern \"C\"\n{\n    //--- Engine passed context\n    __declspec (dllexport) void __stdcall RVExtensionContext(const char **args, unsigned int argsCnt);\n}\n\n//--- name callExtension function\nvoid CALL_CONVENTION RVExtension(char *output, unsigned int outputSize, const char *function)\n{\n    //--- Not used here\n    (void)function;\n\n    if (!contextInfo.empty())\n    {\n        std::ostringstream oss;\n        const char qt = '\"';\n\n        for (auto it = contextInfo.begin(); it != contextInfo.end() - 1; ++it)\n            oss << std::quoted(*it, qt, qt) << \",\";\n        oss << std::quoted(contextInfo.back(), qt, qt);\n\n        //--- Send context info back\n        strncpy_s(output, outputSize, (\"[\" + oss.str() + \"]\").c_str(), _TRUNCATE);\n    }\n}\n\n//--- Context is executed first, copy it\nvoid CALL_CONVENTION RVExtensionContext(const char **args, unsigned int argsCnt)\n{\n    contextInfo.assign(args, std::next(args, argsCnt));\n}\n```\n\nHere is a example using `RVFeature_ContextArgumentsVoidPtr` combined with `RVFeature_ContextStackTrace`:\n\n```cpp\n#include <string>\n#include <vector>\n#include <iterator>\n#include <sstream>\n#include <iomanip>\n\n#include \"RVExtensionUtil.hpp\"\n\nstruct CallContext\n{\n    uint64_t steamId;\n    std::string fileSource;\n    std::string missionName;\n    std::string serverName;\n    int16_t remoteExecutedOwner;\n\n    struct StackTraceLine\n    {\n        // Line number in file (before preprocessing if preprocessed with line numbers)\n        uint32_t lineNumber;\n        // File offset in bytes from the start of the file (after preprocessing)\n        uint32_t fileOffset;\n        // Filepath to the source file\n        std::string sourceFile;\n        // scopeName set on that level\n        std::string scopeName;\n        // Complete fileContent of the sourceFile (after preprocessing, can be combined with fileOffset to find exact location)\n        std::string fileContent;\n    };\n\n    std::vector<StackTraceLine> stackTrace;\n} contextInfo;\n\nextern \"C\"\n{\n    //--- Engine passed context\n    __declspec (dllexport) void __stdcall RVExtensionContext(const void **args, unsigned int argsCnt);\n\n    __declspec(dllexport) uint64_t RVExtensionFeatureFlags = RVFeature_ContextArgumentsVoidPtr | RVFeature_ContextStackTrace;\n}\n\n//--- name callExtension function\nvoid CALL_CONVENTION RVExtension(char *output, unsigned int outputSize, const char *function)\n{\n    //--- Not used here\n    (void)function;\n\n    {\n        std::ostringstream oss;\n        const char qt = '\"';\n\n        oss << qt << contextInfo.steamId << qt << \",\";\n        oss << std::quoted(contextInfo.fileSource, qt, qt) << \",\";\n        oss << std::quoted(contextInfo.missionName, qt, qt) << \",\";\n        oss << std::quoted(contextInfo.serverName, qt, qt) << \",\";\n        oss << contextInfo.remoteExecutedOwner << \",\";\n\n        //--- Send context info back\n        strncpy_s(output, outputSize, (\"[\" + oss.str() + \"]\").c_str(), _TRUNCATE);\n    }\n}\n\n//--- Context is executed before RVExtension is called, and its values are only valid while inside RVExtensionContext function, so we need to copy them to use them later\nvoid CALL_CONVENTION RVExtensionContext(const void **args, unsigned int argsCnt)\n{\n    size_t index = 0;\n    contextInfo.steamId = *static_cast<const uint64_t*>(args[index++]);\n    contextInfo.fileSource = static_cast<const char*>(args[index++]);\n    contextInfo.missionName = static_cast<const char*>(args[index++]);\n    contextInfo.serverName = static_cast<const char*>(args[index++]);\n    contextInfo.remoteExecutedOwner = *static_cast<const int16_t*>(args[index++]);\n\n    if (RVExtensionFeatureFlags & RVFeature_ContextStackTrace) {\n        auto stackTrace = static_cast<const RVContext_StackTrace*>(argv[index++]);\n\n        std::transform(stackTrace->lines, stackTrace->lines + stackTrace->lineCount, std::back_inserter(contextInfo.stackTrace),\n            [](const RVContext_StackTrace::StackTraceLine& line)\n            {\n                    return CallContext::StackTraceLine\n                    {\n                        line.lineNumber,\n                        line.fileOffset,\n                        line.sourceFile,\n                        line.scopeName,\n                        line.fileContent // File content is large and expensive to copy\n                    };\n            });\n    }\n}\n```"},{"heading":"Manually Request Context","level":4,"markdown":"Having RVExtensionContext be called every time before every extension execution can be a waste of performance if the context is not required every time.\nThis automatic call can be disabled with the `RVFeature_ContextNoDefaultCall` Feature Flag. When the flag is set, the context must be requested.\n\nThe Game is exporting a function called `RVExtensionRequestContext` that can be used to request it, it will then call `RVExtensionContext`.\nThe `RVExtensionContext` execution will be completed when `RVExtensionRequestContext` returns.\nIf the `RVExtensionContext` exported function is not defined, requesting context will have no effect.\nThe `RVExtensionContext` call will respect the currently set `RVExtensionFeatureFlags`, `RVExtensionContext` can also be called multiple times, with different feature flags.\n\nHere is a example on how to fetch the RequestContext function, and how to use it:\n\n```cpp\n#include <string>\n#include <vector>\n#include <iterator>\n#include <sstream>\n#include <iomanip>\n\n#include \"RVExtensionUtil.hpp\"\n\nstruct CallContext\n{\n    uint64_t steamId;\n    std::string fileSource;\n    std::string missionName;\n    std::string serverName;\n    int16_t remoteExecutedOwner;\n} contextInfo;\n\nextern \"C\"\n{\n    //--- Engine passed context\n    __declspec (dllexport) void __stdcall RVExtensionContext(const void **args, unsigned int argsCnt);\n\n    __declspec(dllexport) uint64_t RVExtensionFeatureFlags = RVFeature_ContextArgumentsVoidPtr | RVFeature_ContextStackTrace | RVFeature_ContextNoDefaultCall;\n}\n\nRVExtensionRequestContextProc* requestContext;\n\nvoid CALL_CONVENTION RVExtensionVersion(char *output, unsigned int outputSize)\n{\n    requestContext = reinterpret_cast<RVExtensionRequestContextProc*>(FindRVFunction(\"RVExtensionRequestContext\"));\n    std::strncpy(output, \"Test-Extension v1.0\", outputSize - 1);\n}\n\n//--- name callExtension function\nvoid CALL_CONVENTION RVExtension(char *output, unsigned int outputSize, const char *function)\n{\n    //--- Not used here\n    (void)function;\n\n    requestContext(); // Request the context dynamically when we need it\n\n    {\n        std::ostringstream oss;\n        const char qt = '\"';\n\n        oss << qt << contextInfo.steamId << qt << \",\";\n        oss << std::quoted(contextInfo.fileSource, qt, qt) << \",\";\n        oss << std::quoted(contextInfo.missionName, qt, qt) << \",\";\n        oss << std::quoted(contextInfo.serverName, qt, qt) << \",\";\n        oss << contextInfo.remoteExecutedOwner << \",\";\n\n        //--- Send context info back\n        strncpy_s(output, outputSize, (\"[\" + oss.str() + \"]\").c_str(), _TRUNCATE);\n    }\n}\n\n//--- Its values are only valid while inside RVExtensionContext function, so we need to copy them to use them later\nvoid CALL_CONVENTION RVExtensionContext(const void **args, unsigned int argsCnt)\n{\n    size_t index = 0;\n    contextInfo.steamId = *static_cast<const uint64_t*>(args[index++]);\n    contextInfo.fileSource = static_cast<const char*>(args[index++]);\n    contextInfo.missionName = static_cast<const char*>(args[index++]);\n    contextInfo.serverName = static_cast<const char*>(args[index++]);\n    contextInfo.remoteExecutedOwner = *static_cast<const int16_t*>(args[index++]);\n}\n```"},{"heading":"Extension UI (Arma 3 v2.20)","level":3,"markdown":"> [!WARNING]\n> This is a experimental feature currently available for testing on Development-Branch. It is not yet decided whether this will become available in Stable branch.\n\nUsing RVExtensionRequestUI and CT_EXTENSION, Extensions can implement their own UI Controls.\n\n`RVExtensionRequestUI` receives a \"uiClass\" name that is defined in the UI Control's config, based on that the Extension should decide what UI to create internally.\nIt then has to fill out the passed `CExtensionControlInterface` structure.\nVerify that the `size` member of the passed structure, matches the expected size of the structure. If it does not, then a Game Update has changed the structure and additional code for backwards compatibility might be needed.\n\nA sample is available at https://github.com/arma3/RVExtensionImGui/blob/main/dllmain.cpp#L38\n\n`OnSizeChanged` will be called before the first `OnDraw` call. And every time that the control's size is changed via ctrlSetScale, ctrlSetPosition or others.\nThe passed size, will also be the size of the RenderTarget/Texture, used in `OnDraw`.\n\nInside OnDraw, DirectX functions must be used to draw into the RenderTarget/Texture. DirectX device and context are available via \"RVExtensionGData\", see Game Functions.\n\nThere are two methods for drawing.\n\nThe default is a RenderTarget, the Game will set the active RenderTarget before the call to \"OnDraw\", and every DirectX render command will render into it.\nThe alternative is setting `CExtensionControlInterface::Flags::DynamicTexture` into the \"flags\" member of the interface structure. Then the Game will provide a texture allocated with `D3D11_USAGE_DYNAMIC`, which can be mapped using `d3dDeviceContext->Map` and the `D3D11_MAP_WRITE_DISCARD` MapType, and the CPU can then write data into the mapped memory."},{"heading":"Extension Texture Source (Arma 3 v2.22)","level":3,"markdown":"Procedural texture source (#TODO link to the wiki page, and also document it there)\n\n1. (rgb,width,height,nMips)extension(\"RVExtensionName\",\"UniqueName\", isAnimated, textureType)\n\nRVExtensionFillTextureSource will be called whenever the engine needs to fill the texture.\nThe Texture will be stored in video memory. It may be deleted from Video memory at any time, and when its visible on screen again it will be re-fetched from extension.\nThe extension fetch is done on a separate texture loading thread.\n\nIn video/animated mode, the texture is re-loaded/re-requested from the extension, every frame that the texture is visible on-screen. Be mindful of performance.\n\nWhen using animated textures that do not have to update every frame, it is recommended to implement RVExtensionHasTextureSourceUpdate.\nThat function will be called every frame to check if an update is required, and provides a performance advantage over updating the texture anyway even though it didn't change.\nTextures are only checked for updates, if it is visible on screen and considered for rendering."},{"heading":"Extension Signatures (Arma 3 v2.22)","level":3,"markdown":"DLLs can be signed on windows with SignTool [\"How to sign an app package using SignTool\"](https://learn.microsoft.com/en-us/windows/win32/appxpkg/how-to-sign-a-package-using-signtool)\nSelf signed certificates are possible!\n\nallExtensions and \"ExtensionLoaded\" eventhandler provide signature information.\nImportant is the \"status\" field.\n\"badDigest\" means the signature does not match the file, likely the file was modified after it was signed, the signature cannot be trusted.\n\"expired\" means the signature is now expired, but it might still have been valid when the signature was created.\n\"revoked\" means the signature was revoked after the file was signed, likely because the certificate is compromised, it might not be trustworthy.\n\"partialChain\" means the full certificate chain could not be verified, while the known certificate is valid, it might have been created by a invalid authority, it might not be trustworthy.\n\"ok\" is set if there were no errors with the validation (Certificate is valid and matches the file, but we ignore expiry date, ignore self signed certificates ), it is better to check individual status values than just relying on this.\n\nThe recommended method to check is to make sure that \"badDigest\" is not set (The file was actually really signed by the certificate, and not modified afterwards), and that the certificate's public key matches the expected one (It was signed by the intended certificate, and not a self-signed certificate someone else created).\n\nSignature verification script can be set in config\n\n```cpp\nclass CfgExtensions {\n  class MyModName {\n    extensions[] = {\"extension1\", \"extension2\"}; // Whitelist, which extension names to run this check on (so its not ran on unrelated extensions we don't care about)\n    onCheckLoad = \"call MyMod_fnc_checkExtensionLoad\";\n    publicKey = \"308201...E4EA81730203010001\"; // The handler has access to the config class, so we can store some properties here)\n  }\n}\n```\n\n```sqf\nMyMod_fnc_checkExtensionLoad = {\n\n  if !(\"signatures\" in _this) exitWith { true; }; // This must be linux, it doesn't have signature reading. We'll just skip validation here\n\n  // must have signature, so signatures can't be empty\n  if ((_this get \"signatures\") isNotEqualTo []) exitWith { false; };\n\n  // Find a signature that is..\n  _validSignatureFound = (_this get \"signatures\"]) findIf {\n    (_x get \"publicKey\") == getText((_this get \"config\") >> \"publicKey\") // Signed by my key (Easy access to config that added the handler, so can store the key there)\n    &&\n    !(\"expired\" in (_x get \"status\")) // Not expired\n    &&\n    !(\"badDigest\" in (_x get \"status\")) // The signature actually matches the binary (it was not edited after signing)\n  };\n\n  _validSignatureFound != -1 // If signature found we load, otherwise we block\n}\n```"},{"heading":"External References","level":2,"markdown":"The [Community-Lead Examples Repository](https://github.com/arma3/RV-Extension-Examples) can be used for further examples which go more into detail.\nTo get your example on there, just fork the repository and create a Pull-Request afterwards."},{"heading":"C/C++","level":5,"markdown":"Killzone_Kid's \"How To Make an Arma Extension\" tutorials:\n\n[Part 1](http://killzonekid.com/arma-scripting-tutorials-how-to-make-arma-extension-part-1/) [Part 2](http://killzonekid.com/arma-scripting-tutorials-how-to-make-arma-extension-part-2/) [Part 3](http://killzonekid.com/arma-scripting-tutorials-how-to-make-arma-extension-part-3/) [Part 4](http://killzonekid.com/arma-scripting-tutorials-how-to-make-arma-extension-part-4/)"},{"heading":"C\\#","level":5,"markdown":"- [Write An Arma Extension In C#/.NET](http://maca134.co.uk/tutorial/write-an-arma-extension-in-c-sharp-dot-net/)"},{"heading":"Python","level":5,"markdown":"- [Pythia - an Arma 3 extension that lets you write Python extensions for the game](https://github.com/overfl0/Pythia)"},{"heading":"Rust","level":5,"markdown":"- [arma-rs - Crate for easily and quickly building powerful extensions](https://github.com/BrettMayson/arma-rs)"},{"heading":"Golang","level":5,"markdown":"- [a3go](https://github.com/indig0fox/a3go), an Arma Go library by IndigoFox - for Go **1.20**\n- [Armago](https://github.com/code34/armago_x64/), an Arma template extension - for Go ***1.16***"},{"heading":"External Applications","level":5,"markdown":"- [KK's Arma 3 Extension Tester](http://killzonekid.com/arma-3-extension-tester-callextension-exe-callextension_x64-exe/)\n- [KK's Arma 3 Extension Tester v2.0 with callback](http://killzonekid.com/callextension-v2-0/)\n- [SQF-VM - Community implementation of SQF](https://github.com/SQFvm/vm/releases)"}],"categories":["Arma 2: Editing","Arma 3: Editing"],"groups":["Scripting"]}