From 0155a242fed8e969a4482227dc71d037ce8a9e7c Mon Sep 17 00:00:00 2001 From: Davide Beatrici Date: Wed, 11 Nov 2020 05:21:02 +0100 Subject: [PATCH] FEAT(positional-audio): Add plugin for Among Us Tested with v2020.10.22s and v2020.09.22s. Unless the pattern we're searching for becomes invalid or the structures we're using change, the plugin should keep working. --- plugins/amongus/CMakeLists.txt | 19 +++ plugins/amongus/Game.cpp | 84 +++++++++++ plugins/amongus/Game.h | 41 ++++++ plugins/amongus/amongus.cpp | 123 ++++++++++++++++ plugins/amongus/structs.h | 253 +++++++++++++++++++++++++++++++++ 5 files changed, 520 insertions(+) create mode 100644 plugins/amongus/CMakeLists.txt create mode 100644 plugins/amongus/Game.cpp create mode 100644 plugins/amongus/Game.h create mode 100644 plugins/amongus/amongus.cpp create mode 100644 plugins/amongus/structs.h diff --git a/plugins/amongus/CMakeLists.txt b/plugins/amongus/CMakeLists.txt new file mode 100644 index 00000000000..3cae1336af3 --- /dev/null +++ b/plugins/amongus/CMakeLists.txt @@ -0,0 +1,19 @@ +# 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 . + +add_library(amongus SHARED + "amongus.cpp" + "Game.cpp" + + "../Module.cpp" + "../Process.cpp" + "../ProcessWindows.cpp" +) + +if(WIN32) + target_sources(amongus PRIVATE "../HostWindows.cpp") +else() + target_sources(amongus PRIVATE "../HostLinux.cpp") +endif() diff --git a/plugins/amongus/Game.cpp b/plugins/amongus/Game.cpp new file mode 100644 index 00000000000..9c7b2e26153 --- /dev/null +++ b/plugins/amongus/Game.cpp @@ -0,0 +1,84 @@ +// 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 . + +#include "Game.h" + +#include "mumble_plugin_utils.h" + +Game::Game(const procid_t id, const std::string name) : m_ok(false), m_proc(id, name) { + if (!m_proc.isOk()) { + return; + } + + const auto &modules = m_proc.modules(); + const auto iter = modules.find("GameAssembly.dll"); + if (iter == modules.cend()) { + return; + } + + // 74 89 jz short loc_???????? + // A1 ?? ?? ?? ?? mov eax, AmongUsClient_c ** + // 8B 40 5C mov eax, [eax+5Ch] + const std::vector< uint8_t > clientPattern = { 0x74, 0x39, 0xA1, '?', '?', '?', '?', 0x8B, 0x40, 0x5C }; + m_client = m_proc.findPattern(clientPattern, iter->second); + if (!m_client) { + return; + } + + // +3 in order to skip to the memory address we actually care about + m_client = m_proc.peekPtr(m_proc.peekPtr(m_client + 3)); + if (!m_client) { + return; + } + + const auto clientC = m_proc.peek< AmongUsClient_c >(m_client); + if (!clientC.staticFields) { + return; + } + + const auto clientStaticFields = m_proc.peek< AmongUsClient_StaticFields >(clientC.staticFields); + if (!clientStaticFields.instance) { + return; + } + + m_client = clientStaticFields.instance; + + const auto fields = clientFields(); + + const auto playerControlO = m_proc.peek< PlayerControl_o >(fields.playerPrefab); + if (!playerControlO.klass) { + return; + } + + const auto playerControlC = m_proc.peek< PlayerControl_c >(playerControlO.klass); + if (!playerControlC.staticFields) { + return; + } + + m_playerControlStaticFields = playerControlC.staticFields; + + m_ok = true; +} + +PlayerControl_Fields Game::playerControlFields() { + const auto playerControlStaticFields = m_proc.peek< PlayerControl_StaticFields >(m_playerControlStaticFields); + if (playerControlStaticFields.localPlayer) { + return m_proc.peek< PlayerControl_o >(playerControlStaticFields.localPlayer).fields; + } + + return {}; +} + +std::string Game::string(const procptr_t address) { + const auto object = m_proc.peek< String_o >(address); + + std::u16string string; + string.resize(object.fields.length); + if (m_proc.peek(address + sizeof(object), &string[0], sizeof(char16_t) * string.size())) { + return utf16ToUtf8(string.data()); + } + + return {}; +} diff --git a/plugins/amongus/Game.h b/plugins/amongus/Game.h new file mode 100644 index 00000000000..cee8fed53aa --- /dev/null +++ b/plugins/amongus/Game.h @@ -0,0 +1,41 @@ +// 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 . + +#ifndef AMONGUS_GAME +#define AMONGUS_GAME + +#include "structs.h" + +#include "ProcessWindows.h" + +class Game { +protected: + bool m_ok; + ptr_t m_client; + ptr_t m_playerControlStaticFields; + ProcessWindows m_proc; + +public: + inline bool isOk() const { return m_ok; } + + inline AmongUsClient_Fields clientFields() const { return m_proc.peek< AmongUsClient_o >(m_client).fields; } + + inline GameData_PlayerInfo_Fields playerInfoFields(const ptr_t cachedData) const { + return m_proc.peek< GameData_PlayerInfo_o >(cachedData).fields; + } + + inline UnityEngine_Vector2_Fields playerPosition(const ptr_t netTransform) const { + const auto networkTransform = m_proc.peek< CustomNetworkTransform_o >(netTransform); + return networkTransform.fields.prevPosSent.fields; + } + + PlayerControl_Fields playerControlFields(); + + std::string string(const procptr_t address); + + Game(const procid_t id, const std::string name); +}; + +#endif diff --git a/plugins/amongus/amongus.cpp b/plugins/amongus/amongus.cpp new file mode 100644 index 00000000000..f92ffeb3788 --- /dev/null +++ b/plugins/amongus/amongus.cpp @@ -0,0 +1,123 @@ +// 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 . + +#include "Game.h" + +#include "mumble_plugin.h" +#include "mumble_plugin_utils.h" + +#include + +std::unique_ptr< Game > game; + +static inline bool inGame(const AmongUsClient_Fields &fields) { + if (fields.gameMode != GameMode::FreePlay && fields.gameState != GameState::NotJoined) { + return true; + } + + return false; +} + +static int fetch(float *avatarPos, float *avatarFront, float *avatarTop, float *cameraPos, float *cameraFront, + float *cameraTop, std::string &context, std::wstring &identity) { + for (uint8_t i = 0; i < 3; ++i) { + avatarPos[i] = avatarFront[i] = avatarTop[i] = cameraPos[i] = cameraFront[i] = cameraTop[i] = 0.f; + } + + const auto fields = game->clientFields(); + + if (!inGame(fields)) { + return false; + } + + if (fields.gameState == GameState::Ended) { + return true; + } + + const auto playerControlFields = game->playerControlFields(); + + // Mumble | Game + // X | X + // Y | - + // Z | Y + const auto position = game->playerPosition(playerControlFields.netTransform); + avatarPos[0] = cameraPos[0] = position.x; + avatarPos[2] = cameraPos[2] = position.y; + + // Context + std::ostringstream stream; + stream << " {\"Game ID\": " << fields.gameId << "}"; + context = stream.str(); + + stream.str(std::string()); + stream.clear(); + + // Identity + const auto playerFields = game->playerInfoFields(playerControlFields.cachedData); + + stream << "Name: " << game->string(playerFields.playerName) << '\n'; + stream << "Client ID: " << std::to_string(fields.clientId) << '\n'; + stream << "Player ID: " << std::to_string(playerFields.playerId) << '\n'; + stream << "Color ID: " << std::to_string(playerFields.colorId) << '\n'; + stream << "Skin ID: " << std::to_string(playerFields.skinId) << '\n'; + stream << "Hat ID: " << std::to_string(playerFields.hatId) << '\n'; + stream << "Pet ID: " << std::to_string(playerFields.petId) << '\n'; + stream << "Dead: " << (playerFields.isDead ? "true" : "false") << '\n'; + stream << "Host ID: " << std::to_string(fields.hostId) << '\n'; + stream << "Public game: " << (fields.isGamePublic ? "true" : "false"); + + identity = utf8ToUtf16(stream.str()); + + return true; +} + +static int tryLock(const std::multimap< std::wstring, unsigned long long int > &pids) { + const std::string name = "Among Us.exe"; + const auto id = Process::find(name, pids); + if (!id) { + return false; + } + + game = std::make_unique(id, name); + if (!game->isOk()) { + game.reset(); + return false; + } + + const auto fields = game->clientFields(); + if (!inGame(fields)) { + game.reset(); + return false; + } + + return true; +} + +static const std::wstring longDesc() { + return std::wstring(L"Supports Among Us with context and identity support."); +} + +static std::wstring description(L"Among Us"); +static std::wstring shortName(L"Among Us"); + +static int tryLock1() { + return tryLock(std::multimap< std::wstring, unsigned long long int >()); +} + +static void nullUnlock() { +} + +static MumblePlugin amongusPlug = { MUMBLE_PLUGIN_MAGIC, description, shortName, nullptr, nullptr, tryLock1, + nullUnlock, longDesc, fetch }; + +static MumblePlugin2 amongusPlug2 = { MUMBLE_PLUGIN_MAGIC_2, MUMBLE_PLUGIN_VERSION, tryLock }; + +extern "C" MUMBLE_PLUGIN_EXPORT MumblePlugin *getMumblePlugin() { + return &amongusPlug; +} + +extern "C" MUMBLE_PLUGIN_EXPORT MumblePlugin2 *getMumblePlugin2() { + return &amongusPlug2; +} diff --git a/plugins/amongus/structs.h b/plugins/amongus/structs.h new file mode 100644 index 00000000000..5b0256d37f6 --- /dev/null +++ b/plugins/amongus/structs.h @@ -0,0 +1,253 @@ +#ifndef AMONGUS_STRUCTS +#define AMONGUS_STRUCTS + +#include + +typedef uint32_t ptr_t; +typedef int32_t sptr_t; + +typedef ptr_t il2cpp_array_size_t; // Originally uintptr_t + +enum class DisconnectReason { + ExitGame = 0x0, + GameFull = 0x1, + GameStarted = 0x2, + GameNotFound = 0x3, + IncorrectVersion = 0x5, + Banned = 0x6, + Kicked = 0x7, + Custom = 0x8, + InvalidName = 0x9, + Hacking = 0xA, + Destroy = 0x10, + Error = 0x11, + IncorrectGame = 0x12, + ServerRequest = 0x13, + ServerFull = 0x14, + IntentionalLeaving = 0xD0, + FocusLostBackground = 0xCF, + FocusLost = 0xD1, + NewConnection = 0xD2 +}; + +enum class DiscoverState { Off, Broadcast }; + +enum class GameMode { LocalGame, OnlineGame, FreePlay }; + +enum class GameState { NotJoined, Joined, Started, Ended }; + +enum class Mode { None, Client, HostAndClient }; + +struct Il2CppType { + ptr_t data; + uint32_t bits; +}; + +struct Il2CppClass_1 { + ptr_t image; + ptr_t gcDesc; + ptr_t name; + ptr_t namespaze; + Il2CppType byvalArg; + Il2CppType thisArg; + ptr_t elementClass; + ptr_t castClass; + ptr_t declaringType; + ptr_t parent; + ptr_t genericClass; + ptr_t typeDefinition; + ptr_t interopData; + ptr_t klass; + ptr_t fields; + ptr_t events; + ptr_t properties; + ptr_t methods; + ptr_t nestedTypes; + ptr_t implementedInterfaces; + ptr_t interfaceOffsets; +}; + +struct UnityEngine_Vector2_Fields { + float x; + float y; +}; + +struct UnityEngine_Vector2_o { + UnityEngine_Vector2_Fields fields; +}; + +struct String_Fields { + int32_t length; + // char16_t string[]; +}; + +struct String_o { + ptr_t klass; + ptr_t monitor; + String_Fields fields; +}; + +struct UnityEngine_Object_Fields { + sptr_t cachedPtr; +}; + +struct InnerNet_InnerNetClient_Fields : UnityEngine_Object_Fields { + float minSendInterval; + uint32_t netIdCnt; + float timer; + ptr_t spawnableObjects; + bool inOnlineScene; + ptr_t destroyedObjects; + ptr_t allObjects; + ptr_t allObjectsFast; + ptr_t streams; + ptr_t networkAddress; + int32_t networkPort; + ptr_t connection; + Mode mode; + int32_t gameId; + int32_t hostId; + int32_t clientId; + ptr_t allClients; + DisconnectReason lastDisconnectReason; + ptr_t lastCustomDisconnect; + ptr_t preSpawnDispatcher; + ptr_t dispatcher; + bool isGamePublic; + GameState gameState; + ptr_t tempQueue; + bool appPaused; +}; + +struct InnerNet_InnerNetObject_Fields : UnityEngine_Object_Fields { + uint32_t spawnId; + uint32_t netId; + uint32_t dirtyBits; + uint8_t spawnFlags; + uint8_t sendMode; + int32_t ownerId; + bool despawnOnDestroy; +}; + +struct CustomNetworkTransform_Fields : InnerNet_InnerNetObject_Fields { + ptr_t xRange; + ptr_t yRange; + float sendInterval; + float snapThreshold; + float interpolateMovement; + ptr_t body; + UnityEngine_Vector2_o targetSyncPosition; + UnityEngine_Vector2_o targetSyncVelocity; + uint16_t lastSequenceId; + UnityEngine_Vector2_o prevPosSent; + UnityEngine_Vector2_o prevVelSent; +}; + +struct CustomNetworkTransform_o { + ptr_t klass; + ptr_t monitor; + CustomNetworkTransform_Fields fields; +}; + +struct AmongUsClient_Fields : InnerNet_InnerNetClient_Fields { + int32_t autoOpenStore; + GameMode gameMode; + ptr_t onlineScene; + ptr_t mainMenuScene; + ptr_t gameDataPrefab; + ptr_t playerPrefab; + ptr_t shipPrefabs; + int32_t tutorialMapId; + float spawnRadius; + DiscoverState discoverState; + ptr_t disconnectHandlers; + ptr_t gameListHandlers; +}; + +struct AmongUsClient_StaticFields { + ptr_t instance; +}; + +struct AmongUsClient_c { + Il2CppClass_1 _1; + ptr_t staticFields; +}; + +struct AmongUsClient_o { + ptr_t klass; + ptr_t monitor; + AmongUsClient_Fields fields; +}; + +struct PlayerControl_Fields : InnerNet_InnerNetObject_Fields { + int32_t lastStartCounter; + uint8_t playerId; + float maxReportDistance; + bool moveable; + bool inVent; + ptr_t cachedData; + ptr_t footSteps; + ptr_t killSfx; + ptr_t killAnimations; + float killTimer; + int32_t remainingEmergencies; + ptr_t nameText; + ptr_t lightPrefab; + ptr_t myLight; + ptr_t collider; + ptr_t myPhysics; + ptr_t netTransform; + ptr_t currentPet; + ptr_t hatRenderer; + ptr_t myRend; + ptr_t hitBuffer; + ptr_t myTasks; + ptr_t scannerAnims; + ptr_t scannersImages; + ptr_t closest; + bool isNew; + ptr_t cache; + ptr_t itemsInRange; + ptr_t newItemsInRange; + uint8_t scannerCount; + bool infectedSet; +}; + +struct PlayerControl_StaticFields { + ptr_t localPlayer; + ptr_t gameOptions; + ptr_t allPlayers; +}; + +struct PlayerControl_c { + Il2CppClass_1 _1; + ptr_t staticFields; +}; + +struct PlayerControl_o { + ptr_t klass; + ptr_t monitor; + PlayerControl_Fields fields; +}; + +struct GameData_PlayerInfo_Fields { + uint8_t playerId; + ptr_t playerName; + uint8_t colorId; + uint32_t hatId; + uint32_t petId; + uint32_t skinId; + bool disconnected; + ptr_t tasks; + bool isImpostor; + bool isDead; + ptr_t object; +}; + +struct GameData_PlayerInfo_o { + ptr_t klass; + ptr_t monitor; + GameData_PlayerInfo_Fields fields; +}; + +#endif