Creating a PS4 bot in C# - Part 2 | Macro Utility

In the second part of the tutorial, we will be making a UI for our bot and implement a simple macro recording feature so that we can record our controls and play it in a loop.

In part 1, we covered the core functions of the method that we are using for the bot, which is by using DLL injection. If you are interested in how it works then check out part 1 or see the video. However, what we achieved in part 1 only shows us that a PS4 bot can easily be made, but doesn’t really give us any practical uses. So hopefully by the end of this tutorial you will be able to use your creativity to automate your grinding routines in any PS4 game, such as GTA Online.

Let’s Begin

Create a new Visual Studio project using Windows Forms Application template and call it PS4Macro.

Install libraries

We’ll be using a library that I created called PS4RemotePlayInterceptor, so that we don’t have to rewrite any of our code from part 1. To install the package open Nuget package manager console and type the following command.

Install-Package PS4RemotePlayInterceptor -Version 0.1.0

When asked to overwrite, type A to let it overwrite all files and press enter.

Testing the libraries

You can copy the example code from PS4RemotePlayInterceptor repository to this project and test it out. The example code should make the bot hold the X button while moving the left analog stick forward. If everything is working as we expected then we can move on to the UI.

Creating the UI

Our UI will be based on a typical audio recording program with buttons for play, pause, stop, and record. With an additional “clear” button for clearing out the recorded macro.

Some audio recording program

Our UI design

We’ll be using unicodes instead of icons which you can copy them from here.

1
▶ ⏸ ⏹ ⏺

Implement MacroPlayer class

Next, we are going to create a class that will handle the implementation of our macro playback and recording, and we’ll name it MacroPlayer. Before we continue we need to change the class to public because we are going to be using in from our form. We also need some properties so that we can store the current state of the macro and a constructor to initialize the properties that we created.

1
2
3
4
5
6
7
8
9
10
11
12
13
public bool IsPlaying { get; private set; }
public bool IsRecording { get; private set; }
public int CurrentTick { get; private set; }
public List<DualshockState> Sequence { get; private set; }
/* Constructor */
public MacroPlayer()
{
IsPlaying = false;
IsRecording = false;
CurrentTick = 0;
Sequence = new List<DualshockState>();
}

Let’s go through the meaning of the properties. IsPlaying and IsRecording is a simple boolean flag and they are self explanatory. CurrentTick will be used to store the current position of the playback. Sequence is list of controls that will be used to store the data that we recorded.

We’re going to create public methods for each button that will be called when a button is press from the UI.

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
public void Play()
{
IsPlaying = true;
}
public void Pause()
{
IsPlaying = false;
}
public void Stop()
{
IsPlaying = false;
CurrentTick = 0;
}
public void Record()
{
IsRecording = !IsRecording;
}
public void Clear()
{
Sequence = new List<DualshockState>();
}

For Play() we want to set IsPlaying to true. For Pause() we set IsPlaying to false. For Stop we set IsPlaying to false, but we also want to reset CurrentTick to 0, so that when we press play the next time it will start from the beginning. For Record() we want to toggle the value of IsRecording. Finally, for Clear() we will reset the Sequence by creating a new list.

Now copy the method OnReceivedInput from our form that we used to test earlier but we are going to make some changes to it.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public void OnReceiveData(ref DualshockState state)
{
if (IsPlaying)
{
if (IsRecording)
{
Sequence.Add(state);
}
else
{
if (Sequence[CurrentTick] != null)
state = Sequence[CurrentTick];
}
// Increment tick
CurrentTick++;
// Reset tick if out of bounds
if (CurrentTick >= Sequence.Count)
CurrentTick = 0;
}
}

We’ll start by checking if the macro IsPlaying. If it is then we check if the macro IsRecording and we will add the current state to the sequence if it is. If it’s not recording then that means it is playing, so we want to replace the controls with what we recorded. Since the state variable is passed by reference, we can assign the item in the sequence directly to the variable, but we only want it if the value is not null.

At the bottom of the method we increase CurrentTick by one, and if CurrentTick exceeds the size of the Sequence we’ll reset it to 0 to make it loop.

Also change the method to public instead of private static so that it can be called by our UI.

Back to the UI

