Writing a Phasmophobia cheat
Fri Dec 29 2023 • 12 min read
Okay, so you’ve decided you wanna cheat in a co-op horror game, fucking weirdo. Anyway before we start we need some info about the game, like what game engine is it using, is it x64 or x86 and does it have an anticheat? Phasmophobia is made in Unity, x64 and does not have an anticheat which makes our job a lot easier.
Setting up the project
Ight, open up Visual Studio and create a new DLL C++ project. I am kinda really fucking lazy so instead of writing our own hook functions we’ll just use MS-Detours for this project. To add Detours to our project head into project properties -> Linker -> Input
then add your detours.lib
file to Additional Dependencies. You’ll also need to include the detours.h
header file, How the fuck do I do this?. Also set the C++ Language standard and the C Language standard to latest
or something similar.
Getting function addresses and structs
Phasmophobia uses something called IL2CPP which means we can’t just throw the dll in something like DnSpy and look at the code. To get past this I normally would just use Il2CppInspector but that project has been dead for quite some time and doesn’t work with the newer Unity version that Phasmophobia is using, so we’ll be using Cpp2IL instead.
To use this, all you have to do is run this command:
.\Cpp2IL.exe --game-path="C:\Program Files (x86)\Steam\steamapps\common\Phasmophobia" --parallel --analyze-all
or wherever you have installed your Phasmophobia. Once this is finished you can open up something like DnSpy, I’ll be using Jetbrains dotPeek for this however.
Now that we have our decompiler open we can start looking for things we want, like the ghost type. Most things will be located in the Assembly-CSharp.dll
file, so open that up and look for the GhostAI
class. To get the GhostAI object we can hook the GhostAI::Start
function.
[Address(RVA = "0x158C940", Offset = "0x158B740", VA = "0x18158C940")]
private void Start()
{
}
We can see that the function is located at base address + RVA (0x158F920
). That’s great and all but how do we get the ghost type? Below are all the fields of the GhostAI
class.
[Token(Token = "0x20000E9")]
public class GhostAI : MonoBehaviour
{
[Cpp2IlInjected.FieldOffset(Offset = "0x20")]
[Token(Token = "0x40005D2")]
private readonly ഠദവടസളഠഺദ മപജജഫലസഞട;
[Cpp2IlInjected.FieldOffset(Offset = "0x28")]
[Token(Token = "0x40005D3")]
public GhostAI.വബബരരറമഞഥ \u0D3Bഺര\u0D3Bപഠനളസ;
[Cpp2IlInjected.FieldOffset(Offset = "0x30")]
[Token(Token = "0x40005D4")]
public PhotonView ശനടധലപണഴവ;
[Cpp2IlInjected.FieldOffset(Offset = "0x38")]
[Token(Token = "0x40005D5")]
public GhostInfo ഝറഴജഴഫഹഥജ;
[Cpp2IlInjected.FieldOffset(Offset = "0x40")]
[Token(Token = "0x40005D6")]
public NavMeshAgent ലഠഴഺമഫമ\u0D3Bഠ;
[Cpp2IlInjected.FieldOffset(Offset = "0x48")]
[Token(Token = "0x40005D7")]
public GhostAudio വദറതയല��മഝ;
[Cpp2IlInjected.FieldOffset(Offset = "0x50")]
[Token(Token = "0x40005D8")]
public GhostInteraction ഷഥഴഡണഫഞഢഺ;
[Cpp2IlInjected.FieldOffset(Offset = "0x58")]
[Token(Token = "0x40005D9")]
public GhostActivity യഞമഡശദദനണ;
[Cpp2IlInjected.FieldOffset(Offset = "0x60")]
[Token(Token = "0x40005DA")]
[HideInInspector]
public GhostModel നഢനനഴബഞസത;
[Cpp2IlInjected.FieldOffset(Offset = "0x68")]
[Token(Token = "0x40005DB")]
[SerializeField]
private GhostModel eventModel;
[Cpp2IlInjected.FieldOffset(Offset = "0x70")]
[Token(Token = "0x40005DC")]
public GhostModel[] സയഠഝഷഠറഭബ;
[Cpp2IlInjected.FieldOffset(Offset = "0x78")]
[Token(Token = "0x40005DD")]
public GhostModel[] നറഫളശഴശഠണ;
[Cpp2IlInjected.FieldOffset(Offset = "0x80")]
[Token(Token = "0x40005DE")]
private bool \u0D3Bടധഢദജഞയധ;
[Cpp2IlInjected.FieldOffset(Offset = "0x84")]
[Token(Token = "0x40005DF")]
[HideInInspector]
public ShadowCastingMode തശപളഠളതഡവ;
[Cpp2IlInjected.FieldOffset(Offset = "0x88")]
[Token(Token = "0x40005E0")]
[HideInInspector]
public List<Vector3> മനഥഴഴണഢഺജ;
[Cpp2IlInjected.FieldOffset(Offset = "0x90")]
[Token(Token = "0x40005E1")]
private float ഭജ\u0D3Bഢഴബശഹഫ;
[Cpp2IlInjected.FieldOffset(Offset = "0x98")]
[Token(Token = "0x40005E2")]
public SanityDrainer \u0D3Bഺഝഷബഷഩണഩ;
[Cpp2IlInjected.FieldOffset(Offset = "0xA0")]
[Token(Token = "0x40005E3")]
[HideInInspector]
public bool ണഺശഥശഥധതറ;
[Cpp2IlInjected.FieldOffset(Offset = "0xA4")]
[Token(Token = "0x40005E4")]
public LayerMask ജഥഭടജഞലഹപ;
[Cpp2IlInjected.FieldOffset(Offset = "0xA8")]
[Token(Token = "0x40005E5")]
public Transform പനതദയഢഠശസ;
[Cpp2IlInjected.FieldOffset(Offset = "0xB0")]
[Token(Token = "0x40005E6")]
public Transform ജളഠദഹളഝദഝ;
[Cpp2IlInjected.FieldOffset(Offset = "0xB8")]
[Token(Token = "0x40005E7")]
public Transform ബഺപടഫബധസഭ;
[Cpp2IlInjected.FieldOffset(Offset = "0xC0")]
[Token(Token = "0x40005E8")]
[HideInInspector]
public float പഫപഞഺദഴദഷ;
[Cpp2IlInjected.FieldOffset(Offset = "0xC4")]
[Token(Token = "0x40005E9")]
[HideInInspector]
public float ഢറഴഝണബനഭസ;
[Cpp2IlInjected.FieldOffset(Offset = "0xC8")]
[Token(Token = "0x40005EA")]
[HideInInspector]
public float ജ\u0D3Bഹ\u0D3Bമഭ\u0D3Bശബ;
[Cpp2IlInjected.FieldOffset(Offset = "0xCC")]
[Token(Token = "0x40005EB")]
[HideInInspector]
public bool ജയമടയഩപമഞ;
[Cpp2IlInjected.FieldOffset(Offset = "0xCD")]
[Token(Token = "0x40005EC")]
[HideInInspector]
public bool ടലഡഹഫഢയമഩ;
[Cpp2IlInjected.FieldOffset(Offset = "0xD0")]
[Token(Token = "0x40005ED")]
[HideInInspector]
public Vector3 ലന\u0D3Bസതഷ\u0D3Bഴഹ;
[Cpp2IlInjected.FieldOffset(Offset = "0xE0")]
[Token(Token = "0x40005EE")]
public GameObject ധഺമലയളടണഥ;
[Cpp2IlInjected.FieldOffset(Offset = "0xE8")]
[Token(Token = "0x40005EF")]
[HideInInspector]
public bool ഥനള\u0D3B\u0D3Bഝശവഭ;
[Cpp2IlInjected.FieldOffset(Offset = "0xE9")]
[Token(Token = "0x40005F0")]
[HideInInspector]
public bool ഭമഫവഫസഢഞണ;
[Cpp2IlInjected.FieldOffset(Offset = "0xEA")]
[Token(Token = "0x40005F1")]
[HideInInspector]
public bool ണഷഺവദഠഭഹസ;
[Cpp2IlInjected.FieldOffset(Offset = "0xEB")]
[Token(Token = "0x40005F2")]
[HideInInspector]
public bool യഫവമളവറഺമ;
[Cpp2IlInjected.FieldOffset(Offset = "0xF0")]
[Token(Token = "0x40005F3")]
[HideInInspector]
public WhiteSage ഷഹഴശരഥധഩപ;
[Cpp2IlInjected.FieldOffset(Offset = "0xF8")]
[Token(Token = "0x40005F4")]
private float പഴളണഞഞഷഹഥ;
[Cpp2IlInjected.FieldOffset(Offset = "0xFC")]
[Token(Token = "0x40005F5")]
[HideInInspector]
public bool ണഩഞബഹമഝഝന;
[Cpp2IlInjected.FieldOffset(Offset = "0xFD")]
[Token(Token = "0x40005F6")]
[HideInInspector]
public bool ഷധ\u0D3Bതതഩലളത;
[Cpp2IlInjected.FieldOffset(Offset = "0xFE")]
[Token(Token = "0x40005F7")]
[HideInInspector]
public bool രഠ\u0D3Bലതബബനജ;
[Cpp2IlInjected.FieldOffset(Offset = "0x100")]
[Token(Token = "0x40005F8")]
[HideInInspector]
public Player ദഫഥഩഩഭഹഩമ;
[Cpp2IlInjected.FieldOffset(Offset = "0x108")]
[Token(Token = "0x40005F9")]
[HideInInspector]
public int റഹവധനലഷ\u0D3Bള;
[Cpp2IlInjected.FieldOffset(Offset = "0x10C")]
[Token(Token = "0x40005FA")]
[HideInInspector]
public Vector3 ഭണഺവഢഞരഹ\u0D3B;
[Cpp2IlInjected.FieldOffset(Offset = "0x118")]
[Token(Token = "0x40005FB")]
private readonly float[] ജഡജസഠമററല;
[Cpp2IlInjected.FieldOffset(Offset = "0x120")]
[Token(Token = "0x40005FC")]
private readonly float[] നസഩണഥവവരഭ;
[Cpp2IlInjected.FieldOffset(Offset = "0x128")]
[Token(Token = "0x40005FD")]
private readonly float[] ഝയഩബതഥഥശയ;
[Cpp2IlInjected.FieldOffset(Offset = "0x130")]
[Token(Token = "0x40005FE")]
private int ഡലടഫഡണഭഢഷ;
[Cpp2IlInjected.FieldOffset(Offset = "0x134")]
[Token(Token = "0x40005FF")]
private int ധശഷണനരളളഹ;
[Cpp2IlInjected.FieldOffset(Offset = "0x138")]
[Token(Token = "0x4000600")]
private int ഩഞബഴഝഥഺദധ;
[Cpp2IlInjected.FieldOffset(Offset = "0x13C")]
[Token(Token = "0x4000601")]
private int മഹധശതദഫഫഷ;
[Cpp2IlInjected.FieldOffset(Offset = "0x140")]
[Token(Token = "0x4000602")]
private int ഩധഝഭളഠയശര;
[Cpp2IlInjected.FieldOffset(Offset = "0x148")]
[Token(Token = "0x4000603")]
private readonly float[] ണഭബധബശവ\u0D3Bണ;
[Cpp2IlInjected.FieldOffset(Offset = "0x150")]
[Token(Token = "0x4000604")]
private readonly float[] ഹഡ\u0D3Bറനഫഫഝന;
[Cpp2IlInjected.FieldOffset(Offset = "0x158")]
[Token(Token = "0x4000605")]
private readonly float[] വബശഠജണളഠഺ;
[Cpp2IlInjected.FieldOffset(Offset = "0x160")]
[Token(Token = "0x4000606")]
private float ഩളഢരറരധ\u0D3Bഴ;
[Cpp2IlInjected.FieldOffset(Offset = "0x168")]
[Token(Token = "0x4000607")]
private readonly int[] ഭഢഝഭതഫദവ\u0D3B;
[Cpp2IlInjected.FieldOffset(Offset = "0x170")]
[Token(Token = "0x4000608")]
private readonly int[] ശയഹഡറഡഥടല;
[Cpp2IlInjected.FieldOffset(Offset = "0x178")]
[Token(Token = "0x4000609")]
private readonly int[] ധലഢപബഡമഭല;
[Cpp2IlInjected.FieldOffset(Offset = "0x180")]
[Token(Token = "0x400060A")]
private readonly int[] \u0D3Bണഫസഫയഠസന;
}
Ghost type is probably an enum but where is it? You can find it in the GhostTraits
class, to get there, go the GhostInfo
class then go the first type that is being used in the GhostInfo
class, in this case it is പദഴലടഫഺഷവ
.
[Token(Token = "0x20000F7")]
public class GhostInfo : MonoBehaviourPun
{
[FieldOffset(Offset = "0x28")]
[Token(Token = "0x4000652")]
[HideInInspector]
public പദഴലടഫഺഷവ ളവയഥസരഹഥട;
[FieldOffset(Offset = "0x68")]
[Token(Token = "0x4000653")]
[SerializeField]
private GhostAI ghost;
[FieldOffset(Offset = "0x70")]
[Token(Token = "0x4000654")]
[HideInInspector]
public LevelRoom ഹഷഹധതജഺലള;
[FieldOffset(Offset = "0x78")]
[Token(Token = "0x4000655")]
[HideInInspector]
public float ഫഥമതഹഷഫഩബ;
[FieldOffset(Offset = "0x7C")]
[Token(Token = "0x4000656")]
private bool ണടറളശഥസറപ;
}
Which will bring us to this struct, the first field (ബണജഷസഷണഞഴ) is the ghost type and the second one (ഹഭഴഞഴന\u0D3Bഥഝ) is the mimic type.
[Token(Token = "0x200015E")]
[SerializeField]
public struct പദഴലടഫഺഷവ
{
[FieldOffset(Offset = "0x0")]
[Token(Token = "0x400086D")]
public പദഴലടഫഺഷവ.ഝഡശഭഡഡഷഴജ ബണജഷസഷണഞഴ;
[FieldOffset(Offset = "0x4")]
[Token(Token = "0x400086E")]
public പദഴലടഫഺഷവ.ഝഡശഭഡഡഷഴജ ഹഭഴഞഴന\u0D3Bഥഝ;
[FieldOffset(Offset = "0x8")]
[Token(Token = "0x400086F")]
public List<ദസഫനഠശഢപവ> ഠഥളഡഠലഹമത;
[FieldOffset(Offset = "0x10")]
[Token(Token = "0x4000870")]
public List<ദസഫനഠശഢപവ> ഭടഞ\u0D3Bറഝമനത;
[FieldOffset(Offset = "0x18")]
[Token(Token = "0x4000871")]
public int \u0D3Bദഞന\u0D3Bഫഺടഡ;
[FieldOffset(Offset = "0x1C")]
[Token(Token = "0x4000872")]
public bool ഥഭദടബശനദവ;
[FieldOffset(Offset = "0x20")]
[Token(Token = "0x4000873")]
public string തണവബഝഝഹവഹ;
[FieldOffset(Offset = "0x28")]
[Token(Token = "0x4000874")]
public int തഺറഡദഠഹരജ;
[FieldOffset(Offset = "0x2C")]
[Token(Token = "0x4000875")]
public int സയഡലരണഷദന;
[FieldOffset(Offset = "0x30")]
[Token(Token = "0x4000876")]
public bool ദജഷനജഭധജദ;
[FieldOffset(Offset = "0x34")]
[Token(Token = "0x4000877")]
public int ബഭഞപരയധഠണ;
[FieldOffset(Offset = "0x38")]
[Token(Token = "0x4000878")]
public int യലളനഹഝയതഠ;
[FieldOffset(Offset = "0x3C")]
[Token(Token = "0x4000879")]
public bool ല\u0D3Bസഺഢവസളഫ;
[Token(Token = "0x200015F")]
public enum ഝഡശഭഡഡഷഴജ
{
[Token(Token = "0x400087B")] Spirit,
[Token(Token = "0x400087C")] Wraith,
[Token(Token = "0x400087D")] Phantom,
[Token(Token = "0x400087E")] Poltergeist,
[Token(Token = "0x400087F")] Banshee,
[Token(Token = "0x4000880")] Jinn,
[Token(Token = "0x4000881")] Mare,
[Token(Token = "0x4000882")] Revenant,
[Token(Token = "0x4000883")] Shade,
[Token(Token = "0x4000884")] Demon,
[Token(Token = "0x4000885")] Yurei,
[Token(Token = "0x4000886")] Oni,
[Token(Token = "0x4000887")] Yokai,
[Token(Token = "0x4000888")] Hantu,
[Token(Token = "0x4000889")] Goryo,
[Token(Token = "0x400088A")] Myling,
[Token(Token = "0x400088B")] Onryo,
[Token(Token = "0x400088C")] TheTwins,
[Token(Token = "0x400088D")] Raiju,
[Token(Token = "0x400088E")] Obake,
[Token(Token = "0x400088F")] Mimic,
[Token(Token = "0x4000890")] Moroi,
[Token(Token = "0x4000891")] Deogen,
[Token(Token = "0x4000892")] Thaye,
}
}
Creating our SDK
Back in Visual Studio we are, let’s start by creating a simple SDK that we can use. I’ll create a new header file called sdk.h
and add a base address and a macro to define functions. I’ll also create another header file GhostAI.h
and add the GhostAI structs to it and define the GhostAI::Start function.
Because GhostAI derives from MonoBehaviour and GhostInfo from MonoBehaviourPun we’ll have to add those fields to the GhostAIFields
struct too, I’ll give you the MonoBehaviour
and the MonoBehaviourPun
structs. We won’t need to add every field from the actual GhostAI class, just the ones we need; so up until the GhostInfo field.
// sdk.h
#pragma once
#define DECLARE_FUNCTION_POINTER(NAME, TYPE, ADDRESS) \
using NAME = TYPE; \
inline NAME NAME##_ptr = reinterpret_cast<NAME>(BASE_ADDRESS + ADDRESS);
namespace SDK
{
const auto BASE_ADDRESS = reinterpret_cast<uintptr_t>(GetModuleHandleW(L"GameAssembly.dll"));
}
// MonoBehaviour.h
namespace SDK
{
struct __declspec(align(8)) Object1Fields
{
void* m_CachedPtr;
void* m_CancellationTokenSource;
};
struct Component1Fields
{
Object1Fields _;
};
struct BehaviourFields
{
Component1Fields _;
};
struct MonoBehaviourFields
{
BehaviourFields _;
};
struct MonoBehaviour
{
void* Clazz; // MonoBehaviourClass
void* Monitor; // MonitorData
MonoBehaviourFields Fields;
};
struct MonoBehaviourPunFields
{
MonoBehaviourFields _;
void* pvCache;
};
struct MonoBehaviourPunCallbacksFields
{
MonoBehaviourPunFields _;
};
}
// GhostAI.h
#pragma once
#include "sdk.h"
namespace SDK
{
struct GhostAIFields
{
MonoBehaviourFields _;
void* Field0;
int32_t Field1;
void* Field2;
GhostInfo* GhostInfo; // Only field we care about
};
struct GhostAI
{
void* Clazz; // GhostAI class
void* Monitor; // Monitor Data
GhostAIFields Fields;
};
DECLARE_FUNCTION_POINTER(GhostAI_Start, void(*)(GhostAI* ghostAI, void* methodInfo), 0x158C940);
}
Okay great, now we’ll need to create the GhostInfo
struct and the GhostTrait
struct. Don’t forget to include them in the sdk.h
. We will need every field from the GhostTrait
struct because the GhostType
field will only be valid after Name
isn’t a nullptr anymore.
// GhostInfo.h
#pragma once
#include "sdk.h"
namespace SDK
{
struct GhostInfoFields
{
MonoBehaviourPunFields _;
GhostTraits GhostTraits; // very important 1!!!1
// don't care about other fields
};
struct GhostInfo
{
void* Clazz; // GhostInfo class
void* Monitor; // Monitor Data
GhostInfoFields Fields;
};
}
// GhostTraits.h
#pragma once
#include "sdk.h"
namespace SDK
{
enum class GhostType: int32_t
{
Spirit,
Wraith,
Phantom,
Poltergeist,
Banshee,
Jinn,
Mare,
Revenant,
Shade,
Demon,
Yurei,
Oni,
Yokai,
Hantu,
Goryo,
Myling,
Onryo,
TheTwins,
Raiju,
Obake,
Mimic,
Moroi,
Deogen,
Thaye,
};
struct GhostTraits
{
GhostType GhostType_;
GhostType MimicType;
// don't care about the other ones
void* Field2;
void* Field3;
int32_t Field4;
bool Field5;
void* Name;
int32_t Field7;
int Field8;
bool Field9;
int32_t Field10;
int32_t Field11;
bool Field12;
};
}
Hooking the GhostAI::Start function
Back in our dllmain.cpp
we can create our “HackThread”, define our GhostAI_Start
function and hook it using Detours.
// dllmain.cpp
HMODULE hHackModule = nullptr;
HANDLE hHackThread = nullptr;
void hkGhostAI_Start(SDK::GhostAI* _ghostAI, void* methodInfo)
{
// Calling original function
SDK::GhostAI_Start_ptr(_ghostAI, methodInfo);
}
DWORD WINAPI HackThread()
{
// Detouring GhostAI.Start
DetourTransactionBegin();
DetourUpdateThread(GetCurrentThread());
DetourAttach(&reinterpret_cast<PVOID&>(SDK::GhostAI_Start_ptr), hkGhostAI_Start);
DetourTransactionCommit();
// No exiting the cheat yet
while (true)
{
if (GetAsyncKeyState(VK_END) & 1)
{
break;
}
Sleep(100);
}
// Un-detouring GhostAI.Start
DetourTransactionBegin();
DetourUpdateThread(GetCurrentThread());
DetourDetach(&reinterpret_cast<PVOID&>(SDK::GhostAI_Start_ptr), hkGhostAI_Start);
DetourTransactionCommit();
CloseHandle(hHackThread);
FreeLibraryAndExitThread(hHackModule, 0);
}
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ulReasonForCall, LPVOID lpReserved)
{
if (ulReasonForCall == DLL_PROCESS_ATTACH)
{
hHackModule = hModule;
hHackThread = CreateThread(nullptr, 0, reinterpret_cast<LPTHREAD_START_ROUTINE>(HackThread), hModule, 0, nullptr);
}
return TRUE;
}
Displaying the ghost type
Woah almost there, now to display the ghost type I’ll just add a simple console using AllocConsole
. We’ll also need to save the GhostAI pointer to a global variable so we can use it.
// dllmain.cpp
SDK::GhostAI* ghostAI = nullptr;
void hkGhostAI_Start(SDK::GhostAI* _ghostAI, void* methodInfo)
{
// Saving ghostAI pointer
ghostAI = _ghostAI;
// Calling original function
SDK::GhostAI_Start_ptr(ghostAI, methodInfo);
}
DWORD WINAPI HackThread()
{
// Beep boop I'm a console
FILE* f;
AllocConsole();
freopen_s(&f, "CONOUT$", "w", stdout);
// other code
// when exiting after removing hook
// Bye bye console
FreeConsole();
}
Cool, now that we can print stuff to a console, let’s print the ghost type. To do this we’ll need to convert the enum to a string, I’ll just use a switch statement for this because I’m stupid. We will print via the HackThread while loop ‘cause ghostInfo
can be a nullptr.
// GhostTraits.h
inline std::string GhostTypeToString(GhostType ghostType)
{
switch (ghostType)
{
case GhostType::Spirit:
return "Spirit";
case GhostType::Wraith:
return "Wraith";
case GhostType::Phantom:
return "Phantom";
case GhostType::Poltergeist:
return "Poltergeist";
case GhostType::Banshee:
return "Banshee";
case GhostType::Jinn:
return "Jinn";
case GhostType::Mare:
return "Mare";
case GhostType::Revenant:
return "Revenant";
case GhostType::Shade:
return "Shade";
case GhostType::Demon:
return "Demon";
case GhostType::Yurei:
return "Yurei";
case GhostType::Oni:
return "Oni";
case GhostType::Yokai:
return "Yokai";
case GhostType::Hantu:
return "Hantu";
case GhostType::Goryo:
return "Goryo";
case GhostType::Myling:
return "Myling";
case GhostType::Onryo:
return "Onryo";
case GhostType::TheTwins:
return "The Twins";
case GhostType::Raiju:
return "Raiju";
case GhostType::Obake:
return "Obake";
case GhostType::Mimic:
return "Mimic";
case GhostType::Moroi:
return "Moroi";
case GhostType::Deogen:
return "Deogen";
case GhostType::Thaye:
return "Thaye";
default:
return "Unknown";
}
}
// dllmain.cpp
bool shouldPrint = false;
void hkGhostAI_Start(SDK::GhostAI* _ghostAI, void* methodInfo)
{
// Saving ghostAI pointer
ghostAI = _ghostAI;
shouldPrint = true;
// Calling original function
SDK::GhostAI_Start_ptr(ghostAI, methodInfo);
}
DWORD WINAPI HackThread()
{
// code
while (true)
{
/// oooooo spooky ghost
if (shouldPrint && ghostAI)
{
if (const auto ghostInfo = ghostAI->Fields.GhostInfo)
{
// if name is a nullptr then the ghost type isn't valid yet
if (ghostInfo->Fields.GhostTraits.Name)
{
const auto ghostType = ghostInfo->Fields.GhostTraits.GhostType_;
std::cout << "Ghost type: " << GhostTypeToString(ghostType) << std::endl;
shouldPrint = false;
}
}
}
if (GetAsyncKeyState(VK_END) & 1)
{
break;
}
Sleep(100);
}
// more code
}
Anti-kick
During the development of Asthmaphobia I found out that you can create an anti-kick hack by just hooking ServerManager_KickPlayerNetworked
and then not calling the original function :) Have fun.
Conclusion
That’s it, the basics for creating your own shitty Phasmophobia cheat. Source code for this project can be found on GitHub. Yes I know the code is bad, it isn’t meant to be good code.