X39 – User talk

From Bohemia Interactive Community
Revision as of 12:22, 7 May 2018 by X39 (talk | contribs)
Jump to navigation Jump to search
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


Historical

This functionality arrived in an ArmA 2 post-1.60 beta, making 1.61 the first stable version to support it. Linux (server) support was not available until Arma 3 v1.18.

FAQ

How to use Extensions?

Extensions can be accessed using callExtension.

The exact way about how to use an extension is up to the developer of said extension

Where to put Extensions?

Arma will look at the following places to find your extension files:

  • In all loaded mods root folders (for example: ..\arma3\@ModXYZ\yourextension.dll).
  • In your arma installation folder (for example: ..\arma3\yourextension.dll).

At best, you place your extensions inside some mod folder and load it. At worst, you just throw it where the arma executable is located.

Extensions may require other libraries and runtimes to be installed on the computer it is to run on. Consult the authors installation manual if it exists.

What are Extensions?

Extensions are Dynamic Link Library (DLL) files written in, for example, C.

Linux systems use a different file type: Shared Object (SO).

What do i have to look out for?

Extensions fall under the same rules as executable files. They may require additional runtime libraries or cause the game to crash.

Worst case: They can contain malicious code.

How to know which extensions can be trusted?

Theoretically, never trust anything! Including Arma and your OS itself. Technically however, this would make a boring experience, as all you would be left with is an expensive heater.

Thus a better rule is: If the are sources freely available, you probably can trust the extension. Even if you cannot read the source code, chances are that somebody out there can and would find something odd. That will not protect you! Developers might add malicious code that is not exposed in the available source files, they not even need to be aware of this. They may be infected already and the virus is just writing itself into the compiled extension.

When i call the extension method, Arma freezes.

This is most likely caused by the extension taking too much time. Due to the low-level nature of extensions, an extension cannot be suspended like scheduled scripts can be. You should notify the developer of said extension about this freezing issue or use the extension less extensively.

Can i join BattlEye protected servers with my extension?

If the extension is whitelisted by BattlEye, it will be allowed to load. If it is not, it just will be blocked causing any callExtension attempt to fail.

Generally speaking, joining BattlEye protected servers should be no problem with or without extensions.

If in doubt, ask the extensions author.

Can i use extensions in my mission?

Yes, you can use extensions in your missions, but you should consider if it is worth it. Using extensions requires that all hosts (servers and clients) which use the extension have it installed. Important to know here: If only the server is using the extension, the clients do not need it.

Extensions are not packed into the mission pbo.

Creating Extensions

Preamble

Before we start, ask yourself the following questions:

  • is SQF unable to fulfill your needs?
  • Making an extension requires knowledge of a programming language. Are you capable of performing that programming task?
  • Why should a user trust you and use your extension?
  • Is it worth adding an extension for the feature you want to implement?

If the answer to any question was no, you probably should not proceed to create an extension but rather use plain SQF.

Getting Started

At first, we have to choose what language we want to use.

This wiki currently covers the following: C, C++, C#

Preparations

C/C++

Create a new library project in the IDE of your choice.

C#

C# requires you to install additional dependencies to work out of the box. The snippets in here all are using DllExport (sources) which can be installed using NuGet.

Create a new Class Library project in the IDE of your choice.

Available Interfaces

The Extension is required to contain at least the entry point in a form of a method named RVExtension or RVExtensionArgs. Everything else is optional.

The actual exports need to look like this (Windows only):

32-bit

  • _RVExtension@12
  • _RVExtensionArgs@20
  • _RVExtensionVersion@8

64-bit

  • RVExtension
  • RVExtensionArgs
  • RVExtensionVersion

If you use the Visual C++ compiler, this should work out of the box. But other compilers might require some manual work.

C/C++

Visual C++

	//--- Called by Engine on extension load 
	__declspec (dllexport) void __stdcall RVExtensionVersion(char *dest, int num);
	//--- STRING callExtension STRING
	__declspec (dllexport) void __stdcall RVExtension(char *dest, int num, const char *fnc);
	//--- STRING callExtension ARRAY
	__declspec (dllexport) int __stdcall RVExtensionArgs(char *dest, int num, const char *fnc, const char **argv, int argc);

GCC

	//--- Called by Engine on extension load 
	__attribute__((dllexport)) void RVExtensionVersion(char *dest, int num);
	//--- STRING callExtension STRING
	__attribute__((dllexport)) void RVExtension(char *dest, int num, const char *fnc);
	//--- STRING callExtension ARRAY
	__attribute__((dllexport)) int RVExtensionArgs(char *dest, int num, const char *fnc, const char **argv, int argc);

C#

        /// <summary>
        /// Gets called when arma starts up and loads all extension.
        /// It's perfect to load in static objects in a seperate thread so that the extension doesn't needs any seperate initalization
        /// </summary>
        /// <param name="output">The string builder object that contains the result of the function</param>
        /// <param name="outputSize">The maximum size of bytes that can be returned</param>
#if WIN64
        [DllExport("RVExtensionVersion", CallingConvention = CallingConvention.Winapi)]
