Content Warning is a co-op horror game where you film spooky stuff with your friends to try and go viral. You can buy it on steam. However, please remember that this article is a technical exchange for learning purposes. Please DO NOT affect other people’s gaming experience.
It is assumed here that readers already have relevant practical experience in ESP. We’ll mainly focuses on describing how to obtain the coordinates of ghosts on the screen with uniref and how to draw it non-intrusively using Python, rather than the principles of ESP.
Get World Coordinates
Because the game is not packaged using IL2CPP, we can use dnSpy to decompile it at the source code level. Let’s take a look at these class names.
As we can see, there are multiple classes starting with Bot_, representing different types of ghosts. In the BotHandler class, there is a bots member, which is of type List<Bot>. Then this variable probably stores all the ghost instances in the current level.
Fortunately, there is also a static member instance in this class. This way we don’t have to work hard to find the instance address of BotHandler. So let’s write some basic code first.
Now we have all active instances of Bot. So how to get the coordinates of these objects? Note that Bot inherits from MonoBehaviour, which means that we can obtain the Vector3 coordinates of the object through bot.transform.position in the C# style. So the question becomes how to translate this C# code into uniref based code.
out = ref.injector.mem_alloc() method_get_position(args=(out,)) position = Vector3(ref.injector.mem_read_float_array(out, 3))
ref.injector.mem_free(out) return position
print(get_object_position(get_all_bots()[0]))
As the function get_transform_instance shows, getting the transform of an object is simple. Just assign the instance address to its base class Component and call the get_transform method. However, when getting the position member of the Transform instance, you cannot directly call the get_position method, otherwise the game will crash. After my testing, CE also has this problem.
Let’s see how this function is implemented.
It calls the get_position_Injected function internally and passes a variable to receive the return value. btw, this function is implemented in C and you can find it by analyzing UnityPlayer.dll. So we allocate a new page and use its address as the parameter to directly call get_position_Injected, which can achieve the same effect without crashing.
World To Screen
You can use traditional methods and calculate the screen coordinates yourself based on the camera matrix. You can also use the WorldToScreenPoint function that comes with the Unity camera to calculate it like I did. So now, our goal is to call Camera.main.WorldToScreenPoint.
In order to prevent crashes, we also prepare parameters ourselves to call WorldToScreenPoint_Injected.
ref.injector.mem_alloc will allocate a memory page by default. So we can use the beginning of the page as the first parameter, and the offset 0x100 to receive the return value.
Draw Text
Now we have everything, just need to draw it on the screen. As writing d3d hook in Python is painful, I prefer to use pygame to draw a transparent window on top.
# check if the target is within the screen if screen_x < screen_width and screen_y < screen_height and screen_pos.z >= 0: draw_text(screen, f"bot_{idx+1}", 25, screen_x, screen_y)
pygame.display.flip() time.sleep(0.01)
How does it feel?
You can get the full code here. But for educational purposes, the code does not have any optimizations. If you run it, you will find that the coordinates update very slowly. You can optimize by moving the unnecessary find_* function call outside the loop. Good luck!