diff --git a/RPH1/FodyWeavers.xml b/RPH1/FodyWeavers.xml new file mode 100644 index 0000000..c6e1b7c --- /dev/null +++ b/RPH1/FodyWeavers.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/RPH1/Properties/AssemblyInfo.cs b/RPH1/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..6debf55 --- /dev/null +++ b/RPH1/Properties/AssemblyInfo.cs @@ -0,0 +1,38 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("SpotifyRage")] +[assembly: AssemblyDescription("A plugin for Rage Plugin Hook that enables the use of Spotify within Grand Theft Auto V.")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("SpotifyRage")] +[assembly: AssemblyCopyright("Copyright © Lucas Ritter, Spotify 2018")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("ab9168d5-9bdb-43ab-9a90-9eeddf83b099")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] + +[assembly: Rage.Attributes.Plugin("SpotifyRage", Author = "Lucas Ritter, Spotify", ShouldTickInPauseMenu = false)] diff --git a/RPH1/RPH1.csproj b/RPH1/RPH1.csproj new file mode 100644 index 0000000..bd22987 --- /dev/null +++ b/RPH1/RPH1.csproj @@ -0,0 +1,85 @@ + + + + + Debug + AnyCPU + {AB9168D5-9BDB-43AB-9A90-9EEDDF83B099} + Library + Properties + SpotifyRage + SpotifyRage + v4.6.2 + 512 + + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + x64 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + Spotify.ico + + + + ..\packages\SpotifyWebHelperAPI.1.0.4\lib\Newtonsoft.Json.dll + True + + + E:\Games\Standalone\GTA\Vanilla\SDK\RagePluginHookSDK.dll + + + ..\packages\SpotifyWebHelperAPI.1.0.4\lib\SpotifyWebHelperAPI.dll + + + + + + + + + + + + + + + + + + + + + + + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + + + + xcopy $(TargetPath) "E:\Games\Standalone\GTA\Vanilla\Plugins" /F /Y + + \ No newline at end of file diff --git a/RPH1/Scaleform.cs b/RPH1/Scaleform.cs new file mode 100644 index 0000000..e6bb3cd --- /dev/null +++ b/RPH1/Scaleform.cs @@ -0,0 +1,92 @@ +using Rage.Native; + +namespace SpotifyRage +{ + class Scaleform + { + public int Handle { get; private set; } + public string ScaleformID { get; private set; } + + public Scaleform(string scaleformId, bool forceLoad) + { + if (forceLoad) + Load(scaleformId); + } + + public Scaleform(int handle, bool forceLoad = false) + { + this.Handle = handle; + } + + public bool Load(string scaleformId) + { + // Request a Scaleform movie identifier from the game. + int handle = NativeFunction.Natives.RequestScaleformMovie(scaleformId); + + // If a handle was not given, cancel the load process. + if (handle == 0) return false; + + // Set local values. + this.Handle = handle; + this.ScaleformID = scaleformId; + + // Loading was successful, return true. + return true; + } + + public void Render2D() + { + // Draw a Scaleform movie in Fullscreen with 255 on all RGBA values. + NativeFunction.Natives.x0DF606929C105BE1(this.Handle, 255, 255, 255, 255); + } + + public void CallFunction (string function, params object[] arguments) + { + // Start calling the function. + NativeFunction.Natives.xF6E48914C7A8694E(this.Handle, function); + + // Loop through all arguments. + foreach (object argument in arguments) + { + // Do type checking + + // 32bit Integer + if (argument.GetType() == typeof(int)) + { + // Call native GRAPHICS::_PUSH_SCALEFORM_MOVIE_METHOD_PARAMETER_INT + NativeFunction.Natives.xC3D0841A0CC546A6((int)argument); + } + + else if (argument.GetType() == typeof(string)) + { + // Call native GRAPHICS::_PUSH_SCALEFORM_MOVIE_METHOD_PARAMETER_STRING + NativeFunction.Natives.xBA7148484BD90365((string)argument); + } + + } + + // Pop the function over to the game. + NativeFunction.Natives.xC6796A8FFA375E53(); + } + + public void CallFunctionArray (string function, params string[] arguments) + { + string[] argArray = new string[] { "", "", "", "", "" }; + + for (int i = 0; i < 5; i++) + { + if (arguments.Length > i && arguments[i] != null) + { + argArray[i] = arguments[i]; + } + } + + // Call native GRAPHICS::_CALL_SCALEFORM_MOVIE_FUNCTION_STRING_PARAMS + NativeFunction.Natives.x51BC1ED3CC44E8F7(this.Handle, function, + argArray[0], argArray[1], argArray[2], argArray[3], argArray[4]); + + // Pop the function over to the game. + NativeFunction.Natives.EndScaleformMovieMethod(); + } + } +} diff --git a/RPH1/Spotify.cs b/RPH1/Spotify.cs new file mode 100644 index 0000000..f021c2c --- /dev/null +++ b/RPH1/Spotify.cs @@ -0,0 +1,110 @@ +using System; +using Rage; +using Rage.Native; +using SpotifyWebHelperAPI; + +namespace SpotifyRage +{ + public sealed class EntryPoint + { + public static int RadioStation { get; private set; } = 0; + + private static ISpotifyWebHelperCommunicationService CommunicationService; + private static Scaleform DashboardScaleform; + + private static bool DebugDraw = false; + + public static void Main() + { + Game.Console.Print($"SpotifyRage is now starting.."); + + // Initialize a connection to the Spotify Web Helper application. + // TODO: Catch if the App is not running. + Game.Console.Print("Connecting to the Spotify Web Helper application."); + CommunicationService = SpotifyWebHelperApi.Create(); + + // Request Dashboard Scaleform Movie + DashboardScaleform = new Scaleform("dashboard", true); + + + // Update on every game tick. + Game.FrameRender += Update; + + while (true) + GameFiber.Yield(); + } + + private static void Update(object sender, GraphicsEventArgs e) + { + + if (Game.LocalPlayer.Character.IsInAnyVehicle(false)) + { + if (NativeFunction.Natives.GetPlayerRadioStationIndex() == RadioStation) + { + var status = CommunicationService.GetStatus(); + + // Disable radio in car. + NativeFunction.Natives.SetFrontendRadioActive(false); + NativeFunction.Natives.SetVehicleRadioLoud(Game.LocalPlayer.Character.CurrentVehicle, false); + NativeFunction.Natives.SetVehicleRadioEnabled(Game.LocalPlayer.Character.CurrentVehicle, false); + + PushSpotifyInfo(status.Track.ArtistResource.Name, status.Track.TrackResource.Name, status.Playing); + + if (DebugDraw) + DashboardScaleform.Render2D(); + } + + // Check if vehicle radio is disabled or if it is set to be loud. + if (!NativeFunction.Natives.x5F43D83FD6738741() || !NativeFunction.Natives.x032A116663A4D5AC(Game.LocalPlayer.Character.CurrentVehicle)) + { + NativeFunction.Natives.SetFrontendRadioActive(true); + NativeFunction.Natives.SetVehicleRadioLoud(Game.LocalPlayer.Character.CurrentVehicle, true); + NativeFunction.Natives.SetVehicleRadioEnabled(Game.LocalPlayer.Character.CurrentVehicle, true); + } + } + } +#if DEBUG + [Rage.Attributes.ConsoleCommand()] + private static void Command_GetSpotifyInfo() + { + var status = CommunicationService.GetStatus(); + Game.Console.Print($"Artist: {status.Track.ArtistResource.Name}, Track: {status.Track.TrackResource.Name}, Is Playing: {status.Playing}"); + } + + [Rage.Attributes.ConsoleCommand()] + private static void Command_SetTestInfo() + { + var status = CommunicationService.GetStatus(); + Game.Console.Print($"Artist: {status.Track.ArtistResource.Name}, Track: {status.Track.TrackResource.Name}, Is Playing: {status.Playing}"); + } + + [Rage.Attributes.ConsoleCommand()] + private static void Command_GetDashboard() + { + Game.Console.Print(DashboardScaleform.Handle.ToString()); + } + + [Rage.Attributes.ConsoleCommand()] + private static void Command_DrawDebug(bool enabled) + { + DebugDraw = enabled; + } + +#endif + + private static void PushSpotifyInfo(string artist, string track, bool playing) + { + try + { + DashboardScaleform.CallFunction("SET_RADIO", + "", playing ? "Spotify" : "Spotify - Paused", + artist, track); + } + catch (Exception exception) + { + Game.Console.Print("Spotify fucked up, " + exception.ToString()); + } + + } + } +} diff --git a/RPH1/Spotify.ico b/RPH1/Spotify.ico new file mode 100644 index 0000000..df225ff Binary files /dev/null and b/RPH1/Spotify.ico differ diff --git a/RPH1/packages.config b/RPH1/packages.config new file mode 100644 index 0000000..f262c24 --- /dev/null +++ b/RPH1/packages.config @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/SHVDN/FodyWeavers.xml b/SHVDN/FodyWeavers.xml new file mode 100644 index 0000000..c6e1b7c --- /dev/null +++ b/SHVDN/FodyWeavers.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/SHVDN/Properties/AssemblyInfo.cs b/SHVDN/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..ec42c72 --- /dev/null +++ b/SHVDN/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("SpotifyRage")] +[assembly: AssemblyDescription("A plugin for ScriptHookV.Net that enables the use of Spotify within Grand Theft Auto V.")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("SpotifyRage")] +[assembly: AssemblyCopyright("Copyright © Lucas Ritter, Spotify 2018")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("697e407f-5571-49ff-8e41-044c6550c12b")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/SHVDN/SHVDN.csproj b/SHVDN/SHVDN.csproj new file mode 100644 index 0000000..e6be980 --- /dev/null +++ b/SHVDN/SHVDN.csproj @@ -0,0 +1,86 @@ + + + + + Debug + AnyCPU + {697E407F-5571-49FF-8E41-044C6550C12B} + Library + Properties + SpotifyRage + SpotifyRage + v4.6.2 + 512 + + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + Spotify.ico + + + + ..\packages\Costura.Fody.1.6.2\lib\dotnet\Costura.dll + False + + + ..\packages\SpotifyWebHelperAPI.1.0.4\lib\Newtonsoft.Json.dll + True + + + ..\packages\ScriptHookVDotNet2.2.10.5\lib\net452\ScriptHookVDotNet2.dll + + + ..\packages\SpotifyWebHelperAPI.1.0.4\lib\SpotifyWebHelperAPI.dll + + + + + + + + + + + + + + + + + + + + + + + + + xcopy $(TargetPath) "E:\Games\Standalone\GTA\Vanilla\Scripts" /F /Y + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + + + \ No newline at end of file diff --git a/SHVDN/Scaleform.cs b/SHVDN/Scaleform.cs new file mode 100644 index 0000000..b5224b6 --- /dev/null +++ b/SHVDN/Scaleform.cs @@ -0,0 +1,92 @@ +using GTA.Native; + +namespace SpotifyRage +{ + class Scaleform + { + public int Handle { get; private set; } + public string ScaleformID { get; private set; } + + public Scaleform(string scaleformId, bool forceLoad) + { + if (forceLoad) + Load(scaleformId); + } + + public Scaleform(int handle, bool forceLoad = false) + { + this.Handle = handle; + } + + public bool Load(string scaleformId) + { + // Request a Scaleform movie identifier from the game. + int handle = Function.Call(Hash.REQUEST_SCALEFORM_MOVIE, scaleformId); + + // If a handle was not given, cancel the load process. + if (handle == 0) return false; + + // Set local values. + this.Handle = handle; + this.ScaleformID = scaleformId; + + // Loading was successful, return true. + return true; + } + + public void Render2D() + { + // Draw a Scaleform movie in Fullscreen with 255 on all RGBA values. + Function.Call(Hash.DRAW_SCALEFORM_MOVIE, this.Handle, 255, 255, 255, 255); + } + + public void CallFunction (string function, params object[] arguments) + { + // Start calling the function. + Function.Call(Hash._PUSH_SCALEFORM_MOVIE_FUNCTION, this.Handle, function); + + // Loop through all arguments. + foreach (object argument in arguments) + { + // Do type checking + + // 32bit Integer + if (argument.GetType() == typeof(int)) + { + // Call native GRAPHICS::_PUSH_SCALEFORM_MOVIE_METHOD_PARAMETER_INT + Function.Call(Hash._PUSH_SCALEFORM_MOVIE_FUNCTION_PARAMETER_INT, (int)argument); + } + + else if (argument.GetType() == typeof(string)) + { + // Call native GRAPHICS::_PUSH_SCALEFORM_MOVIE_METHOD_PARAMETER_STRING + Function.Call(Hash._PUSH_SCALEFORM_MOVIE_FUNCTION_PARAMETER_STRING, (string)argument); + } + + } + + // Pop the function over to the game. + Function.Call(Hash._POP_SCALEFORM_MOVIE_FUNCTION_VOID); + } + + public void CallFunctionArray (string function, params string[] arguments) + { + string[] argArray = new string[] { "", "", "", "", "" }; + + for (int i = 0; i < 5; i++) + { + if (arguments.Length > i && arguments[i] != null) + { + argArray[i] = arguments[i]; + } + } + + // Call native GRAPHICS::_CALL_SCALEFORM_MOVIE_FUNCTION_STRING_PARAMS + Function.Call(Hash._CALL_SCALEFORM_MOVIE_FUNCTION_STRING_PARAMS, this.Handle, function, + argArray[0], argArray[1], argArray[2], argArray[3], argArray[4]); + + // Pop the function over to the game. + Function.Call(Hash._POP_SCALEFORM_MOVIE_FUNCTION_VOID); + } + } +} diff --git a/SHVDN/Spotify.cs b/SHVDN/Spotify.cs new file mode 100644 index 0000000..27dcb13 --- /dev/null +++ b/SHVDN/Spotify.cs @@ -0,0 +1,131 @@ +using System; +using GTA; +using GTA.Native; +using SpotifyWebHelperAPI; +using System.IO; + +namespace SpotifyRage +{ + public class Spotify : Script + { + public int RadioStation { get; private set; } = 0; + + // The communication service between SpotifyWebHelper.exe and SHVDN. + private ISpotifyWebHelperCommunicationService CommunicationService; + + // The Scaleform used by the dashboard. + private Scaleform DashboardScaleform; + + // Enable this to show the Dashboard on screen. + private bool DebugDraw = false; + + // This stores the current configuration. + private ScriptSettings Settings; + + /// + /// The constructor for the Spotify class. + /// + public Spotify() + { + // Load the latest config. + LoadConfig(); + + // Initialize a connection to the Spotify Web Helper application. + // TODO: Catch if the App is not running. + CommunicationService = SpotifyWebHelperApi.Create(); + + // Request Dashboard Scaleform Movie + DashboardScaleform = new Scaleform("dashboard", true); + + + // Update on every game tick. + this.Tick += Update; + + + } + + /// + /// Updates the information. Runs every gametick. + /// + /// + /// + private void Update(object sender, EventArgs e) + { + // Check if the player is in a vehicle. + if (Game.Player.Character.IsInVehicle()) + { + // Check if the radio channel is set correctly. + if (Function.Call(Hash.GET_PLAYER_RADIO_STATION_INDEX) == RadioStation) + { + // Get the latest Spotify status. + var status = CommunicationService.GetStatus(); + + // Disable radio in car. + Function.Call(Hash.SET_FRONTEND_RADIO_ACTIVE, false); + Function.Call(Hash.SET_VEHICLE_RADIO_LOUD, false); + Function.Call(Hash.SET_VEHICLE_RADIO_ENABLED, false); + + // Push over the spotify information. + PushSpotifyInfo(status.Track.ArtistResource.Name, status.Track.TrackResource.Name, status.Playing); + + // Draw the dashboard on the screen. + if (DebugDraw) + DashboardScaleform.Render2D(); + } + + // Check if vehicle radio is disabled or if it is set to be loud. + if (!Function.Call(Hash._0x5F43D83FD6738741) || !Function.Call(Hash._0x032A116663A4D5AC, Game.Player.Character.CurrentVehicle)) + { + // Enable radio in car. + Function.Call(Hash.SET_FRONTEND_RADIO_ACTIVE, true); + Function.Call(Hash.SET_VEHICLE_RADIO_LOUD, true); + Function.Call(Hash.SET_VEHICLE_RADIO_ENABLED, true); + } + } + } + + /// + /// Loads the config from the hard drive. + /// + public void LoadConfig() + { + // Check if config file exists. + if (!File.Exists(@"scripts\SpotifyRage.ini")) + { + // Write to SpotifyRage.ini if it can't be found. + File.WriteAllLines(@"scripts\SpotifyRage.ini", + new string[] { "[Spotify]", "RadioStation=0", "DebugMode = false" }); + } + + // Load the config file. + Settings = ScriptSettings.Load(@"scripts\SpotifyRage.ini"); + + // Set all values. + DebugDraw = Settings.GetValue("Spotify", "DebugMode", false); + RadioStation = Settings.GetValue("Spotify", "RadioStation", 0); + + } + + /// + /// Pushes the given info over to the Dashboard scaleform for use ingame. + /// + /// The artist of the song. + /// The song name itself. + /// Whether the song is playing or not. + private void PushSpotifyInfo(string artist, string track, bool playing) + { + try + { + // Call function. + DashboardScaleform.CallFunction("SET_RADIO", + "", playing ? "Spotify" : "Spotify - Paused", + artist, track); + } + catch (Exception exception) + { + UI.Notify(exception.ToString()); + } + + } + } +} diff --git a/SHVDN/Spotify.ico b/SHVDN/Spotify.ico new file mode 100644 index 0000000..df225ff Binary files /dev/null and b/SHVDN/Spotify.ico differ diff --git a/SHVDN/packages.config b/SHVDN/packages.config new file mode 100644 index 0000000..7015b8b --- /dev/null +++ b/SHVDN/packages.config @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/Spotify.sln b/Spotify.sln new file mode 100644 index 0000000..cbcd484 --- /dev/null +++ b/Spotify.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.27130.2024 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RPH1", "RPH1\RPH1.csproj", "{AB9168D5-9BDB-43AB-9A90-9EEDDF83B099}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SHVDN", "SHVDN\SHVDN.csproj", "{697E407F-5571-49FF-8E41-044C6550C12B}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {AB9168D5-9BDB-43AB-9A90-9EEDDF83B099}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AB9168D5-9BDB-43AB-9A90-9EEDDF83B099}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AB9168D5-9BDB-43AB-9A90-9EEDDF83B099}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AB9168D5-9BDB-43AB-9A90-9EEDDF83B099}.Release|Any CPU.Build.0 = Release|Any CPU + {697E407F-5571-49FF-8E41-044C6550C12B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {697E407F-5571-49FF-8E41-044C6550C12B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {697E407F-5571-49FF-8E41-044C6550C12B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {697E407F-5571-49FF-8E41-044C6550C12B}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {93BF8C92-0A19-4A65-8C0E-7ECB58EFADED} + EndGlobalSection +EndGlobal