#else
        [DllExport("_RVExtensionVersion@8", CallingConvention = CallingConvention.Winapi)]
#endif
        public static void RvExtensionVersion(StringBuilder output, int outputSize) { }

        /// <summary>
        /// The entry point for the default callExtension command.
        /// </summary>
        /// <param name="output">The string builder object that contains the result of the function</param>
        /// <param name="outputSize">The maximum size of bytes that can be returned</param>
        /// <param name="function">The string argument that is used along with callExtension</param>
#if WIN64
        [DllExport("RVExtension", CallingConvention = CallingConvention.Winapi)]
#else
        [DllExport("_RVExtension@12", CallingConvention = CallingConvention.Winapi)]
#endif
        public static void RvExtension(StringBuilder output, int outputSize,
           [MarshalAs(UnmanagedType.LPStr)] string function) { }

        /// <summary>
        /// The entry point for the callExtensionArgs command.
        /// </summary>
        /// <param name="output">The string builder object that contains the result of the function</param>
        /// <param name="outputSize">The maximum size of bytes that can be returned</param>
        /// <param name="function">The string argument that is used along with callExtension</param>
        /// <param name="args">The args passed to callExtension as a string array</param>
        /// <param name="argsCount">The size of the string array args</param>
        /// <returns>The result code</returns>
#if WIN64
        [DllExport("RVExtensionArgs", CallingConvention = CallingConvention.Winapi)]
#else
        [DllExport("_RVExtensionArgs@20", CallingConvention = CallingConvention.Winapi)]
#endif
        public static int RvExtensionArgs(StringBuilder output, int outputSize,
            [MarshalAs(UnmanagedType.LPStr)] string function,
            [MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.LPStr, SizeParamIndex = 4)] string[] args, int argCount) { }

Getting your extension whitelisted by BattlEye

Since Arma 3 1.46, extensions need to be whitelisted in order to get loaded.

The SitRep #00109 said the following on that topic:

 The enhanced BattlEye protection in 1.46 will require extensions to be white-listed, or their access to the game's processes will be blocked.
 In order to get your extension white-listed, please contact BattlEye support via its web form or support[at]battleye.com.
 Be sure to include the file name, a description and possibly a download link.

This boils down to the following tasks to get your extension whitelisted:

  1. Upload the extension to whitelist somewhere.
  2. Open the BattlEye Contact Page.
  3. Pick the topic Other requests (may change in future, select most appropriate).
  4. Fill in your Name and E-Mail.
  5. Set the subject to something like Arma extension whitelisting.
  6. Add link to your extension from step 1 (you may include other informations too) into the Message.

After a certain time, your extension should be whitelisted.

Minimum Viable Example

The following examples will copy the input into the output. They are provided to show some practical example.

C

Visual C++

myextension.c
__declspec(dllexport) void __stdcall RVExtension(char *dest, int num, const char *fnc);
__declspec(dllexport) int __stdcall RVExtensionArgs(char *dest, int num, const char *fnc, const char **argv, int argc);
__declspec(dllexport) void __stdcall RVExtensionVersion(char *dest, int num);

int strncpy_safe(char *dest, const char *src, int size)
{
	int i;
	size--;
	for (i = 0; i < size && src[i] != '\0'; i++)
	{
		dest[i] = src[i];
	}
	dest[i] = '\0';
	return i;
}

void RVExtension(char *dest, int num, const char *fnc)
{
	strncpy_safe(dest, function, num);
}

int RVExtensionArgs(char *dest, int num, const char *fnc, const char **argv, int argc)
{
	int index = 0;
	int i;
	for (i = 0; i < argc && index < num; i++)
	{
		index += strncpy_safe(dest + index, argv[i], num - 1 - index);
	}
	return 0;
}

void RVExtensionVersion(char *dest, int num)
{
	strncpy_safe(dest, "Test-Extension v1.0", num);
}

GCC

myextension.c
__attribute__((dllexport)) void RVExtension(char *dest, int num, const char *fnc);
__attribute__((dllexport)) int RVExtensionArgs(char *dest, int num, const char *fnc, const char **argv, int argc);
__attribute__((dllexport)) void RVExtensionVersion(char *dest, int num);

int strncpy_safe(char *dest, const char *src, int size)
{
	int i;
	size--;
	for (i = 0; i < size && src[i] != '\0'; i++)
	{
		dest[i] = src[i];
	}
	dest[i] = '\0';
	return i;
}

void RVExtension(char *dest, int num, const char *fnc)
{
	strncpy_safe(dest, function, num);
}

int RVExtensionArgs(char *dest, int num, const char *fnc, const char **argv, int argc)
{
	int index = 0;
	int i;
	for (i = 0; i < argc && index < num; i++)
	{
		index += strncpy_safe(dest + index, argv[i], num - 1 - index);
	}
	return 0;
}

void RVExtensionVersion(char *dest, int num)
{
	strncpy_safe(dest, "Test-Extension v1.0", num);
}

C++

Visual C++

