Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: Smertig/among-us-replay-mod
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: v0.6.1
Choose a base ref
...
head repository: Smertig/among-us-replay-mod
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: master
Choose a head ref
Loading
5 changes: 3 additions & 2 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
name: Build On Push

on:
on:
workflow_dispatch:
push:
branches:
- master
- '*'

jobs:
build:
29 changes: 29 additions & 0 deletions .github/workflows/inspect-code.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
name: Inspect Code with R++

on: [push, pull_request]

jobs:
build:
runs-on: windows-latest
name: Build

steps:
- uses: actions/checkout@v3
with:
submodules: true

- name: Create Build Environment
run: cmake -E make_directory ${{github.workspace}}/build

- name: Configure CMake
shell: bash
working-directory: ${{github.workspace}}/build
run: |
cmake .. -DCMAKE_BUILD_TYPE=Debug -A Win32
- name: Inspect solution
uses: muno92/resharper_inspectcode@1.6.7
with:
exclude: "**/external/**.*"
failOnIssue: 0
solutionPath: ${{github.workspace}}/build/among-us-replay-mod.sln
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -22,6 +22,6 @@ add_library(${PROJECT_NAME} SHARED
target_compile_definitions(${PROJECT_NAME} PRIVATE WIN32_LEAN_AND_MEAN NOMINMAX)
target_link_libraries(${PROJECT_NAME} PRIVATE rcmp fmt::fmt magic_enum spdlog)
target_include_directories(${PROJECT_NAME} PRIVATE ${PROJECT_SOURCE_DIR}/source)
target_compile_options(${PROJECT_NAME} PRIVATE /W4)
target_compile_options(${PROJECT_NAME} PRIVATE /W4 /WX /w14062)

add_executable(injector ${PROJECT_SOURCE_DIR}/injector/main.cpp)
58 changes: 53 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -6,12 +6,29 @@
<img src="https://img.shields.io/badge/License-MIT-blue.svg" alt="License: MIT">
<a href="https://github.com/Smertig/among-us-replay-mod/releases/latest"><img src="https://img.shields.io/github/v/release/Smertig/among-us-replay-mod.svg" alt="Latest Release"></a>
<a href="https://github.com/Smertig/among-us-replay-mod/actions"><img src="https://github.com/Smertig/among-us-replay-mod/workflows/Build%20On%20Push/badge.svg" alt="GitHub Actions"></a>
<img src="https://img.shields.io/github/downloads/smertig/among-us-replay-mod/total">
</p>

# Among Us Replay Mod</b>

This repository contains source code of Replay Mod for [Among Us](https://store.steampowered.com/app/945360/Among_Us/) PC version. This mod allows you to record every played game round and automatically save it. All the recorded rounds can be later _replayed_ using [Among Us Replayer](https://github.com/Smertig/among-us-replayer).

| Game version | Mod version |
|-----------------|---------------|
| v2020.6.9s | 0.6.0 - 0.6.5 |
| v2020.9.22s | 0.5.0 - 0.6.5 |
| v2020.10.8i | 0.6.0 - 0.6.5 |
| v2020.10.22s | 0.6.2 - 0.6.5 |
| v2020.11.4s | 0.6.3 - 0.6.5 |
| v2020.11.17s | 0.6.4 - 0.6.5 |
| v2020.12.9s | 0.6.5 - 0.6.5 |
| v2021.3.5s | 0.7.0 |
| v2021.4.12s | 0.7.1 |
| v2022.3.29s | 0.8.0 - 0.8.1 |
| v2022.4.19e | 0.8.1 |
| v2022.6.21s | ≥0.8.2 |
| **v2022.7.12s** | **≥0.9.0** |

## Usage

### Recording game
@@ -23,22 +40,53 @@ This repository contains source code of Replay Mod for [Among Us](https://store.

:warning: **You should run `injector.exe` on every game restart.**

All the replays can be later found at `/Steam/steamapps/common/Among Us/replay` folder.
All the replays can be later found for **Steam** at `/Steam/steamapps/common/Among Us/replay` and
for **Epic Games** at `/Epic Games/AmongUs/replay`

### Replaying game

- Check out [Among Us Replayer](https://github.com/Smertig/among-us-replayer) for detailed instruction

## Replay Demo
### Replay Demo

[Youtube](https://youtu.be/j3DKQzkoJLM)

## Development

:warning: This section is only for developers, you don't need all this stuff if you just want to use mod.

### How To Build

Requirements:
- Windows
- C++20 compiler (tested on MSVC 16.7.5)
- CMake 3.17

```shell script
# 0. Clone repo *with submodules*
git clone --recursive https://github.com/Smertig/among-us-replay-mod
cd among-us-replay-mod

# 1. Create build directory
mkdir build
cd build

# 2. Configure CMake project
cmake .. -DCMAKE_BUILD_TYPE=Release -A Win32

# 3. Build project (both mod and injector)
cmake --build . --config Release

[Youtube](https://youtu.be/WmfwYmQp_js)
# 4. Check out build artifacts at 'among-us-replay-mod/build/Release' directory
```

## License
### License

- MIT

## References
### References

- ![](https://img.shields.io/github/stars/Neargye/magic_enum.svg?style=social) [magic_enum](https://github.com/Neargye/magic_enum) by [Neargye](https://github.com/Neargye)
- ![](https://img.shields.io/github/stars/fmtlib/fmt.svg?style=social) [fmtlib](https://github.com/fmtlib/fmt) by [vitaut](https://github.com/vitaut)
- ![](https://img.shields.io/github/stars/gabime/spdlog.svg?style=social) [spdlog](https://github.com/gabime/spdlog) by [gabime](https://github.com/gabime)
- ![](https://img.shields.io/github/stars/Smertig/rcmp.svg?style=social) [rcmp](https://github.com/Smertig/rcmp) by [Smertig](https://github.com/Smertig)
27 changes: 23 additions & 4 deletions injector/main.cpp
Original file line number Diff line number Diff line change
@@ -3,11 +3,28 @@
#include <optional>
#include <array>
#include <filesystem>
#include <sstream>

#include <Windows.h>
#include <TlHelp32.h>
#include <psapi.h>

class error_message {
std::stringstream m_stream;

public:
~error_message() {
::MessageBoxA(nullptr, m_stream.str().c_str(), "Injector", MB_ICONERROR);
}

template <class T>
error_message& operator<<(const T& value) {
m_stream << value;
std::cerr << value;
return *this;
}
};

std::optional<DWORD> find_process(std::string_view name) {
HANDLE snapshot = ::CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL);
PROCESSENTRY32 process_entry{};
@@ -55,7 +72,7 @@ const char* default_dll_name = "among-us-replay-mod.dll";
const char* default_process_name = "Among Us.exe";

void print_usage() {
std::cerr << "usage: injector.exe [dll_path=\"" << default_dll_name << "\"] [process_name=\"" << default_process_name << "\"]\n";
error_message{} << "usage: injector.exe [dll_path=\"" << default_dll_name << "\"] [process_name=\"" << default_process_name << "\"]\n";
}

int main(int argc, const char** argv) {
@@ -73,12 +90,12 @@ int main(int argc, const char** argv) {
std::array<char, MAX_PATH> dll_path{};
::GetFullPathNameA(short_dll_path, dll_path.size(), dll_path.data(), nullptr);

std::cout << "injecting " << dll_path.data() << " into " << process_name << "..\n";
std::cout << "injecting " << dll_path.data() << " into " << process_name << ".." << std::endl;

// get process
auto process_id = find_process(process_name);
if (!process_id) {
std::cerr << "unable to find process '" << process_name << "'\n";
error_message{} << "unable to find process '" << process_name << "'\n";
return 1;
}

@@ -90,9 +107,11 @@ int main(int argc, const char** argv) {
::WriteProcessMemory(process_handle, allocated_memory, dll_path.data(), dll_path.size(), nullptr);

::CreateRemoteThread(process_handle, nullptr, 0, (LPTHREAD_START_ROUTINE)LoadLibraryA, allocated_memory, 0, nullptr);

::MessageBoxA(nullptr, "Successfully injected", "Injector", MB_ICONINFORMATION);
}
else {
std::cerr << "dll " << dll_path.data() << " is already loaded into '" << process_name << "'\n";
error_message{} << "dll " << dll_path.data() << " is already loaded into '" << process_name << "'\n";
}
::CloseHandle(process_handle);

18 changes: 7 additions & 11 deletions source/autogen/AmongUsClient.hpp
Original file line number Diff line number Diff line change
@@ -2,36 +2,32 @@

#include <autogen/InnerNet/InnerNetClient.hpp>

// KIIHPMJOOGI in 2020.10.8i
// il2cpp: struct AmongUsClient_Fields
struct AmongUsClient : InnerNet::InnerNetClient {
std::int32_t AutoOpenStore; // [marker]
std::int32_t GameMode;
struct System_String_o* OnlineScene;
struct System_String_o* MainMenuScene;
struct GameData_o* GameDataPrefab;
struct PlayerControl_o* PlayerPrefab;
struct System_Collections_Generic_List_ShipStatus__o* ShipPrefabs;
struct System_Collections_Generic_List_AssetReference__o* ShipPrefabs;
std::int32_t TutorialMapId;
float SpawnRadius;
std::int32_t discoverState;
struct System_Collections_Generic_List_IDisconnectHandler__o* DisconnectHandlers;
struct System_Collections_Generic_List_IGameListHandler__o* GameListHandlers;
std::int32_t CrossplayPrivilegeError;
std::int32_t MAX_CLIENT_WAIT_TIME;

// il2cpp: struct AmongUsClient_StaticFields
struct StaticFields {
AmongUsClient* Instance;
};

static Class<AmongUsClient>* get_class() {
switch (mod_info::get_game_version()) {
case game_version::v2020_6_9s: return Class<AmongUsClient>::find("AmongUsClient");
case game_version::v2020_9_22s: return Class<AmongUsClient>::find("AmongUsClient");
case game_version::v2020_10_8i: return Class<AmongUsClient>::find("KIIHPMJOOGI");
}
return nullptr;
return Class<AmongUsClient>::find("AmongUsClient");
}

static AmongUsClient* Instance() {
return get_class()->statics()->Instance;
}
};
CHECK_TYPE(AmongUsClient, 0x98);
CHECK_TYPE(AmongUsClient, 0xB4);
4 changes: 2 additions & 2 deletions source/autogen/CustomNetworkTransform.hpp
Original file line number Diff line number Diff line change
@@ -4,11 +4,11 @@
#include <autogen/UnityEngine/Vector2.hpp>
#include <autogen/UnityEngine/Rigidbody2D.hpp>

// LEAFKMLHONI in 2020.10.8i
// il2cpp: struct CustomNetworkTransform_Fields
struct CustomNetworkTransform : InnerNet::InnerNetObject {
struct FloatRange_o *XRange;
struct FloatRange_o *YRange;
float sendInterval; // [marker]
float sendInterval;
float snapThreshold;
float interpolateMovement;
UnityEngine::Rigidbody2D* body;
62 changes: 38 additions & 24 deletions source/autogen/GameData.hpp
Original file line number Diff line number Diff line change
@@ -2,59 +2,73 @@

#include <autogen/InnerNet/InnerNetObject.hpp>
#include <autogen/System/Collections/Generic/List.hpp>
#include <autogen/System/Collections/Generic/Dictionary.hpp>
#include <autogen/System/String.hpp>
#include <autogen/PlayerOutfitType.hpp>

struct PlayerControl;
struct RoleBehaviour;

// KIOIFEIADMB in 2020.10.8i
// il2cpp: struct GameData_Fields
struct GameData : InnerNet::InnerNetObject {
// GameData.CBOMPDNBEIF in 2020.9.22
// KIOIFEIADMB.FAECJOFPICI in 2020.10.8i
// il2cpp: struct GameData_TaskInfo_Fields
struct TaskInfo : ::Object {
// [marker] is PlayerInfo.Tasks
std::uint32_t Id;
std::uint8_t TypeId;
bool Complete;
};
CHECK_TYPE(TaskInfo, 0x8);

// GameData.IHEKEPMDGIJ in 2020.9.22
// KIOIFEIADMB.IOHELPMOCLM in 2020.10.8i
// il2cpp: struct GameData_PlayerOutfit_Fields
struct PlayerOutfit : ::Object {
bool dontCensorName;
std::int32_t ColorId;
System::String* HatId;
System::String* PetId;
System::String* SkinId;
System::String* VisorId;
System::String* NamePlateId;
System::String* preCensorName;
System::String* postCensorName;
};
CHECK_TYPE(PlayerOutfit, 0x24);

// il2cpp: struct GameData_PlayerInfo_Fields
struct PlayerInfo : ::Object {
// no [marker], search for 'killer'
std::uint8_t PlayerId;
System::String* PlayerName;
std::uint8_t ColorId;
std::uint32_t HatId;
std::uint32_t PetId;
std::uint32_t SkinId;
System::String* FriendCode;
System::String* Puid;
std::uint16_t RoleType;
System::Collections::Generic::Dictionary<PlayerOutfitType, PlayerOutfit>* Outfits;
std::uint32_t PlayerLevel;
bool Disconnected;
System::Collections::Generic::List<TaskInfo>* Tasks;
bool IsImpostor;
RoleBehaviour* Role;
System::Collections::Generic::List<TaskInfo*>* Tasks;
bool IsDead;
PlayerControl *_object;
PlayerControl* _object;

System::String* get_PlayerName() const {
return get_cached_method<&PlayerInfo::get_PlayerName>("get_PlayerName")(this);
}
};
CHECK_TYPE(PlayerInfo, 0x28);
CHECK_TYPE(PlayerInfo, 0x2C);

System::Collections::Generic::List<PlayerInfo>* AllPlayers; // [marker]
System::Collections::Generic::List<PlayerInfo*>* AllPlayers;
std::int32_t TotalTasks;
std::int32_t CompletedTasks;
struct RoleBehaviour_o* DefaultRole;

// il2cpp: struct GameData_StaticFields
struct StaticFields {
GameData* instance;
};

static Class<GameData>* get_class() {
switch (mod_info::get_game_version()) {
case game_version::v2020_6_9s: return Class<GameData>::find("GameData");
case game_version::v2020_9_22s: return Class<GameData>::find("GameData");
case game_version::v2020_10_8i: return Class<GameData>::find("KIOIFEIADMB");
}
return nullptr;
return Class<GameData>::find("GameData");
}

static GameData* instance() {
return get_class()->statics()->instance;
}
};
CHECK_TYPE(GameData, 0x28);
CHECK_TYPE(GameData, 0x2C);
25 changes: 25 additions & 0 deletions source/autogen/InnerNet/ClientData.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#pragma once

#include <autogen/Object.hpp>

struct PlayerControl;

namespace InnerNet {

// il2cpp: struct InnerNet_ClientData_Fields
struct ClientData : ::Object {
std::int32_t Id;
bool InScene;
bool IsReady;
bool HasBeenReported;
PlayerControl* Character;
std::uint32_t PlayerLevel;
struct PlatformSpecificData_o* PlatformData;
System::String* PlayerName;
System::String* ProductUserId;
System::String* FriendCode;
std::int32_t ColorId;
};
CHECK_TYPE(ClientData, 0x24);

}
Loading