mumble-voip_mumble/plugins/se/se.cpp

349 lines
8.8 KiB
C++

// Copyright 2020-2023 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>.
#define MUMBLE_ALLOW_DEPRECATED_LEGACY_PLUGIN_API
#include "mumble_legacy_plugin.h"
#include "mumble_positional_audio_utils.h"
#ifdef OS_LINUX
# include "ProcessLinux.h"
#endif
#include "ProcessWindows.h"
#include <cstring>
#include <memory>
#include <sstream>
std::unique_ptr< ProcessBase > proc;
#ifdef OS_WINDOWS
static constexpr bool isWin32 = true;
#else
static bool isWin32 = false;
#endif
#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 = proc->peek< 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 = proc->peek< 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 (!proc->peek(localPlayer + rotationOffset, rotation, sizeof(rotation))) {
return false;
}
float originPosition[3];
if (!proc->peek(localPlayer + originPositionOffset, originPosition, sizeof(originPosition))) {
return false;
}
float eyesPositionOffset[3];
if (!proc->peek(localPlayer + eyesPositionOffsetOffset, eyesPositionOffset, sizeof(eyesPositionOffset))) {
return false;
}
NetInfo ni;
if (!proc->peek(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 (!proc->peek(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) {
const std::vector< std::string > names{
#ifdef OS_LINUX
"hl2_linux",
#endif
"left4dead2.exe", "left4dead.exe"
};
for (const auto &name : names) {
const auto id = ProcessBase::find(name, pids);
if (!id) {
continue;
}
#ifdef OS_WINDOWS
proc.reset(new ProcessWindows(id, name));
#else
isWin32 = HostLinux::isWine(id);
if (isWin32) {
proc.reset(new ProcessWindows(id, name));
} else {
proc.reset(new ProcessLinux(id, name));
}
#endif
if (proc->isOk()) {
return true;
}
}
return false;
}
static int tryLock(const std::multimap< std::wstring, unsigned long long int > &pids) {
if (!tryInit(pids)) {
return false;
}
const auto modules = proc->modules();
auto iter = modules.find(isWin32 ? "engine.dll" : "engine.so");
if (iter == modules.cend()) {
return false;
}
const auto engine = iter->second.baseAddress();
if (!engine) {
return false;
}
iter = modules.find(isWin32 ? "client.dll" : "client.so");
if (iter == modules.cend()) {
return false;
}
const auto client = iter->second.baseAddress();
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 = proc->peek< 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;
}
return true;
}
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 void nullUnlock() {
}
static MumblePlugin sePlug = { MUMBLE_PLUGIN_MAGIC, description, shortName, nullptr, nullptr, tryLock1,
nullUnlock, 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;
}