mirror of
https://github.com/mumble-voip/mumble.git
synced 2025-10-26 11:19:16 +00:00
FEAT(positional-audio): Plugin for Grounded
Positional audio support for the Steam release of Grounded. Tested on 1.4.3.4578, 1.4.4.4634, and 1.4.5.4679.
This commit is contained in:
parent
2b64b2cb52
commit
2fa25dd479
@ -57,6 +57,7 @@ if(WIN32 OR (UNIX AND CMAKE_SYSTEM_NAME STREQUAL "Linux"))
|
||||
"ffxiv"
|
||||
"ffxiv_x64"
|
||||
"gmod"
|
||||
"grounded"
|
||||
"gtaiv"
|
||||
"gtasa"
|
||||
"gtav"
|
||||
|
||||
@ -18,6 +18,10 @@ HostWindows::~HostWindows() {
|
||||
}
|
||||
}
|
||||
|
||||
HostWindows::HostWindows(HostWindows &&other) noexcept
|
||||
: m_pid(other.m_pid), m_handle(std::exchange(other.m_handle, nullptr)) {
|
||||
}
|
||||
|
||||
bool HostWindows::peek(const procptr_t address, void *dst, const size_t size) const {
|
||||
SIZE_T read;
|
||||
const auto ok = ReadProcessMemory(m_handle, reinterpret_cast< void * >(address), dst, size, &read);
|
||||
|
||||
@ -22,6 +22,8 @@ public:
|
||||
Modules modules() const;
|
||||
|
||||
HostWindows(const procid_t pid);
|
||||
HostWindows(const HostWindows &) = delete;
|
||||
HostWindows(HostWindows &&other) noexcept;
|
||||
virtual ~HostWindows();
|
||||
};
|
||||
|
||||
|
||||
@ -108,6 +108,7 @@ public:
|
||||
procptr_t findPattern(const std::vector< uint8_t > &pattern, procptr_t address, const size_t size);
|
||||
|
||||
ProcessBase(const procid_t id, const std::string &name);
|
||||
ProcessBase(ProcessBase &&other) = default;
|
||||
virtual ~ProcessBase();
|
||||
};
|
||||
|
||||
|
||||
@ -14,6 +14,7 @@ public:
|
||||
procptr_t exportedSymbol(const std::string &symbol, const procptr_t module) const override;
|
||||
|
||||
ProcessWindows(const procid_t id, const std::string &name);
|
||||
ProcessWindows(ProcessWindows &&other) = default;
|
||||
virtual ~ProcessWindows();
|
||||
};
|
||||
|
||||
|
||||
17
plugins/grounded/CMakeLists.txt
Normal file
17
plugins/grounded/CMakeLists.txt
Normal file
@ -0,0 +1,17 @@
|
||||
# Copyright 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(grounded SHARED
|
||||
"grounded.cpp"
|
||||
|
||||
"../Module.cpp"
|
||||
"../ProcessBase.cpp"
|
||||
"../ProcessWindows.cpp")
|
||||
|
||||
if(WIN32)
|
||||
target_sources(grounded PRIVATE "../HostWindows.cpp")
|
||||
else()
|
||||
target_sources(grounded PRIVATE "../HostLinux.cpp")
|
||||
endif()
|
||||
263
plugins/grounded/grounded.cpp
Normal file
263
plugins/grounded/grounded.cpp
Normal file
@ -0,0 +1,263 @@
|
||||
// Copyright 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 "ProcessWindows.h"
|
||||
#include "MumblePlugin.h"
|
||||
|
||||
#include "mumble_positional_audio_utils.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
|
||||
using GroundedHandle = std::tuple< ProcessWindows, procptr_t >;
|
||||
|
||||
static std::unique_ptr< GroundedHandle > handle;
|
||||
|
||||
/* the indexes of these float[3] from the game are
|
||||
* 0: south low, north high
|
||||
* 1: west low, east high
|
||||
* 2: altitude, high toward sky, goes up when you jump */
|
||||
struct GroundedCam {
|
||||
float top[3];
|
||||
std::uint8_t _unused1[4];
|
||||
float front[3];
|
||||
std::uint8_t _unused2[4];
|
||||
float pos[3];
|
||||
};
|
||||
|
||||
static_assert(sizeof(struct GroundedCam) == 16 + 16 + 12, "GroundedCam struct has unexpected size");
|
||||
|
||||
constexpr float unreal_to_mumble_units(float unreal) {
|
||||
return unreal / 100.0f;
|
||||
}
|
||||
|
||||
float float3_magnitude(float f[3]) {
|
||||
return sqrtf(f[0] * f[0] + f[1] * f[1] + f[2] * f[2]);
|
||||
}
|
||||
|
||||
bool float3_is_unit(float f[3]) {
|
||||
const float err = 0.001f;
|
||||
const float mag = float3_magnitude(f);
|
||||
return mag > (1.0f - err) && mag < (1.0f + err);
|
||||
}
|
||||
|
||||
|
||||
mumble_error_t mumble_init(uint32_t) {
|
||||
return MUMBLE_STATUS_OK;
|
||||
}
|
||||
|
||||
void mumble_shutdown() {
|
||||
}
|
||||
|
||||
MumbleStringWrapper mumble_getName() {
|
||||
static const char name[] = "Grounded";
|
||||
|
||||
MumbleStringWrapper wrapper;
|
||||
wrapper.data = name;
|
||||
wrapper.size = strlen(name);
|
||||
wrapper.needsReleasing = false;
|
||||
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
MumbleStringWrapper mumble_getDescription() {
|
||||
static const char description[] = "Positional audio support for Grounded. Steam release version >= 1.4.3.4578.";
|
||||
|
||||
MumbleStringWrapper wrapper;
|
||||
wrapper.data = description;
|
||||
wrapper.size = strlen(description);
|
||||
wrapper.needsReleasing = false;
|
||||
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
MumbleStringWrapper mumble_getAuthor() {
|
||||
static const char author[] = "MumbleDevelopers";
|
||||
|
||||
MumbleStringWrapper wrapper;
|
||||
wrapper.data = author;
|
||||
wrapper.size = strlen(author);
|
||||
wrapper.needsReleasing = false;
|
||||
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
mumble_version_t mumble_getAPIVersion() {
|
||||
return MUMBLE_PLUGIN_API_VERSION;
|
||||
}
|
||||
|
||||
void mumble_registerAPIFunctions(void *) {
|
||||
}
|
||||
|
||||
void mumble_releaseResource(const void *) {
|
||||
}
|
||||
|
||||
mumble_version_t mumble_getVersion() {
|
||||
return { 1, 0, 0 };
|
||||
}
|
||||
|
||||
uint32_t mumble_getFeatures() {
|
||||
return MUMBLE_FEATURE_POSITIONAL;
|
||||
}
|
||||
|
||||
uint8_t mumble_initPositionalData(const char *const *programNames, const uint64_t *programPIDs, size_t programCount) {
|
||||
const std::string exename = "Maine-Win64-Shipping.exe";
|
||||
|
||||
for (size_t i = 0; i < programCount; ++i) {
|
||||
if (programNames[i] != exename) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ProcessWindows proc(programPIDs[i], programNames[i]);
|
||||
|
||||
if (!proc.isOk()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const Modules &modules = proc.modules();
|
||||
const auto iter = modules.find(exename);
|
||||
|
||||
if (iter == modules.cend()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// An address pointing to the start our pointer chain is moved
|
||||
// into r10. findPattern and peekRIP read executable regions of
|
||||
// memory and give us the address that would be copied to r10.
|
||||
//
|
||||
// This address is in executable code and points to a data
|
||||
// page in the executable. It's not moving around between
|
||||
// program launches. Instead, we use findPattern to support
|
||||
// different versions of the game, hopefully future versions.
|
||||
// If a new executable is shipped with a game update, and the
|
||||
// start of our pointer chain in the data page moves, we can
|
||||
// find it again by finding this code pattern and seeing what
|
||||
// it points to.
|
||||
//
|
||||
// 4C 8B 15 ?? ?? ?? ?? mov r10,qword ptr ds:[?? ?? ?? ??]
|
||||
// 44 8D 4A FF lea r9d,qword ptr ds:[rdx-1]
|
||||
// 49 63 C1 movsxd rax,r9d
|
||||
// 49 8B 34 C2 mov rsi,qword ptr ds:[r10+rax*8]
|
||||
const std::vector< uint8_t > pattern = {
|
||||
0x4C, 0x8B, 0x15, '?', '?', '?', '?', /**/
|
||||
0x44, 0x8D, 0x4A, 0xFF, /**/
|
||||
0x49, 0x63, 0xC1, /**/
|
||||
0x49, 0x8B, 0x34, 0xC2, /**/
|
||||
};
|
||||
|
||||
procptr_t addr, ok;
|
||||
|
||||
if (!(addr = proc.findPattern(pattern, iter->second))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!(addr = proc.peekRIP(addr + 0x3))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Only test that we can read the memory in the address we got.
|
||||
*
|
||||
* Normally, the memory at that address is another pointer to
|
||||
* the start of our pointer chain. But, if we're at the main
|
||||
* menu and haven't loaded in to any world yet, the pointer at
|
||||
* this address will be null. We should be able to read this
|
||||
* memory early on, but expect to read out a null pointer if we
|
||||
* haven't loaded into the world yet. */
|
||||
|
||||
if (!(proc.peek(addr, ok))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
handle = std::make_unique< GroundedHandle >(std::move(proc), addr);
|
||||
|
||||
return MUMBLE_PDEC_OK;
|
||||
}
|
||||
|
||||
return MUMBLE_PDEC_ERROR_TEMP;
|
||||
}
|
||||
|
||||
void mumble_shutdownPositionalData() {
|
||||
handle.reset();
|
||||
}
|
||||
|
||||
enum FollowChain {
|
||||
CHAIN_OK = 0,
|
||||
CHAIN_LATER,
|
||||
CHAIN_BAD,
|
||||
};
|
||||
|
||||
enum FollowChain followPointerChain(const ProcessWindows &proc, const procptr_t start, GroundedCam &cam) {
|
||||
procptr_t chain;
|
||||
|
||||
/* If we can't read the starting address, the program probably quit;
|
||||
* we should shut down our positional audio */
|
||||
if (!proc.peek< procptr_t >(start, chain)) {
|
||||
return CHAIN_BAD;
|
||||
}
|
||||
|
||||
if (chain == 0) {
|
||||
/* Probably at the main menu. We expect this to point to a
|
||||
* valid address later. */
|
||||
return CHAIN_LATER;
|
||||
}
|
||||
|
||||
if (!(chain = proc.peekPtr(chain))) {
|
||||
return CHAIN_BAD;
|
||||
}
|
||||
|
||||
if (!(chain = proc.peekPtr(chain + 0x8))) {
|
||||
return CHAIN_BAD;
|
||||
}
|
||||
|
||||
if (!proc.peek(chain + 0x700, cam)) {
|
||||
return CHAIN_BAD;
|
||||
}
|
||||
|
||||
return CHAIN_OK;
|
||||
}
|
||||
|
||||
bool mumble_fetchPositionalData(float *avatarPos, float *avatarDir, float *avatarAxis, float *cameraPos,
|
||||
float *cameraDir, float *cameraAxis, const char **contextPtr,
|
||||
const char **identityPtr) {
|
||||
*contextPtr = "";
|
||||
*identityPtr = "";
|
||||
|
||||
const ProcessWindows &proc = std::get< 0 >(*handle);
|
||||
const procptr_t start = std::get< 1 >(*handle);
|
||||
GroundedCam cam;
|
||||
auto result = followPointerChain(proc, start, cam);
|
||||
|
||||
switch (result) {
|
||||
case CHAIN_OK:
|
||||
/* We expect top and front to be unit vectors in the game. */
|
||||
assert(float3_is_unit(cam.top));
|
||||
assert(float3_is_unit(cam.front));
|
||||
|
||||
avatarAxis[0] = cameraAxis[0] = -cam.top[0];
|
||||
avatarAxis[1] = cameraAxis[1] = cam.top[2];
|
||||
avatarAxis[2] = cameraAxis[2] = -cam.top[1];
|
||||
|
||||
avatarDir[0] = cameraDir[0] = -cam.front[0];
|
||||
avatarDir[1] = cameraDir[1] = cam.front[2];
|
||||
avatarDir[2] = cameraDir[2] = -cam.front[1];
|
||||
|
||||
avatarPos[0] = cameraPos[0] = unreal_to_mumble_units(cam.pos[0]);
|
||||
avatarPos[1] = cameraPos[1] = unreal_to_mumble_units(cam.pos[2]);
|
||||
avatarPos[2] = cameraPos[2] = unreal_to_mumble_units(cam.pos[1]);
|
||||
return true;
|
||||
|
||||
default:
|
||||
std::fill_n(avatarPos, 3, 0.f);
|
||||
std::fill_n(avatarDir, 3, 0.f);
|
||||
std::fill_n(avatarAxis, 3, 0.f);
|
||||
|
||||
std::fill_n(cameraPos, 3, 0.f);
|
||||
std::fill_n(cameraDir, 3, 0.f);
|
||||
std::fill_n(cameraAxis, 3, 0.f);
|
||||
|
||||
return result == CHAIN_LATER;
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user