diff options
Diffstat (limited to 'plugins/mumble_positional_audio_main.h')
-rw-r--r-- | plugins/mumble_positional_audio_main.h | 236 |
1 files changed, 236 insertions, 0 deletions
diff --git a/plugins/mumble_positional_audio_main.h b/plugins/mumble_positional_audio_main.h new file mode 100644 index 000000000..c8966535f --- /dev/null +++ b/plugins/mumble_positional_audio_main.h @@ -0,0 +1,236 @@ +// Copyright 2021 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>. + +// This is the main header for process/OS stuff; it's included in most plugins +// because values can rarely be retrieved without accessing the process' memory. +// +// Various functions are provided for common tasks when reading from memory. +// The most important are getModuleAddr() and peekProc(); the first returns the +// base address of a module (e.g. shared library), the latter reads memory at the +// specified address and stores it in a variable. + +#ifndef MUMBLE_POSITIONAL_AUDIO_MAIN_H_ +#define MUMBLE_POSITIONAL_AUDIO_MAIN_H_ + +#if !defined(OS_WINDOWS) && !defined(OS_LINUX) +# error "Please define either OS_WINDOWS or OS_LINUX" +#endif + +#include "mumble_positional_audio_win32_internals.h" + +#include <chrono> +#include <cstring> +#include <vector> + +#ifdef OS_WINDOWS +static const bool isWin32 = true; +#else +static bool isWin32; +#endif + +static bool is64Bit; +static procid_t pPid; +static procptr_t pModule; + +#define GET_POINTER_SIZE (is64Bit ? 8 : 4) + +static inline bool peekProc(const procptr_t &addr, void *dest, const size_t &len); +static inline procptr_t getModuleAddr(const procid_t &pid, const wchar_t *modname); + +static inline procptr_t getModuleAddr(const wchar_t *modname) { + return getModuleAddr(pPid, modname); +} + +template< class T > static inline bool peekProc(const procptr_t &addr, T &dest) { + return peekProc(addr, &dest, sizeof(T)); +} + +template< class T > static inline T peekProc(const procptr_t &addr) { + T ret; + + if (!peekProc(addr, &ret, sizeof(T))) { + memset(&ret, 0, sizeof(ret)); + } + + return ret; +} + +template< typename T > static inline std::vector< T > peekProcVector(const procptr_t addr, const size_t elements) { + try { + std::vector< T > var(elements); + peekProc(addr, &var[0], sizeof(T) * elements); + return var; + } catch (std::bad_alloc &) { + return std::vector< T >(); + } +} + +/// This function is very useful when there is an array of structures and we only need to store a part of each. +/// The Source Engine plugin needs it because there is a structure that happens to be larger in the Linux version of +/// Left 4 Dead 2. Since the extra members are unknown, we simply discard the extra bytes when reading the array of +/// structures. +template< typename T > +static inline std::vector< T > peekProcVector(const procptr_t addr, const size_t elements, + const size_t realStructSize) { + if (realStructSize == sizeof(T)) { + // If the structure's size is correct, there is no need for this special function. + return peekProcVector< T >(addr, elements); + } else if (realStructSize < sizeof(T)) { + return std::vector< T >(); + } + + // Read the full structures into a vector. + const std::vector< uint8_t > fullStructs = peekProcVector< uint8_t >(addr, realStructSize * elements); + + // Allocate vector for the known structures. + std::vector< T > ret(elements); + + // Copy elements from the vector containing the full structures, discarding extra bytes. + const uint8_t *seek = &fullStructs[0]; + + for (size_t i = 0; i < elements; ++i, seek += realStructSize) { + memcpy(&ret[i], seek, sizeof(T)); + } + + return ret; +} + +/// Reads the specified amount of data at the specified address and returns it as std::string. +/// An empty std::string is returned in case of error. +/// +/// If \p length is 0, the function reads one byte at a time and stops when either '\0' is found or 3 seconds have +/// passed. The successfully read data is returned, also in case of error. +static inline std::string peekProcString(const procptr_t addr, const size_t length = 0) { + std::string string; + + if (length > 0) { + string.resize(length); + + if (!peekProc(addr, &string[0], length)) { + return std::string(); + } + } else { + auto now = std::chrono::steady_clock::now(); + const auto end = now + std::chrono::seconds(3); + + for (procptr_t i = 0; now < end; ++i) { + char ch = 0; + if (!peekProc(addr + i, &ch, sizeof(ch)) || ch == '\0') { + break; + } + + string += ch; + + // Update current time. + now = std::chrono::steady_clock::now(); + } + } + + return string; +} + +static inline procptr_t peekProcPtr(const procptr_t &addr) { + procptr_t v = 0; + + if (!peekProc(addr, &v, GET_POINTER_SIZE)) { + return 0; + } + + return v; +} + +static inline procptr_t getVirtualFunction(const procptr_t classObject, const size_t index) { + const auto vTable = peekProcPtr(classObject); + if (!vTable) { + return 0; + } + + return peekProcPtr(vTable + (index * GET_POINTER_SIZE)); +} + +#ifdef OS_WINDOWS +static inline procptr_t getExportedSymbol(const std::string &symbol, const procptr_t module) { +#else +/// We use a different name because the function is called by the Linux version +/// of getExportedSymbol() in case the process is running through Wine. +static inline procptr_t getWin32ExportedSymbol(const std::string &symbol, const procptr_t module) { +#endif + const auto dos = peekProc< ImageDosHeader >(module); + if (!(dos.magic[0] == 'M' && dos.magic[1] == 'Z')) { + // Invalid DOS signature + return -1; + } + + procptr_t dataAddress; + + if (is64Bit) { + const auto nt = peekProc< ImageNtHeaders64 >(module + dos.addressOfNtHeader); + dataAddress = nt.optionalHeader.dataDirectory[0].virtualAddress; + } else { + const auto nt = peekProc< ImageNtHeaders32 >(module + dos.addressOfNtHeader); + dataAddress = nt.optionalHeader.dataDirectory[0].virtualAddress; + } + + if (!dataAddress) { + return 0; + } + + const auto exportDir = peekProc< ImageExportDirectory >(module + dataAddress); + + const auto funcs = peekProcVector< uint32_t >(module + exportDir.addressOfFunctions, exportDir.numberOfFunctions); + const auto names = peekProcVector< uint32_t >(module + exportDir.addressOfNames, exportDir.numberOfNames); + const auto ords = peekProcVector< uint16_t >(module + exportDir.addressOfNameOrdinals, exportDir.numberOfNames); + + for (uint32_t i = 0; i < exportDir.numberOfNames; ++i) { + if (names[i]) { + const auto name = peekProcString(module + names[i], symbol.size()); + + if (name == symbol) { + return module + funcs[ords[i]]; + } + } + } + + return 0; +} + +// This function returns: +// -1 in case of failure. +// 0 if the process is 32-bit. +// 1 if the process is 64-bit. +#ifdef OS_WINDOWS +static inline int8_t isProcess64Bit(const procptr_t &baseAddress) { +#else +// We use a different name because the function is called by the Linux version +// of isProcess64Bit() in case the process is running through Wine. +static inline int8_t isWin32Process64Bit(const procptr_t &baseAddress) { +#endif + const auto dos = peekProc< ImageDosHeader >(baseAddress); + if (!(dos.magic[0] == 'M' && dos.magic[1] == 'Z')) { + // Invalid DOS signature + return -1; + } + + const auto nt = peekProc< ImageNtHeadersNoOptional >(baseAddress + dos.addressOfNtHeader); + if (!(nt.signature[0] == 'P' && nt.signature[1] == 'E' && nt.signature[2] == '\0' && nt.signature[3] == '\0')) { + // Invalid NT signature + return -1; + } + + switch (nt.fileHeader.machine) { + case 0x14c: // IMAGE_FILE_MACHINE_I386 + return 0; + default: + return 1; + } +} + +#ifdef WIN32 +# include "../mumble_positional_audio_win32.h" +#else +# include "../mumble_positional_audio_linux.h" +#endif + +#endif |