Extensions

From Bohemia Interactive Community
Revision as of 03:58, 22 February 2013 by Axek Axyl (talk | contribs) (→‎DLL Interface: - Removing the new assignment, already assigned by the ArmA engine...)
Jump to navigation Jump to search

This topic is about the dlls addons may supply, that a mission or addon may use with the callExtension script command. This command is available from an arma2 post-1.60 beta. Thus the first stable version that will support it is arma2 1.61 (and reportedly this will be available in TakeOnH and Arma3 as well).

So far this functionality had been achieved by JayArma2Lib, but it was brittle, requiring an update for every beta and patch released.

For players

DLLs are natively executed code. It has access to the computer as the user account arma is running as. Anything you can do to the computer (without invoking a UAC prompt, the yellow/blue shield) the dll can do.

Prior to extensions, an addon maker was quite limited in what harm he could cause to the system. About the worst is bad effects to gameplay whenever the addon is loaded - stop loading it and all will again be fine. With extensions, this is no longer quite the case; the bad addon could potentially destroy everything you have on that computer, or start interfering with online banking. (In theory, JayArma2Lib, precursor to extensions, could do the same. We don't have any reason to think it actually did.)

Extensions may require other libraries and runtimes to be installed on the computer it is to run on. For example, if it is compiled with the Microsoft Visual C++ 2008 compiler, the corresponding runtime library must be installed. If it accesses a mysql database, the mysql client library dlls may need to be installed.

Defending against malicious extensions

Establish that the extension writer has reputation on the line. If it's somebody nobody ever heard of, who could just disappear again without a lot of people noticing, then the risk is higher. Even if he were to be exposed, it would happen at no cost to him.

Make sure the sources are provided. Even if you can't read them, someone else can, and there is a reasonable chance someone will find any backdoors. This increases the risk of getting the crimes exposed; see above.

Whoever compiles the sources into a dll can add whatever they want, last minute, in a separate copy from the sources added. You'll be trusting whoever compiles them for you, trustworthy or not. (They can also be tampered with after the dll has been built, but this is harder. Forensics should be able to tell the difference.)


The easy way out is to simply trust the people involved, and take the fall if/when you do get hacked from it. Problem is, you might not know.

Defending against vulnerable extensions

Another concern is when the extension is vulnerable. Security vulnerabilities have plagued a lot of software. An extension may well have buffer overflows and other problems which an attacker can exploit to gain access to your system.

If the sources are available, it's possible to see if the programmer has employed coding practices which decrease the risk of buffer overflows and similar basic bugs. This provides no guarantee, but can inspire confidence. By default, we should assume that such practices are not used: most tutorials, university courses, etc teach the new programmer to do it in the worst possible way. (I'll spare you the rant.)

For server admins

As server admin, your selection of mods will have some role in choosing what addons the users will be installing - and thus what extensions they are using. Many users will blindly follow your instructions, which makes the for users section content even more important for you.

And we haven't even mentioned using extensions on the server.

Linux servers are not supported by this feature, beyond not producing errors compiling scripts that use it. No support is planned. [1] Thus, any mission made that relies on extensions will be broken on linux servers, except in special cases where the mission is made for a particular set of (windows) servers like some of the persistent worlds (*life rpgs).

For these persistent world servers, however, the above caveats apply even more - these dlls can potentially give a player full access to the server, to the databases. Wether backdoored or exploited.

For mission makers

A few notes in no particular order

  • Depending on what that feature is, it may be desirable to make it optional - if the extension is not available, the mission would still work, but without that feature.
  • Extensions are not available on linux servers, and there are no plans to fix that.
  • Using an extension adds a dependency on that extension. At the outset this is no different from a dependency on an addon, except this dependency is never automatically checked unless you're using objects from that addon as well.
  • A user may have an addon downloaded, but not have the extension enabled even though it's supposed to come as part of that addon. (This depends largely on the distribution method.)

Scripting

See callExtension. The exact content of the strings will depend entirely on the extension in use.

For addon makers

