Skip to content

Instantly share code, notes, and snippets.

@capntrips
Forked from robert-nix/Inject.cpp
Last active October 11, 2021 10:46
Show Gist options
  • Save capntrips/659bec02ab8f65347f40bd88aa0ef059 to your computer and use it in GitHub Desktop.
Save capntrips/659bec02ab8f65347f40bd88aa0ef059 to your computer and use it in GitHub Desktop.
Unity 2017.1.2p4 type information
{"1": "GameObject", "2": "Component", "4": "Transform", "5": "TimeManager", "8": "Component", "11": "AudioManager", "12": "ParticleAnimator", "13": "InputManager", "15": "EllipsoidParticleEmitter", "19": "Physics2DSettings", "20": "Camera", "21": "Material", "23": "MeshRenderer", "25": "Component", "26": "ParticleRenderer", "28": "Texture2D", "29": "OcclusionCullingSettings", "30": "GraphicsSettings", "33": "MeshFilter", "41": "OcclusionPortal", "43": "Mesh", "45": "Skybox", "47": "QualitySettings", "48": "Shader", "49": "TextAsset", "50": "Rigidbody2D", "53": "Component", "54": "Rigidbody", "55": "PhysicsManager", "56": "Component", "57": "Component", "58": "CircleCollider2D", "59": "HingeJoint", "60": "PolygonCollider2D", "61": "BoxCollider2D", "62": "PhysicsMaterial2D", "64": "MeshCollider", "65": "BoxCollider", "66": "CompositeCollider2D", "68": "EdgeCollider2D", "70": "CapsuleCollider2D", "72": "ComputeShader", "74": "AnimationClip", "75": "ConstantForce", "76": "WorldParticleCollider", "78": "TagManager", "81": "AudioListener", "82": "AudioSource", "83": "AudioClip", "84": "RenderTexture", "86": "CustomRenderTexture", "87": "MeshParticleEmitter", "88": "Component", "89": "Cubemap", "90": "Avatar", "91": "AnimatorController", "92": "GUILayer", "94": "ScriptMapper", "95": "Animator", "96": "TrailRenderer", "98": "DelayedCallManager", "102": "TextMesh", "104": "RenderSettings", "108": "Light", "109": "CGProgram", "111": "Animation", "114": "MonoBehaviour", "115": "MonoScript", "116": "MonoManager", "117": "Texture3D", "119": "Projector", "120": "LineRenderer", "121": "Flare", "122": "Halo", "123": "LensFlare", "124": "FlareLayer", "125": "HaloLayer", "126": "NavMeshProjectSettings", "128": "Font", "129": "PlayerSettings", "131": "GUITexture", "132": "GUIText", "133": "Component", "134": "PhysicMaterial", "135": "SphereCollider", "136": "CapsuleCollider", "137": "SkinnedMeshRenderer", "138": "FixedJoint", "141": "BuildSettings", "142": "AssetBundle", "143": "CharacterController", "144": "CharacterJoint", "145": "SpringJoint", "146": "WheelCollider", "147": "ResourceManager", "148": "NetworkView", "149": "NetworkManager", "150": "PreloadData", "152": "MovieTexture", "153": "ConfigurableJoint", "154": "TerrainCollider", "155": "MasterServerInterface", "156": "TerrainData", "157": "LightmapSettings", "158": "WebCamTexture", "164": "AudioReverbFilter", "165": "AudioHighPassFilter", "166": "AudioChorusFilter", "167": "AudioReverbZone", "168": "AudioEchoFilter", "169": "AudioLowPassFilter", "170": "AudioDistortionFilter", "171": "SparseTexture", "180": "Component", "181": "Component", "182": "WindZone", "183": "Cloth", "184": "SubstanceArchive", "185": "ProceduralMaterial", "186": "ProceduralTexture", "187": "Texture2DArray", "188": "CubemapArray", "191": "OffMeshLink", "192": "OcclusionArea", "193": "Tree", "195": "NavMeshAgent", "196": "NavMeshSettings", "198": "ParticleSystem", "199": "ParticleSystemRenderer", "200": "ShaderVariantCollection", "205": "LODGroup", "208": "NavMeshObstacle", "210": "SortingGroup", "212": "SpriteRenderer", "213": "Sprite", "215": "ReflectionProbe", "218": "Terrain", "220": "LightProbeGroup", "221": "AnimatorOverrideController", "222": "CanvasRenderer", "223": "Canvas", "224": "RectTransform", "225": "CanvasGroup", "226": "BillboardAsset", "227": "BillboardRenderer", "228": "SpeedTreeWindAsset", "229": "Component", "230": "Component", "231": "SpringJoint2D", "232": "DistanceJoint2D", "233": "HingeJoint2D", "234": "SliderJoint2D", "235": "WheelJoint2D", "236": "ClusterInputManager", "238": "NavMeshData", "240": "AudioMixer", "246": "Component", "247": "ConstantForce2D", "248": "Component", "249": "AreaEffector2D", "250": "PointEffector2D", "251": "PlatformEffector2D", "252": "SurfaceEffector2D", "253": "BuoyancyEffector2D", "254": "RelativeJoint2D", "255": "FixedJoint2D", "256": "FrictionJoint2D", "257": "TargetJoint2D", "258": "LightProbes", "259": "LightProbeProxyVolume", "271": "SampleClip", "272": "AudioMixerSnapshot", "273": "AudioMixerGroup", "280": "NScreenBridge", "290": "AssetBundleManifest", "300": "RuntimeInitializeOnLoadManager", "301": "CloudWebServicesManager", "303": "UnityAnalyticsManager", "304": "CrashReportManager", "305": "PerformanceReportingManager", "310": "UnityConnectSettings", "319": "AvatarMask", "320": "PlayableDirector", "328": "VideoPlayer", "329": "VideoClip", "331": "SpriteMask", "363": "OcclusionCullingData"}
// A quick and dirty DLL injector
// This method relies on static linkage and the fact that kernel32 doesn't move
// Compile with the same bitness as the target and the dll.
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#include <Psapi.h>
#include <stdio.h>
int GetPid(char *modName)
{
DWORD processIds[2048];
DWORD actual;
EnumProcesses(processIds, sizeof(processIds), &actual);
actual /= 4;
for (DWORD i = 0; i < actual; i++)
{
TCHAR processName[MAX_PATH];
memset(processName, 0, MAX_PATH);
memcpy(processName, "(null)", 6);
int pid = processIds[i];
if (pid)
{
HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid);
if (hProcess)
{
HMODULE main;
DWORD needed;
if (EnumProcessModules(hProcess, &main, sizeof(main), &needed))
{
GetModuleBaseName(hProcess, main, processName, sizeof(processName));
}
}
}
if (stricmp(processName, modName) == 0)
{
return pid;
}
}
return 0;
}
int main(int argc, char *argv[])
{
if (argc < 3)
{
fprintf(stderr, "Usage: inject [exe name] [dll name]\n");
return 0;
}
int pid = GetPid(argv[1]);
if (pid == 0)
{
fprintf(stderr, "Couldn't find process\n");
return 1;
}
HANDLE hProcess = OpenProcess(
PROCESS_CREATE_THREAD | PROCESS_QUERY_INFORMATION | PROCESS_VM_OPERATION | PROCESS_VM_READ | PROCESS_VM_WRITE,
FALSE, pid);
if (!hProcess)
{
fprintf(stderr, "Couldn't open process: %x\n", GetLastError());
return 1;
}
LPVOID page = VirtualAllocEx(hProcess, (void *)0, 0x1000, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
if (page == NULL)
{
fprintf(stderr, "Couldn't allocate memory in target: %x", GetLastError());
}
SIZE_T bw = 0;
int len = 1 + strlen(argv[2]);
WriteProcessMemory(hProcess, page, argv[2], len, &bw);
if (bw != len)
{
fprintf(stderr, "Couldn't write dll name: %x\n", GetLastError());
return 1;
}
printf("allocated at %x\n", page);
if (!CreateRemoteThread(hProcess, 0, 0, (LPTHREAD_START_ROUTINE)LoadLibrary, page, 0, 0))
{
fprintf(stderr, "Couldn't create thread: %x\n", GetLastError());
return 1;
}
printf("created remote thread\n");
return 0;
}
AABBAnimationClipAnimationCurveAnimationStateArrayBaseBitFieldbitsetboolcharColorRGBAComponentdatadequedoubledynamic_arrayFastPropertyNamefirstfloatFontGameObjectGeneric MonoGradientNEWGUIDGUIStyleintlistlong longmapMatrix4x4fMdFourMonoBehaviourMonoScriptm_ByteSizem_Curvem_EditorClassIdentifierm_EditorHideFlagsm_Enabledm_ExtensionPtrm_GameObjectm_Indexm_IsArraym_IsStaticm_MetaFlagm_Namem_ObjectHideFlagsm_PrefabInternalm_PrefabParentObjectm_Scriptm_StaticEditorFlagsm_Typem_VersionObjectpairPPtr<Component>PPtr<GameObject>PPtr<Material>PPtr<MonoBehaviour>PPtr<MonoScript>PPtr<Object>PPtr<Prefab>PPtr<Sprite>PPtr<TextAsset>PPtr<Texture>PPtr<Texture2D>PPtr<Transform>PrefabQuaternionfRectfRectIntRectOffsetsecondsetshortsizeSInt16SInt32SInt64SInt8staticvectorstringTextAssetTextMeshTextureTexture2DTransformTypelessDataUInt16UInt32UInt64UInt8unsigned intunsigned long longunsigned shortvectorVector2fVector3fVector4fm_ScriptingClassIdentifierGradientType*
// UnityStructGen - Dump the Unity Runtime base type info
// Compile with /MT(d), NOT /MD(d) (i.e. use static-linked runtime)
// Tested with VS2015 x64.
//
// To use:
// Compile Inject.cpp:
// $ cl /O2 /Zi /MT Inject.cpp
// Compile UnityStructGen.cpp:
// $ cl /O2 /Zi /MT /LD UnityStructGen.cpp
//
// 64-bit player now. Clear relocatable flag, use the debugger env variable.
// ...
// $ Inject.exe Player.exe UnityStructGen.dll
// This should write some files and crash the player.
// --- Types ---
// This allows compiling the in Debug mode, since Debug and Release normally
// affect the STL container layout:
#define _ITERATOR_DEBUG_LEVEL 0
#include <cstdlib>
#include <string>
#include <list>
#include <memory>
#include <vector>
#include <stddef.h>
#include <stdint.h>
// Fake allocator to allow us to interop with UnityPlayer's STL objects.
template <class T = char>
class stl_allocator : public std::allocator<T>
{
public:
template<class _Other>
struct rebind
{
typedef stl_allocator<_Other> other;
};
stl_allocator()
{
}
stl_allocator(const std::allocator<T> &)
{
}
private:
int rootref;
};
typedef std::basic_string<char, std::char_traits<char>, stl_allocator<char>> TypeTreeString;
struct Object__RTTI
{
Object__RTTI *base;
void *factory;
int classId;
TypeTreeString className;
int size;
bool isAbstract;
bool unk0;
bool unk1;
};
struct TypeTreeNode
{
int16_t m_Version;
int8_t m_Depth;
int8_t m_IsArray;
int32_t m_Type; // offset; &(1<<31) => uses common buffer; otherwise local buffer
int32_t m_Name; // same as Type
int32_t m_ByteSize;
int32_t m_Index;
int32_t m_MetaFlag;
};
struct MemLabelId
{
int id;
// int *rootref; <-- only in debug builds
};
template <typename T>
struct dynamic_array
{
T *data;
MemLabelId label;
size_t size;
size_t cap; // < 0 => data is in union{T*, T[N]}
}; // 0x14
char *globalBuf;
char *unityVersion;
char *stringsBuf;
struct TypeTree
{
// +0
dynamic_array<TypeTreeNode> m_Nodes;
// +20
dynamic_array<char> m_StringData;
// +40
dynamic_array<void *> m_ByteOffsets;
// +60
// Recursive dumper
std::string Dump() const
{
std::string result{};
char debug[512];
memset(debug, 0, 512);
for (int i = 0; i < m_Nodes.size; i++) {
auto &node = m_Nodes.data[i];
char *type;
char *name;
if (node.m_Type < 0) {
type = globalBuf + (0x7fffffff & node.m_Type);
} else {
type = m_StringData.data + node.m_Type;
}
if (node.m_Name < 0) {
name = globalBuf + (0x7fffffff & node.m_Name);
} else {
name = m_StringData.data + node.m_Name;
}
sprintf(debug, "%s %s // ByteSize{%x}, Index{%x}, IsArray{%d}, MetaFlag{%x}",
type, name, node.m_ByteSize, node.m_Index, node.m_IsArray, node.m_MetaFlag);
for (int j = 0; j < node.m_Depth; j++) {
result += " ";
}
result += std::string(debug);
result += "\n";
}
return result;
}
// Binary serializer
void Write(FILE *file) const
{
fwrite(&m_Nodes.size, 4, 1, file);
fwrite(&m_StringData.size, 4, 1, file);
fwrite(m_Nodes.data, m_Nodes.size * sizeof(TypeTreeNode), 1, file);
fwrite(m_StringData.data, m_StringData.size, 1, file);
}
};
class ProxyTransfer;
class Object;
typedef int ObjectCreationMode;
#if 0
typedef Object*(__cdecl * Object__Produce_t)(int classID, int instanceID, MemLabelId memLabel, ObjectCreationMode mode);
typedef Object__RTTI*(__cdecl * Object__ClassIDToRTTI_t)(int classID);
typedef void(__thiscall * TypeTree__TypeTree_t)(TypeTree *self);
#endif
typedef void(__cdecl * GenerateTypeTree_t)(Object *object, TypeTree *typeTree, int options);
typedef Object*(__cdecl * Object__Produce_t)(struct RTTIClass *classInfo, int instanceID, int memLabel, ObjectCreationMode mode);
typedef void(__thiscall * TypeTree__TypeTree_t)(TypeTree *self, int memLabel);
// --- Offsets ---
// 5.1.3p2, x86 Windows; pdbs are nice
#if 0
GenerateTypeTree_t GenerateTypeTree = (GenerateTypeTree_t)(void *)0x6346B0;
Object__Produce_t Object__Produce = (Object__Produce_t)(void *)0x40C5E0;
TypeTree__TypeTree_t TypeTree__TypeTree = (TypeTree__TypeTree_t)(void *)0x634D80;
Object__ClassIDToRTTI_t Object__ClassIDToRTTI = (Object__ClassIDToRTTI_t)(void *)0x4093D0;
MemLabelId *kMemBaseObject = (MemLabelId *)0x12540DC;
#endif
// 5.5.2f1, x64 Windows; pdbs are now less nice because RTTI system changed, but hey it's cool Unity you do you fam
GenerateTypeTree_t GenerateTypeTree;
Object__Produce_t Object__Produce;
TypeTree__TypeTree_t TypeTree__TypeTree;
struct RTTIClass {
RTTIClass *base;
void *unk1; // probably constructor
const char *name;
const char *unk3;
int classID;
int objectSize;
int typeIndex;
int unk4;
bool isAbstract;
bool unk5;
bool unk6;
};
struct RTTIData {
size_t length;
RTTIClass *data[1];
};
RTTIData *rttiData;
RTTIClass *classFromClassID(int classID) {
for (size_t i = 0; i < rttiData->length; i++) {
RTTIClass *curr = rttiData->data[i];
if (!curr) continue;
if (classID == curr->classID) return curr;
}
return nullptr;
}
// --- Dumper ---
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
FILE *dumpFile, *treeFile, *stringsFile, *jsonFile;
bool GenStruct(int classID)
{
RTTIClass *rtti = classFromClassID(classID);
if (rtti == nullptr)
{
return false;
}
// Dump the base class chain:
RTTIClass *base = rtti;
std::string inheritance{ };
while (true)
{
inheritance += base->name;
base = base->base;
if (base != NULL)
{
inheritance += " <- ";
}
else
{
break;
}
}
char buf[256];
sprintf(buf, "\n// classID{%d}: %s\n", classID, inheritance.c_str());
OutputDebugString(buf);
fputs(buf, dumpFile);
// Go up the inheritance chain until we find some data to dump:
int newClassID = classID;
while (rtti->isAbstract)
{
sprintf(buf, "// %s is abstract\n", rtti->name);
OutputDebugString(buf);
fputs(buf, dumpFile);
rtti = rtti->base;
if (rtti == NULL)
{
return false;
}
newClassID = rtti->classID;
}
Object *value = Object__Produce(rtti, 0x32, 0, 0);
// We can't call our TypeTree dtor since we haven't properly implemented
// stl_allocator and ~std::list() will crash.
// If this leak is concerning, link the dtor func (::~TypeTree()) and
// call it explicitly.
TypeTree *typeTree = (TypeTree *)malloc(sizeof(TypeTree));
TypeTree__TypeTree(typeTree, 0x32);
GenerateTypeTree(value, typeTree, 0x2000);
fputs(typeTree->Dump().c_str(), dumpFile);
fflush(dumpFile);
fwrite(&newClassID, 4, 1, treeFile);
char fakeGuid[0x20];
memset(fakeGuid, 0, 0x20);
if (newClassID < 0) {
fwrite(fakeGuid, 0x20, 1, treeFile);
} else {
fwrite(fakeGuid, 0x10, 1, treeFile);
}
typeTree->Write(treeFile);
if (ftell(jsonFile) > 1) {
fprintf(jsonFile, ", ");
}
fprintf(jsonFile, "\"%d\": \"%s\"", classID, rtti->name);
return true;
}
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
if (fdwReason != DLL_PROCESS_ATTACH)
{
return TRUE;
}
HMODULE baseAddress = GetModuleHandle(NULL);
globalBuf = (char *)(baseAddress + (0x14113E7E0 - 0x140000000) / 4);
GenerateTypeTree = (GenerateTypeTree_t)(void *)(baseAddress + (0x1406FC870 - 0x140000000) / 4);
Object__Produce = (Object__Produce_t)(void *)(baseAddress + (0x1401F9F80 - 0x140000000) / 4);
TypeTree__TypeTree = (TypeTree__TypeTree_t)(void *)(baseAddress + (0x1406F99B0 - 0x140000000) / 4);
unityVersion = (char *)(baseAddress + (0x141190470 - 0x140000000) / 4);
rttiData = (RTTIData *)(baseAddress + (0x1414C9C80 - 0x140000000) / 4);
stringsBuf = globalBuf;
dumpFile = fopen("structs.dump", "wb");
treeFile = fopen("structs.dat", "wb");
stringsFile = fopen("strings.dat", "wb");
jsonFile = fopen("classes.json", "w");
const int targetPlatform = 5;
const bool hasTypeTrees = 1;
fwrite(unityVersion, 1, 1 + strlen(unityVersion), treeFile);
fwrite(&targetPlatform, 4, 1, treeFile);
fwrite(&hasTypeTrees, 1, 1, treeFile);
fpos_t countPos;
fgetpos(treeFile, &countPos);
int typeCount = 0;
fwrite(&typeCount, 4, 1, treeFile);
fprintf(jsonFile, "{");
for (int classId = 0; classId < 1024; classId++)
{
if (GenStruct(classId))
{
typeCount++;
}
}
int zero = 0;
fwrite(&zero, 4, 1, treeFile);
fsetpos(treeFile, &countPos);
fwrite(&typeCount, 4, 1, treeFile);
while (strlen(stringsBuf)) {
fwrite(stringsBuf, 1, 1 + strlen(stringsBuf), stringsFile);
stringsBuf += 1 + strlen(stringsBuf);
}
fprintf(jsonFile, "}");
fclose(treeFile);
fclose(dumpFile);
fclose(stringsFile);
fclose(jsonFile);
DebugBreak();
return TRUE;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment