diff --git a/ForzaHorizon6Plugin/Config.cs b/ForzaHorizon6Plugin/Config.cs new file mode 100644 index 0000000..1a6ea09 --- /dev/null +++ b/ForzaHorizon6Plugin/Config.cs @@ -0,0 +1,15 @@ +using YawGLAPI; + +namespace ForzaHorizon6Plugin +{ + public struct Config + { + [Info(Description = "UDP port the game is using")] + public int Port = 20127; + + public Config() + { + } + } +} + diff --git a/ForzaHorizon6Plugin/ForzaHorizon6Plugin.cs b/ForzaHorizon6Plugin/ForzaHorizon6Plugin.cs new file mode 100644 index 0000000..7dd2695 --- /dev/null +++ b/ForzaHorizon6Plugin/ForzaHorizon6Plugin.cs @@ -0,0 +1,179 @@ +using SharedLib; +using System; +using System.Collections.Generic; +using System.ComponentModel.Composition; +using System.Diagnostics; +using System.IO; +using System.Net; +using System.Net.Sockets; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Threading; +using YawGLAPI; +namespace ForzaHorizon6Plugin +{ + [Export(typeof(Game))] + [ExportMetadata("Name", "Forza Horizon 6")] + [ExportMetadata("Version", "1.1")] + public class ForzaHorizon6Plugin : Game { + + + private bool stop = false; + private Thread readthread; + UdpClient receivingUdpClient; + IPEndPoint RemoteIpEndPoint = new IPEndPoint(IPAddress.Any, 0); + private IMainFormDispatcher dispatcher; + private IProfileManager controller; + + public string PROCESS_NAME => "ForzaHorizon6"; + public int STEAM_ID => 2483190; + public bool PATCH_AVAILABLE => true; + public string AUTHOR => "Drowhunter"; + + public string Description => ResourceHelper.Description; + + public Stream Logo => ResourceHelper.Logo; + public Stream SmallLogo => ResourceHelper.SmallLogo; + public Stream Background => ResourceHelper.Background; + + + + public LedEffect DefaultLED() { + return new LedEffect( + + EFFECT_TYPE.KNIGHT_RIDER_2, + 4, + new YawColor[] { + new YawColor(66, 135, 245), + new YawColor(80,80,80), + new YawColor(128, 3, 117), + new YawColor(110, 201, 12), + }, + 25f); + + } + + public List DefaultProfile() { + + return dispatcher.JsonToComponents(ResourceHelper.DefaultProfile); + + } + + public void Exit() { + receivingUdpClient.Close(); + receivingUdpClient = null; + stop = true; + //readthread.Abort(); + + } + public string[] GetInputData() { + + Type t = typeof(ForzaTelemetry); + FieldInfo[] fields = t.GetFields(); + + string[] inputs = new string[fields.Length]; + + for (int i = 0; i < fields.Length; i++) { + inputs[i] = fields[i].Name; + } + return inputs; + } + + + + public void SetReferences(IProfileManager controller, IMainFormDispatcher dispatcher) + { + this.dispatcher = dispatcher; + this.controller = controller; + } + public void Init() { + Addloopback(); + stop = false; + + var pConfig = dispatcher.GetConfigObject(); + receivingUdpClient = new UdpClient(pConfig.Port); + receivingUdpClient.Client.ReceiveTimeout = 2000; + readthread = new Thread(new ThreadStart(ReadFunction)); + readthread.Start(); + } + + private void ReadFunction() { + + Console.WriteLine("ForzaRD"); + ForzaTelemetry obj = new ForzaTelemetry(); + FieldInfo[] fields = typeof(ForzaTelemetry).GetFields(); + try + { + while (!stop) + { + try + { + // Blocks until a message returns on this socket from a remote host. + byte[] rawData = receivingUdpClient.Receive(ref RemoteIpEndPoint); + Console.Write(BitConverter.ToSingle(rawData, 0)); + + IntPtr unmanagedPointer = Marshal.AllocHGlobal(rawData.Length); + Marshal.Copy(rawData, 0, unmanagedPointer, rawData.Length); + // Call unmanaged code + Marshal.FreeHGlobal(unmanagedPointer); + Marshal.PtrToStructure(unmanagedPointer, obj); + + obj.Yaw *= 57.295f; + obj.Pitch *= 57.295f; + obj.Roll *= 57.295f; + + obj.AngularVelocityX *= 57.295f; + obj.AngularVelocityY *= 57.295f; + obj.AngularVelocityZ *= 57.295f; + obj.speed = 4 * (float)Math.Sqrt(Math.Pow(obj.VelocityX, 2) + Math.Pow(obj.VelocityY, 2) + Math.Pow(obj.VelocityZ, 2)); + if (obj.IsRaceOn == 1) + { + for (int i = 0; i < fields.Length; i++) + { + controller.SetInput(i, (float)Convert.ChangeType(fields[i].GetValue(obj), TypeCode.Single)); + } + } + + } + catch (SocketException) { } + + } + } catch(ObjectDisposedException) + { + dispatcher.ExitGame(); + } + } + + + public void PatchGame() { + + Addloopback(); + + + } + public void Addloopback() { + var proc1 = new ProcessStartInfo(); + proc1.UseShellExecute = true; + + proc1.WorkingDirectory = @"C:\Windows\System32"; + + proc1.FileName = @"C:\Windows\System32\cmd.exe"; + proc1.Verb = "runas"; + proc1.Arguments = "/c " + "checknetisolation loopbackexempt -a -n=Microsoft.SunriseBaseGame_1.351.461.2_x64__8wekyb3d8bbwe"; + proc1.WindowStyle = ProcessWindowStyle.Normal; + Process.Start(proc1); + + } + + public Dictionary GetFeatures() + { + return null; + } + + public Type GetConfigBody() + { + return typeof(Config); + } + } +} + diff --git a/ForzaHorizon6Plugin/ForzaHorizon6Plugin.csproj b/ForzaHorizon6Plugin/ForzaHorizon6Plugin.csproj new file mode 100644 index 0000000..9336185 --- /dev/null +++ b/ForzaHorizon6Plugin/ForzaHorizon6Plugin.csproj @@ -0,0 +1,35 @@ + + + net8.0 + Library + All;Debug;Release + ForzaHorizon6Plugin + ForzaHorizon6Plugin + Copyright © 2021 + 1.0.0.0 + 1.0.0.0 + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ForzaHorizon6Plugin/ForzaTelemetry.cs b/ForzaHorizon6Plugin/ForzaTelemetry.cs new file mode 100644 index 0000000..7ef2ebb --- /dev/null +++ b/ForzaHorizon6Plugin/ForzaTelemetry.cs @@ -0,0 +1,88 @@ +using System.Runtime.InteropServices; + +namespace ForzaHorizon6Plugin +{ + [StructLayout(LayoutKind.Sequential)] + internal class ForzaTelemetry + { + public uint IsRaceOn; // = 1 when race is on. = 0 when in menus/race stopped … + + public uint TimestampMS; //Can overflow to 0 eventually + + public float EngineMaxRpm; + public float EngineIdleRpm; + public float CurrentEngineRpm; + + public float AccelerationX; //In the car's local space; X = right, Y = up, Z = forward + public float AccelerationY; + public float AccelerationZ; + + public float VelocityX; //In the car's local space; X = right, Y = up, Z = forward + public float VelocityY; + public float VelocityZ; + + public float AngularVelocityX; //In the car's local space; X = pitch, Y = yaw, Z = roll + public float AngularVelocityY; + public float AngularVelocityZ; + + public float Yaw; + public float Pitch; + public float Roll; + + public float NormalizedSuspensionTravelFrontLeft; // Suspension travel normalized: 0.0f = max stretch; 1.0 = max compression + public float NormalizedSuspensionTravelFrontRight; + public float NormalizedSuspensionTravelRearLeft; + public float NormalizedSuspensionTravelRearRight; + + public float TireSlipRatioFrontLeft; // Tire normalized slip ratio, = 0 means 100% grip and |ratio| > 1.0 means loss of grip. + public float TireSlipRatioFrontRight; + public float TireSlipRatioRearLeft; + public float TireSlipRatioRearRight; + + public float WheelRotationSpeedFrontLeft; // Wheel rotation speed radians/sec. + public float WheelRotationSpeedFrontRight; + public float WheelRotationSpeedRearLeft; + public float WheelRotationSpeedRearRight; + + public uint WheelOnRumbleStripFrontLeft; // = 1 when wheel is on rumble strip, = 0 when off. + public uint WheelOnRumbleStripFrontRight; + public uint WheelOnRumbleStripRearLeft; + public uint WheelOnRumbleStripRearRight; + + public float WheelInPuddleDepthFrontLeft; // = from 0 to 1, where 1 is the deepest puddle + public float WheelInPuddleDepthFrontRight; + public float WheelInPuddleDepthRearLeft; + public float WheelInPuddleDepthRearRight; + + public float SurfaceRumbleFrontLeft; // Non-dimensional surface rumble values passed to controller force feedback + public float SurfaceRumbleFrontRight; + public float SurfaceRumbleRearLeft; + public float SurfaceRumbleRearRight; + + public float TireSlipAngleFrontLeft; // Tire normalized slip angle, = 0 means 100% grip and |angle| > 1.0 means loss of grip. + public float TireSlipAngleFrontRight; + public float TireSlipAngleRearLeft; + public float TireSlipAngleRearRight; + + public float TireCombinedSlipFrontLeft; // Tire normalized combined slip, = 0 means 100% grip and |slip| > 1.0 means loss of grip. + public float TireCombinedSlipFrontRight; + public float TireCombinedSlipRearLeft; + public float TireCombinedSlipRearRight; + + public float SuspensionTravelMetersFrontLeft; // Actual suspension travel in meters + public float SuspensionTravelMetersFrontRight; + public float SuspensionTravelMetersRearLeft; + public float SuspensionTravelMetersRearRight; + + public uint CarOrdinal; //Unique ID of the car make/model + public uint CarClass; //Between 0 (D -- worst cars) and 7 (X class -- best cars) inclusive + public uint CarPerformanceIndex; //Between 100 (slowest car) and 999 (fastest car) inclusive + public uint DrivetrainType; //Corresponds to EDrivetrainType; 0 = FWD, 1 = RWD, 2 = AWD + public uint NumCylinders; //Number of cylinders in the engine + + + public float speed; + + } +} + diff --git a/ForzaHorizon6Plugin/Resources/Default.yawglprofile b/ForzaHorizon6Plugin/Resources/Default.yawglprofile new file mode 100644 index 0000000..f3c396c --- /dev/null +++ b/ForzaHorizon6Plugin/Resources/Default.yawglprofile @@ -0,0 +1 @@ +{"GameName":"Forza Horizon 6","Name":"Default","components":[{"Constant":false,"Input_index":14,"Output_index":0,"MultiplierPos":1.0,"MultiplierNeg":1.0,"Offset":0.0,"Inverse":false,"Limit":-1.0,"Smoothing":1.0,"Enabled":true,"Spikeflatter":{"Enabled":false,"Limit":100.0,"Strength":0.5},"Deadzone":0.0,"Type":0,"Condition":[],"Math":[]},{"Constant":false,"Input_index":15,"Output_index":1,"MultiplierPos":1.0,"MultiplierNeg":1.0,"Offset":0.0,"Inverse":false,"Limit":-1.0,"Smoothing":1.0,"Enabled":true,"Spikeflatter":{"Enabled":false,"Limit":100.0,"Strength":0.5},"Deadzone":0.0,"Type":0,"Condition":[],"Math":[]},{"Constant":false,"Input_index":16,"Output_index":2,"MultiplierPos":1.0,"MultiplierNeg":1.0,"Offset":0.0,"Inverse":false,"Limit":-1.0,"Smoothing":1.0,"Enabled":true,"Spikeflatter":{"Enabled":false,"Limit":100.0,"Strength":0.5},"Deadzone":0.0,"Type":0,"Condition":[],"Math":[]},{"Constant":false,"Input_index":7,"Output_index":1,"MultiplierPos":0.5,"MultiplierNeg":1.0,"Offset":0.0,"Inverse":true,"Limit":-1.0,"Smoothing":1.0,"Enabled":true,"Spikeflatter":{"Enabled":false,"Limit":100.0,"Strength":0.5},"Deadzone":0.0,"Type":0,"Condition":[],"Math":[]},{"Constant":false,"Input_index":5,"Output_index":2,"MultiplierPos":0.15,"MultiplierNeg":0.15,"Offset":0.0,"Inverse":false,"Limit":-1.0,"Smoothing":1.0,"Enabled":true,"Spikeflatter":{"Enabled":false,"Limit":100.0,"Strength":0.5},"Deadzone":0.0,"Type":0,"Condition":[],"Math":[]},{"Constant":false,"Input_index":21,"Output_index":3,"MultiplierPos":10.0,"MultiplierNeg":10.0,"Offset":0.0,"Inverse":false,"Limit":-1.0,"Smoothing":1.0,"Enabled":true,"Spikeflatter":{"Enabled":false,"Limit":100.0,"Strength":0.5},"Deadzone":0.0,"Type":0,"Condition":[],"Math":[]},{"Constant":true,"Input_index":0,"Output_index":4,"MultiplierPos":17.0,"MultiplierNeg":17.0,"Offset":0.0,"Inverse":false,"Limit":-1.0,"Smoothing":1.0,"Enabled":true,"Spikeflatter":{"Enabled":false,"Limit":100.0,"Strength":0.5},"Deadzone":0.0,"Type":0,"Condition":[],"Math":[]},{"Constant":false,"Input_index":37,"Output_index":3,"MultiplierPos":50.0,"MultiplierNeg":50.0,"Offset":0.0,"Inverse":false,"Limit":-1.0,"Smoothing":1.0,"Enabled":true,"Spikeflatter":{"Enabled":false,"Limit":100.0,"Strength":0.5},"Deadzone":0.0,"Type":0,"Condition":[],"Math":[]},{"Constant":false,"Input_index":17,"Output_index":2,"MultiplierPos":15.0,"MultiplierNeg":15.0,"Offset":0.0,"Inverse":false,"Limit":-1.0,"Smoothing":1.0,"Enabled":true,"Spikeflatter":{"Enabled":false,"Limit":100.0,"Strength":0.5},"Deadzone":0.0,"Type":0,"Condition":[],"Math":[]},{"Constant":false,"Input_index":18,"Output_index":2,"MultiplierPos":15.0,"MultiplierNeg":15.0,"Offset":0.0,"Inverse":true,"Limit":-1.0,"Smoothing":1.0,"Enabled":true,"Spikeflatter":{"Enabled":false,"Limit":100.0,"Strength":0.5},"Deadzone":0.0,"Type":0,"Condition":[],"Math":[]},{"Constant":false,"Input_index":0,"Output_index":0,"MultiplierPos":1.0,"MultiplierNeg":1.0,"Offset":0.0,"Inverse":false,"Limit":-1.0,"Smoothing":1.0,"Enabled":false,"Spikeflatter":{"Enabled":false,"Limit":100.0,"Strength":0.5},"Deadzone":0.0,"Type":0,"Condition":[],"Math":[]}],"effects":{"EffectID":1,"InputID":4,"Multiplier":25.0,"Colors":[{"R":66,"G":135,"B":245},{"R":80,"G":80,"B":80},{"R":128,"G":3,"B":117},{"R":110,"G":201,"B":12}]},"functions":[]} \ No newline at end of file diff --git a/ForzaHorizon6Plugin/Resources/description.html b/ForzaHorizon6Plugin/Resources/description.html new file mode 100644 index 0000000..6d29a88 --- /dev/null +++ b/ForzaHorizon6Plugin/Resources/description.html @@ -0,0 +1,7 @@ +Press patch game for first use. Then, +Go into ingame Settings -> HUD and Gameplay +Scroll down until you see the option DATA OUT. Set this option to ON. +
+Then enter DATA OUT IP ADDRESS = 127.0.0.1 +
+Now enter the DATA OUT IP PORT = 20127 diff --git a/ForzaHorizon6Plugin/Resources/logo.png b/ForzaHorizon6Plugin/Resources/logo.png new file mode 100644 index 0000000..198b227 Binary files /dev/null and b/ForzaHorizon6Plugin/Resources/logo.png differ diff --git a/ForzaHorizon6Plugin/Resources/recent.png b/ForzaHorizon6Plugin/Resources/recent.png new file mode 100644 index 0000000..bc1fd84 Binary files /dev/null and b/ForzaHorizon6Plugin/Resources/recent.png differ diff --git a/ForzaHorizon6Plugin/Resources/wide.png b/ForzaHorizon6Plugin/Resources/wide.png new file mode 100644 index 0000000..4b4a2ca Binary files /dev/null and b/ForzaHorizon6Plugin/Resources/wide.png differ diff --git a/Plugins.sln b/Plugins.sln index d9d0607..fae3d50 100644 --- a/Plugins.sln +++ b/Plugins.sln @@ -147,6 +147,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RFactor2Plugin", "rFactor2P EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "XPlane12Plugin", "XPlane12Plugin\XPlane12Plugin.csproj", "{38C3765B-AFD3-C9DD-94F8-90C5CD3890D3}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ForzaHorizon6Plugin", "ForzaHorizon6Plugin\ForzaHorizon6Plugin.csproj", "{0CE66456-3E03-49E1-8035-BDB41270FF8D}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -997,6 +999,18 @@ Global {38C3765B-AFD3-C9DD-94F8-90C5CD3890D3}.Release|x64.Build.0 = Release|Any CPU {38C3765B-AFD3-C9DD-94F8-90C5CD3890D3}.Release|x86.ActiveCfg = Release|Any CPU {38C3765B-AFD3-C9DD-94F8-90C5CD3890D3}.Release|x86.Build.0 = Release|Any CPU + {0CE66456-3E03-49E1-8035-BDB41270FF8D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0CE66456-3E03-49E1-8035-BDB41270FF8D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0CE66456-3E03-49E1-8035-BDB41270FF8D}.Debug|x64.ActiveCfg = Debug|Any CPU + {0CE66456-3E03-49E1-8035-BDB41270FF8D}.Debug|x64.Build.0 = Debug|Any CPU + {0CE66456-3E03-49E1-8035-BDB41270FF8D}.Debug|x86.ActiveCfg = Debug|Any CPU + {0CE66456-3E03-49E1-8035-BDB41270FF8D}.Debug|x86.Build.0 = Debug|Any CPU + {0CE66456-3E03-49E1-8035-BDB41270FF8D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0CE66456-3E03-49E1-8035-BDB41270FF8D}.Release|Any CPU.Build.0 = Release|Any CPU + {0CE66456-3E03-49E1-8035-BDB41270FF8D}.Release|x64.ActiveCfg = Release|Any CPU + {0CE66456-3E03-49E1-8035-BDB41270FF8D}.Release|x64.Build.0 = Release|Any CPU + {0CE66456-3E03-49E1-8035-BDB41270FF8D}.Release|x86.ActiveCfg = Release|Any CPU + {0CE66456-3E03-49E1-8035-BDB41270FF8D}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE