Pluginguide

From Mumble Wiki
Revision as of 21:46, 21 January 2010 by Snares2 (talk | contribs)
Jump to: navigation, search

Introduction

Game positional audio is a feature of Mumble that many users consider very useful. However, creating the game plugin can sometimes be complicated, and for the average person, daunting. This guide will help you understand how a game plugin works, what it does, and how you can make one.

First Steps

Tools Needed

Cheat Engine. Just use the default options when installing. Note that Mumble does NOT support cheating of any kind. We use Cheat Engine because the interface is easy to use, and the program is fits our purposes; Cheat Engine is simply a memory searching tool, which is required to find the positional addresses in the game.

Visual C++ 2008 Express edition. Again, default options, except for the SQL server, which you can uncheck.

Notepad++. After you install Notepad++, start it, go to Preferences -> New Document/Default Directory, and check "Unix" in the Format box.

Learn a Little C++

Although you do not need to be an expert programmer in order to write a plugin, you do need to understand fundamental data types. Here are a few of the most important:

float: This is the data type that almost all positional audio game addresses use. They are 32 bit, decimal numbers stored in the memory. A float data type is 4 bytes * 8 = 32 bits. An example of a floating point value would be "1234.0123456".

byte: This is the smallest data type in Intel x86-based computing. This type of memory address holds 1 byte of information (1 byte * 8 = 8 bits). From this type of memory address, you can get 0-255 base^10 values, or -127 to 128, depending on whether or not you use a signed byte (it has a + or - on the front of the value), or an unsigned byte (no + or -). An example of a byte value would be "12".

How a Plugin Works

Below is a standard template that you can use for your plugin making. The code itself will be explained in the comments that follow.

/* <your copyright here>
   Copyright (C) 2005-2010, Thorvald Natvig <thorvald@natvig.com> 

   All rights reserved.
 
   Redistribution and use in source and binary forms, with or without
   modification, are permitted provided that the following conditions
   are met: 

   - Redistributions of source code must retain the above copyright notice,
     this list of conditions and the following disclaimer.
   - Redistributions in binary form must reproduce the above copyright notice,
     this list of conditions and the following disclaimer in the documentation
     and/or other materials provided with the distribution.
   - Neither the name of the Mumble Developers nor the names of its
     contributors may be used to endorse or promote products derived from this
     software without specific prior written permission.

   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
   ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
   LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
   A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR
   CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
   EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
   PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
   PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
   LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
   NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
   SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/ 
 
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
#include <tlhelp32.h>
#include <math.h> 

#include "../mumble_plugin.h"  

HANDLE h; 
 
BYTE *posptr;
BYTE *faceptr;
BYTE *topptr;

static DWORD getProcess(const wchar_t *exename) {
	PROCESSENTRY32 pe;
	DWORD pid = 0;

	pe.dwSize = sizeof(pe);
	HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
	if (hSnap != INVALID_HANDLE_VALUE) {
		BOOL ok = Process32First(hSnap, &pe);

		while (ok) {
			if (wcscmp(pe.szExeFile, exename)==0) {
				pid = pe.th32ProcessID;
				break;
			}
			ok = Process32Next(hSnap, &pe);
		}
		CloseHandle(hSnap);
	}
	return pid;
}

static BYTE *getModuleAddr(DWORD pid, const wchar_t *modname) {
	MODULEENTRY32 me;
	BYTE *addr = NULL;
	me.dwSize = sizeof(me);
	HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, pid);
	if (hSnap != INVALID_HANDLE_VALUE) {
		BOOL ok = Module32First(hSnap, &me);

		while (ok) {
			if (wcscmp(me.szModule, modname)==0) {
				addr = me.modBaseAddr;
				break;
			}
			ok = Module32Next(hSnap, &me);
		}
		CloseHandle(hSnap);
	}
	return addr;
}


static bool peekProc(VOID *base, VOID *dest, SIZE_T len) {
	SIZE_T r;
	BOOL ok=ReadProcessMemory(h, base, dest, len, &r);
	return (ok && (r == len));
}

static DWORD peekProc(VOID *base) {
	DWORD v = 0;
	peekProc(base, reinterpret_cast<BYTE *>(&v), sizeof(DWORD));
	return v;
}

static BYTE *peekProcPtr(VOID *base) {
	DWORD v = peekProc(base);
	return reinterpret_cast<BYTE *>(v);
}

static void about(HWND h) {
	::MessageBox(h, L"Reads audio position information from <game>", L"Mumble <game acronym> Plugin", MB_OK);
}

static int fetch(float *avatar_pos, float *avatar_front, float *avatar_top, float *camera_pos, float *camera_front, float *camera_top, std::string &context, std::wstring &identity) {
	for (int i=0;i<3;i++)
		avatar_pos[i]=avatar_front[i]=avatar_top[i]=0.0f;

	char state;
	bool ok;


	/*
		description of your state value
	*/
	ok = peekProc((BYTE *) 0x00A1D0A8, &state, 1); // Magical state value
	if (! ok)
		return false;

	if (state == 0)
		return true; // This results in all vectors beeing zero which tells Mumble to ignore them.

	ok = peekProc(posptr, avatar_pos, 12) &&
	     peekProc(faceptr, avatar_front, 12) &&
	     peekProc(topptr, avatar_top, 12);

	if (! ok)
		return false;

	for (int i=0;i<3;i++) {
		camera_pos[i] = avatar_pos[i];
		camera_front[i] = avatar_front[i];
		camera_top[i] = avatar_top[i];
	}

	return ok;
}

