Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/mumble-voip/mumble.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavide Beatrici <git@davidebeatrici.dev>2020-09-27 21:12:17 +0300
committerDavide Beatrici <git@davidebeatrici.dev>2020-09-27 21:12:17 +0300
commit501072ef50ae7d8bcd5168f1aea23013d04ce8c0 (patch)
tree29b66e1944882c6cb069259578ca737bccb939d6 /plugins/se
parent5df2bb2c0bdf91ed078e3cd3fafb04bdec5333c8 (diff)
FEAT(client): add Source Engine plugin, retract Left 4 Dead 1 & 2 plugins
5df2bb2c0bdf91ed078e3cd3fafb04bdec5333c8 explains how we gain access to the game's interfaces. Once we have access to the interfaces, we retrieve the address of the variables' we're interested in by reading the assembly of one or more functions accessing them. The process for each function is documented in the code.
Diffstat (limited to 'plugins/se')
-rw-r--r--plugins/se/CMakeLists.txt6
-rw-r--r--plugins/se/client.h177
-rw-r--r--plugins/se/common.h86
-rw-r--r--plugins/se/engine.h183
-rw-r--r--plugins/se/se.cpp311
5 files changed, 763 insertions, 0 deletions
diff --git a/plugins/se/CMakeLists.txt b/plugins/se/CMakeLists.txt
new file mode 100644
index 000000000..348abcaa7
--- /dev/null
+++ b/plugins/se/CMakeLists.txt
@@ -0,0 +1,6 @@
+# Copyright 2020 The Mumble Developers. All rights reserved.
+# Use of this source code is governed by a BSD-style license
+# that can be found in the LICENSE file at the root of the
+# Mumble source tree or at <https://www.mumble.info/LICENSE>.
+
+add_library(se SHARED "se.cpp")
diff --git a/plugins/se/client.h b/plugins/se/client.h
new file mode 100644
index 000000000..583e971f7
--- /dev/null
+++ b/plugins/se/client.h
@@ -0,0 +1,177 @@
+// Copyright 2019-2020 The Mumble Developers. All rights reserved.
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file at the root of the
+// Mumble source tree or at <https://www.mumble.info/LICENSE>.
+
+#ifndef MUMBLE_MUMBLE_PLUGIN_SE_CLIENT_
+#define MUMBLE_MUMBLE_PLUGIN_SE_CLIENT_
+
+struct TypeDescription {
+ uint32_t fieldType;
+ uint32_t fieldName;
+ int32_t fieldOffset;
+ uint16_t fieldSize;
+ int16_t flags;
+ uint32_t externalName;
+ uint32_t saveRestoreOps;
+ uint32_t inputFunc;
+ uint32_t td;
+ int32_t fieldSizeInBytes;
+ uint32_t overrideField;
+ int32_t overrideCount;
+ float fieldTolerance;
+ int32_t flatOffset[2];
+ uint16_t flatGroup;
+};
+
+struct DataMap {
+ uint32_t dataDesc;
+ int32_t dataNumFields;
+ uint32_t dataClassName;
+ uint32_t baseMap;
+ int32_t nPackedSize;
+ uint32_t optimizedDataMap;
+};
+
+struct EntityCacheInfo {
+ uint32_t networkable;
+ uint16_t baseEntitiesIndex;
+ uint16_t isDormant;
+};
+
+static int32_t getDataVar(const std::string &name, procptr_t predMap) {
+ while (predMap) {
+ DataMap dataMap;
+ if (!peekProc(predMap, dataMap)) {
+ return 0;
+ }
+
+ // The structure is 4 bytes bigger on Linux.
+ const size_t realStructSize = isWin32 ? sizeof(TypeDescription) : sizeof(TypeDescription) + 4;
+
+ const auto descs = peekProcVector< TypeDescription >(dataMap.dataDesc, dataMap.dataNumFields, realStructSize);
+ for (const auto &desc : descs) {
+ if (!desc.fieldName) {
+ continue;
+ }
+
+ const auto fieldName = peekProcString(desc.fieldName);
+
+ if (fieldName == name) {
+ return desc.fieldOffset;
+ }
+
+ if (desc.td) {
+ const auto offset = getDataVar(name, desc.td);
+ if (offset) {
+ return offset;
+ }
+ }
+ }
+
+ predMap = dataMap.baseMap;
+ }
+
+ return 0;
+}
+
+static int32_t getDataVarFromEntity(const std::string &name, const procptr_t entity) {
+ procptr_t GetPredDescMap;
+
+ // Brute-force virtual function index.
+ for (uint8_t i = 20; i > 16; --i) {
+ GetPredDescMap = getVirtualFunction(entity, i);
+
+ if (peekProc< uint8_t >(GetPredDescMap + (isWin32 ? 0 : 1)) == 0xB8) {
+ break;
+ }
+ }
+
+ // Windows:
+ // B8 ?? ?? ?? ?? mov eax, offset dword_????????
+ // C3 retn
+ //
+ // Linux:
+ // 55 push ebp
+ // B8 ?? ?? ?? ?? mov eax, offset dword_????????
+ // 89 E5 mov ebp, esp
+ // 5D pop ebp
+ // C3 retn
+ return getDataVar(name, peekProc< uint32_t >(GetPredDescMap + (isWin32 ? 1 : 2)));
+}
+
+static procptr_t getLocalPlayer(const procptr_t localClient, procptr_t clientEntityList, const procptr_t engineClient) {
+ const auto GetLocalPlayer = getVirtualFunction(engineClient, 12);
+
+ // Windows:
+ // 6A FF push 0FFFFFFFFh
+ // E8 ?? ?? ?? ?? call GetLocalClient
+ // 8B 80 ?? ?? ?? ?? mov eax, [eax+?]
+ // 83 C4 04 add esp, 4
+ // 40 inc eax
+ // C3 retn
+ //
+ // Linux:
+ // 55 push ebp
+ // 89 E5 mov ebp, esp
+ // 83 EC 18 sub esp, 18h
+ // C7 04 24 FF FF FF FF mov dword ptr [esp], 0FFFFFFFFh
+ // E8 ?? ?? ?? ?? call GetLocalClient
+ // 8B 80 ?? ?? ?? ?? mov eax, [eax+?]
+ // C9 leave
+ // 83 C0 01 add eax, 1
+ // C3 retn
+ const auto localPlayerIndexOffset = peekProc< int32_t >(GetLocalPlayer + (isWin32 ? 9 : 14));
+ const auto localPlayerIndex = peekProc< uint32_t >(localClient + localPlayerIndexOffset) + 1;
+
+ auto GetClientNetworkable = getVirtualFunction(clientEntityList, 0);
+
+ // Left 4 Dead:
+ // 8B 44 24 04 mov eax, [esp+arg_0]
+ // 8B 44 C1 ?? mov eax, [ecx+eax*8+?]
+ // C2 04 00 retn 4
+
+ // Windows:
+ // 55 push ebp
+ // 8B EC mov ebp, esp
+ // 8B 45 08 mov eax, [ebp+arg_0]
+ // 8B 44 C1 ?? mov eax, [ecx+eax*8+?]
+ // 5D pop ebp
+ // C2 04 00 retn 4
+ //
+ // Linux:
+ // 81 6C 24 04 ?? ?? ?? ?? sub [esp+arg_0], ????????
+ // EB ?? jmp short GetClientNetworkable
+ if (!isWin32) {
+ clientEntityList -= peekProc< int32_t >(GetClientNetworkable + 4);
+ GetClientNetworkable = GetClientNetworkable + 10 + peekProc< int8_t >(GetClientNetworkable + 9);
+
+ // 55 push ebp
+ // 89 E5 mov ebp, esp
+ // 8B 55 0C mov edx, [ebp+arg_4]
+ // 8B 45 08 mov eax, [ebp+arg_0]
+ // 5D pop ebp
+ // 8B 84 D0 ?? ?? ?? ?? mov eax, [eax+edx*8+?]
+ // C3 retn
+ }
+
+ procptr_t entityCacheInfo;
+
+ if (isWin32) {
+ if (peekProc< uint8_t >(GetClientNetworkable + 6) == 0xC1) {
+ // Left 4 Dead
+ entityCacheInfo = clientEntityList + peekProc< int8_t >(GetClientNetworkable + 7);
+ } else {
+ entityCacheInfo = clientEntityList + peekProc< int8_t >(GetClientNetworkable + 9);
+ }
+ } else {
+ entityCacheInfo = clientEntityList + peekProc< int32_t >(GetClientNetworkable + 13);
+ }
+
+ const auto entity = peekProc< EntityCacheInfo >(entityCacheInfo + sizeof(EntityCacheInfo) * localPlayerIndex);
+
+ // We subtract 8 bytes in order to cast from IClientNetworkable to IClientEntity.
+ return entity.networkable ? (entity.networkable - 8) : 0;
+}
+
+#endif
diff --git a/plugins/se/common.h b/plugins/se/common.h
new file mode 100644
index 000000000..d0e020938
--- /dev/null
+++ b/plugins/se/common.h
@@ -0,0 +1,86 @@
+// Copyright 2019-2020 The Mumble Developers. All rights reserved.
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file at the root of the
+// Mumble source tree or at <https://www.mumble.info/LICENSE>.
+
+#ifndef MUMBLE_MUMBLE_PLUGIN_SE_COMMON_
+#define MUMBLE_MUMBLE_PLUGIN_SE_COMMON_
+
+typedef std::map< std::string, procptr_t > Interfaces;
+
+struct InterfaceReg {
+ uint32_t createFunction;
+ uint32_t name;
+ uint32_t next;
+};
+
+static Interfaces getInterfaces(const procptr_t module) {
+ Interfaces interfaces;
+
+ // s_pInterfaceRegs is exported on Linux
+ auto s_pInterfaceRegs = getExportedSymbol("s_pInterfaceRegs", module);
+ if (!s_pInterfaceRegs) {
+ const auto CreateInterface = getExportedSymbol("CreateInterface", module);
+ if (CreateInterface == 0) {
+ return interfaces;
+ }
+
+ bool jmpOnly;
+
+ if (peekProc< uint8_t >(CreateInterface) == 0xE9) {
+ // Left 4 Dead:
+ // E9 ?? ?? ?? ?? jmp CreateInterface_0
+ jmpOnly = true;
+ } else {
+ // Other games:
+ // 55 push ebp
+ // 8B EC mov ebp, esp
+ // 5D pop ebp
+ // E9 ?? ?? ?? ?? jmp CreateInterfaceInternal
+ jmpOnly = false;
+ }
+
+ const auto jmpTarget = peekProc< int32_t >(CreateInterface + (jmpOnly ? 1 : 5));
+ const auto jmpInstructionEnd = CreateInterface + (jmpOnly ? 5 : 9);
+ const auto CreateInterfaceInternal = jmpInstructionEnd + jmpTarget;
+
+ // Left 4 Dead:
+ // 56 push esi
+ // 8B 35 ?? ?? ?? ?? mov esi, s_pInterfaceRegs
+ // 85 F6 test esi, esi
+ // 57 push edi
+ //
+ // Other games:
+ // 55 push ebp
+ // 8B EC mov ebp, esp
+ // 56 push esi
+ // 8B 35 ?? ?? ?? ?? mov esi, s_pInterfaceRegs
+ if (peekProc< uint16_t >(CreateInterfaceInternal + (jmpOnly ? 1 : 4)) != 0x358B) {
+ return interfaces;
+ }
+
+ s_pInterfaceRegs = peekProc< uint32_t >(CreateInterfaceInternal + (jmpOnly ? 3 : 6));
+ }
+
+ auto iface = peekProc< InterfaceReg >(peekProcPtr(s_pInterfaceRegs));
+
+ do {
+ const auto name = peekProcString(iface.name);
+ const auto address = peekProc< uint32_t >(iface.createFunction + (isWin32 ? 1 : 2));
+
+ interfaces.insert(Interfaces::value_type(name, address));
+ } while (iface.next && peekProc(iface.next, iface));
+
+ return interfaces;
+}
+
+static procptr_t getInterfaceAddress(const std::string &name, const Interfaces &interfaces) {
+ const auto iface = interfaces.find(name);
+ if (iface == interfaces.end()) {
+ return 0;
+ }
+
+ return iface->second;
+}
+
+#endif
diff --git a/plugins/se/engine.h b/plugins/se/engine.h
new file mode 100644
index 000000000..77f9a90fd
--- /dev/null
+++ b/plugins/se/engine.h
@@ -0,0 +1,183 @@
+// Copyright 2019-2020 The Mumble Developers. All rights reserved.
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file at the root of the
+// Mumble source tree or at <https://www.mumble.info/LICENSE>.
+
+#ifndef MUMBLE_MUMBLE_PLUGIN_SE_ENGINE_
+#define MUMBLE_MUMBLE_PLUGIN_SE_ENGINE_
+
+struct NetInfo {
+ uint32_t type;
+ uint8_t ip[4];
+ uint16_t port;
+};
+
+static procptr_t getLocalClient(const procptr_t engineClient) {
+ // We use GetBaseLocalClient() instead of GetLocalClient() because we just need the main client.
+ // GetLocalClient() gets the client from an array at the index passed to the function.
+ // There are multiple clients because of the split screen feature.
+
+ const auto GetNetChannelInfo = getVirtualFunction(engineClient, 74);
+
+ // Windows:
+ // E8 ?? ?? ?? ?? call GetBaseLocalClient
+ // 8B 40 ?? mov eax, [eax+?]
+ // C3 retn
+ //
+ // Linux:
+ // 55 push ebp
+ // 89 E5 mov ebp, esp
+ // 83 EC 08 sub esp, 8
+ // E8 ?? ?? ?? ?? call GetBaseLocalClient
+ // 8B 40 ?? mov eax, [eax+?]
+ // C9 leave
+ // C3 retn
+ const auto callTarget = peekProc< int32_t >(GetNetChannelInfo + (isWin32 ? 1 : 7));
+ const auto callInstructionEnd = GetNetChannelInfo + (isWin32 ? 5 : 11);
+ const auto GetBaseLocalClient = callInstructionEnd + callTarget;
+
+ // Windows:
+ // A1 ?? ?? ?? ?? mov eax, dword_????????
+ // 83 C0 ?? add eax, ?
+ // C3 retn
+ if (isWin32) {
+ return peekProcPtr(peekProc< uint32_t >(GetBaseLocalClient + 1)) + peekProc< int8_t >(GetBaseLocalClient + 7);
+ }
+
+ // Linux:
+ // 55 push ebp
+ // 89 E5 mov ebp, esp
+ // 83 EC 18 sub esp, 18h
+ // C7 44 24 04 00 00 00 00 mov dword ptr [esp+4], 0
+ // C7 04 24 ?? ?? ?? ?? mov dword ptr [esp], offset dword_????????
+ // E8 ?? ?? ?? ?? call sub_????????
+ // C9 leave
+ // C3 retn
+ //
+ // The function is quite different on Linux. It returns the result of an unknown function:
+ //
+ // 55 push ebp
+ // 89 E5 mov ebp, esp
+ // 53 push ebx
+ // 83 EC 14 sub esp, 14h
+ // 8B 45 0C mov eax, [ebp+arg_4]
+ // 8B 5D 08 mov ebx, [ebp+arg_0]
+ // 83 F8 FF cmp eax, 0FFFFFFFFh
+ // 74 0E jz short loc_1
+ // 8B 44 83 04 mov eax, [ebx+eax*4+4]
+ // 83 C0 04 add eax, 4
+ //
+ // loc_0:
+ // 83 C4 14 add esp, 14h
+ // 5B pop ebx
+ // 5D pop ebp
+ // C3 retn
+ //
+ // 90 align 10h
+ //
+ // loc_1:
+ // 8B 03 mov eax, [ebx]
+ // 89 1C 24 mov [esp], ebx
+ // FF 50 14 call dword ptr [eax+14h]
+ // 8B 44 83 04 mov eax, [ebx+eax*4+4]
+ // 83 C0 04 add eax, 4
+ // EB E8 jmp short loc_0
+ //
+ // Its purpose seem to be to iterate over the clients array, which is done directly by GetBaseLocalClient() and
+ // GetLocalClient() on Windows.
+ return peekProcPtr(peekProc< uint32_t >(GetBaseLocalClient + 17) + 4) + 4;
+}
+
+static int8_t getSignOnStateOffset(const procptr_t engineClient) {
+ const auto IsInGame = getVirtualFunction(engineClient, 26);
+
+ // Windows:
+ // E8 ?? ?? ?? ?? call GetBaseLocalClient
+ // 33 C9 xor ecx, ecx
+ // 83 78 ?? 06 cmp dword ptr [eax+?], 6
+ // 0F 94 C0 setz al
+ // C3 retn
+ //
+ // Linux:
+ // 55 push ebp
+ // 89 E5 mov ebp, esp
+ // 83 EC 08 sub esp, 8
+ // E8 ?? ?? ?? ?? call GetBaseLocalClient
+ // 83 78 ?? 06 cmp dword ptr [eax+?], 6
+ // C9 leave
+ // 0F 94 C0 setz al
+ // C3 retn
+ return peekProc< int8_t >(IsInGame + (isWin32 ? 9 : 13));
+}
+
+static int32_t getLevelNameOffset(const procptr_t engineClient) {
+ const auto GetLevelNameShort = getVirtualFunction(engineClient, 53);
+
+ // Windows:
+ // ...
+ //
+ // E8 ?? ?? ?? ?? call GetBaseLocalClient
+ // 05 ?? ?? ?? ?? add eax, ?
+ // C3 retn
+ //
+ // Linux:
+ // ...
+ //
+ // E8 ?? ?? ?? ?? call GetBaseLocalClient
+ // C9 leave
+ // 05 ?? ?? ?? ?? add eax, ?
+ // C3 retn
+ if (isWin32) {
+ if (peekProc< uint8_t >(GetLevelNameShort + 37) == 0x05) {
+ // Left 4 Dead
+ return peekProc< int32_t >(GetLevelNameShort + 38);
+ } else {
+ return peekProc< int32_t >(GetLevelNameShort + 40);
+ }
+ }
+
+ return peekProc< int32_t >(GetLevelNameShort + 57);
+}
+
+static int32_t getNetInfoOffset(const procptr_t localClient, const procptr_t engineClient) {
+ const auto GetNetChannelInfo = getVirtualFunction(engineClient, 74);
+
+ // Windows:
+ // E8 ?? ?? ?? ?? call GetBaseLocalClient
+ // 8B 40 ?? mov eax, [eax+?]
+ // C3 retn
+ //
+ // Linux:
+ // 55 push ebp
+ // 89 E5 mov ebp, esp
+ // 83 EC 08 sub esp, 8
+ // E8 ?? ?? ?? ?? call GetBaseLocalClient
+ // 8B 40 ?? mov eax, [eax+?]
+ // C9 leave
+ // C3 retn
+ const auto NetChannelInfo = peekProcPtr(localClient + peekProc< int8_t >(GetNetChannelInfo + (isWin32 ? 7 : 13)));
+ const auto GetAddress = getVirtualFunction(NetChannelInfo, 1);
+
+ // Windows:
+ // 6A 00 push 0
+ // 81 C1 ?? ?? ?? ?? add ecx, ?
+ // E8 C3 9D 1D 00 call ToString
+ // C3 retn
+ //
+ // Linux:
+ // 55 push ebp
+ // 89 E5 mov ebp, esp
+ // 83 EC ?? sub esp, ?
+ // 8B 45 08 mov eax, [ebp+arg_0]
+ // C7 44 24 04 00 00 00 00 mov dword ptr [esp+4], 0
+ // 05 ?? ?? ?? ?? add eax, ?
+ // 89 04 24 mov [esp], eax
+ // E8 ?? ?? ?? ?? call ToString
+ // C9 leave
+ // C3 retn
+ const auto netInfo = NetChannelInfo + peekProc< int32_t >(GetAddress + (isWin32 ? 4 : 18));
+
+ return static_cast< int32_t >(netInfo - localClient);
+}
+
+#endif
diff --git a/plugins/se/se.cpp b/plugins/se/se.cpp
new file mode 100644
index 000000000..b84363e3a
--- /dev/null
+++ b/plugins/se/se.cpp
@@ -0,0 +1,311 @@
+// Copyright 2019-2020 The Mumble Developers. All rights reserved.
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file at the root of the
+// Mumble source tree or at <https://www.mumble.info/LICENSE>.
+
+#include "../mumble_plugin_main.h"
+#include "../mumble_plugin_utils.h"
+
+#include "client.h"
+#include "common.h"
+#include "engine.h"
+
+static inline void anglesToVectors(const float (&angles)[3], float *front, float *top) {
+ // Calculate pitch
+ float pitchSin, pitchCos;
+ sinCos(degreesToRadians(angles[0]), pitchSin, pitchCos);
+
+ // Calculate yaw
+ float yawSin, yawCos;
+ sinCos(degreesToRadians(angles[1]), yawSin, yawCos);
+
+ // Calculate roll
+ float rollSin, rollCos;
+ sinCos(degreesToRadians(angles[2]), rollSin, rollCos);
+
+ // Calculate front vector
+ front[0] = pitchCos * yawCos;
+ front[1] = -pitchSin;
+ front[2] = pitchCos * yawSin;
+
+ // Calculate top vector
+ top[0] = (rollCos * pitchSin * yawCos + -rollSin * -yawSin);
+ top[1] = rollCos * pitchCos;
+ top[2] = (rollCos * pitchSin * yawSin + -rollSin * yawCos);
+}
+
+procptr_t localClient, localPlayer;
+int32_t signOnStateOffset, deadFlagOffset, rotationOffset, originPositionOffset, eyesPositionOffsetOffset,
+ netInfoOffset, levelNameOffset;
+
+static int fetch(float *avatarPos, float *avatarFront, float *avatarTop, float *cameraPos, float *cameraFront,
+ float *cameraTop, std::string &context, std::wstring &identity) {
+ const auto signOnState = peekProc< uint32_t >(localClient + signOnStateOffset);
+
+ // 0: SIGNONSTATE_NONE
+ // 1: SIGNONSTATE_CHALLENGE
+ // 2: SIGNONSTATE_CONNECTED
+ // 3: SIGNONSTATE_NEW
+ // 4: SIGNONSTATE_PRESPAWN
+ // 5: SIGNONSTATE_SPAWN
+ // 6: SIGNONSTATE_FULL
+ // 7: SIGNONSTATE_CHANGELEVEL
+ if (signOnState != 6) {
+ return false;
+ }
+
+ const auto isPlayerDead = peekProc< bool >(localPlayer + deadFlagOffset);
+ if (isPlayerDead) {
+ context.clear();
+ identity.clear();
+
+ for (uint8_t i = 0; i < 3; ++i) {
+ avatarPos[i] = avatarFront[i] = avatarTop[i] = cameraPos[i] = cameraFront[i] = cameraTop[i] = 0.0f;
+ }
+
+ return true;
+ }
+
+ float rotation[3];
+ if (!peekProc(localPlayer + rotationOffset, rotation, sizeof(rotation))) {
+ return false;
+ }
+
+ float originPosition[3];
+ if (!peekProc(localPlayer + originPositionOffset, originPosition, sizeof(originPosition))) {
+ return false;
+ }
+
+ float eyesPositionOffset[3];
+ if (!peekProc(localPlayer + eyesPositionOffsetOffset, eyesPositionOffset, sizeof(eyesPositionOffset))) {
+ return false;
+ }
+
+ NetInfo ni;
+ if (!peekProc(localClient + netInfoOffset, ni)) {
+ return false;
+ }
+
+ std::string host;
+
+ // 0: NA_NULL
+ // 1: NA_LOOPBACK
+ // 2: NA_BROADCAST
+ // 3: NA_IP
+ switch (ni.type) {
+ case 3: {
+ std::ostringstream ss;
+ for (uint8_t i = 0; i < 4; ++i) {
+ ss << std::to_string(ni.ip[i]);
+ if (i < 3) {
+ ss << ".";
+ }
+ }
+
+ ss << ":" << networkToHost(ni.port);
+ host = ss.str();
+ break;
+ }
+ default:
+ return true;
+ }
+
+ // 40 is the size of the char array in the engine's code
+ char map[40];
+ if (!peekProc(localClient + levelNameOffset, map, sizeof(map))) {
+ map[0] = '\0';
+ }
+
+ // Begin context
+ std::ostringstream ocontext;
+ ocontext << " {\"Host\": \"" << host << "\"}"; // Set context with server's IP address and port.
+ context = ocontext.str();
+ // End context
+
+ // Begin identity
+ std::wostringstream oidentity;
+ oidentity << "{";
+
+ // Map
+ escape(map, sizeof(map));
+ if (strcmp(map, "") != 0) {
+ oidentity << std::endl << "\"Map\": \"" << map << "\""; // Set map name in identity.
+ } else {
+ oidentity << std::endl << "\"Map\": null";
+ }
+
+ oidentity << std::endl << "}";
+ identity = oidentity.str();
+ // End identity
+
+ // Mumble | Game
+ // X | X
+ // Y | Z
+ // Z | Y
+ //
+ // originPosition corresponds to the origin of the player model,
+ // usually the point (i.e. feet) where it touches the ground.
+ // eyesPositionOffset is the difference between the eyes and the origin position.
+ // We use the eyes' position because they are closer to the mouth.
+ avatarPos[0] = originPosition[0] + eyesPositionOffset[0];
+ avatarPos[1] = originPosition[2] + eyesPositionOffset[2];
+ avatarPos[2] = originPosition[1] + eyesPositionOffset[1];
+
+ anglesToVectors(rotation, avatarFront, avatarTop);
+
+ // Sync camera vectors with avatar ones.
+ //
+ // This is not ideal, but it's not an issue unless 3rd person mode is enabled.
+ // The command to set it is "thirdperson" and requires "sv_cheats" to be enabled.
+ // For reference, the functions that return what we need are MainViewOrigin() and MainViewAngles().
+ for (uint8_t i = 0; i < 3; ++i) {
+ cameraPos[i] = avatarPos[i] /= 39.37f; // Convert from inches to meters.
+ cameraFront[i] = avatarFront[i];
+ cameraTop[i] = avatarTop[i];
+ }
+
+ return true;
+}
+
+static bool tryInit(const std::multimap< std::wstring, unsigned long long int > &pids) {
+#ifdef OS_LINUX
+ if (initialize(pids, L"hl2_linux")) {
+ return true;
+ }
+#endif
+
+ if (initialize(pids, L"left4dead2.exe")) {
+ return true;
+ }
+
+ if (initialize(pids, L"left4dead.exe")) {
+ return true;
+ }
+
+ return false;
+}
+
+static int tryLock(const std::multimap< std::wstring, unsigned long long int > &pids) {
+ if (!tryInit(pids)) {
+ return false;
+ }
+
+ const auto engine = getModuleAddr(isWin32 ? L"engine.dll" : L"engine.so");
+ if (!engine) {
+ return false;
+ }
+
+ const auto client = getModuleAddr(isWin32 ? L"client.dll" : L"client.so");
+ if (!client) {
+ return false;
+ }
+
+ const auto engineInterfaces = getInterfaces(engine);
+
+ const auto engineClient = getInterfaceAddress("VEngineClient013", engineInterfaces);
+ if (!engineClient) {
+ return false;
+ }
+
+ localClient = getLocalClient(engineClient);
+ if (!localClient) {
+ return false;
+ }
+
+ signOnStateOffset = getSignOnStateOffset(engineClient);
+ if (!signOnStateOffset) {
+ return false;
+ }
+
+ const auto signOnState = peekProc< uint32_t >(localClient + signOnStateOffset);
+ if (signOnState != 6) {
+ return false;
+ }
+
+ const auto clientInterfaces = getInterfaces(client);
+
+ const auto clientEntityList = getInterfaceAddress("VClientEntityList003", clientInterfaces);
+ if (!clientEntityList) {
+ return false;
+ }
+
+ localPlayer = getLocalPlayer(localClient, clientEntityList, engineClient);
+ if (!localPlayer) {
+ return false;
+ }
+
+ // "pl" is the offset to the internal player class.
+ const auto pl = getDataVarFromEntity("pl", localPlayer);
+ if (!pl) {
+ return false;
+ }
+
+ // "deadflag" is the offset relative to "pl".
+ //
+ // For some reason it's missing from Left 4 Dead 2 on Linux, along with many other datavars we don't need.
+ // The offset (0x4) has never changed across the various engine versions released during the years (10+), thus we
+ // force it.
+ const auto deadflag = getDataVarFromEntity("deadflag", localPlayer);
+ deadFlagOffset = pl + (deadflag ? deadflag : 0x4);
+
+ originPositionOffset = getDataVarFromEntity("m_vecAbsOrigin", localPlayer);
+ if (!originPositionOffset) {
+ return false;
+ }
+
+ eyesPositionOffsetOffset = getDataVarFromEntity("m_vecViewOffset", localPlayer);
+ if (!eyesPositionOffsetOffset) {
+ return false;
+ }
+
+ rotationOffset = getDataVarFromEntity("m_angAbsRotation", localPlayer);
+ if (!rotationOffset) {
+ return false;
+ }
+
+ netInfoOffset = getNetInfoOffset(localClient, engineClient);
+ if (!netInfoOffset) {
+ return false;
+ }
+
+ levelNameOffset = getLevelNameOffset(engineClient);
+ if (!levelNameOffset) {
+ return false;
+ }
+
+ float avatarPos[3], avatarFront[3], avatarTop[3];
+ float cameraPos[3], cameraFront[3], cameraTop[3];
+ std::string context;
+ std::wstring identity;
+
+ if (fetch(avatarPos, avatarFront, avatarTop, cameraPos, cameraFront, cameraTop, context, identity)) {
+ return true;
+ } else {
+ generic_unlock();
+ return false;
+ }
+}
+
+static const std::wstring longDesc() {
+ return std::wstring(L"Supports Source Engine games with context and identity support.");
+}
+
+static std::wstring description(L"Source Engine");
+static std::wstring shortName(L"Source Engine");
+
+static int tryLock1() {
+ return tryLock(std::multimap< std::wstring, unsigned long long int >());
+}
+
+static MumblePlugin sePlug = { MUMBLE_PLUGIN_MAGIC, description, shortName, NULL, NULL, tryLock1,
+ generic_unlock, longDesc, fetch };
+
+static MumblePlugin2 sePlug2 = { MUMBLE_PLUGIN_MAGIC_2, MUMBLE_PLUGIN_VERSION, tryLock };
+
+extern "C" MUMBLE_PLUGIN_EXPORT MumblePlugin *getMumblePlugin() {
+ return &sePlug;
+}
+
+extern "C" MUMBLE_PLUGIN_EXPORT MumblePlugin2 *getMumblePlugin2() {
+ return &sePlug2;
+}