myextension.cpp
#include <string>
#include <sstream>
extern "C"
{
	__declspec(dllexport) void __stdcall RVExtension(char *dest, int num, const char *fnc);
	__declspec(dllexport) int __stdcall RVExtensionArgs(char *dest, int num, const char *fnc, const char **argv, int argc);
	__declspec(dllexport) void __stdcall RVExtensionVersion(char *dest, int num);
}
void RVExtension(char *dest, int num, const char *fnc)
{
	strncpy(dest, fnc, num);
	dest[i] = '\0';
}

int RVExtensionArgs(char *dest, int num, const char *fnc, const char **argv, int argc)
{
	std::stringstream sstream;
	for (i = 0; i < argc; i++)
	{
		sstream << argv[i];
	}
	strncpy(dest, sstream.str(), num - 1);
	dest[num - 1] = '\0';
	return 0;
}

void RVExtensionVersion(char *dest, int num)
{
	strncpy(dest, "Test-Extension v1.0", num - 1);
	dest[num - 1] = '\0';
}

GCC

myextension.cpp
#include <string>
#include <sstream>
extern "C"
{
	__attribute__((dllexport)) void RVExtension(char *dest, int num, const char *fnc);
	__attribute__((dllexport)) int RVExtensionArgs(char *dest, int num, const char *fnc, const char **argv, int argc);
	__attribute__((dllexport)) void RVExtensionVersion(char *dest, int num);
}
void RVExtension(char *dest, int num, const char *fnc)
{
	strncpy(dest, fnc, num);
	dest[i] = '\0';
}

int RVExtensionArgs(char *dest, int num, const char *fnc, const char **argv, int argc)
{
	std::stringstream sstream;
	for (i = 0; i < argc; i++)
	{
		sstream << argv[i];
	}
	strncpy(dest, sstream.str(), num - 1);
	dest[num - 1] = '\0';
	return 0;
}

void RVExtensionVersion(char *dest, int num)
{
	strncpy(dest, "Test-Extension v1.0", num - 1);
	dest[num - 1] = '\0';
}

C#

MyExtension.cs
using System.Runtime.InteropServices;

class MyExtension {

#if WIN64
        [DllExport("RVExtensionVersion", CallingConvention = CallingConvention.Winapi)]
#else
        [DllExport("_RVExtensionVersion@8", CallingConvention = CallingConvention.Winapi)]
#endif
        public static void RvExtensionVersion(StringBuilder output, int outputSize)
		{
            output.Append("Test-Extension v1.0");
        }

#if WIN64
        [DllExport("RVExtension", CallingConvention = CallingConvention.Winapi)]
#else
        [DllExport("_RVExtension@12", CallingConvention = CallingConvention.Winapi)]
#endif
        public static void RvExtension(StringBuilder output, int outputSize,
		    [MarshalAs(UnmanagedType.LPStr)] string function)
		{
            output.Append(function);
        }

#if WIN64
        [DllExport("RVExtensionArgs", CallingConvention = CallingConvention.Winapi)]
#else
        [DllExport("_RVExtensionArgs@20", CallingConvention = CallingConvention.Winapi)]
#endif
        public static int RvExtensionArgs(StringBuilder output, int outputSize,
		    [MarshalAs(UnmanagedType.LPStr)] string function,
		    [MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.LPStr, SizeParamIndex = 4)] string[] args, int argCount)
		{
			foreach(var arg in args)
			{
				output.Append(arg);
			}
            return 0;
        }
}

Other Languages

This section contains other languages which may or may not work. Finding out requirements etc. is up to you.

Help Wanted

If you know any of the following languages, consider editing this page to add the language to the upper list properly.

Delphi/Pascal

<unknown>
library dllTest;

uses
  SysUtils;

{Return value is not used.}
procedure RVExtension(toArma: PAnsiChar; outputSize: Integer; fromArma: PAnsiChar); stdcall; export;
begin
  StrCopy(toArma, fromArmA);
end;

exports
  { 32-bit }
  RVExtension name '_RVExtension@12';
  { 64-bit }
  RVExtension name 'RVExtension';
begin
end.

D

<unknown>
import std.c.windows.windows;
import core.sys.windows.dll;

__gshared HINSTANCE g_hInst;

extern (Windows) BOOL DllMain(HINSTANCE hInstance, ULONG ulReason, LPVOID pvReserved) {
	final switch (ulReason) { 
		case DLL_PROCESS_ATTACH:
			g_hInst = hInstance;
			dll_process_attach(hInstance, true);
			break;

		case DLL_PROCESS_DETACH:
			dll_process_detach(hInstance, true);
			break;

		case DLL_THREAD_ATTACH:
			dll_thread_attach(true, true);
			break;

		case DLL_THREAD_DETACH:
			dll_thread_detach(true, true);
			break;
	}

	return true;
}

import std.conv;
import std.exception;

export extern (Windows) void RVExtension(char* output, int output_size, const char* cinput) {
	auto dinput = to!string(cinput);
	auto doutput = output[0 .. output_size];
	string result;
	
	// ...
	
	enforce(result.length <= output_size, "Output length too long");
	doutput[0 .. result.length] = result[];
	doutput[result.length] = '\0';
}

External References

C/C++

C#