In the UI’s code-behind, create a variable for MacroPlayer assign a new instance to it in the constructor. We also need to assign the callback of the interceptor into the one we prepared in MacroPlayer and call Inject() to start the injection.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public partial class PS4Macro : Form
{
private MacroPlayer m_MacroPlayer;
public PS4Macro()
{
InitializeComponent();
m_MacroPlayer = new MacroPlayer();
// Inject into PS4 Remote Play
Interceptor.Callback = new InterceptionDelegate(m_MacroPlayer.OnReceiveData);
Interceptor.Inject();
}
}

In order to see the state of the buttons, we can highlight the buttons with some color when the buttons are pressed. To do that, we will create a method called UpdateButtons that will be called when any buttons are clicked.

1
2
3
4
5
private void UpdateButtons()
{
playButton.ForeColor = m_MacroPlayer.IsPlaying ? Color.Green : DefaultForeColor;
recordButton.ForeColor = m_MacroPlayer.IsRecording ? Color.Red : DefaultForeColor;
}

Finally, Go back to form design and double click on each buttons to create OnClicked events for them, then just simply call their corresponding methods follow by UpdateButtons() on every button event.

1
2
3
4
5
6
7
8
9
private void playButton_Click(object sender, EventArgs e)
{
m_MacroPlayer.Play();
UpdateButtons();
}
// ...
// ...
// ...

Full source code

PS4Macro.cs (form)
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
56
57
58
59
60
61
62
63
64
using PS4RemotePlayInterceptor;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace PS4Macro
{
public partial class PS4Macro : Form
{
private MacroPlayer m_MacroPlayer;
public PS4Macro()
{
InitializeComponent();
m_MacroPlayer = new MacroPlayer();
// Inject into PS4 Remote Play
Interceptor.Callback = new InterceptionDelegate(m_MacroPlayer.OnReceiveData);
Interceptor.Inject();
}
private void UpdateButtons()
{
playButton.ForeColor = m_MacroPlayer.IsPlaying ? Color.Green : DefaultForeColor;
recordButton.ForeColor = m_MacroPlayer.IsRecording ? Color.Red : DefaultForeColor;
}
private void playButton_Click(object sender, EventArgs e)
{
m_MacroPlayer.Play();
UpdateButtons();
}
private void pauseButton_Click(object sender, EventArgs e)
{
m_MacroPlayer.Pause();
UpdateButtons();
}
private void stopButton_Click(object sender, EventArgs e)
{
m_MacroPlayer.Stop();
UpdateButtons();
}
private void recordButton_Click(object sender, EventArgs e)
{
m_MacroPlayer.Record();
UpdateButtons();
}
private void clearButton_Click(object sender, EventArgs e)
{
m_MacroPlayer.Clear();
UpdateButtons();
}
}
}
MacroPlayer.cs
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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
using PS4RemotePlayInterceptor;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace PS4Macro
{
public class MacroPlayer
{
public bool IsPlaying { get; private set; }
public bool IsRecording { get; private set; }
public int CurrentTick { get; private set; }
public List<DualshockState> Sequence { get; private set; }
/* Constructor */
public MacroPlayer()
{
IsPlaying = false;
IsRecording = false;
CurrentTick = 0;
Sequence = new List<DualshockState>();
}
public void Play()
{
IsPlaying = true;
}
public void Pause()
{
IsPlaying = false;
}
public void Stop()
{
IsPlaying = false;
CurrentTick = 0;
}
public void Record()
{
IsRecording = !IsRecording;
}
public void Clear()
{
Sequence = new List<DualshockState>();
}
public void OnReceiveData(ref DualshockState state)
{
if (IsPlaying)
{
if (IsRecording)
{
Sequence.Add(state);
}
else
{
if (Sequence[CurrentTick] != null)
state = Sequence[CurrentTick];
}
// Increment tick
CurrentTick++;
// Reset tick if out of bounds
if (CurrentTick >= Sequence.Count)
CurrentTick = 0;
}
}
}
}

Now we can finally run the bot and see some results. Check out the video to see the macro working on GTA V by repeatedly activating the weather cheat.

Conclusion

There is a few things that I wanted to cover on this part like saving a macro to a file so it can be loaded but it was a little out of scope for this tutorial. However, you can download or see the source for PS4Macro on GitHub, which is an ongoing project that I do in my spare time.

Screenshot of PS4Macro (current version)

Changing the weather in GTA 5 is just a simple example, but you can get creative with how you use the macro to make some kind of bot to any ps4 game.

If you like this tutorial, you can support me by subscribing to my YouTube Channel where I upload video tutorials on various programming topics.

Resources

Share Comments