How to get access to the data Link to heading

EA Sports WRC was a completely new development using unreal engine, which I guess meant that all software from dirt rally to a new platform. Therefore, it took some patches until the development team was able to push a way for users to get access to data. Before the “official” data, some very smart people developed a patch.

I decided to wait until the oficial release, which came out during November 2023. You can read the original EA documentation, but the overall picture is the following

Theres a config.json where you configure the UDP packets that will be sent out The packets definition is stored on another json file with a custom name, I called them CustomXYZ:

  • CustomStart.json
  • CustomResume.json
  • CustomPause.json
  • CustomEnd.json
  • CustomUpdate.json

For the current time Im only using the CustomUpdate structure because I noticed that the other packets were always being sent (so I had no way to use them to know Im in the start end or in the pause menu), this is one of the bugs I still need to figure out.

My json packet description

CustomUpdate.json Link to heading

 1{
 2	"versions":
 3	{
 4		"schema": 1,
 5		"data": 3
 6	},
 7	"id": "CustomUpdate",
 8	"header":
 9	{
10		"channels": []
11	},
12	"packets": [
13		{
14			"id": "session_update",
15			"channels": [
16				"packet_4cc",
17				"packet_uid",
18				"shiftlights_fraction",
19				"shiftlights_rpm_start",
20				"shiftlights_rpm_end",
21				"shiftlights_rpm_valid",
22				"vehicle_gear_index",
23				"vehicle_gear_index_neutral",
24				"vehicle_gear_index_reverse",
25				"vehicle_gear_maximum",
26				"vehicle_speed",
27				"vehicle_transmission_speed",
28				"vehicle_position_x",
29				"vehicle_position_y",
30				"vehicle_position_z",
31				"vehicle_velocity_x",
32				"vehicle_velocity_y",
33				"vehicle_velocity_z",
34				"vehicle_acceleration_x",
35				"vehicle_acceleration_y",
36				"vehicle_acceleration_z",
37				"vehicle_left_direction_x",
38				"vehicle_left_direction_y",
39				"vehicle_left_direction_z",
40				"vehicle_forward_direction_x",
41				"vehicle_forward_direction_y",
42				"vehicle_forward_direction_z",
43				"vehicle_up_direction_x",
44				"vehicle_up_direction_y",
45				"vehicle_up_direction_z",
46				"vehicle_hub_position_bl",
47				"vehicle_hub_position_br",
48				"vehicle_hub_position_fl",
49				"vehicle_hub_position_fr",
50				"vehicle_hub_velocity_bl",
51				"vehicle_hub_velocity_br",
52				"vehicle_hub_velocity_fl",
53				"vehicle_hub_velocity_fr",
54				"vehicle_cp_forward_speed_bl",
55				"vehicle_cp_forward_speed_br",
56				"vehicle_cp_forward_speed_fl",
57				"vehicle_cp_forward_speed_fr",
58				"vehicle_brake_temperature_bl",
59				"vehicle_brake_temperature_br",
60				"vehicle_brake_temperature_fl",
61				"vehicle_brake_temperature_fr",
62				"vehicle_engine_rpm_max",
63				"vehicle_engine_rpm_idle",
64				"vehicle_engine_rpm_current",
65				"vehicle_throttle",
66				"vehicle_brake",
67				"vehicle_clutch",
68				"vehicle_steering",
69				"vehicle_handbrake",
70				"game_total_time",
71				"game_delta_time",
72				"game_frame_count",
73				"stage_current_time",
74				"stage_current_distance",
75				"stage_length"
76			]
77		}
78	]
79}

How to acquire and handle the data Link to heading

Now that the game should be sending out the packet via UDP, we can check it with a network analyzer tool As expected, wireshark shows the packets being sent out.

Wireshark packets

Wireshark packets

To get the information on the data frame we need to

  • Setup a UDP listener socket
  • split the data frame into usable variables

First, information storage needs to be defined

 1struct EAtelemetry_data_t {
 2	
 3	int gear;
 4	
 5	float shiftlightFraction;
 6	float shiftlightStart;
 7	float shiftlightEnd;
 8	float VehSpeed;
 9	float VehTransSpeed;
10	float VehPosX;
11	float VehPosY;
12	float VehPosZ;
13	float VehSpdX;
14	float VehSpdY;
15	float VehSpdZ;
16	float VehAccelX;
17	float VehAccelY;
18	float VehAccelZ;
19	float VehLeftDirX;
20	float VehLeftDirY;
21	float VehLeftDirZ;
22	float VehFwrdDirX;
23	float VehFwrdDirY;
24	float VehFwrdDirZ;
25	float VehUpDirX;
26	float VehUpDirY;
27	float VehUpDirZ;
28	float HubVertPosBL;
29	float HubVertPosBR;
30	float HubVertPosFL;
31	float HubVertPosFR;
32	float HubVertSpdBL;
33	float HubVertSpdBR;
34	float HubVertSpdFL;
35	float HubVertSpdFR;
36	float WheelSpeedBL;
37	float WheelSpeedBR;
38	float WheelSpeedFL;
39	float WheelSpeedFR;
40	float stear;
41	float clutch;
42	float brake;
43	float throttle;
44	float rpm;
45	float rpm_idle;
46	float max_rpm;
47	float handbrake;
48	float current_time;
49	float current_minutes;
50	float current_seconds;
51	float game_total_time;
52	float game_delta_time;
53	float game_frame_count;
54	float brake_temp_bl;
55	float brake_temp_br;
56	float brake_temp_fl;
57	float brake_temp_fr;
58
59	double track_length;
60	double lap_distance;
61};