static int trylock() {
	h = NULL;
	posptr = faceptr = topptr = NULL;

	DWORD pid=getProcess(L"<game executable name>.exe");
	if (!pid)
		return false;
	BYTE *mod=getModuleAddr(pid, L"<module name, if you need it>.dll");
	if (!mod)
		return false;

	h=OpenProcess(PROCESS_VM_READ, false, pid);
	if (!h)
		return false;

	posptr = mod + 0x<offset>);
	faceptr = mod + 0x<offset>);
	topptr = mod + 0x<offset>);

	float apos[3], afront[3], atop[3], cpos[3], cfront[3], ctop[3];
	std::string context;
	std::wstring identity;

	if (fetch(apos, afront, atop, cpos, cfront, ctop, context, identity))
		return true;

	CloseHandle(h);
	h = NULL;
	return false;
}

static void unlock() {
	if (h) {
		CloseHandle(h);
		h = NULL;
	}
	return;
}

static const std::wstring longdesc() {
	return std::wstring(L"Supports <game name> <version>. No identity support yet.");
}

static std::wstring description(L"<game name> <version>");
static std::wstring shortname(L"<game name>");

static MumblePlugin <game acronym, lowercase>plug = {
	MUMBLE_PLUGIN_MAGIC,
	description,
	shortname,
	about,
	NULL,
	trylock,
	unlock,
	longdesc,
	fetch
};

extern "C" __declspec(dllexport) MumblePlugin *getMumblePlugin() {
	return &<game acronym, lowercase>plug;
}

Explanation

All that probably looks pretty daunting, right? It isn't really, but you actually don't need to understand all of it. You just need to understand the parts that need to be changed in order to make this standard plugin hook to the right game, and fetch the right memory addresses.

Beginning the Hunt

For this game, we will be memory searching the game Alien Arena, an FPS game. If you are trying to create a third-person or camera-based game plugin, you will need to find the avatar positional data and the camera positional data. This is explained further in a few minutes.

Now it's time for a little Cheat Engine tutorial. Note, again, that Mumble does not support cheating in any way, and this guide does in no way try to teach any cheating methods.

Note that you need to make sure that your server has NO anti-cheat setting enabled, as it might flag Cheat Engine as a hack. NEVER use Cheat Engine on a game that has an anti-cheat method currently engaged, or risk getting banned from that server/game!

First, start your game. If there is a way to make the game windowed, do so. Usually Googling for "<game name> windowed mode" will get the results you need. If you can start your own server for the game, that is preferred. Now load into a map. The game needs to change as little as possible, so make sure you don't have bots or artificial intelligence players enabled.

Now you need to start Cheat Engine. On the main window, you will see a little computer icon, that is flashing red/green/blue. Click it, find your game executable name on the list, and then double click it. In this case, you would click "alienarena.exe".

You are now hooked to the executable. In the main Cheat Engine window, set "Value type" to "Float" and on "Scan type", select "Unknown initial value". Depending on how fast your computer is, this could take from a few seconds to a few minutes.

Now move ingame a little. Move forwards, backwards, whatever.