Extensions are distributed as part of an addon. Thus this section will end up being rather expansive.

A few considerations

  • Can what you're trying to achieve be done in pure sqf?
  • Keeping in mind the things listed in the players section - why should you be trusted?
  • Making an extension is a programming task. And fairly low-level at that.
  • Determine what is to be the responsibility of the sqf/java side and what is the responsibility of the native side.
  • ... and the interface between the two. You only get text strings, after all. (With netId and objectFromNetId and groupFromNetId you can refer to specific objects in strings.)

A few technical considerations

  • Extensions can not be used by serverside scripts. (Unless you can guarantee all instances it will run on be windows servers.)
  • Extensions have no access to the game environment except what sqf scripts (arma3/takonh presumably also java code) are using the return results for. (call compile means the extension can emit sqf code.)
  • Extensions are called only when the sqf code calls it. If you need to do work all the time, you need to call it all the time - or spawn a thread to do it in the background, where appropriate.
  • Currently, the extension code doesn't need to be reentrant - sqf evaluation proceeds single threaded and halts until you return. It is unknown how that may change wrt java, though, so you may want to write as-if you can be called by multiple threads.
  • The extension can transfer data back to the game only in blocks of 4kB. Extensions should not trust this value to remain 4k. With further patches it may increase and it may also decrease. We have not been promised it will always be the same every time since the extension was loaded, but it seems a reasonable assumption to make. IOW, if it says 4k, you can partition the data for 4k.
  • When you need to transfer data larger than said limit, you need an interface for getting the rest of the data in subsequent calls. (See example - TODO)

How to make an extension

Begin by setting up your development environment to create a .dll - dllmain must do the work for initializing and cleaning up, RVExtension is the only function called by the game engine.

(TODO: Insert a couple example projects)

DLL Interface

The dll is expected to contain an entry point of a form _RVExtension@12, with a following C signature:

void __stdcall RVExtension(char *output, int outputSize, const char *function);

The game currently calls the RVExtension function with outputSize of 4096 (can be increased in future versions if needed, called extensions should always check this value and never produce output larger than this size). If the function is to receive arguments, they can be concatenated to the function name and the dll is responsible to perform any spliting / parsing / decoding as needed.

The engine supplies (via function) and expects (via output) a UTF-8 encoded C string. Conversion to/from wide character set strings (LPCWSTR/LPWSTR) can be performed using MultiByteToWideChar and WideCharToMultiByte functions.

The example dll source example follows. In this example the dll simply copies the input to the output:

// dllmain.cpp : Defines the entry point for the DLL application.
#define WIN32_LEAN_AND_MEAN             // Exclude rarely-used stuff from Windows headers
// Windows Header Files:
#include <windows.h>

BOOL APIENTRY DllMain( HMODULE hModule, DWORD  ul_reason_for_call, LPVOID lpReserved)
{
	switch (ul_reason_for_call)
	{
	case DLL_PROCESS_ATTACH:
	case DLL_THREAD_ATTACH:
	case DLL_THREAD_DETACH:
	case DLL_PROCESS_DETACH:
		break;
	}
	return TRUE;
}

extern "C"
{
  __declspec(dllexport) void __stdcall RVExtension(char *output, int outputSize, const char *function); 
};

void __stdcall RVExtension(char *output, int outputSize, const char *function)
{
  outputSize -= 1;
  strncpy(output,function,outputSize);
}

Another example, written in the D programming language:

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';
}

Another example, written in Delphi/Pascal:

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
  RVExtension name '_RVExtension@12';

begin
end.

Extension existence/version checking

Each extension should include a function which allows scripts to test for existence (and version) of the extension. When the extension is not present, calls to callExtension return an empty string. When you provide a function "version" returning e.g. "1.0", anyone can test if the extension is present by calling this function.

void __stdcall RVExtension(char *output, int outputSize, const char *function)
{
  outputSize -= 1;
  if (!strcmp(function,"version")
  {
    strncpy(output,"1.0",outputSize);
  }
  else
  {
    strncpy(output,function,outputSize);
  }
}

References