Creating a PS4 bot in C# - Part 1

In this tutorial, we are going to create a PlayStation 4 bot in C# using EasyHook. By the end of this tutorial, you will be able to automate controls in any PS4 game through PS4 Remote Play.

EDIT - Check out PS4RemotePlayInterceptor, a library that allows you to achieve the result of this tutorial with only a few lines of code.

The reason I created this tutorial is because I could not find any libraries or tutorials on this topic. PS4 Remote Play doesn’t accept third-party inputs except for their DualShock 4 controller and a few keys to emulate the D-Pad from the keyboard. As far as I know the PS4 is still not easily hackable at this time, so this is probably a feasible way to automate tedious tasks such as grinding in GTA Online without writing a lot of code or hardware hacking. However, a small disadvantage of this method is that the controller must be plugged in via USB at all times.

Why EasyHook?

EasyHook is a very useful library that allows us to hook Windows APIs within C# with only a few lines of code. Even though PS4 Remote Play were built on .NET WinForms, but most of the core functionalities are delegated to RpCtrlWrapper.dll which is a native DLL. However, the controller were treated like any other USB HID devices that uses Windows kernel32.dll. So instead of trying to go through obfuscated code or decompiled native assembly, we can instead attack from a low-level API. This is where EasyHook becomes the most important part of this project.

They also provided a tutorial project called RemoteFileMonitor which will log any usages of CreateFileW, ReadFile, and WriteFile. Which we will use it to intercept and manipulate any data sent from the DualShock controller. We will be using only ReadFile function for this tutorial. This diagram shows how we will intercept Remote Play.

How we will intercept

Let’s Begin

Download or clone EasyHook’s tutorial projects from this repository, and open EasyHook-Tutorials-master/Unmanged/RemoteFileMonitor.sln in Visual Studio.

You can compile and run the project after you restore the NuGet packages. The program will ask for the PID for running processes, and this case PS4 Remote Play is our target.

FileMonitor Prompt

To find the PID open task manager and locate PS4 Remote Play process. Right click on the process and select Go to details. You will be taken to the process details that shows the PID.

Finding PID in Task Manager

The console will be flooded with mostly ReadFile function usages log with a few of CreateFile. Notice that the data is 64 bytes and has an empty filename, this will become important later on.

Intercepting data from Remote Play

Data Format

At this point, we now have the data in our hands, but we don’t know what it means yet. Luckily, there is this wiki page from PSDevWiki that combined most the information about the USB protocol of DualShock 4 controllers from other sources. You can find an example of the data in Report Structure section of the wiki page.

DualShock 4 Samples Report

As you can see that the size of the report is 64 bytes like we intercepted previously. Also the header of the report will always be 0x01 which will be the Report ID.

We can programmatically press any combinations of controls with this information, but for this tutorial, we are going to keep the objective simple and make the bot move forwards. So according to the data format we have to set the byte index [2] to 0 for the analog stick to move upwards.

Modifying FileMonitor

There are two files that we have to focus on. These are ServerInterface.cs and InjectionEntryPoint.cs in FileMonitorHook project.

ServerInterface.cs

We have to remove console logs from this class because they can compete with the injection as they both gets called rapidly. The exceptions are IsIntalled and ReportException because they are rarely called, plus they are useful for us.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public void IsInstalled(int clientPID)
{
Console.WriteLine("FileMonitor has injected FileMonitorHook into process {0}.\r\n", clientPID);
}
/// <summary>
/// Output the message to the console.
/// </summary>
/// <param name="fileNames"></param>
public void ReportMessages(string[] messages)
{
}
public void ReportMessage(string message)
{
}
/// <summary>
/// Report exception
/// </summary>
/// <param name="e"></param>
public void ReportException(Exception e)
{
Console.WriteLine("The target process has reported an error:\r\n" + e.ToString());
}
/// <summary>
/// Called to confirm that the IPC channel is still open / host application has not closed
/// </summary>
public void Ping()
{
}

InjectionEntryPoint.cs

We will ignore other hook implementations except for ReadFile, so scroll down until you find ReadFile Hook region. We will modify only ReadFile_Hook() method to manipulate the data instead of logging information about it. All this block of code does is verify if the data we intercepted is “likely” to be the data we are looking for, then we proceed to modifying that data. However, to modify the data we have to use unsafe code, which will allow us to convert lpBuffer into byte* type. From there, the byte* variable can be accessed by index like byte[].

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
/// <summary>
/// The ReadFile hook function. This will be called instead of the original ReadFile once hooked.
/// </summary>
/// <param name="hFile"></param>
/// <param name="lpBuffer"></param>
/// <param name="nNumberOfBytesToRead"></param>
/// <param name="lpNumberOfBytesRead"></param>
/// <param name="lpOverlapped"></param>
/// <returns></returns>
bool ReadFile_Hook(
IntPtr hFile,
IntPtr lpBuffer,
uint nNumberOfBytesToRead,
out uint lpNumberOfBytesRead,
IntPtr lpOverlapped)
{
bool result = false;
lpNumberOfBytesRead = 0;
// Call original first so we have a value for lpNumberOfBytesRead
result = ReadFile(hFile, lpBuffer, nNumberOfBytesToRead, out lpNumberOfBytesRead, lpOverlapped);
try
{
// Retrieve filename from the file handle
StringBuilder filename = new StringBuilder(255);
GetFinalPathNameByHandle(hFile, filename, 255, 0);
// Check if the data is from the controller
if (string.IsNullOrWhiteSpace(filename.ToString()) && lpNumberOfBytesRead == 64)
{
unsafe
{
// This can be accessed like an array
byte* ptr = (byte*)lpBuffer.ToPointer();
// Check if the header is correct
if (ptr[0] == 0x1)
{
// Move forward
// ptr[2] = 0x0;
// Move backward
ptr[2] = 0xFF;
}
}
}
}
catch
{
// swallow exceptions so that any issues caused by this code do not crash target process
}
return result;
}

To get rid of the warning for using unsafe code, open project properties (Alt+Enter). On Build tab, check the Allow unsafe code box for both Debug and Release builds.

Allow unsafe code

Run the program

Compile the solution and test the program again to see the bot in action. To stop the injection, simply stop the program.

Final Result (GTAV)

You can see the full video on YouTube here

Conclusion

This is the first part of a tutorial series, and it is about introducing the concept of hooking into PS4 Remote Play rather than making a functional bot. We will go into greater details in Part 2 and create our own application with UI. I hope that this tutorial helps anyone who wanted to make a bot for PS4 but could not find resources online. A better way I have in mind is to disassemble PS4 Remote Play’s protocol and build on top of that, but that is a little bit overkill for now.

The source code for this tutorial can be found on GitHub.

References

Resources

Share Comments