├── FH4RP.csproj
├── Helpers
├── Logger.cs
└── VehicleDB.cs
├── FH4RP.sln
├── DataStructs
├── Vehicle.cs
└── TelemetryData.cs
├── Networking
├── DiscordRPC.cs
└── TelemetryServer.cs
├── Program.cs
├── LICENSE
├── readme.md
└── .gitignore
/FH4RP.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | netcoreapp2.2
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/Helpers/Logger.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 |
5 | namespace FH4RP.Helpers
6 | {
7 | public static class Logger
8 | {
9 | public static void Log(string msg)
10 | {
11 | Console.Clear();
12 | Console.WriteLine(msg);
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/FH4RP.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 16
4 | VisualStudioVersion = 16.0.29201.188
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FH4RP", "FH4RP.csproj", "{DA21B4BE-7AAA-4CD9-AA35-963C281B2D9F}"
7 | EndProject
8 | Global
9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
10 | Debug|Any CPU = Debug|Any CPU
11 | Release|Any CPU = Release|Any CPU
12 | EndGlobalSection
13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
14 | {DA21B4BE-7AAA-4CD9-AA35-963C281B2D9F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
15 | {DA21B4BE-7AAA-4CD9-AA35-963C281B2D9F}.Debug|Any CPU.Build.0 = Debug|Any CPU
16 | {DA21B4BE-7AAA-4CD9-AA35-963C281B2D9F}.Release|Any CPU.ActiveCfg = Release|Any CPU
17 | {DA21B4BE-7AAA-4CD9-AA35-963C281B2D9F}.Release|Any CPU.Build.0 = Release|Any CPU
18 | EndGlobalSection
19 | GlobalSection(SolutionProperties) = preSolution
20 | HideSolutionNode = FALSE
21 | EndGlobalSection
22 | GlobalSection(ExtensibilityGlobals) = postSolution
23 | SolutionGuid = {6F6515E8-D679-4386-98B5-B1FE5BABDB2D}
24 | EndGlobalSection
25 | EndGlobal
26 |
--------------------------------------------------------------------------------
/DataStructs/Vehicle.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 |
5 | namespace FH4RP.DataStructs
6 | {
7 | public struct Vehicle
8 | {
9 |
10 | public enum DrivetrainType
11 | {
12 | FWD = 0,
13 | RWD = 1,
14 | AWD = 2
15 | }
16 |
17 | public enum CarClass
18 | {
19 | D = 0,
20 | C = 1,
21 | B = 2,
22 | A = 3,
23 | S1 = 4,
24 | S2 = 5,
25 | X = 6
26 | }
27 |
28 | public int ID { get; set; }
29 |
30 | ///
31 | /// Vehicle Manufacturer
32 | ///
33 | public string Make { get; set; }
34 |
35 | ///
36 | /// Vehicle Model
37 | ///
38 | public string Model { get; set; }
39 |
40 | ///
41 | /// Vehicle Year
42 | ///
43 | public int Year { get; set; }
44 |
45 | public string GetVehicleInfo ()
46 | {
47 | return $"{(Year == 0 ? 2019 : Year)} {(Make == "" ? "Unknown" : Make)} {(Model == "" ? "Unknown" : Model)}";
48 | }
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/Networking/DiscordRPC.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 | using DiscordRPC;
5 | using DiscordRPC.Logging;
6 | using FH4RP.DataStructs;
7 | using FH4RP.Helpers;
8 |
9 | namespace FH4RP.Networking
10 | {
11 | public static class DiscordRPC
12 | {
13 | public static DiscordRpcClient discordClient;
14 |
15 | public static void Initialize()
16 | {
17 | discordClient = new DiscordRpcClient("619736536333811722");
18 | discordClient.Logger = new ConsoleLogger() { Level = LogLevel.Warning };
19 | discordClient.OnReady += (sender, e) => { Console.WriteLine($"[Discord] Received ready from {e.User.Username}!"); };
20 | discordClient.OnPresenceUpdate += (sender, e) => { /*Console.WriteLine("[Discord] Received presence update!");*/ };
21 | discordClient.Initialize();
22 | }
23 |
24 | public static void SetPresence(TelemetryData data)
25 | {
26 | discordClient.SetPresence(new RichPresence()
27 | {
28 | Details = VehicleDB.Instance.GetVehicle(data.CarOrdinal).GetVehicleInfo(),
29 | State = $"[{data.CarClass.ToString()} | {data.CarPI}] - {data.GetMPH()} mph",
30 | Assets = new Assets()
31 | {
32 | LargeImageKey = "fm"
33 | }
34 | });
35 | }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Timers;
3 | using FH4RP.Helpers;
4 | using FH4RP.Networking;
5 |
6 | namespace FH4RP
7 | {
8 | class Program
9 | {
10 | private static int ServerPort = 9909;
11 | public static TelemetryServer Server { get; private set; }
12 |
13 | static void Main(string[] args)
14 | {
15 |
16 | Console.WriteLine("Forza Horizon 4 | Discord Rich Presence App");
17 | Console.WriteLine("Developed by digital#0001 | https://dgtl.dev");
18 | Console.WriteLine();
19 | Console.WriteLine("If you haven't already, open a command prompt as admin");
20 | Console.WriteLine("and run the following command to allow Forza Horizon 4's");
21 | Console.WriteLine("Telemetry Data Out to send to the local PC:");
22 | Console.WriteLine();
23 | Console.WriteLine("> CheckNetIsolation.exe LoopbackExempt -a -n=Microsoft.SunriseBaseGame_8wekyb3d8bbwe");
24 | Console.WriteLine();
25 | Server = new TelemetryServer(ServerPort);
26 | Server.Start();
27 |
28 | Networking.DiscordRPC.Initialize();
29 |
30 | var timer = new Timer(1250);
31 | timer.Elapsed += (sender, a) => { Networking.DiscordRPC.SetPresence(Server.LastUpdate); };
32 | timer.Start();
33 |
34 | // Wait for keypress to close
35 | Console.Read();
36 | timer.Stop();
37 | Networking.DiscordRPC.discordClient.Dispose();
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | BSD 3-Clause License
2 |
3 | Copyright (c) 2021, jaiden-d
4 | All rights reserved.
5 |
6 | Redistribution and use in source and binary forms, with or without
7 | modification, are permitted provided that the following conditions are met:
8 |
9 | 1. Redistributions of source code must retain the above copyright notice, this
10 | list of conditions and the following disclaimer.
11 |
12 | 2. Redistributions in binary form must reproduce the above copyright notice,
13 | this list of conditions and the following disclaimer in the documentation
14 | and/or other materials provided with the distribution.
15 |
16 | 3. Neither the name of the copyright holder nor the names of its
17 | contributors may be used to endorse or promote products derived from
18 | this software without specific prior written permission.
19 |
20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 |
--------------------------------------------------------------------------------
/Helpers/VehicleDB.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 | using System.IO;
5 | using FH4RP.DataStructs;
6 | using Newtonsoft.Json;
7 | using System.Net;
8 |
9 | namespace FH4RP.Helpers
10 | {
11 | public class VehicleDB
12 | {
13 |
14 | public struct VehicleDBEntry
15 | {
16 | public int id;
17 | public int ordinal;
18 | public string make;
19 | public string model;
20 | public int year;
21 | public string game;
22 | [JsonProperty("class")] public string _class;
23 | public int pi;
24 | public string drivetrain;
25 | public int power;
26 | public int torque;
27 | public int weight;
28 | public int weight_percent;
29 | public int displacement;
30 | public string description;
31 | public int added_by;
32 | public int updated_by;
33 | public string created_at;
34 | public string updated_at;
35 | }
36 |
37 | public static VehicleDB Instance { get; private set; }
38 |
39 | public VehicleDB()
40 | {
41 | if (Instance == null) Instance = this;
42 | else return;
43 | }
44 |
45 | public Vehicle GetVehicle(int vehicleId)
46 | {
47 | Vehicle v = default;
48 | VehicleDBEntry entry = GetVehicleEntry(vehicleId);
49 | v.ID = entry.ordinal;
50 | v.Year = entry.year;
51 | v.Make = entry.make;
52 | v.Model = entry.model;
53 |
54 | string carInfo = $"[{vehicleId}] {v.GetVehicleInfo()}";
55 | Logger.Log(carInfo + (v.Make == "Unknown" ? "\n\nThis vehicle does not exist in the database!\nPlease consider adding it by going to https://forzadb.dgtl.dev!" : ""));
56 | return v;
57 | }
58 |
59 | private string HttpGet(string uri)
60 | {
61 | string retStr = "";
62 | HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uri);
63 | request.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate;
64 | try
65 | {
66 | using(HttpWebResponse response = (HttpWebResponse)request.GetResponse())
67 | using(Stream stream = response.GetResponseStream())
68 | using(StreamReader reader = new StreamReader(stream))
69 | {
70 | retStr = reader.ReadToEnd();
71 | }
72 | }
73 | catch (Exception e) { Logger.Log($"[API] Exception: {e.ToString()}"); }
74 | return retStr;
75 | }
76 |
77 | private VehicleDBEntry GetVehicleEntry(int vehicleId)
78 | {
79 | return JsonConvert.DeserializeObject(HttpGet("https://forzadb.dgtl.dev/api/vehicles/" + vehicleId));
80 | }
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | # Discord Rich Presence for Forza Horizon 4
2 |
3 | ## What does this do?
4 |
5 | This program will allow you to display [Discord Rich Presence](https://discordapp.com/developers/docs/rich-presence/how-to#so-what-is-it) information about your current car, performance index, and speed in Discord.
6 |
7 | ## How does it work?
8 |
9 | Forza Horizon 4 has the ability to send car telemetry data out to a server. This is commonly used for racing simulation devices such as dashboards and hydraulic-powered racing seats. This program uses the same telemetry data to show car information in Discord.
10 |
11 | ## Can I use this for Forza Horizon 5?
12 |
13 | Technically yes, but I recommend using my [other project for it](https://github.com/jaiden-d/FH5RP) as I have some other future plans for it.
14 |
15 | ## Running the program
16 |
17 | ---
18 |
19 | **Note:** Due to limitations with Universal Windows Platform (UWP) apps, you ***must*** run the following command in an elevated command prompt window (CMD as admin) if you want to run this program on the same machine that you're playing Forza Horizon 4 on!
20 |
21 | Make sure Forza Horizon 4 is closed before running this command!
22 |
23 | `CheckNetIsolation.exe LoopbackExempt -a -n=Microsoft.SunriseBaseGame_8wekyb3d8bbwe`
24 |
25 | ---
26 |
27 | Steps to configure Rich Presence:
28 |
29 | 1. Download the [latest release](https://github.com/zackdevine/FH4RP/releases) and extract it somewhere on your desktop.
30 | 2. Run the command above (only need to run it once per installation of Forza Horizon 4).
31 | 3. In Forza Horizon 4, load into the game world, then go to `Settings > HUD and Gameplay` and scroll down to the bottom.
32 | - Set `Data Out` to `On`
33 | - Set `Data Out IP Address` to `127.0.0.1` (or whatever local IP address is running the application)
34 | - Set `Data Out IP Port` to `9909`
35 | 4. Start the application!
36 |
37 | You should see some console output in the window that appears. Once you see the message stating that Discord is ready, you're good to go!
38 |
39 | ## Contributing
40 |
41 | The info that shows the vehicle manufacturer and model is pulled from a separate [web API](https://github.com/zackdevine/forzadb) that I created. Recording this data is a very time consuming task, as the only vehicle metadata that Forza returns is a unique ordinal ID for each vehicle. As such, every vehicle needs to be manually entered into the API database to be retrieved from the application.
42 |
43 | If you would like to help out (it would mean a lot!), then please feel free to download the debug version of the application and run it to get the ID of the vehicle you're currently using. New vehicles can be submitted at the [ForzaDB](https://forzadb.dgtl.dev) website.
44 |
45 | If you would like to contribute to this project, feel free to send PRs with updates!
46 |
47 | ## References
48 |
49 | This project could not have been possible without the Turn 10 community and [Lachee's Discord RPC](https://github.com/Lachee/discord-rpc-csharp) package.
50 |
51 | Forza logo from [Forza Horizon 4 Icon Pack - Windows 10 by tfphoenix](https://www.deviantart.com/tfphoenix/art/Forza-Horizon-4-Icon-Pack-Windows-10-767768186) on DeviantArt.
52 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 | ##
4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
5 |
6 | # User-specific files
7 | *.rsuser
8 | *.suo
9 | *.user
10 | *.userosscache
11 | *.sln.docstates
12 |
13 | # User-specific files (MonoDevelop/Xamarin Studio)
14 | *.userprefs
15 |
16 | # Mono auto generated files
17 | mono_crash.*
18 |
19 | # Build results
20 | [Dd]ebug/
21 | [Dd]ebugPublic/
22 | [Rr]elease/
23 | [Rr]eleases/
24 | x64/
25 | x86/
26 | [Aa][Rr][Mm]/
27 | [Aa][Rr][Mm]64/
28 | bld/
29 | [Bb]in/
30 | [Oo]bj/
31 | [Ll]og/
32 |
33 | # Visual Studio 2015/2017 cache/options directory
34 | .vs/
35 | # Uncomment if you have tasks that create the project's static files in wwwroot
36 | #wwwroot/
37 |
38 | # Visual Studio 2017 auto generated files
39 | Generated\ Files/
40 |
41 | # MSTest test Results
42 | [Tt]est[Rr]esult*/
43 | [Bb]uild[Ll]og.*
44 |
45 | # NUnit
46 | *.VisualState.xml
47 | TestResult.xml
48 | nunit-*.xml
49 |
50 | # Build Results of an ATL Project
51 | [Dd]ebugPS/
52 | [Rr]eleasePS/
53 | dlldata.c
54 |
55 | # Benchmark Results
56 | BenchmarkDotNet.Artifacts/
57 |
58 | # .NET Core
59 | project.lock.json
60 | project.fragment.lock.json
61 | artifacts/
62 |
63 | # StyleCop
64 | StyleCopReport.xml
65 |
66 | # Files built by Visual Studio
67 | *_i.c
68 | *_p.c
69 | *_h.h
70 | *.ilk
71 | *.meta
72 | *.obj
73 | *.iobj
74 | *.pch
75 | *.pdb
76 | *.ipdb
77 | *.pgc
78 | *.pgd
79 | *.rsp
80 | *.sbr
81 | *.tlb
82 | *.tli
83 | *.tlh
84 | *.tmp
85 | *.tmp_proj
86 | *_wpftmp.csproj
87 | *.log
88 | *.vspscc
89 | *.vssscc
90 | .builds
91 | *.pidb
92 | *.svclog
93 | *.scc
94 |
95 | # Chutzpah Test files
96 | _Chutzpah*
97 |
98 | # Visual C++ cache files
99 | ipch/
100 | *.aps
101 | *.ncb
102 | *.opendb
103 | *.opensdf
104 | *.sdf
105 | *.cachefile
106 | *.VC.db
107 | *.VC.VC.opendb
108 |
109 | # Visual Studio profiler
110 | *.psess
111 | *.vsp
112 | *.vspx
113 | *.sap
114 |
115 | # Visual Studio Trace Files
116 | *.e2e
117 |
118 | # TFS 2012 Local Workspace
119 | $tf/
120 |
121 | # Guidance Automation Toolkit
122 | *.gpState
123 |
124 | # ReSharper is a .NET coding add-in
125 | _ReSharper*/
126 | *.[Rr]e[Ss]harper
127 | *.DotSettings.user
128 |
129 | # JustCode is a .NET coding add-in
130 | .JustCode
131 |
132 | # TeamCity is a build add-in
133 | _TeamCity*
134 |
135 | # DotCover is a Code Coverage Tool
136 | *.dotCover
137 |
138 | # AxoCover is a Code Coverage Tool
139 | .axoCover/*
140 | !.axoCover/settings.json
141 |
142 | # Visual Studio code coverage results
143 | *.coverage
144 | *.coveragexml
145 |
146 | # NCrunch
147 | _NCrunch_*
148 | .*crunch*.local.xml
149 | nCrunchTemp_*
150 |
151 | # MightyMoose
152 | *.mm.*
153 | AutoTest.Net/
154 |
155 | # Web workbench (sass)
156 | .sass-cache/
157 |
158 | # Installshield output folder
159 | [Ee]xpress/
160 |
161 | # DocProject is a documentation generator add-in
162 | DocProject/buildhelp/
163 | DocProject/Help/*.HxT
164 | DocProject/Help/*.HxC
165 | DocProject/Help/*.hhc
166 | DocProject/Help/*.hhk
167 | DocProject/Help/*.hhp
168 | DocProject/Help/Html2
169 | DocProject/Help/html
170 |
171 | # Click-Once directory
172 | publish/
173 |
174 | # Publish Web Output
175 | *.[Pp]ublish.xml
176 | *.azurePubxml
177 | # Note: Comment the next line if you want to checkin your web deploy settings,
178 | # but database connection strings (with potential passwords) will be unencrypted
179 | *.pubxml
180 | *.publishproj
181 |
182 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
183 | # checkin your Azure Web App publish settings, but sensitive information contained
184 | # in these scripts will be unencrypted
185 | PublishScripts/
186 |
187 | # NuGet Packages
188 | *.nupkg
189 | # NuGet Symbol Packages
190 | *.snupkg
191 | # The packages folder can be ignored because of Package Restore
192 | **/[Pp]ackages/*
193 | # except build/, which is used as an MSBuild target.
194 | !**/[Pp]ackages/build/
195 | # Uncomment if necessary however generally it will be regenerated when needed
196 | #!**/[Pp]ackages/repositories.config
197 | # NuGet v3's project.json files produces more ignorable files
198 | *.nuget.props
199 | *.nuget.targets
200 |
201 | # Microsoft Azure Build Output
202 | csx/
203 | *.build.csdef
204 |
205 | # Microsoft Azure Emulator
206 | ecf/
207 | rcf/
208 |
209 | # Windows Store app package directories and files
210 | AppPackages/
211 | BundleArtifacts/
212 | Package.StoreAssociation.xml
213 | _pkginfo.txt
214 | *.appx
215 | *.appxbundle
216 | *.appxupload
217 |
218 | # Visual Studio cache files
219 | # files ending in .cache can be ignored
220 | *.[Cc]ache
221 | # but keep track of directories ending in .cache
222 | !?*.[Cc]ache/
223 |
224 | # Others
225 | ClientBin/
226 | ~$*
227 | *~
228 | *.dbmdl
229 | *.dbproj.schemaview
230 | *.jfm
231 | *.pfx
232 | *.publishsettings
233 | orleans.codegen.cs
234 |
235 | # Including strong name files can present a security risk
236 | # (https://github.com/github/gitignore/pull/2483#issue-259490424)
237 | #*.snk
238 |
239 | # Since there are multiple workflows, uncomment next line to ignore bower_components
240 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
241 | #bower_components/
242 |
243 | # RIA/Silverlight projects
244 | Generated_Code/
245 |
246 | # Backup & report files from converting an old project file
247 | # to a newer Visual Studio version. Backup files are not needed,
248 | # because we have git ;-)
249 | _UpgradeReport_Files/
250 | Backup*/
251 | UpgradeLog*.XML
252 | UpgradeLog*.htm
253 | ServiceFabricBackup/
254 | *.rptproj.bak
255 |
256 | # SQL Server files
257 | *.mdf
258 | *.ldf
259 | *.ndf
260 |
261 | # Business Intelligence projects
262 | *.rdl.data
263 | *.bim.layout
264 | *.bim_*.settings
265 | *.rptproj.rsuser
266 | *- [Bb]ackup.rdl
267 | *- [Bb]ackup ([0-9]).rdl
268 | *- [Bb]ackup ([0-9][0-9]).rdl
269 |
270 | # Microsoft Fakes
271 | FakesAssemblies/
272 |
273 | # GhostDoc plugin setting file
274 | *.GhostDoc.xml
275 |
276 | # Node.js Tools for Visual Studio
277 | .ntvs_analysis.dat
278 | node_modules/
279 |
280 | # Visual Studio 6 build log
281 | *.plg
282 |
283 | # Visual Studio 6 workspace options file
284 | *.opt
285 |
286 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
287 | *.vbw
288 |
289 | # Visual Studio LightSwitch build output
290 | **/*.HTMLClient/GeneratedArtifacts
291 | **/*.DesktopClient/GeneratedArtifacts
292 | **/*.DesktopClient/ModelManifest.xml
293 | **/*.Server/GeneratedArtifacts
294 | **/*.Server/ModelManifest.xml
295 | _Pvt_Extensions
296 |
297 | # Paket dependency manager
298 | .paket/paket.exe
299 | paket-files/
300 |
301 | # FAKE - F# Make
302 | .fake/
303 |
304 | # CodeRush personal settings
305 | .cr/personal
306 |
307 | # Python Tools for Visual Studio (PTVS)
308 | __pycache__/
309 | *.pyc
310 |
311 | # Cake - Uncomment if you are using it
312 | # tools/**
313 | # !tools/packages.config
314 |
315 | # Tabs Studio
316 | *.tss
317 |
318 | # Telerik's JustMock configuration file
319 | *.jmconfig
320 |
321 | # BizTalk build output
322 | *.btp.cs
323 | *.btm.cs
324 | *.odx.cs
325 | *.xsd.cs
326 |
327 | # OpenCover UI analysis results
328 | OpenCover/
329 |
330 | # Azure Stream Analytics local run output
331 | ASALocalRun/
332 |
333 | # MSBuild Binary and Structured Log
334 | *.binlog
335 |
336 | # NVidia Nsight GPU debugger configuration file
337 | *.nvuser
338 |
339 | # MFractors (Xamarin productivity tool) working folder
340 | .mfractor/
341 |
342 | # Local History for Visual Studio
343 | .localhistory/
344 |
345 | # BeatPulse healthcheck temp database
346 | healthchecksdb
347 |
348 | # Backup folder for Package Reference Convert tool in Visual Studio 2017
349 | MigrationBackup/
350 |
351 | # Ionide (cross platform F# VS Code tools) working folder
352 | .ionide/
353 |
--------------------------------------------------------------------------------
/Networking/TelemetryServer.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 | using System.Net;
5 | using System.Net.Sockets;
6 | using FH4RP.DataStructs;
7 | using FH4RP.Helpers;
8 |
9 | namespace FH4RP.Networking
10 | {
11 | public class TelemetryServer
12 | {
13 | private UdpClient udpClient;
14 | private IPEndPoint ep;
15 | public TelemetryData LastUpdate { get; private set; }
16 |
17 | public TelemetryServer(int port = 9909)
18 | {
19 | try
20 | {
21 | ep = new IPEndPoint(IPAddress.Loopback, port);
22 | udpClient = new UdpClient(ep);
23 | udpClient.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
24 | }
25 | catch (Exception e) { Console.WriteLine($"Error: {e.ToString()}"); }
26 | new VehicleDB();
27 | }
28 |
29 | public void Start()
30 | {
31 | Console.WriteLine($"Started telemetry server on {ep.Address.ToString()}:{ep.Port}.");
32 | udpClient.BeginReceive(new AsyncCallback(OnUdpReceive), null);
33 | }
34 |
35 | private void OnUdpReceive(IAsyncResult ar)
36 | {
37 | byte[] data = udpClient.EndReceive(ar, ref ep);
38 | TelemetryData telemetryData = CreateDataStruct(data);
39 | if (telemetryData.CarOrdinal != 0) LastUpdate = telemetryData;
40 | #if DEBUG
41 | // System.IO.File.WriteAllText("output.json", Newtonsoft.Json.JsonConvert.SerializeObject(LastUpdate, Newtonsoft.Json.Formatting.Indented));
42 | #endif
43 | udpClient.BeginReceive(new AsyncCallback(OnUdpReceive), null);
44 | }
45 |
46 | private TelemetryData CreateDataStruct(byte[] data)
47 | {
48 | return new TelemetryData
49 | {
50 | IsRaceOn = BitConverter.ToInt32(data, 0),
51 | TimestampMS = BitConverter.ToUInt32(data, 4),
52 |
53 | EngineMaxRPM = BitConverter.ToSingle(data, 8),
54 | EngineIdleRPM = BitConverter.ToSingle(data, 12),
55 | EngineCurrentRPM = BitConverter.ToSingle(data, 16),
56 |
57 | AccelerationX = BitConverter.ToSingle(data, 20),
58 | AccelerationY = BitConverter.ToSingle(data, 24),
59 | AccelerationZ = BitConverter.ToSingle(data, 28),
60 |
61 | VelocityX = BitConverter.ToSingle(data, 32),
62 | VelocityY = BitConverter.ToSingle(data, 36),
63 | VelocityZ = BitConverter.ToSingle(data, 40),
64 |
65 | AngularVelocityX = BitConverter.ToSingle(data, 44),
66 | AngularVelocityY = BitConverter.ToSingle(data, 48),
67 | AngularVelocityZ = BitConverter.ToSingle(data, 52),
68 |
69 | Yaw = BitConverter.ToSingle(data, 56),
70 | Pitch = BitConverter.ToSingle(data, 60),
71 | Roll = BitConverter.ToSingle(data, 64),
72 |
73 | NormalizedSuspensionTravelFrontLeft = BitConverter.ToSingle(data, 68),
74 | NormalizedSuspensionTravelFrontRight = BitConverter.ToSingle(data, 72),
75 | NormalizedSuspensionTravelRearLeft = BitConverter.ToSingle(data, 76),
76 | NormalizedSuspensionTravelRearRight = BitConverter.ToSingle(data, 80),
77 |
78 | TireSlipRatioFrontLeft = BitConverter.ToSingle(data, 84),
79 | TireSlipRatioFrontRight = BitConverter.ToSingle(data, 88),
80 | TireSlipRatioRearLeft = BitConverter.ToSingle(data, 92),
81 | TireSlipRatioRearRight = BitConverter.ToSingle(data, 96),
82 |
83 | WheelRotationSpeedFrontLeft = BitConverter.ToSingle(data, 100),
84 | WheelRotationSpeedFrontRight = BitConverter.ToSingle(data, 104),
85 | WheelRotationSpeedRearLeft = BitConverter.ToSingle(data, 108),
86 | WheelRotationSpeedRearRight = BitConverter.ToSingle(data, 112),
87 |
88 | WheelOnRumbleStripFrontLeft = BitConverter.ToInt32(data, 116),
89 | WheelOnRumbleStripFrontRight = BitConverter.ToInt32(data, 120),
90 | WheelOnRumbleStripRearLeft = BitConverter.ToInt32(data, 124),
91 | WheelOnRumbleStripRearRight = BitConverter.ToInt32(data, 128),
92 |
93 | WheelInPuddleDepthFrontLeft = BitConverter.ToSingle(data, 132),
94 | WheelInPuddleDepthFrontRight = BitConverter.ToSingle(data, 136),
95 | WheelInPuddleDepthRearLeft = BitConverter.ToSingle(data, 140),
96 | WheelInPuddleDepthRearRight = BitConverter.ToSingle(data, 144),
97 |
98 | SurfaceRumbleFrontLeft = BitConverter.ToSingle(data, 148),
99 | SurfaceRumbleFrontRight = BitConverter.ToSingle(data, 152),
100 | SurfaceRumbleRearLeft = BitConverter.ToSingle(data, 156),
101 | SurfaceRumbleRearRight = BitConverter.ToSingle(data, 160),
102 |
103 | TireSlipAngleFrontLeft = BitConverter.ToSingle(data, 164),
104 | TireSlipAngleFrontRight = BitConverter.ToSingle(data, 168),
105 | TireSlipAngleRearLeft = BitConverter.ToSingle(data, 172),
106 | TireSlipAngleRearRight = BitConverter.ToSingle(data, 176),
107 |
108 | TireCombinedSlipFrontLeft = BitConverter.ToSingle(data, 180),
109 | TireCombinedSlipFrontRight = BitConverter.ToSingle(data, 184),
110 | TireCombinedSlipRearLeft = BitConverter.ToSingle(data, 188),
111 | TireCombinedSlipRearRight = BitConverter.ToSingle(data, 192),
112 |
113 | SuspensionTravelFrontLeft = BitConverter.ToSingle(data, 196),
114 | SuspensionTravelFrontRight = BitConverter.ToSingle(data, 200),
115 | SuspensionTravelRearLeft = BitConverter.ToSingle(data, 204),
116 | SuspensionTravelRearRight = BitConverter.ToSingle(data, 208),
117 |
118 | CarOrdinal = BitConverter.ToInt32(data, 212),
119 |
120 | CarClass = (Vehicle.CarClass)BitConverter.ToInt32(data, 216),
121 | CarPI = BitConverter.ToInt32(data, 220),
122 | DrivetrainType = (Vehicle.DrivetrainType)BitConverter.ToInt32(data, 224),
123 | NumCylinders = BitConverter.ToInt32(data, 228),
124 |
125 | PositionX = BitConverter.ToSingle(data, 232),
126 | PositionY = BitConverter.ToSingle(data, 236),
127 | PositionZ = BitConverter.ToSingle(data, 240),
128 |
129 | Speed = BitConverter.ToSingle(data, 244),
130 | Power = BitConverter.ToSingle(data, 248),
131 | Torque = BitConverter.ToSingle(data, 252),
132 |
133 | TireTempFrontLeft = BitConverter.ToSingle(data, 256),
134 | TireTempFrontRight = BitConverter.ToSingle(data, 260),
135 | TireTempRearLeft = BitConverter.ToSingle(data, 264),
136 | TireTempRearRight = BitConverter.ToSingle(data, 268),
137 |
138 | Boost = BitConverter.ToSingle(data, 272),
139 | Fuel = BitConverter.ToSingle(data, 276),
140 | DistanceTraveled = BitConverter.ToSingle(data, 280),
141 |
142 | BestLap = BitConverter.ToSingle(data, 284),
143 | LastLap = BitConverter.ToSingle(data, 288),
144 | CurrentLap = BitConverter.ToSingle(data, 292),
145 | CurrentRaceTime = BitConverter.ToSingle(data, 296),
146 | LapNumber = BitConverter.ToUInt16(data, 300),
147 |
148 | RacePosition = data[302],
149 | Accel = data[303],
150 | Brake = data[304],
151 | Clutch = data[305],
152 | Handbrake = data[306],
153 | Gear = data[307],
154 | Steer = (sbyte)data[308],
155 | NormalizedDrivingLine = (sbyte)data[309],
156 | NormalizedAIBrakeDifference = (sbyte)data[310]
157 | };
158 | }
159 | }
160 | }
161 |
--------------------------------------------------------------------------------
/DataStructs/TelemetryData.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 |
5 | namespace FH4RP.DataStructs
6 | {
7 | public struct TelemetryData
8 | {
9 | ///
10 | /// Returns 1 when in a race, 0 when in menus or otherwise
11 | ///
12 | public int IsRaceOn { get; set; }
13 |
14 | ///
15 | /// Timestamp in MS, can overflow to 0 eventually
16 | ///
17 | public uint TimestampMS { get; set; }
18 |
19 |
20 |
21 | public float EngineMaxRPM { get; set; }
22 | public float EngineIdleRPM { get; set; }
23 | public float EngineCurrentRPM { get; set; }
24 |
25 |
26 |
27 | ///
28 | /// Right (x) position axis in local space
29 | ///
30 | public float AccelerationX { get; set; }
31 |
32 | ///
33 | /// Up (y) position axis in local space
34 | ///
35 | public float AccelerationY { get; set; }
36 |
37 | ///
38 | /// Forward (z) position axis in local space
39 | ///
40 | public float AccelerationZ { get; set; }
41 |
42 |
43 |
44 | ///
45 | /// Right (x) velocity axis in local space
46 | ///
47 | public float VelocityX { get; set; }
48 |
49 | ///
50 | /// Up (y) velocity axis in local space
51 | ///
52 | public float VelocityY { get; set; }
53 |
54 | ///
55 | /// Forward (z) velocity axis in local space
56 | ///
57 | public float VelocityZ { get; set; }
58 |
59 |
60 |
61 | ///
62 | /// Pitch (x) velocity in local space
63 | ///
64 | public float AngularVelocityX { get; set; }
65 |
66 | ///
67 | /// Yaw (y) velocity in local space
68 | ///
69 | public float AngularVelocityY { get; set; }
70 |
71 | ///
72 | /// Roll (z) velocity in local space
73 | ///
74 | public float AngularVelocityZ { get; set; }
75 |
76 |
77 |
78 | public float Yaw { get; set; }
79 | public float Pitch { get; set; }
80 | public float Roll { get; set; }
81 |
82 |
83 |
84 | ///
85 | /// Normalized suspension travel (Front Left Tire) - 0.0 = max stretch | 1.0 = max compression
86 | ///
87 | public float NormalizedSuspensionTravelFrontLeft { get; set; }
88 |
89 | ///
90 | /// Normalized suspension travel (Front Right Tire) - 0.0 = max stretch | 1.0 = max compression
91 | ///
92 | public float NormalizedSuspensionTravelFrontRight { get; set; }
93 |
94 | ///
95 | /// Normalized suspension travel (Rear Left Tire) - 0.0 = max stretch | 1.0 = max compression
96 | ///
97 | public float NormalizedSuspensionTravelRearLeft { get; set; }
98 |
99 | ///
100 | /// Normalized suspension travel (Rear Right Tire) - 0.0 = max stretch | 1.0 = max compression
101 | ///
102 | public float NormalizedSuspensionTravelRearRight { get; set; }
103 |
104 |
105 |
106 | ///
107 | /// Normalized tire slip ratio (Front Left Tire) - 0.0 = 100% grip and |ratio| > 1.0 means loss of grip
108 | ///
109 | public float TireSlipRatioFrontLeft { get; set; }
110 |
111 | ///
112 | /// Normalized tire slip ratio (Front Right Tire) - 0.0 = 100% grip and |ratio| > 1.0 means loss of grip
113 | ///
114 | public float TireSlipRatioFrontRight { get; set; }
115 |
116 | ///
117 | /// Normalized tire slip ratio (Rear Left Tire) - 0.0 = 100% grip and |ratio| > 1.0 means loss of grip
118 | ///
119 | public float TireSlipRatioRearLeft { get; set; }
120 |
121 | ///
122 | /// Normalized tire slip ratio (Rear Right Tire) - 0.0 = 100% grip and |ratio| > 1.0 means loss of grip
123 | ///
124 | public float TireSlipRatioRearRight { get; set; }
125 |
126 |
127 |
128 | ///
129 | /// Wheel rotation speed in radians/sec - Front Left Tire
130 | ///
131 | public float WheelRotationSpeedFrontLeft { get; set; }
132 |
133 | ///
134 | /// Wheel rotation speed in radians/sec - Front Right Tire
135 | ///
136 | public float WheelRotationSpeedFrontRight { get; set; }
137 |
138 | ///
139 | /// Wheel rotation speed in radians/sec - Rear Left Tire
140 | ///
141 | public float WheelRotationSpeedRearLeft { get; set; }
142 |
143 | ///
144 | /// Wheel rotation speed in radians/sec - Rear Right Tire
145 | ///
146 | public float WheelRotationSpeedRearRight { get; set; }
147 |
148 |
149 |
150 | ///
151 | /// Returns 1 when the front left wheel is on a rumble strip, 0 when not
152 | ///
153 | public int WheelOnRumbleStripFrontLeft { get; set; }
154 |
155 | ///
156 | /// Returns 1 when the front right wheel is on a rumble strip, 0 when not
157 | ///
158 | public int WheelOnRumbleStripFrontRight { get; set; }
159 |
160 | ///
161 | /// Returns 1 when the rear left wheel is on a rumble strip, 0 when not
162 | ///
163 | public int WheelOnRumbleStripRearLeft { get; set; }
164 |
165 | ///
166 | /// Returns 1 when the rear right wheel is on a rumble strip, 0 when not
167 | ///
168 | public int WheelOnRumbleStripRearRight { get; set; }
169 |
170 |
171 |
172 | // From 0 to 1, where 1 is the deepest puddle
173 | public float WheelInPuddleDepthFrontLeft { get; set; }
174 | public float WheelInPuddleDepthFrontRight { get; set; }
175 | public float WheelInPuddleDepthRearLeft { get; set; }
176 | public float WheelInPuddleDepthRearRight { get; set; }
177 |
178 | // Non-dimensional surface rumble values passed to controller force feedback
179 | public float SurfaceRumbleFrontLeft { get; set; }
180 | public float SurfaceRumbleFrontRight { get; set; }
181 | public float SurfaceRumbleRearLeft { get; set; }
182 | public float SurfaceRumbleRearRight { get; set; }
183 |
184 | // Tire normalized slip angle, = 0 means 100% grip and |angle| > 1.0 means loss of grip.
185 | public float TireSlipAngleFrontLeft { get; set; }
186 | public float TireSlipAngleFrontRight { get; set; }
187 | public float TireSlipAngleRearLeft { get; set; }
188 | public float TireSlipAngleRearRight { get; set; }
189 |
190 | // Tire normalized combined slip, = 0 means 100% grip and |slip| > 1.0 means loss of grip.
191 | public float TireCombinedSlipFrontLeft { get; set; }
192 | public float TireCombinedSlipFrontRight { get; set; }
193 | public float TireCombinedSlipRearLeft { get; set; }
194 | public float TireCombinedSlipRearRight { get; set; }
195 |
196 | // Actual suspension travel in meters
197 | public float SuspensionTravelFrontLeft { get; set; }
198 | public float SuspensionTravelFrontRight { get; set; }
199 | public float SuspensionTravelRearLeft { get; set; }
200 | public float SuspensionTravelRearRight { get; set; }
201 |
202 | ///
203 | /// The Car ID
204 | ///
205 | public int CarOrdinal { get; set; }
206 |
207 | ///
208 | /// The car class - see
209 | ///
210 | public Vehicle.CarClass CarClass { get; set; }
211 |
212 | ///
213 | /// The performance index value
214 | ///
215 | public int CarPI { get; set; }
216 |
217 | ///
218 | /// The current drivetrain type - see
219 | ///
220 | public Vehicle.DrivetrainType DrivetrainType { get; set; }
221 |
222 | ///
223 | /// The number of cylinders
224 | ///
225 | public int NumCylinders { get; set; }
226 |
227 |
228 |
229 | ///
230 | /// X position in meters
231 | ///
232 | public float PositionX { get; set; }
233 |
234 | ///
235 | /// Y position in meters
236 | ///
237 | public float PositionY { get; set; }
238 |
239 | ///
240 | /// Z position in meters
241 | ///
242 | public float PositionZ { get; set; }
243 |
244 | ///
245 | /// Current speed in meters per second
246 | ///
247 | public float Speed { get; set; }
248 |
249 | ///
250 | /// The amount of power in watts
251 | ///
252 | public float Power { get; set; }
253 |
254 | ///
255 | /// The amount of torque in newton-meters
256 | ///
257 | public float Torque { get; set; }
258 |
259 | // Tire temperature (celcius)
260 | public float TireTempFrontLeft { get; set; }
261 | public float TireTempFrontRight { get; set; }
262 | public float TireTempRearLeft { get; set; }
263 | public float TireTempRearRight { get; set; }
264 |
265 | ///
266 | /// Boost amount
267 | ///
268 | public float Boost { get; set; }
269 |
270 | ///
271 | /// Fuel amount
272 | ///
273 | public float Fuel { get; set; }
274 |
275 | ///
276 | /// Distance traveled (meters)
277 | ///
278 | public float DistanceTraveled { get; set; }
279 |
280 | ///
281 | /// Best lap time
282 | ///
283 | public float BestLap { get; set; }
284 |
285 | ///
286 | /// Last lap time
287 | ///
288 | public float LastLap { get; set; }
289 |
290 | ///
291 | /// Current lap time
292 | ///
293 | public float CurrentLap { get; set; }
294 |
295 | ///
296 | /// Current race (total) time
297 | ///
298 | public float CurrentRaceTime { get; set; }
299 |
300 | ///
301 | /// Current lap number
302 | ///
303 | public UInt16 LapNumber { get; set; }
304 |
305 | ///
306 | /// Current race position
307 | ///
308 | public byte RacePosition { get; set; }
309 |
310 | ///
311 | /// Acceleration amount
312 | ///
313 | public byte Accel { get; set; }
314 |
315 | ///
316 | /// Brake amount
317 | ///
318 | public byte Brake { get; set; }
319 |
320 | ///
321 | /// Clutch amount
322 | ///
323 | public byte Clutch { get; set; }
324 |
325 | ///
326 | /// Handbrake amount
327 | ///
328 | public byte Handbrake { get; set; }
329 |
330 | ///
331 | /// Current gear
332 | ///
333 | public byte Gear { get; set; }
334 |
335 | ///
336 | /// Current steering amount
337 | ///
338 | public sbyte Steer { get; set; }
339 |
340 | public sbyte NormalizedDrivingLine { get; set; }
341 | public sbyte NormalizedAIBrakeDifference { get; set; }
342 |
343 | public int GetMPH()
344 | {
345 | return (int)(VelocityZ * 2.23694f);
346 | }
347 | }
348 | }
349 |
--------------------------------------------------------------------------------