Once that is defined, its time to create the UDP connection The communication is started by the startClient method:

 1int EASportsWRC::startClient()
 2{
 3	if (!isRunning_b)
 4	{
 5		// Initialize Winsock
 6		WSADATA wsaData;
 7		if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
 8			std::cerr << "Failed to initialize Winsock" << std::endl;
 9			//return 1;
10		}
11
12		// Create a UDP socket
13		serverSocket = socket(AF_INET, SOCK_DGRAM, 0);
14		if (serverSocket == INVALID_SOCKET) {
15			std::cerr << "Error creating socket" << std::endl;
16			WSACleanup();
17			return 1;
18		}
19
20		// Set up the server address
21		sockaddr_in serverAddr;
22		serverAddr.sin_family = AF_INET;
23		serverAddr.sin_addr.s_addr = INADDR_ANY;
24		serverAddr.sin_port = htons(PORT_i);
25
26		// Bind the socket to the specified port
27		if (bind(serverSocket, (const sockaddr*)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR) {
28			std::cerr << "Error binding socket" << std::endl;
29			closesocket(serverSocket);
30			WSACleanup();
31			return 1;
32		}
33
34		std::cout << "UDP server listening on port " << PORT_i << std::endl;
35		isRunning_b = true;
36
37		m_NetworkThread = std::thread([this]() { receiveData(); });
38
39	}
40
41}

The method receiveData() is started when the UDP connection is achieved and runs until the app is stopped. The cyclic time is defined by the UDP packet configuration

 1void EASportsWRC::receiveData()
 2{
 3	while (isRunning_b)
 4	{
 5		// Wait for incoming messages
 6		char buffer[265];
 7		uint8_t uintBuffer[265];
 8		sockaddr_in clientAddr;
 9		int clientAddrLen = sizeof(clientAddr);
10		int bytesRead = recvfrom(serverSocket, buffer, sizeof(buffer), 0, (sockaddr*)&clientAddr, &clientAddrLen);
11		//int bytesRead = recvfrom(serverSocket, uintBuffer, sizeof(uintBuffer), 0, (sockaddr*)&clientAddr, &clientAddrLen);
12
13		if (bytesRead == SOCKET_ERROR) {
14			std::cerr << "Error receiving message" << std::endl;
15		}
16		else {
17			//buffer[bytesRead] = '\0';
18			//std::cout << "Size of buffer" << bytesRead << std::endl;
19			if (bytesRead <= 264) //Full size packet for Dirt Rally 2
20			{
21				//check if firts activation
22				if (!GetOnStage()) StartStage();
23				memcpy(UDPReceiveArray.data(), buffer, sizeof(buffer));
24				//std::cout << "Received message from client: " << l_DirtTwo.UDPReceiveArray.data() << std::endl;
25				HandleArray();
26			}
27			else
28			{
29				std::cout << "Received package with a different size " << sizeof(buffer) << std::endl;
30			}
31		}
32	}
33
34}

How to read the telemetry Link to heading

To de-serialize the dataframe, the following enum is defined:

 1enum class EAoffset_t : uint16_t {
 2	FourCC = 0,
 3	packet_uid = 4,
 4	shiftlights_fraction = 12,
 5	shiftlights_rpm_start = 16,
 6	shiftlights_rpm_end = 20,
 7	shiftlights_rpm_valid = 24,
 8	vehicle_gear_index = 25,
 9	vehicle_gear_index_neutral = 26,
10	vehicle_gear_index_reverse = 27,
11	vehicle_gear_maximum = 28,
12	vehicle_speed = 29,
13	vehicle_transmission_speed = 33,
14	vehicle_position_x = 37,
15	vehicle_position_y = 41,
16	vehicle_position_z = 45,
17	vehicle_velocity_x = 49,
18	vehicle_velocity_y = 53,
19	vehicle_velocity_z = 57,
20	vehicle_acceleration_x = 61,
21	vehicle_acceleration_y = 65,
22	vehicle_acceleration_z = 69,
23	vehicle_left_direction_x = 73,
24	vehicle_left_direction_y = 77,
25	vehicle_left_direction_z = 81,
26	vehicle_forward_direction_x = 85,
27	vehicle_forward_direction_y = 89,
28	vehicle_forward_direction_z = 93,
29	vehicle_up_direction_x = 97,
30	vehicle_up_direction_y = 101,
31	vehicle_up_direction_z = 105,
32	vehicle_hub_position_bl = 109,
33	vehicle_hub_position_br = 113,
34	vehicle_hub_position_fl = 117,
35	vehicle_hub_position_fr = 121,
36	vehicle_hub_velocity_bl = 125,
37	vehicle_hub_velocity_br = 129,
38	vehicle_hub_velocity_fl = 133,
39	vehicle_hub_velocity_fr = 137,
40	vehicle_cp_forward_speed_bl = 141,
41	vehicle_cp_forward_speed_br = 145,
42	vehicle_cp_forward_speed_fl = 149,
43	vehicle_cp_forward_speed_fr = 153,
44	vehicle_brake_temperature_bl = 157,
45	vehicle_brake_temperature_br = 161,
46	vehicle_brake_temperature_fl = 165,
47	vehicle_brake_temperature_fr = 169,
48	vehicle_engine_rpm_max = 173,
49	vehicle_engine_rpm_idle = 177,
50	vehicle_engine_rpm_current = 181,
51	vehicle_throttle = 185,
52	vehicle_brake = 189,
53	vehicle_clutch = 193,
54	vehicle_steering = 197,
55	vehicle_handbrake = 201,
56	game_total_time = 205,
57	game_delta_time = 209,
58	game_frame_count = 213,
59	stage_current_time = 221,
60	stage_current_distance = 225,
61	stage_length = 233
62
63};

Now with this its possible to get the correct data from the dataframe and store it in whatever structure we want to use it you can find the full project in the github repo

If you like my projects please consider supporting my hobby by buying me a coffee☕ 😄