├── PS4RemotePlayInterceptor ├── EasyHook32.dll ├── EasyHook64.dll ├── EasyLoad32.dll ├── EasyLoad64.dll ├── EasyHook32Svc.exe ├── EasyHook64Svc.exe ├── packages.config ├── PS4RemotePlayInterceptor.nuspec ├── Properties │ └── AssemblyInfo.cs ├── Classes │ ├── InterceptorException.cs │ ├── Watchdog.cs │ ├── InjectionInterface.cs │ ├── Interceptor.cs │ ├── DualShockState.cs │ └── Hooks.cs └── PS4RemotePlayInterceptor.csproj ├── PS4RemotePlayInterceptorDemo ├── Properties │ ├── Settings.settings │ ├── Settings.Designer.cs │ ├── AssemblyInfo.cs │ ├── Resources.Designer.cs │ └── Resources.resx ├── Program.cs ├── Main.cs ├── Main.Designer.cs ├── PS4RemotePlayInterceptorDemo.csproj └── Main.resx ├── LICENSE.md ├── PS4RemotePlayInterceptorConsoleDemo ├── Properties │ └── AssemblyInfo.cs ├── Program.cs └── PS4RemotePlayInterceptorConsoleDemo.csproj ├── PS4RemotePlayInterceptor.sln ├── README.md └── .gitignore /PS4RemotePlayInterceptor/EasyHook32.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/komefai/PS4RemotePlayInterceptor/HEAD/PS4RemotePlayInterceptor/EasyHook32.dll -------------------------------------------------------------------------------- /PS4RemotePlayInterceptor/EasyHook64.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/komefai/PS4RemotePlayInterceptor/HEAD/PS4RemotePlayInterceptor/EasyHook64.dll -------------------------------------------------------------------------------- /PS4RemotePlayInterceptor/EasyLoad32.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/komefai/PS4RemotePlayInterceptor/HEAD/PS4RemotePlayInterceptor/EasyLoad32.dll -------------------------------------------------------------------------------- /PS4RemotePlayInterceptor/EasyLoad64.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/komefai/PS4RemotePlayInterceptor/HEAD/PS4RemotePlayInterceptor/EasyLoad64.dll -------------------------------------------------------------------------------- /PS4RemotePlayInterceptor/EasyHook32Svc.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/komefai/PS4RemotePlayInterceptor/HEAD/PS4RemotePlayInterceptor/EasyHook32Svc.exe -------------------------------------------------------------------------------- /PS4RemotePlayInterceptor/EasyHook64Svc.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/komefai/PS4RemotePlayInterceptor/HEAD/PS4RemotePlayInterceptor/EasyHook64Svc.exe -------------------------------------------------------------------------------- /PS4RemotePlayInterceptor/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /PS4RemotePlayInterceptorDemo/Properties/Settings.settings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /PS4RemotePlayInterceptorDemo/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Windows.Forms; 5 | 6 | namespace PS4RemotePlayInterceptorDemo 7 | { 8 | static class Program 9 | { 10 | /// 11 | /// The main entry point for the application. 12 | /// 13 | [STAThread] 14 | static void Main() 15 | { 16 | Application.EnableVisualStyles(); 17 | Application.SetCompatibleTextRenderingDefault(false); 18 | Application.Run(new Main()); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright 2017 Komefai 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /PS4RemotePlayInterceptorDemo/Properties/Settings.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace PS4RemotePlayInterceptorDemo.Properties 12 | { 13 | 14 | 15 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 16 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")] 17 | internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase 18 | { 19 | 20 | private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); 21 | 22 | public static Settings Default 23 | { 24 | get 25 | { 26 | return defaultInstance; 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /PS4RemotePlayInterceptor/PS4RemotePlayInterceptor.nuspec: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $id$ 5 | $version$ 6 | $title$ 7 | Komefai 8 | Komefai 9 | https://github.com/komefai/PS4RemotePlayInterceptor#license 10 | https://github.com/komefai/PS4RemotePlayInterceptor 11 | true 12 | $description$ 13 | Intercepting PS4 Remote Play 14 | PS4 Remote Play Interceptor 15 | - 0.1.0: First version 16 | - 0.2.0: Serialization support 17 | - 0.2.1: Fix missing Options button during conversion 18 | - 0.3.0: Add watchdog to automatically inject when possible 19 | - 0.4.0: Add support for touchpad, accelerometer, and gyro 20 | - 0.4.1: Fix object cloning on DualShockState and add support for original injection method 21 | - 0.5.0: Add emulator for DualShock 4 controllers and add delegates for watchdog 22 | - 0.5.1: Fix crashes when closing host before RemotePlay 23 | - 0.5.2: Add compressed serialization and allow null values when cloning DualShockState 24 | 25 | Copyright © 2018 Komefai 26 | ps4 bot remoteplay api hooking easyhook dualshock emulator 27 | 28 | -------------------------------------------------------------------------------- /PS4RemotePlayInterceptorDemo/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("PS4RemotePlayInterceptorDemo")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("PS4RemotePlayInterceptorDemo")] 13 | [assembly: AssemblyCopyright("Copyright © 2017")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("ff9e5eae-c9ce-46c6-ae72-5c087074bc56")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /PS4RemotePlayInterceptorConsoleDemo/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("PS4RemotePlayInterceptorConsoleDemo")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("PS4RemotePlayInterceptorConsoleDemo")] 13 | [assembly: AssemblyCopyright("Copyright © 2017")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("bd8b6e3a-f4fc-46a2-9206-42d2c2ef7c01")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /PS4RemotePlayInterceptor/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("PS4RemotePlayInterceptor")] 9 | [assembly: AssemblyDescription("A small .NET library to intercept controls on PS4 Remote Play for Windows")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("Komefai")] 12 | [assembly: AssemblyProduct("PS4RemotePlayInterceptor")] 13 | [assembly: AssemblyCopyright("Copyright © 2018 Komefai")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("7ea5cd79-4454-4be5-bb06-fa4c8122808a")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("0.5.2.0")] 36 | [assembly: AssemblyFileVersion("0.5.2.0")] 37 | -------------------------------------------------------------------------------- /PS4RemotePlayInterceptorConsoleDemo/Program.cs: -------------------------------------------------------------------------------- 1 | using PS4RemotePlayInterceptor; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | 7 | namespace PS4RemotePlayInterceptorConsoleDemo 8 | { 9 | class Program 10 | { 11 | static void Main(string[] args) 12 | { 13 | // Setup callback to interceptor 14 | Interceptor.Callback = new InterceptionDelegate(OnReceiveData); 15 | // Emulate controller (BETA) 16 | Interceptor.EmulateController = true; 17 | 18 | // Start watchdog to automatically inject when possible 19 | Interceptor.Watchdog.Start(); 20 | // Notify watchdog events 21 | Interceptor.Watchdog.OnInjectionSuccess = () => Console.WriteLine("Watchdog OnInjectionSuccess"); 22 | Interceptor.Watchdog.OnInjectionFailure = () => Console.WriteLine("Watchdog OnInjectionFailure"); 23 | 24 | // Or inject manually and handle exceptions yourself 25 | //Interceptor.Inject(); 26 | 27 | Console.WriteLine("-- Press any key to exit"); 28 | Console.ReadKey(); 29 | } 30 | 31 | private static void OnReceiveData(ref DualShockState state) 32 | { 33 | /* -- Modify the controller state here -- */ 34 | 35 | // Force press X 36 | state.Cross = true; 37 | 38 | // Force left analog upwards 39 | state.LY = 0; 40 | 41 | // Force left analog downwards 42 | // state.LY = 255; 43 | 44 | // Force left analog to center 45 | // state.LX = 128; 46 | // state.LY = 128; 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /PS4RemotePlayInterceptor/Classes/InterceptorException.cs: -------------------------------------------------------------------------------- 1 | // PS4RemotePlayInterceptor (File: Classes/InterceptorException.cs) 2 | // 3 | // Copyright (c) 2018 Komefai 4 | // 5 | // Visit http://komefai.com for more information 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | // THE SOFTWARE. 24 | 25 | using System; 26 | 27 | namespace PS4RemotePlayInterceptor 28 | { 29 | public class InterceptorException : Exception 30 | { 31 | public InterceptorException() 32 | { 33 | } 34 | 35 | public InterceptorException(string message) : base(message) 36 | { 37 | } 38 | 39 | public InterceptorException(string message, Exception inner) : base(message, inner) 40 | { 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /PS4RemotePlayInterceptor.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 14 4 | VisualStudioVersion = 14.0.25123.0 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PS4RemotePlayInterceptor", "PS4RemotePlayInterceptor\PS4RemotePlayInterceptor.csproj", "{7EA5CD79-4454-4BE5-BB06-FA4C8122808A}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PS4RemotePlayInterceptorDemo", "PS4RemotePlayInterceptorDemo\PS4RemotePlayInterceptorDemo.csproj", "{FF9E5EAE-C9CE-46C6-AE72-5C087074BC56}" 9 | EndProject 10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PS4RemotePlayInterceptorConsoleDemo", "PS4RemotePlayInterceptorConsoleDemo\PS4RemotePlayInterceptorConsoleDemo.csproj", "{BD8B6E3A-F4FC-46A2-9206-42D2C2EF7C01}" 11 | EndProject 12 | Global 13 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 14 | Debug|Any CPU = Debug|Any CPU 15 | Release|Any CPU = Release|Any CPU 16 | EndGlobalSection 17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 18 | {7EA5CD79-4454-4BE5-BB06-FA4C8122808A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 19 | {7EA5CD79-4454-4BE5-BB06-FA4C8122808A}.Debug|Any CPU.Build.0 = Debug|Any CPU 20 | {7EA5CD79-4454-4BE5-BB06-FA4C8122808A}.Release|Any CPU.ActiveCfg = Release|Any CPU 21 | {7EA5CD79-4454-4BE5-BB06-FA4C8122808A}.Release|Any CPU.Build.0 = Release|Any CPU 22 | {FF9E5EAE-C9CE-46C6-AE72-5C087074BC56}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 23 | {FF9E5EAE-C9CE-46C6-AE72-5C087074BC56}.Debug|Any CPU.Build.0 = Debug|Any CPU 24 | {FF9E5EAE-C9CE-46C6-AE72-5C087074BC56}.Release|Any CPU.ActiveCfg = Release|Any CPU 25 | {FF9E5EAE-C9CE-46C6-AE72-5C087074BC56}.Release|Any CPU.Build.0 = Release|Any CPU 26 | {BD8B6E3A-F4FC-46A2-9206-42D2C2EF7C01}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 27 | {BD8B6E3A-F4FC-46A2-9206-42D2C2EF7C01}.Debug|Any CPU.Build.0 = Debug|Any CPU 28 | {BD8B6E3A-F4FC-46A2-9206-42D2C2EF7C01}.Release|Any CPU.ActiveCfg = Release|Any CPU 29 | {BD8B6E3A-F4FC-46A2-9206-42D2C2EF7C01}.Release|Any CPU.Build.0 = Release|Any CPU 30 | EndGlobalSection 31 | GlobalSection(SolutionProperties) = preSolution 32 | HideSolutionNode = FALSE 33 | EndGlobalSection 34 | EndGlobal 35 | -------------------------------------------------------------------------------- /PS4RemotePlayInterceptorDemo/Main.cs: -------------------------------------------------------------------------------- 1 | using PS4RemotePlayInterceptor; 2 | using System; 3 | using System.Windows.Forms; 4 | 5 | namespace PS4RemotePlayInterceptorDemo 6 | { 7 | public partial class Main : Form 8 | { 9 | private bool IsInjected { get; set; } 10 | private int PID { get; set; } 11 | 12 | public Main() 13 | { 14 | InitializeComponent(); 15 | 16 | // Setup callback to interceptor 17 | Interceptor.InjectionMode = InjectionMode.Compatibility; 18 | Interceptor.Callback = new InterceptionDelegate(OnReceiveData); 19 | } 20 | 21 | private static void OnReceiveData(ref DualShockState state) 22 | { 23 | /* -- Modify the controller state here -- */ 24 | 25 | // Force press X 26 | state.Cross = true; 27 | 28 | // Force left analog upwards 29 | state.LY = 0; 30 | 31 | // Force left analog downwards 32 | // state.LY = 255; 33 | 34 | // Force left analog to center 35 | // state.LX = 128; 36 | // state.LY = 128; 37 | } 38 | 39 | private void UpdateUI() 40 | { 41 | injectButton.Text = IsInjected ? "Stop" : "Inject"; 42 | pidLabel.Text = string.Format("Target PID: {0}", (PID < 0 ? "-" : PID.ToString())); 43 | } 44 | 45 | private void injectButton_Click(object sender, EventArgs e) 46 | { 47 | if (!IsInjected) 48 | { 49 | // Try to inject into PS4 Remote Play 50 | try 51 | { 52 | PID = Interceptor.Inject(); 53 | IsInjected = true; 54 | } 55 | catch(InterceptorException ex) 56 | { 57 | MessageBox.Show(ex.Message + "\n\n" + ex.StackTrace, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); 58 | 59 | if (ex.InnerException != null) 60 | { 61 | MessageBox.Show(ex.InnerException.Message + "\n\n" + ex.InnerException.StackTrace, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); 62 | } 63 | } 64 | } 65 | else 66 | { 67 | // Remove injection from PS4 Remote Play 68 | Interceptor.StopInjection(); 69 | 70 | PID = -1; 71 | IsInjected = false; 72 | } 73 | 74 | UpdateUI(); 75 | } 76 | 77 | private void sendStartSignalButton_Click(object sender, EventArgs e) 78 | { 79 | Interceptor.SendStartSignal(); 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /PS4RemotePlayInterceptorConsoleDemo/PS4RemotePlayInterceptorConsoleDemo.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {BD8B6E3A-F4FC-46A2-9206-42D2C2EF7C01} 8 | Exe 9 | Properties 10 | PS4RemotePlayInterceptorConsoleDemo 11 | PS4RemotePlayInterceptorConsoleDemo 12 | v4.0 13 | 512 14 | 15 | 16 | AnyCPU 17 | true 18 | full 19 | false 20 | bin\Debug\ 21 | DEBUG;TRACE 22 | prompt 23 | 4 24 | 25 | 26 | AnyCPU 27 | pdbonly 28 | true 29 | bin\Release\ 30 | TRACE 31 | prompt 32 | 4 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | {7EA5CD79-4454-4BE5-BB06-FA4C8122808A} 50 | PS4RemotePlayInterceptor 51 | 52 | 53 | 54 | 61 | -------------------------------------------------------------------------------- /PS4RemotePlayInterceptorDemo/Properties/Resources.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace PS4RemotePlayInterceptorDemo.Properties 12 | { 13 | 14 | 15 | /// 16 | /// A strongly-typed resource class, for looking up localized strings, etc. 17 | /// 18 | // This class was auto-generated by the StronglyTypedResourceBuilder 19 | // class via a tool like ResGen or Visual Studio. 20 | // To add or remove a member, edit your .ResX file then rerun ResGen 21 | // with the /str option, or rebuild your VS project. 22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] 23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 25 | internal class Resources 26 | { 27 | 28 | private static global::System.Resources.ResourceManager resourceMan; 29 | 30 | private static global::System.Globalization.CultureInfo resourceCulture; 31 | 32 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] 33 | internal Resources() 34 | { 35 | } 36 | 37 | /// 38 | /// Returns the cached ResourceManager instance used by this class. 39 | /// 40 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 41 | internal static global::System.Resources.ResourceManager ResourceManager 42 | { 43 | get 44 | { 45 | if ((resourceMan == null)) 46 | { 47 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("PS4RemotePlayInterceptorDemo.Properties.Resources", typeof(Resources).Assembly); 48 | resourceMan = temp; 49 | } 50 | return resourceMan; 51 | } 52 | } 53 | 54 | /// 55 | /// Overrides the current thread's CurrentUICulture property for all 56 | /// resource lookups using this strongly typed resource class. 57 | /// 58 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 59 | internal static global::System.Globalization.CultureInfo Culture 60 | { 61 | get 62 | { 63 | return resourceCulture; 64 | } 65 | set 66 | { 67 | resourceCulture = value; 68 | } 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PS4 Remote Play Interceptor 2 | 3 | [![nuget](https://img.shields.io/nuget/v/PS4RemotePlayInterceptor.svg)](https://nuget.org/packages/PS4RemotePlayInterceptor) 4 | [![NuGet](https://img.shields.io/nuget/dt/PS4RemotePlayInterceptor.svg)](https://nuget.org/packages/PS4RemotePlayInterceptor) 5 | [![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](http://paypal.me/Komefai) 6 | [![Twitter](https://img.shields.io/twitter/url/https/twitter.com/fold_left.svg?style=social&label=Follow%20Me)](https://twitter.com/itskomefai) 7 | 8 | A small .NET library to intercept controls on PS4 Remote Play for Windows, powered by [EasyHook](https://easyhook.github.io/). The library can be used to automate any PS4 game. See the [prototype demo](https://youtu.be/QjTZsPR-BcI). 9 | 10 | Also check out [PS4 Macro](https://github.com/komefai/PS4Macro) repository for a ready-to-use software built on this library. 11 | 12 | ## Install 13 | 14 | #### Using NuGet (Recommended) 15 | ``` 16 | Install-Package PS4RemotePlayInterceptor 17 | ``` 18 | 19 | #### From Source 20 | Add reference to PS4RemotePlayInterceptor.dll. 21 | 22 | ## Example Usage 23 | 24 | This console application will hold the X button while moving the left analog stick upwards until interrupted by a keypress. 25 | 26 | You can set `EmulateController` to `true` to use the library without a DualShock 4 controller plugged in (the real controller must be unplugged). 27 | 28 | ```csharp 29 | using PS4RemotePlayInterceptor; 30 | 31 | class Program 32 | { 33 | static void Main(string[] args) 34 | { 35 | // Setup callback to interceptor 36 | Interceptor.Callback = new InterceptionDelegate(OnReceiveData); 37 | // Emulate controller (BETA) 38 | Interceptor.EmulateController = false; 39 | 40 | // Start watchdog to automatically inject when possible 41 | Interceptor.Watchdog.Start(); 42 | // Notify watchdog events 43 | Interceptor.Watchdog.OnInjectionSuccess = () => Console.WriteLine("Watchdog OnInjectionSuccess"); 44 | Interceptor.Watchdog.OnInjectionFailure = () => Console.WriteLine("Watchdog OnInjectionFailure"); 45 | 46 | // Or inject manually and handle exceptions yourself 47 | //Interceptor.Inject(); 48 | 49 | Console.WriteLine("-- Press any key to exit"); 50 | Console.ReadKey(); 51 | } 52 | 53 | private static void OnReceiveData(ref DualShockState state) 54 | { 55 | /* -- Modify the controller state here -- */ 56 | 57 | // Force press X 58 | state.Cross = true; 59 | 60 | // Force left analog upwards 61 | state.LY = 0; 62 | 63 | // Force left analog downwards 64 | // state.LY = 255; 65 | 66 | // Force left analog to center 67 | // state.LX = 128; 68 | // state.LY = 128; 69 | } 70 | } 71 | ``` 72 | 73 | ## To-Do List 74 | 75 | - Intercept ouput reports 76 | 77 | ## Troubleshoot 78 | 79 | - > {"STATUS_INTERNAL_ERROR: Unknown error in injected C++ completion routine. (Code: 15)"} 80 | 81 | SOLUTION: Restart PS4 Remote Play. 82 | 83 | - > Injection IPC failed (on some machines) 84 | 85 | SOLUTION: Inject with Compatibility mode instead 86 | 87 | ```csharp 88 | // Setup callback to interceptor 89 | Interceptor.Callback = new InterceptionDelegate(OnReceiveData); 90 | 91 | // Inject 92 | Interceptor.InjectionMode = InjectionMode.Compatibility; 93 | Interceptor.Inject(); 94 | ``` 95 | 96 | ## Credits 97 | 98 | - https://easyhook.github.io/ 99 | - https://github.com/Jays2Kings/DS4Windows 100 | - http://www.psdevwiki.com/ps4/DS4-USB 101 | -------------------------------------------------------------------------------- /PS4RemotePlayInterceptor/Classes/Watchdog.cs: -------------------------------------------------------------------------------- 1 | // PS4RemotePlayInterceptor (File: Classes/Watchdog.cs) 2 | // 3 | // Copyright (c) 2018 Komefai 4 | // 5 | // Visit http://komefai.com for more information 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | // THE SOFTWARE. 24 | 25 | using System; 26 | using System.Collections.Generic; 27 | using System.Linq; 28 | using System.Text; 29 | using System.Timers; 30 | 31 | namespace PS4RemotePlayInterceptor 32 | { 33 | public delegate void WatchdogEventDelegate(); 34 | 35 | public class Watchdog 36 | { 37 | // Delegates 38 | public WatchdogEventDelegate OnInjectionSuccess { get; set; } 39 | public WatchdogEventDelegate OnInjectionFailure { get; set; } 40 | 41 | private DateTime m_LastPingTime; 42 | 43 | private Timer m_Timer = null; 44 | private Timer Timer 45 | { 46 | get 47 | { 48 | // Lazy instantiate timer 49 | if (m_Timer == null) 50 | { 51 | m_Timer = new Timer(1000); 52 | m_Timer.Elapsed += (sender, e) => 53 | { 54 | PollInjection(); 55 | }; 56 | 57 | } 58 | return m_Timer; 59 | } 60 | } 61 | 62 | private void PollInjection() 63 | { 64 | if (m_LastPingTime == Interceptor.LastPingTime) 65 | { 66 | try 67 | { 68 | Interceptor.StopInjection(); 69 | Interceptor.Inject(); 70 | 71 | // Invoke success callback 72 | OnInjectionSuccess?.Invoke(); 73 | } 74 | catch 75 | { 76 | // Invoke failure callback 77 | OnInjectionFailure?.Invoke(); 78 | } 79 | } 80 | 81 | m_LastPingTime = Interceptor.LastPingTime; 82 | } 83 | 84 | private void CheckCompatibiltyMode() 85 | { 86 | if (Interceptor.InjectionMode == InjectionMode.Compatibility) 87 | throw new InterceptorException("Watchdog is not supported in compatibility mode"); 88 | } 89 | 90 | public void Start(int interval = 1000) 91 | { 92 | CheckCompatibiltyMode(); 93 | PollInjection(); 94 | 95 | Timer.Interval = interval; 96 | Timer.Enabled = true; 97 | } 98 | 99 | public void Stop() 100 | { 101 | CheckCompatibiltyMode(); 102 | Timer.Enabled = false; 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /PS4RemotePlayInterceptorDemo/Main.Designer.cs: -------------------------------------------------------------------------------- 1 | namespace PS4RemotePlayInterceptorDemo 2 | { 3 | partial class Main 4 | { 5 | /// 6 | /// Required designer variable. 7 | /// 8 | private System.ComponentModel.IContainer components = null; 9 | 10 | /// 11 | /// Clean up any resources being used. 12 | /// 13 | /// true if managed resources should be disposed; otherwise, false. 14 | protected override void Dispose(bool disposing) 15 | { 16 | if (disposing && (components != null)) 17 | { 18 | components.Dispose(); 19 | } 20 | base.Dispose(disposing); 21 | } 22 | 23 | #region Windows Form Designer generated code 24 | 25 | /// 26 | /// Required method for Designer support - do not modify 27 | /// the contents of this method with the code editor. 28 | /// 29 | private void InitializeComponent() 30 | { 31 | this.injectButton = new System.Windows.Forms.Button(); 32 | this.pidLabel = new System.Windows.Forms.Label(); 33 | this.sendStartSignalButton = new System.Windows.Forms.Button(); 34 | this.SuspendLayout(); 35 | // 36 | // injectButton 37 | // 38 | this.injectButton.Location = new System.Drawing.Point(12, 47); 39 | this.injectButton.Name = "injectButton"; 40 | this.injectButton.Size = new System.Drawing.Size(318, 23); 41 | this.injectButton.TabIndex = 0; 42 | this.injectButton.Text = "Inject"; 43 | this.injectButton.UseVisualStyleBackColor = true; 44 | this.injectButton.Click += new System.EventHandler(this.injectButton_Click); 45 | // 46 | // pidLabel 47 | // 48 | this.pidLabel.AutoSize = true; 49 | this.pidLabel.Location = new System.Drawing.Point(12, 21); 50 | this.pidLabel.Name = "pidLabel"; 51 | this.pidLabel.Size = new System.Drawing.Size(68, 13); 52 | this.pidLabel.TabIndex = 1; 53 | this.pidLabel.Text = "Target PID: -"; 54 | // 55 | // sendStartSignalButton 56 | // 57 | this.sendStartSignalButton.Location = new System.Drawing.Point(231, 16); 58 | this.sendStartSignalButton.Name = "sendStartSignalButton"; 59 | this.sendStartSignalButton.Size = new System.Drawing.Size(99, 23); 60 | this.sendStartSignalButton.TabIndex = 2; 61 | this.sendStartSignalButton.Text = "Send Start Signal"; 62 | this.sendStartSignalButton.UseVisualStyleBackColor = true; 63 | this.sendStartSignalButton.Click += new System.EventHandler(this.sendStartSignalButton_Click); 64 | // 65 | // Main 66 | // 67 | this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); 68 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; 69 | this.ClientSize = new System.Drawing.Size(342, 82); 70 | this.Controls.Add(this.sendStartSignalButton); 71 | this.Controls.Add(this.pidLabel); 72 | this.Controls.Add(this.injectButton); 73 | this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle; 74 | this.MaximizeBox = false; 75 | this.Name = "Main"; 76 | this.Text = "PS4 Remote Play Interceptor Demo"; 77 | this.ResumeLayout(false); 78 | this.PerformLayout(); 79 | 80 | } 81 | 82 | #endregion 83 | 84 | private System.Windows.Forms.Button injectButton; 85 | private System.Windows.Forms.Label pidLabel; 86 | private System.Windows.Forms.Button sendStartSignalButton; 87 | } 88 | } 89 | 90 | -------------------------------------------------------------------------------- /PS4RemotePlayInterceptor/PS4RemotePlayInterceptor.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {7EA5CD79-4454-4BE5-BB06-FA4C8122808A} 8 | Library 9 | Properties 10 | PS4RemotePlayInterceptor 11 | PS4RemotePlayInterceptor 12 | v4.0 13 | 512 14 | 15 | 16 | true 17 | full 18 | false 19 | bin\Debug\ 20 | DEBUG;TRACE 21 | prompt 22 | 4 23 | true 24 | 25 | 26 | pdbonly 27 | true 28 | bin\Release\ 29 | TRACE 30 | prompt 31 | 4 32 | true 33 | 34 | 35 | 36 | ..\packages\EasyHook.2.7.6270\lib\net40\EasyHook.dll 37 | True 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | PreserveNewest 60 | 61 | 62 | PreserveNewest 63 | 64 | 65 | PreserveNewest 66 | 67 | 68 | PreserveNewest 69 | 70 | 71 | PreserveNewest 72 | 73 | 74 | PreserveNewest 75 | 76 | 77 | 78 | 79 | 80 | 81 | 88 | -------------------------------------------------------------------------------- /PS4RemotePlayInterceptorDemo/PS4RemotePlayInterceptorDemo.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {FF9E5EAE-C9CE-46C6-AE72-5C087074BC56} 8 | WinExe 9 | Properties 10 | PS4RemotePlayInterceptorDemo 11 | PS4RemotePlayInterceptorDemo 12 | v4.0 13 | 512 14 | 15 | 16 | AnyCPU 17 | true 18 | full 19 | false 20 | bin\Debug\ 21 | DEBUG;TRACE 22 | prompt 23 | 4 24 | 25 | 26 | AnyCPU 27 | pdbonly 28 | true 29 | bin\Release\ 30 | TRACE 31 | prompt 32 | 4 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | Form 49 | 50 | 51 | Main.cs 52 | 53 | 54 | 55 | 56 | Main.cs 57 | 58 | 59 | ResXFileCodeGenerator 60 | Resources.Designer.cs 61 | Designer 62 | 63 | 64 | True 65 | Resources.resx 66 | 67 | 68 | SettingsSingleFileGenerator 69 | Settings.Designer.cs 70 | 71 | 72 | True 73 | Settings.settings 74 | True 75 | 76 | 77 | 78 | 79 | {7ea5cd79-4454-4be5-bb06-fa4c8122808a} 80 | PS4RemotePlayInterceptor 81 | 82 | 83 | 84 | 91 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Windows image file caches 2 | Thumbs.db 3 | ehthumbs.db 4 | 5 | ## Ignore Visual Studio temporary files, build results, and 6 | ## files generated by popular Visual Studio add-ons. 7 | 8 | # User-specific files 9 | *.suo 10 | *.user 11 | *.userosscache 12 | *.sln.docstates 13 | 14 | # User-specific files (MonoDevelop/Xamarin Studio) 15 | *.userprefs 16 | 17 | # Build results 18 | [Dd]ebug/ 19 | [Dd]ebugPublic/ 20 | [Rr]elease/ 21 | [Rr]eleases/ 22 | x64/ 23 | x86/ 24 | build/ 25 | bld/ 26 | [Bb]in/ 27 | [Oo]bj/ 28 | 29 | # Visual Studio 2015 cache/options directory 30 | .vs/ 31 | # Uncomment if you have tasks that create the project's static files in wwwroot 32 | #wwwroot/ 33 | 34 | # MSTest test Results 35 | [Tt]est[Rr]esult*/ 36 | [Bb]uild[Ll]og.* 37 | 38 | # NUNIT 39 | *.VisualState.xml 40 | TestResult.xml 41 | 42 | # Build Results of an ATL Project 43 | [Dd]ebugPS/ 44 | [Rr]eleasePS/ 45 | dlldata.c 46 | 47 | # DNX 48 | project.lock.json 49 | artifacts/ 50 | 51 | *_i.c 52 | *_p.c 53 | *_i.h 54 | *.ilk 55 | *.meta 56 | *.obj 57 | *.pch 58 | *.pdb 59 | *.pgc 60 | *.pgd 61 | *.rsp 62 | *.sbr 63 | *.tlb 64 | *.tli 65 | *.tlh 66 | *.tmp 67 | *.tmp_proj 68 | *.log 69 | *.vspscc 70 | *.vssscc 71 | .builds 72 | *.pidb 73 | *.svclog 74 | *.scc 75 | 76 | # Chutzpah Test files 77 | _Chutzpah* 78 | 79 | # Visual C++ cache files 80 | ipch/ 81 | *.aps 82 | *.ncb 83 | *.opensdf 84 | *.sdf 85 | *.cachefile 86 | 87 | # Visual Studio profiler 88 | *.psess 89 | *.vsp 90 | *.vspx 91 | *.sap 92 | 93 | # TFS 2012 Local Workspace 94 | $tf/ 95 | 96 | # Guidance Automation Toolkit 97 | *.gpState 98 | 99 | # ReSharper is a .NET coding add-in 100 | _ReSharper*/ 101 | *.[Rr]e[Ss]harper 102 | *.DotSettings.user 103 | 104 | # JustCode is a .NET coding add-in 105 | .JustCode 106 | 107 | # TeamCity is a build add-in 108 | _TeamCity* 109 | 110 | # DotCover is a Code Coverage Tool 111 | *.dotCover 112 | 113 | # NCrunch 114 | _NCrunch_* 115 | .*crunch*.local.xml 116 | nCrunchTemp_* 117 | 118 | # MightyMoose 119 | *.mm.* 120 | AutoTest.Net/ 121 | 122 | # Web workbench (sass) 123 | .sass-cache/ 124 | 125 | # Installshield output folder 126 | [Ee]xpress/ 127 | 128 | # DocProject is a documentation generator add-in 129 | DocProject/buildhelp/ 130 | DocProject/Help/*.HxT 131 | DocProject/Help/*.HxC 132 | DocProject/Help/*.hhc 133 | DocProject/Help/*.hhk 134 | DocProject/Help/*.hhp 135 | DocProject/Help/Html2 136 | DocProject/Help/html 137 | 138 | # Click-Once directory 139 | publish/ 140 | 141 | # Publish Web Output 142 | *.[Pp]ublish.xml 143 | *.azurePubxml 144 | # TODO: Comment the next line if you want to checkin your web deploy settings 145 | # but database connection strings (with potential passwords) will be unencrypted 146 | *.pubxml 147 | *.publishproj 148 | 149 | # NuGet Packages 150 | *.nupkg 151 | # The packages folder can be ignored because of Package Restore 152 | **/packages/* 153 | # except build/, which is used as an MSBuild target. 154 | !**/packages/build/ 155 | # Uncomment if necessary however generally it will be regenerated when needed 156 | #!**/packages/repositories.config 157 | 158 | # Windows Azure Build Output 159 | csx/ 160 | *.build.csdef 161 | 162 | # Windows Azure Emulator 163 | efc/ 164 | rfc/ 165 | 166 | # Windows Store app package directory 167 | AppPackages/ 168 | 169 | # Visual Studio cache files 170 | # files ending in .cache can be ignored 171 | *.[Cc]ache 172 | # but keep track of directories ending in .cache 173 | !*.[Cc]ache/ 174 | 175 | # Others 176 | ClientBin/ 177 | [Ss]tyle[Cc]op.* 178 | ~$* 179 | *~ 180 | *.dbmdl 181 | *.dbproj.schemaview 182 | *.pfx 183 | *.publishsettings 184 | node_modules/ 185 | orleans.codegen.cs 186 | 187 | # RIA/Silverlight projects 188 | Generated_Code/ 189 | 190 | # Backup & report files from converting an old project file 191 | # to a newer Visual Studio version. Backup files are not needed, 192 | # because we have git ;-) 193 | _UpgradeReport_Files/ 194 | Backup*/ 195 | UpgradeLog*.XML 196 | UpgradeLog*.htm 197 | 198 | # SQL Server files 199 | *.mdf 200 | *.ldf 201 | 202 | # Business Intelligence projects 203 | *.rdl.data 204 | *.bim.layout 205 | *.bim_*.settings 206 | 207 | # Microsoft Fakes 208 | FakesAssemblies/ 209 | 210 | # GhostDoc plugin setting file 211 | *.GhostDoc.xml 212 | 213 | # Node.js Tools for Visual Studio 214 | .ntvs_analysis.dat 215 | 216 | # Visual Studio 6 build log 217 | *.plg 218 | 219 | # Visual Studio 6 workspace options file 220 | *.opt 221 | 222 | # Visual Studio LightSwitch build output 223 | **/*.HTMLClient/GeneratedArtifacts 224 | **/*.DesktopClient/GeneratedArtifacts 225 | **/*.DesktopClient/ModelManifest.xml 226 | **/*.Server/GeneratedArtifacts 227 | **/*.Server/ModelManifest.xml 228 | _Pvt_Extensions 229 | 230 | # Paket dependency manager 231 | .paket/paket.exe 232 | 233 | # FAKE - F# Make 234 | .fake/ -------------------------------------------------------------------------------- /PS4RemotePlayInterceptor/Classes/InjectionInterface.cs: -------------------------------------------------------------------------------- 1 | // PS4RemotePlayInterceptor (File: Classes/InjectionInterface.cs) 2 | // 3 | // Copyright (c) 2018 Komefai 4 | // 5 | // Visit http://komefai.com for more information 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | // THE SOFTWARE. 24 | 25 | using System; 26 | using System.Collections.Generic; 27 | using System.Linq; 28 | using System.Text; 29 | 30 | namespace PS4RemotePlayInterceptor 31 | { 32 | /// 33 | /// Provides an interface for communicating from the client (target) to the server (injector) 34 | /// 35 | class InjectionInterface : MarshalByRefObject 36 | { 37 | /// 38 | /// Called when the hook has been injected successsfully 39 | /// 40 | public void OnInjectionSuccess(int clientPID) 41 | { 42 | try 43 | { 44 | Console.WriteLine("OnInjectionSuccess {0}", clientPID); 45 | } 46 | catch (Exception) { } 47 | } 48 | 49 | /// 50 | /// Called to confirm that the IPC channel is still open / host application has not closed 51 | /// 52 | public void Ping() 53 | { 54 | try 55 | { 56 | //Console.WriteLine("Ping"); 57 | 58 | // Store timestamp 59 | Interceptor.LastPingTime = DateTime.Now; 60 | } 61 | catch (Exception) { } 62 | } 63 | 64 | /// 65 | /// Report log 66 | /// 67 | /// 68 | public void ReportLog(string message) 69 | { 70 | try 71 | { 72 | Console.WriteLine("ReportLog {0}", message); 73 | } 74 | catch (Exception) { } 75 | } 76 | 77 | /// 78 | /// Report exception 79 | /// 80 | /// 81 | public void ReportException(Exception e) 82 | { 83 | try 84 | { 85 | Console.WriteLine("ReportException {0}", e.Message); 86 | } 87 | catch (Exception) { } 88 | } 89 | 90 | 91 | /* Interface for hooks */ 92 | 93 | public void OnCreateFile(string filename, string mode) 94 | { 95 | //Console.WriteLine("OnCreateFile {0} | {1}", filename, mode); 96 | } 97 | 98 | public void OnReadFile(string filename, ref byte[] inputReport) 99 | { 100 | try 101 | { 102 | //Console.WriteLine("OnReadFile {0}", filename); 103 | 104 | // Expect inputReport to be modified 105 | if (Interceptor.Callback != null) 106 | { 107 | // Parse the state 108 | var state = DualShockState.ParseFromDualshockRaw(inputReport); 109 | 110 | // Skip if state is invalid 111 | if (state == null) 112 | return; 113 | 114 | // Expect it to be modified 115 | Interceptor.Callback(ref state); 116 | 117 | // Convert it back 118 | state.ConvertToDualshockRaw(ref inputReport); 119 | } 120 | } 121 | catch (Exception) { } 122 | } 123 | 124 | public void OnWriteFile(string filename, ref byte[] outputReport) 125 | { 126 | try 127 | { 128 | //Console.WriteLine("OnWriteFile {0}", filename); 129 | } 130 | catch (Exception) { } 131 | } 132 | 133 | 134 | /* Config Wrappers */ 135 | 136 | public bool ShouldEmulateController() 137 | { 138 | try 139 | { 140 | return Interceptor.EmulateController; 141 | } 142 | catch (Exception) { return false; } 143 | 144 | } 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /PS4RemotePlayInterceptorDemo/Properties/Resources.resx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | text/microsoft-resx 107 | 108 | 109 | 2.0 110 | 111 | 112 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 113 | 114 | 115 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | -------------------------------------------------------------------------------- /PS4RemotePlayInterceptorDemo/Main.resx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | -------------------------------------------------------------------------------- /PS4RemotePlayInterceptor/Classes/Interceptor.cs: -------------------------------------------------------------------------------- 1 | // PS4RemotePlayInterceptor (File: Classes/Interceptor.cs) 2 | // 3 | // Copyright (c) 2018 Komefai 4 | // 5 | // Visit http://komefai.com for more information 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | // THE SOFTWARE. 24 | 25 | using EasyHook; 26 | using System; 27 | using System.Collections.Generic; 28 | using System.Diagnostics; 29 | using System.IO; 30 | using System.Linq; 31 | using System.Runtime.InteropServices; 32 | using System.Runtime.Remoting; 33 | using System.Runtime.Remoting.Channels.Ipc; 34 | using System.Security.Principal; 35 | using System.Text; 36 | 37 | namespace PS4RemotePlayInterceptor 38 | { 39 | public delegate void InterceptionDelegate(ref DualShockState state); 40 | 41 | public enum InjectionMode 42 | { 43 | Auto, 44 | Compatibility 45 | } 46 | 47 | public class Interceptor 48 | { 49 | #region Win32 API 50 | [DllImport("user32.dll", SetLastError = true)] 51 | private static extern IntPtr PostMessage(IntPtr hWnd, uint msg, uint wParam, uint lParam); 52 | #endregion 53 | 54 | // Constants 55 | private const string TARGET_PROCESS_NAME = "RemotePlay"; 56 | private const string INJECT_DLL_NAME = "PS4RemotePlayInterceptor.dll"; 57 | 58 | // EasyHook 59 | private static string _channelName = null; 60 | private static IpcServerChannel _ipcServer; 61 | private static bool _noGAC = false; 62 | 63 | // Watchdog 64 | private static Watchdog m_Watchdog = new Watchdog(); 65 | public static Watchdog Watchdog => m_Watchdog; 66 | public static DateTime LastPingTime { get; set; } 67 | 68 | // Injection 69 | public static InjectionMode InjectionMode = InjectionMode.Auto; 70 | // Emulation 71 | public static bool EmulateController = false; 72 | 73 | // Delegate 74 | public static InterceptionDelegate Callback { get; set; } 75 | 76 | public static int Inject() 77 | { 78 | // Find the process 79 | var process = FindRemotePlayProcess(); 80 | if (process == null) 81 | throw new InterceptorException(string.Format("{0} not found in list of processes", TARGET_PROCESS_NAME)); 82 | 83 | // Full path to our dll file 84 | string injectionLibrary = Path.Combine(Path.GetDirectoryName(typeof(InjectionInterface).Assembly.Location), INJECT_DLL_NAME); 85 | 86 | try 87 | { 88 | bool shouldInject = false; 89 | 90 | if (InjectionMode == InjectionMode.Auto) 91 | { 92 | if (_ipcServer == null) 93 | { 94 | // Setup remote hooking 95 | _channelName = DateTime.Now.ToString(); 96 | _ipcServer = RemoteHooking.IpcCreateServer(ref _channelName, WellKnownObjectMode.Singleton, WellKnownSidType.WorldSid); 97 | shouldInject = true; 98 | } 99 | } 100 | else if (InjectionMode == InjectionMode.Compatibility) 101 | { 102 | // Setup remote hooking 103 | _channelName = null; 104 | _ipcServer = RemoteHooking.IpcCreateServer(ref _channelName, WellKnownObjectMode.Singleton); 105 | shouldInject = true; 106 | } 107 | 108 | // Inject dll into the process 109 | if (shouldInject) 110 | { 111 | RemoteHooking.Inject( 112 | process.Id, // ID of process to inject into 113 | (_noGAC ? InjectionOptions.DoNotRequireStrongName : InjectionOptions.Default), 114 | // if not using GAC allow assembly without strong name 115 | injectionLibrary, // 32-bit version (the same because AnyCPU) 116 | injectionLibrary, // 64-bit version (the same because AnyCPU) 117 | _channelName 118 | ); 119 | } 120 | 121 | // Success 122 | return process.Id; 123 | } 124 | catch (Exception ex) 125 | { 126 | throw new InterceptorException(string.Format("Failed to inject to target: {0}", ex.Message), ex); 127 | } 128 | } 129 | 130 | public static void StopInjection() 131 | { 132 | if (_ipcServer == null) 133 | return; 134 | 135 | _ipcServer.StopListening(null); 136 | _ipcServer = null; 137 | } 138 | 139 | public static Process FindRemotePlayProcess() 140 | { 141 | // Find by process name 142 | var processes = Process.GetProcessesByName(TARGET_PROCESS_NAME); 143 | foreach (var process in processes) 144 | { 145 | return process; 146 | } 147 | 148 | return null; 149 | } 150 | 151 | public static void SendStartSignal() 152 | { 153 | var process = FindRemotePlayProcess(); 154 | if (process == null) 155 | return; 156 | 157 | PostMessage(process.MainWindowHandle, 0x8001, 0x00000017, 0x00000008); 158 | } 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /PS4RemotePlayInterceptor/Classes/DualShockState.cs: -------------------------------------------------------------------------------- 1 | // PS4RemotePlayInterceptor (File: Classes/DualShockState.cs) 2 | // 3 | // Copyright (c) 2018 Komefai 4 | // 5 | // Visit http://komefai.com for more information 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | // THE SOFTWARE. 24 | 25 | using System; 26 | using System.Collections.Generic; 27 | using System.IO; 28 | using System.Linq; 29 | using System.Reflection; 30 | using System.Text; 31 | using System.Xml; 32 | using System.Xml.Serialization; 33 | 34 | namespace PS4RemotePlayInterceptor 35 | { 36 | // References 37 | // https://github.com/Jays2Kings/DS4Windows/blob/jay/DS4Windows/DS4Library/DS4Device.cs 38 | // https://github.com/Jays2Kings/DS4Windows/blob/jay/DS4Windows/DS4Library/DS4Sixaxis.cs 39 | // https://github.com/Jays2Kings/DS4Windows/blob/jay/DS4Windows/DS4Library/DS4Touchpad.cs 40 | // http://www.psdevwiki.com/ps4/DS4-USB 41 | 42 | public class Touch 43 | { 44 | public byte TouchID { get; set; } 45 | public bool IsTouched { get; set; } 46 | public int X { get; set; } 47 | public int Y { get; set; } 48 | 49 | /* Constructors */ 50 | public Touch() 51 | { 52 | TouchID = 0; 53 | IsTouched = false; 54 | X = 0; 55 | Y = 0; 56 | } 57 | public Touch(byte touchID, bool isTouched, int x, int y) 58 | { 59 | TouchID = touchID; 60 | IsTouched = isTouched; 61 | X = x; 62 | Y = y; 63 | } 64 | public Touch(Touch touch) 65 | { 66 | TouchID = touch.TouchID; 67 | IsTouched = touch.IsTouched; 68 | X = touch.X; 69 | Y = touch.Y; 70 | } 71 | 72 | public Touch Clone() 73 | { 74 | return new Touch(this); 75 | } 76 | } 77 | 78 | public class DualShockState 79 | { 80 | public const int TOUCHPAD_DATA_OFFSET = 35; 81 | 82 | private static DualShockState _defaultState = new DualShockState(); 83 | private const byte AnalogDeadZoneMin = 0x78; 84 | private const byte AnalogDeadZoneCenter = 0x80; 85 | private const byte AnalogDeadZoneMax = 0x88; 86 | 87 | private enum VK : byte 88 | { 89 | L2 = 1 << 2, 90 | R2 = 1 << 3, 91 | Triangle = 1 << 7, 92 | Circle = 1 << 6, 93 | Cross = 1 << 5, 94 | Square = 1 << 4, 95 | DPad_Up = 1 << 3, 96 | DPad_Down = 1 << 2, 97 | DPad_Left = 1 << 1, 98 | DPad_Right = 1 << 0, 99 | L1 = 1 << 0, 100 | R1 = 1 << 1, 101 | Share = 1 << 4, 102 | Options = 1 << 5, 103 | L3 = 1 << 6, 104 | R3 = 1 << 7, 105 | PS = 1 << 0, 106 | TouchButton = 1 << 2 - 1 107 | } 108 | 109 | public DateTime ReportTimeStamp { get; set; } 110 | public byte LX { get; set; } 111 | public byte LY { get; set; } 112 | public byte RX { get; set; } 113 | public byte RY { get; set; } 114 | public byte L2 { get; set; } 115 | public byte R2 { get; set; } 116 | public bool Triangle { get; set; } 117 | public bool Circle { get; set; } 118 | public bool Cross { get; set; } 119 | public bool Square { get; set; } 120 | public bool DPad_Up { get; set; } 121 | public bool DPad_Down { get; set; } 122 | public bool DPad_Left { get; set; } 123 | public bool DPad_Right { get; set; } 124 | public bool L1 { get; set; } 125 | public bool R1 { get; set; } 126 | public bool Share { get; set; } 127 | public bool Options { get; set; } 128 | public bool L3 { get; set; } 129 | public bool R3 { get; set; } 130 | public bool PS { get; set; } 131 | 132 | public Touch Touch1 { get; set; } 133 | public Touch Touch2 { get; set; } 134 | public bool TouchButton { get; set; } 135 | public byte TouchPacketCounter { get; set; } 136 | 137 | public byte FrameCounter { get; set; } 138 | public byte Battery { get; set; } 139 | public bool IsCharging { get; set; } 140 | 141 | public short AccelX { get; set; } 142 | public short AccelY { get; set; } 143 | public short AccelZ { get; set; } 144 | public short GyroX { get; set; } 145 | public short GyroY { get; set; } 146 | public short GyroZ { get; set; } 147 | 148 | private static byte priorInputReport30 = 0xff; 149 | 150 | /* Constructor */ 151 | public DualShockState() 152 | { 153 | LX = 0x80; 154 | LY = 0x80; 155 | RX = 0x80; 156 | RY = 0x80; 157 | FrameCounter = 255; // null 158 | TouchPacketCounter = 255; // null 159 | } 160 | 161 | public DualShockState(DualShockState state) 162 | { 163 | ReportTimeStamp = state.ReportTimeStamp; 164 | LX = state.LX; 165 | LY = state.LY; 166 | RX = state.RX; 167 | RY = state.RY; 168 | L2 = state.L2; 169 | R2 = state.R2; 170 | Triangle = state.Triangle; 171 | Circle = state.Circle; 172 | Cross = state.Cross; 173 | Square = state.Square; 174 | DPad_Up = state.DPad_Up; 175 | DPad_Down = state.DPad_Down; 176 | DPad_Left = state.DPad_Left; 177 | DPad_Right = state.DPad_Right; 178 | L1 = state.L1; 179 | R3 = state.R3; 180 | Share = state.Share; 181 | Options = state.Options; 182 | R1 = state.R1; 183 | L3 = state.L3; 184 | PS = state.PS; 185 | Touch1 = state.Touch1 == null ? null : state.Touch1.Clone(); 186 | Touch2 = state.Touch2 == null ? null : state.Touch2.Clone(); 187 | TouchButton = state.TouchButton; 188 | TouchPacketCounter = state.TouchPacketCounter; 189 | FrameCounter = state.FrameCounter; 190 | Battery = state.Battery; 191 | IsCharging = state.IsCharging; 192 | AccelX = state.AccelX; 193 | AccelY = state.AccelY; 194 | AccelZ = state.AccelZ; 195 | GyroX = state.GyroX; 196 | GyroY = state.GyroY; 197 | GyroZ = state.GyroZ; 198 | } 199 | 200 | public void CopyTo(DualShockState state) 201 | { 202 | state.ReportTimeStamp = ReportTimeStamp; 203 | state.LX = LX; 204 | state.LY = LY; 205 | state.RX = RX; 206 | state.RY = RY; 207 | state.L2 = L2; 208 | state.R2 = R2; 209 | state.Triangle = Triangle; 210 | state.Circle = Circle; 211 | state.Cross = Cross; 212 | state.Square = Square; 213 | state.DPad_Up = DPad_Up; 214 | state.DPad_Down = DPad_Down; 215 | state.DPad_Left = DPad_Left; 216 | state.DPad_Right = DPad_Right; 217 | state.L1 = L1; 218 | state.R3 = R3; 219 | state.Share = Share; 220 | state.Options = Options; 221 | state.R1 = R1; 222 | state.L3 = L3; 223 | state.PS = PS; 224 | state.Touch1 = Touch1 == null ? null : Touch1.Clone(); 225 | state.Touch2 = Touch2 == null ? null : Touch2.Clone(); 226 | state.TouchButton = TouchButton; 227 | state.TouchPacketCounter = TouchPacketCounter; 228 | state.FrameCounter = FrameCounter; 229 | state.Battery = Battery; 230 | state.IsCharging = IsCharging; 231 | state.AccelX = AccelX; 232 | state.AccelY = AccelY; 233 | state.AccelZ = AccelZ; 234 | state.GyroX = GyroX; 235 | state.GyroY = GyroY; 236 | state.GyroZ = GyroZ; 237 | } 238 | 239 | public DualShockState Clone() 240 | { 241 | return new DualShockState(this); 242 | } 243 | 244 | public static bool IsDefaultState(DualShockState state) 245 | { 246 | return (state.LX == _defaultState.LX || 247 | (state.LX >= AnalogDeadZoneMin && state.LX <= AnalogDeadZoneMax)) && 248 | (state.LY == _defaultState.LY || 249 | (state.LY >= AnalogDeadZoneMin && state.LY <= AnalogDeadZoneMax)) && 250 | (state.RX == _defaultState.RX || 251 | (state.RX >= AnalogDeadZoneMin && state.RX <= AnalogDeadZoneMax)) && 252 | (state.RY == _defaultState.RY || 253 | (state.RY >= AnalogDeadZoneMin && state.RY <= AnalogDeadZoneMax)) && 254 | state.L2 == _defaultState.L2 && 255 | state.R2 == _defaultState.R2 && 256 | state.Triangle == _defaultState.Triangle && 257 | state.Circle == _defaultState.Circle && 258 | state.Cross == _defaultState.Cross && 259 | state.Square == _defaultState.Square && 260 | state.DPad_Up == _defaultState.DPad_Up && 261 | state.DPad_Down == _defaultState.DPad_Down && 262 | state.DPad_Left == _defaultState.DPad_Left && 263 | state.DPad_Right == _defaultState.DPad_Right && 264 | state.L1 == _defaultState.L1 && 265 | state.R1 == _defaultState.R1 && 266 | state.Share == _defaultState.Share && 267 | state.Options == _defaultState.Options && 268 | state.L3 == _defaultState.L3 && 269 | state.R3 == _defaultState.R3 && 270 | state.PS == _defaultState.PS && 271 | (state.Touch1 != null && state.Touch1.IsTouched) && 272 | (state.Touch2 != null && state.Touch2.IsTouched); 273 | } 274 | 275 | public static DualShockState ParseFromDualshockRaw(byte[] data) 276 | { 277 | // Validate data 278 | if (data == null) 279 | return null; 280 | if (data[0] != 0x1) 281 | return null; 282 | 283 | // Create empty state to fill in data 284 | var result = new DualShockState(); 285 | 286 | result.ReportTimeStamp = DateTime.UtcNow; // timestamp with UTC in case system time zone changes 287 | 288 | result.LX = data[1]; 289 | result.LY = data[2]; 290 | result.RX = data[3]; 291 | result.RY = data[4]; 292 | result.L2 = data[8]; 293 | result.R2 = data[9]; 294 | result.Triangle = (data[5] & (byte)VK.Triangle) != 0; 295 | result.Circle = (data[5] & (byte)VK.Circle) != 0; 296 | result.Cross = (data[5] & (byte)VK.Cross) != 0; 297 | result.Square = (data[5] & (byte)VK.Square) != 0; 298 | result.DPad_Up = (data[5] & (byte)VK.DPad_Up) != 0; 299 | result.DPad_Down = (data[5] & (byte)VK.DPad_Down) != 0; 300 | result.DPad_Left = (data[5] & (byte)VK.DPad_Left) != 0; 301 | result.DPad_Right = (data[5] & (byte)VK.DPad_Right) != 0; 302 | 303 | //Convert dpad into individual On/Off bits instead of a clock representation 304 | byte dpadState = 0; 305 | 306 | dpadState = (byte)( 307 | ((result.DPad_Right ? 1 : 0) << 0) | 308 | ((result.DPad_Left ? 1 : 0) << 1) | 309 | ((result.DPad_Down ? 1 : 0) << 2) | 310 | ((result.DPad_Up ? 1 : 0) << 3)); 311 | 312 | switch (dpadState) 313 | { 314 | case 0: result.DPad_Up = true; result.DPad_Down = false; result.DPad_Left = false; result.DPad_Right = false; break; // ↑ 315 | case 1: result.DPad_Up = true; result.DPad_Down = false; result.DPad_Left = false; result.DPad_Right = true; break; // ↑→ 316 | case 2: result.DPad_Up = false; result.DPad_Down = false; result.DPad_Left = false; result.DPad_Right = true; break; // → 317 | case 3: result.DPad_Up = false; result.DPad_Down = true; result.DPad_Left = false; result.DPad_Right = true; break; // ↓→ 318 | case 4: result.DPad_Up = false; result.DPad_Down = true; result.DPad_Left = false; result.DPad_Right = false; break; // ↓ 319 | case 5: result.DPad_Up = false; result.DPad_Down = true; result.DPad_Left = true; result.DPad_Right = false; break; // ↓← 320 | case 6: result.DPad_Up = false; result.DPad_Down = false; result.DPad_Left = true; result.DPad_Right = false; break; // ← 321 | case 7: result.DPad_Up = true; result.DPad_Down = false; result.DPad_Left = true; result.DPad_Right = false; break; // ↑← 322 | case 8: result.DPad_Up = false; result.DPad_Down = false; result.DPad_Left = false; result.DPad_Right = false; break; // - 323 | } 324 | 325 | bool L2Pressed = (data[6] & (byte)VK.L2) != 0; 326 | bool R2Pressed = (data[6] & (byte)VK.R2) != 0; 327 | result.L1 = (data[6] & (byte)VK.L1) != 0; 328 | result.R1 = (data[6] & (byte)VK.R1) != 0; 329 | result.Share = (data[6] & (byte)VK.Share) != 0; 330 | result.Options = (data[6] & (byte)VK.Options) != 0; 331 | result.L3 = (data[6] & (byte)VK.L3) != 0; 332 | result.R3 = (data[6] & (byte)VK.R3) != 0; 333 | result.PS = (data[7] & (byte)VK.PS) != 0; 334 | result.TouchButton = (data[7] & (byte)VK.TouchButton) != 0; 335 | 336 | result.FrameCounter = (byte)(data[7] >> 2); 337 | 338 | // Charging/Battery 339 | try 340 | { 341 | result.IsCharging = (data[30] & 0x10) != 0; 342 | result.Battery = (byte)((data[30] & 0x0F) * 10); 343 | 344 | if (data[30] != priorInputReport30) 345 | priorInputReport30 = data[30]; 346 | } 347 | catch { throw new InterceptorException("Index out of bounds: battery"); } 348 | 349 | // Accelerometer 350 | byte[] accel = new byte[6]; 351 | Array.Copy(data, 14, accel, 0, 6); 352 | result.AccelX = (short)((ushort)(accel[2] << 8) | accel[3]); 353 | result.AccelY = (short)((ushort)(accel[0] << 8) | accel[1]); 354 | result.AccelZ = (short)((ushort)(accel[4] << 8) | accel[5]); 355 | 356 | // Gyro 357 | byte[] gyro = new byte[6]; 358 | Array.Copy(data, 20, gyro, 0, 6); 359 | result.GyroX = (short)((ushort)(gyro[0] << 8) | gyro[1]); 360 | result.GyroY = (short)((ushort)(gyro[2] << 8) | gyro[3]); 361 | result.GyroZ = (short)((ushort)(gyro[4] << 8) | gyro[5]); 362 | 363 | try 364 | { 365 | for (int touches = data[-1 + TOUCHPAD_DATA_OFFSET - 1], touchOffset = 0; touches > 0; touches--, touchOffset += 9) 366 | { 367 | //bool touchLeft = (data[1 + TOUCHPAD_DATA_OFFSET + touchOffset] + ((data[2 + TOUCHPAD_DATA_OFFSET + touchOffset] & 0x0F) * 255) >= 1920 * 2 / 5) ? false : true; 368 | //bool touchRight = (data[1 + TOUCHPAD_DATA_OFFSET + touchOffset] + ((data[2 + TOUCHPAD_DATA_OFFSET + touchOffset] & 0x0F) * 255) < 1920 * 2 / 5) ? false : true; 369 | 370 | byte touchID1 = (byte)(data[0 + TOUCHPAD_DATA_OFFSET + touchOffset] & 0x7F); 371 | byte touchID2 = (byte)(data[4 + TOUCHPAD_DATA_OFFSET + touchOffset] & 0x7F); 372 | bool isTouch1 = (data[0 + TOUCHPAD_DATA_OFFSET + touchOffset] >> 7) != 0 ? false : true; // >= 1 touch detected 373 | bool isTouch2 = (data[4 + TOUCHPAD_DATA_OFFSET + touchOffset] >> 7) != 0 ? false : true; // 2 touches detected 374 | int currentX1 = data[1 + TOUCHPAD_DATA_OFFSET + touchOffset] + ((data[2 + TOUCHPAD_DATA_OFFSET + touchOffset] & 0x0F) * 255); 375 | int currentY1 = ((data[2 + TOUCHPAD_DATA_OFFSET + touchOffset] & 0xF0) >> 4) + (data[3 + TOUCHPAD_DATA_OFFSET + touchOffset] * 16); 376 | int currentX2 = data[5 + TOUCHPAD_DATA_OFFSET + touchOffset] + ((data[6 + TOUCHPAD_DATA_OFFSET + touchOffset] & 0x0F) * 255); 377 | int currentY2 = ((data[6 + TOUCHPAD_DATA_OFFSET + touchOffset] & 0xF0) >> 4) + (data[7 + TOUCHPAD_DATA_OFFSET + touchOffset] * 16); 378 | 379 | result.TouchPacketCounter = data[-1 + TOUCHPAD_DATA_OFFSET + touchOffset]; 380 | result.Touch1 = new Touch(touchID1, isTouch1, currentX1, currentY1); 381 | result.Touch2 = new Touch(touchID2, isTouch2, currentX2, currentY2); 382 | } 383 | } 384 | catch { throw new InterceptorException("Index out of bounds: touchpad"); } 385 | 386 | return result; 387 | } 388 | 389 | public void ConvertToDualshockRaw(ref byte[] data) 390 | { 391 | data[1] = LX; 392 | data[2] = LY; 393 | data[3] = RX; 394 | data[4] = RY; 395 | data[8] = L2; 396 | data[9] = R2; 397 | 398 | byte data_5 = 0; 399 | if (Triangle) { data_5 += (byte)VK.Triangle; } 400 | if (Circle) { data_5 += (byte)VK.Circle; } 401 | if (Cross) { data_5 += (byte)VK.Cross; } 402 | if (Square) { data_5 += (byte)VK.Square; } 403 | if (DPad_Up && !DPad_Down && !DPad_Left && !DPad_Right) { data_5 += 0; } // ↑ 404 | if (DPad_Up && !DPad_Down && !DPad_Left && DPad_Right) { data_5 += 1; } // ↑→ 405 | if (!DPad_Up && !DPad_Down && !DPad_Left && DPad_Right) { data_5 += 2; } // → 406 | if (!DPad_Up && DPad_Down && !DPad_Left && DPad_Right) { data_5 += 3; } // ↓→ 407 | if (!DPad_Up && DPad_Down && !DPad_Left && !DPad_Right) { data_5 += 4; } // ↓ 408 | if (!DPad_Up && DPad_Down && DPad_Left && !DPad_Right) { data_5 += 5; } // ↓← 409 | if (!DPad_Up && !DPad_Down && DPad_Left && !DPad_Right) { data_5 += 6; } // ← 410 | if (DPad_Up && !DPad_Down && DPad_Left && !DPad_Right) { data_5 += 7; } // ↑← 411 | if (!DPad_Up && !DPad_Down && !DPad_Left && !DPad_Right) { data_5 += 8; } // - 412 | data[5] = data_5; 413 | 414 | byte data_6 = 0; 415 | if (L2 > 0) { data_6 += (byte)VK.L2; } 416 | if (R2 > 0) { data_6 += (byte)VK.R2; } 417 | if (L1) { data_6 += (byte)VK.L1; } 418 | if (R1) { data_6 += (byte)VK.R1; } 419 | if (Share) { data_6 += (byte)VK.Share; } 420 | if (Options) { data_6 += (byte)VK.Options; } 421 | if (L3) { data_6 += (byte)VK.L3; } 422 | if (R3) { data_6 += (byte)VK.R3; } 423 | data[6] = data_6; 424 | 425 | byte data_7 = 0; 426 | if (PS) { data_7 += (byte)VK.PS; } 427 | if (TouchButton) { data_7 += (byte)VK.TouchButton; } 428 | byte currentFrameCounter = (byte)(data[7] >> 2); 429 | data_7 += (byte)(currentFrameCounter << 2); 430 | data[7] = data_7; 431 | 432 | // Accelerometer 433 | data[14] = (byte)(AccelY >> 8); 434 | data[15] = (byte)(AccelY & 0xFF); 435 | 436 | data[16] = (byte)(AccelX >> 8); 437 | data[17] = (byte)(AccelX & 0xFF); 438 | 439 | data[18] = (byte)(AccelZ >> 8); 440 | data[19] = (byte)(AccelZ & 0xFF); 441 | 442 | // Gyro 443 | data[20] = (byte)(GyroX >> 8); 444 | data[21] = (byte)(GyroX & 0xFF); 445 | 446 | data[22] = (byte)(GyroY >> 8); 447 | data[23] = (byte)(GyroY & 0xFF); 448 | 449 | data[24] = (byte)(GyroZ >> 8); 450 | data[26] = (byte)(GyroZ & 0xFF); 451 | 452 | // Charging/Battery 453 | byte data_30 = 0; 454 | if (IsCharging) data_30 += 0x10; 455 | if (Battery > 0) data_30 += (byte)(Battery / 10); 456 | data[30] = data_30; 457 | 458 | // Touches 459 | try 460 | { 461 | for (int touches = data[-1 + TOUCHPAD_DATA_OFFSET - 1], touchOffset = 0; touches > 0; touches--, touchOffset += 9) 462 | { 463 | // Ignore 464 | // data[-1 + TOUCHPAD_DATA_OFFSET + touchOffset] = TouchPacketCounter; 465 | 466 | // >= 1 Finger 467 | if (Touch1 != null) 468 | { 469 | byte oldTouchID = (byte)(data[0 + TOUCHPAD_DATA_OFFSET + touchOffset] & 0x7F); 470 | if (Touch1.IsTouched) 471 | data[0 + TOUCHPAD_DATA_OFFSET + touchOffset] = oldTouchID; 472 | 473 | var x = Touch1.X; 474 | var xRemain = (int)(x / 255); 475 | var xLeft = x - (xRemain * 255); 476 | var y = Touch1.Y; 477 | var yRemain = (int)(y / 16); 478 | var yLeft = y - (yRemain * 16); 479 | 480 | data[1 + TOUCHPAD_DATA_OFFSET + touchOffset] = (byte)xLeft; 481 | data[2 + TOUCHPAD_DATA_OFFSET + touchOffset] = (byte)(xRemain + (yLeft << 4)); 482 | data[3 + TOUCHPAD_DATA_OFFSET + touchOffset] = (byte)yRemain; 483 | } 484 | 485 | // 2 Fingers 486 | if (Touch2 != null) 487 | { 488 | byte oldTouchID = (byte)(data[4 + TOUCHPAD_DATA_OFFSET + touchOffset] & 0x7F); 489 | if (Touch2.IsTouched) 490 | data[4 + TOUCHPAD_DATA_OFFSET + touchOffset] = oldTouchID; 491 | 492 | var x = Touch2.X; 493 | var xRemain = (int)(x / 255); 494 | var xLeft = x - (xRemain * 255); 495 | var y = Touch2.Y; 496 | var yRemain = (int)(y / 16); 497 | var yLeft = y - (yRemain * 16); 498 | 499 | data[5 + TOUCHPAD_DATA_OFFSET + touchOffset] = (byte)xLeft; 500 | data[6 + TOUCHPAD_DATA_OFFSET + touchOffset] = (byte)(xRemain + (yLeft << 4)); 501 | data[7 + TOUCHPAD_DATA_OFFSET + touchOffset] = (byte)yRemain; 502 | } 503 | } 504 | } 505 | catch { } 506 | } 507 | 508 | /// 509 | /// Serialize a list of DualShockState to xml file 510 | /// 511 | public static void Serialize(string path, List list) 512 | { 513 | XmlSerializer serializer = new XmlSerializer(typeof(List)); 514 | using (TextWriter writer = new StreamWriter(path)) 515 | { 516 | serializer.Serialize(writer, list); 517 | } 518 | } 519 | 520 | /// 521 | /// Deserialize a list of DualShockState from xml file 522 | /// 523 | public static List Deserialize(string path) 524 | { 525 | XmlSerializer deserializer = new XmlSerializer(typeof(List)); 526 | using (TextReader reader = new StreamReader(path)) 527 | { 528 | object obj = deserializer.Deserialize(reader); 529 | List list = obj as List; 530 | return list; 531 | } 532 | } 533 | 534 | /// 535 | /// Serialize a list of DualShockState to xml file and remove unchanged properties for smaller file size 536 | /// 537 | public static void SerializeCompressed(string path, List list, List excludeProperties = null) 538 | { 539 | string containerTypeName = "ArrayOfDualShockState"; 540 | Type type = typeof(DualShockState); 541 | 542 | StringBuilder sb = new StringBuilder(); 543 | 544 | // Header 545 | sb.Append($"<{containerTypeName}>"); 546 | 547 | // Contents 548 | foreach (var s in list) 549 | { 550 | if (excludeProperties == null) excludeProperties = new List(); 551 | 552 | XmlDocument doc = new XmlDocument(); 553 | XmlNode typeNode = doc.CreateNode(XmlNodeType.Element, type.Name, string.Empty); 554 | doc.AppendChild(typeNode); 555 | 556 | // Go through properties 557 | foreach (PropertyInfo info in type.GetProperties(BindingFlags.Instance | BindingFlags.Public)) 558 | { 559 | if (!info.CanRead || !info.CanWrite) continue; 560 | if (excludeProperties.Contains(info.Name)) continue; 561 | 562 | object templateValue = info.GetValue(_defaultState, null); 563 | object changedValue = info.GetValue(s, null); 564 | 565 | if (changedValue == null) continue; 566 | 567 | XmlElement propertyElem = doc.CreateElement(info.Name); 568 | 569 | // Touch 570 | if (changedValue is Touch) 571 | { 572 | Touch t = changedValue as Touch; 573 | if (t.IsTouched) 574 | { 575 | foreach (PropertyInfo t_info in typeof(Touch).GetProperties(BindingFlags.Instance | BindingFlags.Public)) 576 | { 577 | if (!t_info.CanRead || !t_info.CanWrite) continue; 578 | 579 | object t_value = t_info.GetValue(t, null); 580 | 581 | XmlElement t_elem = doc.CreateElement(t_info.Name); 582 | t_elem.InnerText = t_value.ToString().ToLowerInvariant(); 583 | propertyElem.AppendChild(t_elem); 584 | } 585 | 586 | // Append property node 587 | typeNode.AppendChild(propertyElem); 588 | } 589 | } 590 | // Other properties 591 | else 592 | { 593 | if (templateValue == null || templateValue.Equals(changedValue)) continue; 594 | 595 | // Bool 596 | if (changedValue is bool) 597 | { 598 | propertyElem.InnerText = changedValue.ToString().ToLowerInvariant(); 599 | } 600 | // DateTime 601 | else if (changedValue is DateTime) 602 | { 603 | propertyElem.InnerText = ((DateTime)changedValue).ToString("o"); 604 | } 605 | // Object 606 | else 607 | { 608 | propertyElem.InnerText = changedValue.ToString(); 609 | } 610 | 611 | // Append property node 612 | typeNode.AppendChild(propertyElem); 613 | } 614 | } 615 | 616 | using (StringWriter sw = new StringWriter()) 617 | { 618 | doc.WriteContentTo(new XmlTextWriter(sw)); 619 | sb.Append(sw.ToString()); 620 | } 621 | } 622 | 623 | // Footer 624 | sb.Append($""); 625 | 626 | // Write to file 627 | File.WriteAllText(path, sb.ToString()); 628 | } 629 | } 630 | } 631 | -------------------------------------------------------------------------------- /PS4RemotePlayInterceptor/Classes/Hooks.cs: -------------------------------------------------------------------------------- 1 | // PS4RemotePlayInterceptor (File: Classes/Hooks.cs) 2 | // 3 | // Copyright (c) 2018 Komefai 4 | // 5 | // Visit http://komefai.com for more information 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | // THE SOFTWARE. 24 | 25 | using System; 26 | using System.Collections.Generic; 27 | using System.Runtime.InteropServices; 28 | using System.Text; 29 | using EasyHook; 30 | 31 | namespace PS4RemotePlayInterceptor 32 | { 33 | /// 34 | /// EasyHook will look for a class implementing during injection. This 35 | /// becomes the entry point within the target process after injection is complete. 36 | /// 37 | public class Hooks : EasyHook.IEntryPoint 38 | { 39 | /// 40 | /// Reference to the server interface 41 | /// 42 | private readonly InjectionInterface _server = null; 43 | 44 | /// 45 | /// Dummy handle used for controller emulation 46 | /// 47 | private readonly IntPtr _dummyHandle = new IntPtr(0xDABDAB); 48 | 49 | private static byte[] ToManagedArray(IntPtr pointer, int size) 50 | { 51 | byte[] managedArray = new byte[size]; 52 | Marshal.Copy(pointer, managedArray, 0, size); 53 | return managedArray; 54 | } 55 | 56 | private static void RestoreUnmanagedArray(IntPtr pointer, int size, byte[] managedArray) 57 | { 58 | unsafe 59 | { 60 | byte* ptr = (byte*)pointer.ToPointer(); 61 | for (var i = 0; i < size; i++) 62 | { 63 | ptr[i] = managedArray[i]; 64 | } 65 | } 66 | } 67 | 68 | #region Setup 69 | /// 70 | /// EasyHook requires a constructor that matches and any additional parameters as provided 71 | /// in the original call to . 72 | /// 73 | /// Multiple constructors can exist on the same , providing that each one has a corresponding Run method (e.g. ). 74 | /// 75 | /// The RemoteHooking context 76 | /// The name of the IPC channel 77 | public Hooks( 78 | EasyHook.RemoteHooking.IContext context, 79 | string channelName) 80 | { 81 | // Connect to server object using provided channel name 82 | _server = EasyHook.RemoteHooking.IpcConnectClient(channelName); 83 | 84 | // If Ping fails then the Run method will be not be called 85 | _server.Ping(); 86 | } 87 | 88 | /// 89 | /// The main entry point for our logic once injected within the target process. 90 | /// This is where the hooks will be created, and a loop will be entered until host process exits. 91 | /// EasyHook requires a matching Run method for the constructor 92 | /// 93 | /// The RemoteHooking context 94 | /// The name of the IPC channel 95 | public void Run( 96 | EasyHook.RemoteHooking.IContext context, 97 | string channelName) 98 | { 99 | // Injection is now complete and the server interface is connected 100 | _server.OnInjectionSuccess(EasyHook.RemoteHooking.GetCurrentProcessId()); 101 | 102 | // Install hooks 103 | List hooks = new List(); 104 | 105 | // With controller emulation 106 | if (_server.ShouldEmulateController()) 107 | { 108 | // CreateFile https://msdn.microsoft.com/en-us/library/windows/desktop/aa363858(v=vs.85).aspx 109 | var createFileHook = EasyHook.LocalHook.Create( 110 | EasyHook.LocalHook.GetProcAddress("kernel32.dll", "CreateFileW"), 111 | new CreateFile_Delegate(CreateFile_Hook), 112 | this); 113 | 114 | // ReadFile https://msdn.microsoft.com/en-us/library/windows/desktop/aa365467(v=vs.85).aspx 115 | var readFileHook = EasyHook.LocalHook.Create( 116 | EasyHook.LocalHook.GetProcAddress("kernel32.dll", "ReadFile"), 117 | new ReadFile_Delegate(ReadFile_Hook), 118 | this); 119 | 120 | // WriteFile https://msdn.microsoft.com/en-us/library/windows/desktop/aa365747(v=vs.85).aspx 121 | var writeFileHook = EasyHook.LocalHook.Create( 122 | EasyHook.LocalHook.GetProcAddress("kernel32.dll", "WriteFile"), 123 | new WriteFile_Delegate(WriteFile_Hook), 124 | this); 125 | 126 | // HidD_GetAttributes http://www.pinvoke.net/default.aspx/hid.hidd_getattributes 127 | var HidD_GetAttributesHook = EasyHook.LocalHook.Create( 128 | EasyHook.LocalHook.GetProcAddress("hid.dll", "HidD_GetAttributes"), 129 | new HidD_GetAttributes_Delegate(HidD_GetAttributes_Hook), 130 | this); 131 | 132 | // HidD_GetFeature http://www.pinvoke.net/default.aspx/hid.hidd_getfeature 133 | var HidD_GetFeatureHook = EasyHook.LocalHook.Create( 134 | EasyHook.LocalHook.GetProcAddress("hid.dll", "HidD_GetFeature"), 135 | new HidD_GetFeature_Delegate(HidD_GetFeature_Hook), 136 | this); 137 | 138 | // HidD_SetFeature https://msdn.microsoft.com/en-us/library/windows/hardware/ff539684(v=vs.85).aspx 139 | var HidD_SetFeatureHook = EasyHook.LocalHook.Create( 140 | EasyHook.LocalHook.GetProcAddress("hid.dll", "HidD_SetFeature"), 141 | new HidD_SetFeature_Delegate(HidD_SetFeature_Hook), 142 | this); 143 | 144 | // HidD_GetPreparsedData http://www.pinvoke.net/default.aspx/hid.hidd_getfeature 145 | var HidD_GetPreparsedDataHook = EasyHook.LocalHook.Create( 146 | EasyHook.LocalHook.GetProcAddress("hid.dll", "HidD_GetPreparsedData"), 147 | new HidD_GetPreparsedData_Delegate(HidD_GetPreparsedData_Hook), 148 | this); 149 | 150 | // HidD_FreePreparsedData http://www.pinvoke.net/default.aspx/hid.hidd_freepreparseddata 151 | var HidD_FreePreparsedDataHook = EasyHook.LocalHook.Create( 152 | EasyHook.LocalHook.GetProcAddress("hid.dll", "HidD_FreePreparsedData"), 153 | new HidD_FreePreparsedData_Delegate(HidD_FreePreparsedData_Hook), 154 | this); 155 | 156 | // HidD_GetManufacturerString https://msdn.microsoft.com/en-us/library/windows/hardware/ff538959(v=vs.85).aspx 157 | var HidD_GetManufacturerStringHook = EasyHook.LocalHook.Create( 158 | EasyHook.LocalHook.GetProcAddress("hid.dll", "HidD_GetManufacturerString"), 159 | new HidD_GetManufacturerString_Delegate(HidD_GetManufacturerString_Hook), 160 | this); 161 | 162 | // HidD_GetProductString https://msdn.microsoft.com/en-us/library/windows/hardware/ff539681(v=vs.85).aspx 163 | var HidD_GetProductStringHook = EasyHook.LocalHook.Create( 164 | EasyHook.LocalHook.GetProcAddress("hid.dll", "HidD_GetProductString"), 165 | new HidD_GetProductString_Delegate(HidD_GetProductString_Hook), 166 | this); 167 | 168 | // HidD_GetSerialNumberString https://msdn.microsoft.com/en-us/library/windows/hardware/ff539683(v=vs.85).aspx 169 | var HidD_GetSerialNumberStringHook = EasyHook.LocalHook.Create( 170 | EasyHook.LocalHook.GetProcAddress("hid.dll", "HidD_GetSerialNumberString"), 171 | new HidD_GetSerialNumberString_Delegate(HidD_GetSerialNumberString_Hook), 172 | this); 173 | 174 | // HidP_GetCapsHook http://www.pinvoke.net/default.aspx/hid.hidp_getcaps 175 | var HidP_GetCapsHook = EasyHook.LocalHook.Create( 176 | EasyHook.LocalHook.GetProcAddress("hid.dll", "HidP_GetCaps"), 177 | new HidP_GetCaps_Delegate(HidP_GetCaps_Hook), 178 | this); 179 | 180 | // HidP_GetValueCapsHook https://msdn.microsoft.com/en-us/library/windows/hardware/ff539754(v=vs.85).aspx 181 | var HidP_GetValueCapsHook = EasyHook.LocalHook.Create( 182 | EasyHook.LocalHook.GetProcAddress("hid.dll", "HidP_GetValueCaps"), 183 | new HidP_GetValueCaps_Delegate(HidP_GetValueCaps_Hook), 184 | this); 185 | 186 | hooks.Add(createFileHook); 187 | hooks.Add(readFileHook); 188 | hooks.Add(writeFileHook); 189 | hooks.Add(HidD_GetAttributesHook); 190 | hooks.Add(HidD_GetFeatureHook); 191 | hooks.Add(HidD_SetFeatureHook); 192 | hooks.Add(HidD_GetPreparsedDataHook); 193 | hooks.Add(HidD_FreePreparsedDataHook); 194 | hooks.Add(HidD_GetManufacturerStringHook); 195 | hooks.Add(HidD_GetProductStringHook); 196 | hooks.Add(HidD_GetSerialNumberStringHook); 197 | hooks.Add(HidP_GetCapsHook); 198 | hooks.Add(HidP_GetValueCapsHook); 199 | } 200 | // Without controller emulation 201 | else 202 | { 203 | // ReadFile https://msdn.microsoft.com/en-us/library/windows/desktop/aa365467(v=vs.85).aspx 204 | var readFileHook = EasyHook.LocalHook.Create( 205 | EasyHook.LocalHook.GetProcAddress("kernel32.dll", "ReadFile"), 206 | new ReadFile_Delegate(ReadFile_Hook), 207 | this); 208 | 209 | hooks.Add(readFileHook); 210 | } 211 | 212 | // Activate hooks on all threads except the current thread 213 | foreach (var h in hooks) 214 | { 215 | h.ThreadACL.SetExclusiveACL(new Int32[] { 0 }); 216 | } 217 | 218 | // Wake up the process (required if using RemoteHooking.CreateAndInject) 219 | EasyHook.RemoteHooking.WakeUpProcess(); 220 | 221 | try 222 | { 223 | // Loop until injector closes (i.e. IPC fails) 224 | while (true) 225 | { 226 | System.Threading.Thread.Sleep(100); 227 | _server.Ping(); 228 | } 229 | } 230 | catch 231 | { 232 | // Ping() will raise an exception if host is unreachable 233 | } 234 | 235 | // Remove hooks 236 | foreach (var h in hooks) 237 | { 238 | h.Dispose(); 239 | } 240 | 241 | // Finalise cleanup of hooks 242 | EasyHook.LocalHook.Release(); 243 | } 244 | 245 | /// 246 | /// P/Invoke to determine the filename from a file handle 247 | /// https://msdn.microsoft.com/en-us/library/windows/desktop/aa364962(v=vs.85).aspx 248 | /// 249 | /// 250 | /// 251 | /// 252 | /// 253 | /// 254 | [DllImport("Kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)] 255 | static extern uint GetFinalPathNameByHandle(IntPtr hFile, [MarshalAs(UnmanagedType.LPTStr)] StringBuilder lpszFilePath, uint cchFilePath, uint dwFlags); 256 | 257 | #endregion 258 | 259 | #region CreateFileW Hook 260 | /// 261 | /// The CreateFile delegate, this is needed to create a delegate of our hook function . 262 | /// 263 | /// 264 | /// 265 | /// 266 | /// 267 | /// 268 | /// 269 | /// 270 | /// 271 | [UnmanagedFunctionPointer(CallingConvention.StdCall, 272 | CharSet = CharSet.Unicode, 273 | SetLastError = true)] 274 | delegate IntPtr CreateFile_Delegate( 275 | String filename, 276 | UInt32 desiredAccess, 277 | UInt32 shareMode, 278 | IntPtr securityAttributes, 279 | UInt32 creationDisposition, 280 | UInt32 flagsAndAttributes, 281 | IntPtr templateFile); 282 | 283 | /// 284 | /// Using P/Invoke to call original method. 285 | /// https://msdn.microsoft.com/en-us/library/windows/desktop/aa363858(v=vs.85).aspx 286 | /// 287 | /// 288 | /// 289 | /// 290 | /// 291 | /// 292 | /// 293 | /// 294 | /// 295 | [DllImport("kernel32.dll", 296 | CharSet = CharSet.Unicode, 297 | SetLastError = true, CallingConvention = CallingConvention.StdCall)] 298 | static extern IntPtr CreateFileW( 299 | String filename, 300 | UInt32 desiredAccess, 301 | UInt32 shareMode, 302 | IntPtr securityAttributes, 303 | UInt32 creationDisposition, 304 | UInt32 flagsAndAttributes, 305 | IntPtr templateFile); 306 | 307 | /// 308 | /// The CreateFile hook function. This will be called instead of the original CreateFile once hooked. 309 | /// 310 | /// 311 | /// 312 | /// 313 | /// 314 | /// 315 | /// 316 | /// 317 | /// 318 | IntPtr CreateFile_Hook( 319 | String filename, 320 | UInt32 desiredAccess, 321 | UInt32 shareMode, 322 | IntPtr securityAttributes, 323 | UInt32 creationDisposition, 324 | UInt32 flagsAndAttributes, 325 | IntPtr templateFile) 326 | { 327 | // SPOOF 328 | if (filename != null && filename.StartsWith(@"\\?\hid#")) 329 | { 330 | return _dummyHandle; 331 | } 332 | 333 | IntPtr result = CreateFileW( 334 | filename, 335 | desiredAccess, 336 | shareMode, 337 | securityAttributes, 338 | creationDisposition, 339 | flagsAndAttributes, 340 | templateFile 341 | ); 342 | 343 | try 344 | { 345 | string mode = string.Empty; 346 | switch (creationDisposition) 347 | { 348 | case 1: 349 | mode = "CREATE_NEW"; 350 | break; 351 | case 2: 352 | mode = "CREATE_ALWAYS"; 353 | break; 354 | case 3: 355 | mode = "OPEN_ALWAYS"; 356 | break; 357 | case 4: 358 | mode = "OPEN_EXISTING"; 359 | break; 360 | case 5: 361 | mode = "TRUNCATE_EXISTING"; 362 | break; 363 | } 364 | 365 | // Send to server 366 | _server.OnCreateFile(filename.ToString(), result.ToString()); 367 | } 368 | catch 369 | { 370 | // swallow exceptions so that any issues caused by this code do not crash target process 371 | } 372 | 373 | // now call the original API... 374 | return result; 375 | } 376 | #endregion 377 | 378 | #region ReadFile Hook 379 | 380 | // FrameCounter 381 | private static int __frameCounter = -1; 382 | 383 | /// 384 | /// The ReadFile delegate, this is needed to create a delegate of our hook function . 385 | /// 386 | /// 387 | /// 388 | /// 389 | /// 390 | /// 391 | /// 392 | [UnmanagedFunctionPointer(CallingConvention.StdCall, SetLastError = true)] 393 | delegate bool ReadFile_Delegate( 394 | IntPtr hFile, 395 | IntPtr lpBuffer, 396 | uint nNumberOfBytesToRead, 397 | out uint lpNumberOfBytesRead, 398 | IntPtr lpOverlapped); 399 | 400 | /// 401 | /// Using P/Invoke to call the orginal function 402 | /// 403 | /// 404 | /// 405 | /// 406 | /// 407 | /// 408 | /// 409 | [DllImport("kernel32.dll", SetLastError = true, CallingConvention = CallingConvention.StdCall)] 410 | static extern bool ReadFile( 411 | IntPtr hFile, 412 | IntPtr lpBuffer, 413 | uint nNumberOfBytesToRead, 414 | out uint lpNumberOfBytesRead, 415 | IntPtr lpOverlapped); 416 | 417 | /// 418 | /// The ReadFile hook function. This will be called instead of the original ReadFile once hooked. 419 | /// 420 | /// 421 | /// 422 | /// 423 | /// 424 | /// 425 | /// 426 | bool ReadFile_Hook( 427 | IntPtr hFile, 428 | IntPtr lpBuffer, 429 | uint nNumberOfBytesToRead, 430 | out uint lpNumberOfBytesRead, 431 | IntPtr lpOverlapped) 432 | { 433 | bool result = false; 434 | lpNumberOfBytesRead = 0; 435 | 436 | const int bufferSize = 64; 437 | 438 | try 439 | { 440 | if (_server.ShouldEmulateController()) 441 | { 442 | // SPOOF 443 | result = true; 444 | try 445 | { 446 | // Call original for any other files 447 | if (hFile != _dummyHandle) 448 | { 449 | result = ReadFile(hFile, lpBuffer, nNumberOfBytesToRead, out lpNumberOfBytesRead, lpOverlapped); 450 | } 451 | 452 | // Retrieve filename from the file handle 453 | StringBuilder filename = new StringBuilder(255); 454 | GetFinalPathNameByHandle(hFile, filename, 255, 0); 455 | 456 | if (hFile == _dummyHandle && nNumberOfBytesToRead == 2048 && lpNumberOfBytesRead == 0) 457 | { 458 | lpNumberOfBytesRead = bufferSize; 459 | 460 | // Create fake report buffer 461 | byte[] fakeReport = fakeReport = new byte[bufferSize] 462 | { 463 | 1, 128, 126, 125, 125, 8, 0, 0, 0, 0, 151, 201, 14, 7, 0, 5, 0, 255, 255, 179, 7, 74, 31, 464 | 113, 255, 0, 0, 0, 0, 0, 27, 0, 0, 0, 0, 128, 0, 0, 0, 128, 0, 0, 0, 0, 128, 0, 0, 0, 128, 0, 465 | 0, 0, 0, 128, 0, 0, 0, 128, 0, 0, 0, 0, 128, 0 466 | }; 467 | 468 | // Assign the spoofed frame counter 469 | __frameCounter++; 470 | fakeReport[7] = (byte)((__frameCounter << 2) & 0xFF); 471 | 472 | // Send to server 473 | _server.OnReadFile(filename.ToString(), ref fakeReport); 474 | 475 | // Restore managedArray back to unmanaged array 476 | RestoreUnmanagedArray(lpBuffer, fakeReport.Length, fakeReport); 477 | 478 | return result; 479 | } 480 | } 481 | catch 482 | { 483 | // swallow exceptions so that any issues caused by this code do not crash target process 484 | } 485 | } 486 | else 487 | { 488 | // Call original first so we have a value for lpNumberOfBytesRead 489 | result = ReadFile(hFile, lpBuffer, nNumberOfBytesToRead, out lpNumberOfBytesRead, lpOverlapped); 490 | 491 | try 492 | { 493 | // Retrieve filename from the file handle 494 | StringBuilder filename = new StringBuilder(255); 495 | GetFinalPathNameByHandle(hFile, filename, 255, 0); 496 | 497 | //// Log for debug 498 | //_server.ReportLog( 499 | // string.Format("[{0}:{1}]: READ ({2} bytes) \"{3}\"", 500 | // EasyHook.RemoteHooking.GetCurrentProcessId(), EasyHook.RemoteHooking.GetCurrentThreadId() 501 | // , lpNumberOfBytesRead, filename)); 502 | 503 | // Only respond if it is a device stream 504 | if (string.IsNullOrWhiteSpace(filename.ToString()) && lpNumberOfBytesRead == bufferSize) 505 | { 506 | // Copy unmanaged array for server 507 | byte[] managedArray = ToManagedArray(lpBuffer, bufferSize); 508 | 509 | // Make sure it is a input report (USB type) 510 | if (managedArray[0] == 0x1) 511 | { 512 | // Send to server 513 | _server.OnReadFile(filename.ToString(), ref managedArray); 514 | 515 | // Restore managedArray back to unmanaged array 516 | RestoreUnmanagedArray(lpBuffer, bufferSize, managedArray); 517 | } 518 | } 519 | } 520 | catch 521 | { 522 | // swallow exceptions so that any issues caused by this code do not crash target process 523 | } 524 | } 525 | } 526 | catch 527 | { 528 | // swallow exceptions so that any issues caused by this code do not crash target process 529 | } 530 | 531 | return result; 532 | } 533 | #endregion 534 | 535 | #region WriteFile Hook 536 | 537 | /// 538 | /// The WriteFile delegate, this is needed to create a delegate of our hook function . 539 | /// 540 | /// 541 | /// 542 | /// 543 | /// 544 | /// 545 | /// 546 | [UnmanagedFunctionPointer(CallingConvention.StdCall, CharSet = CharSet.Unicode, SetLastError = true)] 547 | [return: MarshalAs(UnmanagedType.Bool)] 548 | delegate bool WriteFile_Delegate( 549 | IntPtr hFile, 550 | IntPtr lpBuffer, 551 | uint nNumberOfBytesToWrite, 552 | out uint lpNumberOfBytesWritten, 553 | IntPtr lpOverlapped); 554 | 555 | /// 556 | /// Using P/Invoke to call original WriteFile method 557 | /// 558 | /// 559 | /// 560 | /// 561 | /// 562 | /// 563 | /// 564 | [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true, CallingConvention = CallingConvention.StdCall)] 565 | [return: MarshalAs(UnmanagedType.Bool)] 566 | static extern bool WriteFile( 567 | IntPtr hFile, 568 | IntPtr lpBuffer, 569 | uint nNumberOfBytesToWrite, 570 | out uint lpNumberOfBytesWritten, 571 | IntPtr lpOverlapped); 572 | 573 | /// 574 | /// The WriteFile hook function. This will be called instead of the original WriteFile once hooked. 575 | /// 576 | /// 577 | /// 578 | /// 579 | /// 580 | /// 581 | /// 582 | bool WriteFile_Hook( 583 | IntPtr hFile, 584 | IntPtr lpBuffer, 585 | uint nNumberOfBytesToWrite, 586 | out uint lpNumberOfBytesWritten, 587 | IntPtr lpOverlapped) 588 | { 589 | bool result = false; 590 | 591 | // Call original first so we get lpNumberOfBytesWritten 592 | result = WriteFile(hFile, lpBuffer, nNumberOfBytesToWrite, out lpNumberOfBytesWritten, lpOverlapped); 593 | 594 | try 595 | { 596 | // Retrieve filename from the file handle 597 | StringBuilder filename = new StringBuilder(255); 598 | GetFinalPathNameByHandle(hFile, filename, 255, 0); 599 | } 600 | catch 601 | { 602 | // swallow exceptions so that any issues caused by this code do not crash target process 603 | } 604 | 605 | return result; 606 | } 607 | 608 | #endregion 609 | 610 | #region HidD_GetAttributes Hook 611 | [StructLayout(LayoutKind.Sequential)] 612 | internal struct HIDD_ATTRIBUTES 613 | { 614 | public Int32 Size; 615 | public Int16 VendorID; 616 | public Int16 ProductID; 617 | public Int16 VersionNumber; 618 | } 619 | 620 | [UnmanagedFunctionPointer(CallingConvention.StdCall, CharSet = CharSet.Unicode, SetLastError = true)] 621 | delegate bool HidD_GetAttributes_Delegate(IntPtr hidDeviceObject, ref HIDD_ATTRIBUTES attributes); 622 | 623 | [DllImport("hid.dll", CharSet = CharSet.Unicode, SetLastError = true)] 624 | static extern bool HidD_GetAttributes(IntPtr hidDeviceObject, ref HIDD_ATTRIBUTES attributes); 625 | 626 | bool HidD_GetAttributes_Hook(IntPtr hidDeviceObject, ref HIDD_ATTRIBUTES attributes) 627 | { 628 | bool result = false; 629 | 630 | try 631 | { 632 | if (_server.ShouldEmulateController()) 633 | { 634 | // SPOOF 635 | result = true; 636 | attributes.Size = 12; 637 | attributes.VendorID = 1356; 638 | attributes.ProductID = 2508; 639 | attributes.VersionNumber = 256; 640 | } 641 | else 642 | { 643 | // Call original first so we get the result 644 | result = HidD_GetAttributes(hidDeviceObject, ref attributes); 645 | } 646 | } 647 | catch 648 | { 649 | // swallow exceptions so that any issues caused by this code do not crash target process 650 | } 651 | 652 | return result; 653 | } 654 | #endregion 655 | 656 | #region HidD_GetFeature Hook 657 | [UnmanagedFunctionPointer(CallingConvention.StdCall, CharSet = CharSet.Unicode, SetLastError = true)] 658 | delegate bool HidD_GetFeature_Delegate(IntPtr hidDeviceObject, ref Byte lpReportBuffer, Int32 reportBufferLength); 659 | 660 | [DllImport("hid.dll", CharSet = CharSet.Unicode, SetLastError = true)] 661 | static extern bool HidD_GetFeature(IntPtr hidDeviceObject, ref Byte lpReportBuffer, Int32 reportBufferLength); 662 | 663 | bool HidD_GetFeature_Hook(IntPtr hidDeviceObject, ref Byte lpReportBuffer, Int32 reportBufferLength) 664 | { 665 | bool result = false; 666 | 667 | try 668 | { 669 | if (_server.ShouldEmulateController()) 670 | { 671 | // SPOOF 672 | result = true; 673 | 674 | // Copy unmanaged array for server 675 | unsafe 676 | { 677 | fixed (byte* p = &lpReportBuffer) 678 | { 679 | IntPtr ptr = (IntPtr)p; 680 | 681 | if (lpReportBuffer == 0x12) 682 | { 683 | byte[] report = 684 | { 685 | 18, 198, 3, 170, 95, 27, 64, 8, 37, 0, 172, 252, 74, 74, 71, 168 686 | }; 687 | RestoreUnmanagedArray(ptr, report.Length, report); 688 | } 689 | else if (lpReportBuffer == 0xA3) 690 | { 691 | byte[] report = 692 | { 693 | 163, 77, 97, 121, 32, 49, 55, 32, 50, 48, 49, 54, 0, 0, 0, 0, 0, 48, 54, 58, 694 | 51, 54, 58, 50, 54, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 4, 100, 1, 0, 0, 0, 8, 112, 0, 2, 0, 695 | 128, 3, 0 696 | }; 697 | RestoreUnmanagedArray(ptr, report.Length, report); 698 | } 699 | else if (lpReportBuffer == 0x02) 700 | { 701 | byte[] report = 702 | { 703 | 2, 6, 0, 3, 0, 252, 255, 133, 34, 133, 221, 237, 34, 31, 221, 228, 35, 13, 704 | 220, 28, 2, 28, 2, 250, 31, 5, 224, 83, 32, 173, 223, 211, 31, 46, 224, 7, 0 705 | }; 706 | RestoreUnmanagedArray(ptr, report.Length, report); 707 | } 708 | } 709 | } 710 | } 711 | else 712 | { 713 | // Call original first so we get the result 714 | result = HidD_GetFeature(hidDeviceObject, ref lpReportBuffer, reportBufferLength); 715 | } 716 | } 717 | catch 718 | { 719 | // swallow exceptions so that any issues caused by this code do not crash target process 720 | } 721 | 722 | return result; 723 | } 724 | #endregion 725 | 726 | #region HidD_SetFeature Hook 727 | [UnmanagedFunctionPointer(CallingConvention.StdCall, CharSet = CharSet.Unicode, SetLastError = true)] 728 | delegate bool HidD_SetFeature_Delegate(IntPtr hidDeviceObject, ref Byte lpReportBuffer, Int32 reportBufferLength); 729 | 730 | [DllImport("hid.dll", CharSet = CharSet.Unicode, SetLastError = true)] 731 | static extern bool HidD_SetFeature(IntPtr hidDeviceObject, ref Byte lpReportBuffer, Int32 reportBufferLength); 732 | 733 | bool HidD_SetFeature_Hook(IntPtr hidDeviceObject, ref Byte lpReportBuffer, Int32 reportBufferLength) 734 | { 735 | bool result = false; 736 | 737 | try 738 | { 739 | if (_server.ShouldEmulateController()) 740 | { 741 | // SPOOF 742 | result = true; 743 | } 744 | else 745 | { 746 | // Call original first so we get the result 747 | result = HidD_SetFeature(hidDeviceObject, ref lpReportBuffer, reportBufferLength); 748 | } 749 | } 750 | catch 751 | { 752 | // swallow exceptions so that any issues caused by this code do not crash target process 753 | } 754 | 755 | return result; 756 | } 757 | #endregion 758 | 759 | #region HidD_GetPreparsedData Hook 760 | [UnmanagedFunctionPointer(CallingConvention.StdCall, CharSet = CharSet.Unicode, SetLastError = true)] 761 | delegate bool HidD_GetPreparsedData_Delegate(IntPtr hidDeviceObject, ref IntPtr preparsedData); 762 | 763 | [DllImport("hid.dll", CharSet = CharSet.Unicode, SetLastError = true)] 764 | static extern bool HidD_GetPreparsedData(IntPtr hidDeviceObject, ref IntPtr preparsedData); 765 | 766 | bool HidD_GetPreparsedData_Hook(IntPtr hidDeviceObject, ref IntPtr preparsedData) 767 | { 768 | bool result = false; 769 | 770 | try 771 | { 772 | if (_server.ShouldEmulateController()) 773 | { 774 | // SPOOF 775 | result = true; 776 | } 777 | else 778 | { 779 | // Call original first so we get the result 780 | result = HidD_GetPreparsedData(hidDeviceObject, ref preparsedData); 781 | } 782 | } 783 | catch 784 | { 785 | // swallow exceptions so that any issues caused by this code do not crash target process 786 | } 787 | 788 | return result; 789 | } 790 | #endregion 791 | 792 | #region HidD_FreePreparsedData Hook 793 | [UnmanagedFunctionPointer(CallingConvention.StdCall, CharSet = CharSet.Unicode, SetLastError = true)] 794 | delegate bool HidD_FreePreparsedData_Delegate(IntPtr preparsedData); 795 | 796 | [DllImport("hid.dll", CharSet = CharSet.Unicode, SetLastError = true)] 797 | static extern bool HidD_FreePreparsedData(IntPtr preparsedData); 798 | 799 | bool HidD_FreePreparsedData_Hook(IntPtr preparsedData) 800 | { 801 | bool result = false; 802 | 803 | try 804 | { 805 | if (_server.ShouldEmulateController()) 806 | { 807 | // SPOOF 808 | result = true; 809 | } 810 | else 811 | { 812 | // Call original first so we get the result 813 | result = HidD_FreePreparsedData(preparsedData); 814 | } 815 | } 816 | catch 817 | { 818 | // swallow exceptions so that any issues caused by this code do not crash target process 819 | } 820 | 821 | return result; 822 | } 823 | #endregion 824 | 825 | #region HidD_GetManufacturerString Hook 826 | [UnmanagedFunctionPointer(CallingConvention.StdCall, CharSet = CharSet.Unicode, SetLastError = true)] 827 | delegate bool HidD_GetManufacturerString_Delegate(IntPtr hidDeviceObject, ref Byte lpReportBuffer, Int32 reportBufferLength); 828 | 829 | [DllImport("hid.dll", CharSet = CharSet.Unicode, SetLastError = true)] 830 | static extern bool HidD_GetManufacturerString(IntPtr hidDeviceObject, ref Byte lpReportBuffer, Int32 reportBufferLength); 831 | 832 | bool HidD_GetManufacturerString_Hook(IntPtr hidDeviceObject, ref Byte lpReportBuffer, Int32 reportBufferLength) 833 | { 834 | bool result = false; 835 | 836 | try 837 | { 838 | if (_server.ShouldEmulateController()) 839 | { 840 | // SPOOF 841 | result = true; 842 | unsafe 843 | { 844 | fixed (byte* p = &lpReportBuffer) 845 | { 846 | IntPtr ptr = (IntPtr)p; 847 | byte[] data = 848 | { 849 | 83, 0, 111, 0, 110, 0, 121, 0, 32, 0, 73, 0, 110, 0, 116, 0, 101, 0, 114, 0, 97, 850 | 0, 99, 0, 116, 0, 105, 0, 118, 0, 101, 0, 32, 0, 69, 0, 110, 0, 116, 0, 101, 0, 114, 0, 116, 851 | 0, 97, 0, 105, 0, 110, 0, 109, 0, 101, 0, 110, 0, 116, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 852 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 853 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 854 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 855 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 856 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 857 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 858 | }; 859 | RestoreUnmanagedArray(ptr, data.Length, data); 860 | } 861 | } 862 | } 863 | else 864 | { 865 | // Call original first so we get the result 866 | result = HidD_GetManufacturerString(hidDeviceObject, ref lpReportBuffer, reportBufferLength); 867 | } 868 | } 869 | catch 870 | { 871 | // swallow exceptions so that any issues caused by this code do not crash target process 872 | } 873 | 874 | return result; 875 | } 876 | #endregion 877 | 878 | #region HidD_GetProductString Hook 879 | [UnmanagedFunctionPointer(CallingConvention.StdCall, CharSet = CharSet.Unicode, SetLastError = true)] 880 | delegate bool HidD_GetProductString_Delegate(IntPtr hidDeviceObject, ref Byte lpReportBuffer, Int32 reportBufferLength); 881 | 882 | [DllImport("hid.dll", CharSet = CharSet.Unicode, SetLastError = true)] 883 | static extern bool HidD_GetProductString(IntPtr hidDeviceObject, ref Byte lpReportBuffer, Int32 reportBufferLength); 884 | 885 | bool HidD_GetProductString_Hook(IntPtr hidDeviceObject, ref Byte lpReportBuffer, Int32 reportBufferLength) 886 | { 887 | bool result = false; 888 | 889 | try 890 | { 891 | if (_server.ShouldEmulateController()) 892 | { 893 | // Call original first so we get the result 894 | result = HidD_GetProductString(hidDeviceObject, ref lpReportBuffer, reportBufferLength); 895 | 896 | // SPOOF 897 | result = true; 898 | unsafe 899 | { 900 | fixed (byte* p = &lpReportBuffer) 901 | { 902 | IntPtr ptr = (IntPtr)p; 903 | byte[] data = 904 | { 905 | 87, 0, 105, 0, 114, 0, 101, 0, 108, 0, 101, 0, 115, 0, 115, 0, 32, 0, 67, 0, 111, 906 | 0, 110, 0, 116, 0, 114, 0, 111, 0, 108, 0, 108, 0, 101, 0, 114, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 907 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 908 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 909 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 910 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 911 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 912 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 913 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 914 | }; 915 | RestoreUnmanagedArray(ptr, data.Length, data); 916 | } 917 | } 918 | } 919 | else 920 | { 921 | // Call original first so we get the result 922 | result = HidD_GetProductString(hidDeviceObject, ref lpReportBuffer, reportBufferLength); 923 | } 924 | } 925 | catch 926 | { 927 | // swallow exceptions so that any issues caused by this code do not crash target process 928 | } 929 | 930 | return result; 931 | } 932 | #endregion 933 | 934 | #region HidD_GetSerialNumberString Hook 935 | [UnmanagedFunctionPointer(CallingConvention.StdCall, CharSet = CharSet.Unicode, SetLastError = true)] 936 | delegate bool HidD_GetSerialNumberString_Delegate(IntPtr hidDeviceObject, ref Byte lpReportBuffer, Int32 reportBufferLength); 937 | 938 | [DllImport("hid.dll", CharSet = CharSet.Unicode, SetLastError = true)] 939 | static extern bool HidD_GetSerialNumberString(IntPtr hidDeviceObject, ref Byte lpReportBuffer, Int32 reportBufferLength); 940 | 941 | bool HidD_GetSerialNumberString_Hook(IntPtr hidDeviceObject, ref Byte lpReportBuffer, Int32 reportBufferLength) 942 | { 943 | bool result = false; 944 | 945 | try 946 | { 947 | if (_server.ShouldEmulateController()) 948 | { 949 | // Call original first so we get the result 950 | result = HidD_GetSerialNumberString(hidDeviceObject, ref lpReportBuffer, reportBufferLength); 951 | 952 | // SPOOF 953 | result = true; 954 | } 955 | else 956 | { 957 | // Call original first so we get the result 958 | result = HidD_GetSerialNumberString(hidDeviceObject, ref lpReportBuffer, reportBufferLength); 959 | } 960 | } 961 | catch 962 | { 963 | // swallow exceptions so that any issues caused by this code do not crash target process 964 | } 965 | 966 | return result; 967 | } 968 | #endregion 969 | 970 | #region HidP_GetCaps Hook 971 | [StructLayout(LayoutKind.Sequential)] 972 | internal struct HIDP_CAPS 973 | { 974 | public Int16 Usage; 975 | public Int16 UsagePage; 976 | public Int16 InputReportByteLength; 977 | public Int16 OutputReportByteLength; 978 | public Int16 FeatureReportByteLength; 979 | [MarshalAs(UnmanagedType.ByValArray, SizeConst = 17)] 980 | public Int16[] Reserved; 981 | public Int16 NumberLinkCollectionNodes; 982 | public Int16 NumberInputButtonCaps; 983 | public Int16 NumberInputValueCaps; 984 | public Int16 NumberInputDataIndices; 985 | public Int16 NumberOutputButtonCaps; 986 | public Int16 NumberOutputValueCaps; 987 | public Int16 NumberOutputDataIndices; 988 | public Int16 NumberFeatureButtonCaps; 989 | public Int16 NumberFeatureValueCaps; 990 | public Int16 NumberFeatureDataIndices; 991 | } 992 | 993 | [UnmanagedFunctionPointer(CallingConvention.StdCall, CharSet = CharSet.Unicode, SetLastError = true)] 994 | delegate int HidP_GetCaps_Delegate(IntPtr preparsedData, ref HIDP_CAPS capabilities); 995 | 996 | [DllImport("hid.dll", CharSet = CharSet.Unicode, SetLastError = true)] 997 | static extern int HidP_GetCaps(IntPtr preparsedData, ref HIDP_CAPS capabilities); 998 | 999 | int HidP_GetCaps_Hook(IntPtr preparsedData, ref HIDP_CAPS capabilities) 1000 | { 1001 | int result = 0; 1002 | 1003 | try 1004 | { 1005 | if (_server.ShouldEmulateController()) 1006 | { 1007 | // SPOOF 1008 | result = 0x110000; 1009 | capabilities.Usage = 5; 1010 | capabilities.UsagePage = 1; 1011 | capabilities.InputReportByteLength = 64; 1012 | capabilities.OutputReportByteLength = 32; 1013 | capabilities.FeatureReportByteLength = 64; 1014 | 1015 | capabilities.NumberLinkCollectionNodes = 1; 1016 | capabilities.NumberInputButtonCaps = 1; 1017 | capabilities.NumberInputValueCaps = 9; 1018 | capabilities.NumberInputDataIndices = 23; 1019 | capabilities.NumberOutputButtonCaps = 0; 1020 | capabilities.NumberOutputValueCaps = 1; 1021 | capabilities.NumberOutputDataIndices = 1; 1022 | capabilities.NumberFeatureButtonCaps = 0; 1023 | capabilities.NumberFeatureValueCaps = 48; 1024 | capabilities.NumberFeatureDataIndices = 48; 1025 | } 1026 | else 1027 | { 1028 | // Call original first so we get the result 1029 | result = HidP_GetCaps(preparsedData, ref capabilities); 1030 | } 1031 | } 1032 | catch 1033 | { 1034 | // swallow exceptions so that any issues caused by this code do not crash target process 1035 | } 1036 | 1037 | return result; 1038 | } 1039 | #endregion 1040 | 1041 | #region HidP_GetValueCaps Hook 1042 | internal enum HIDP_REPORT_TYPE 1043 | { 1044 | HidP_Input, 1045 | HidP_Output, 1046 | HidP_Feature 1047 | } 1048 | 1049 | [StructLayout(LayoutKind.Sequential)] 1050 | public struct HidP_Range 1051 | { 1052 | public short UsageMin; 1053 | public short UsageMax; 1054 | public short StringMin; 1055 | public short StringMax; 1056 | public short DesignatorMin; 1057 | public short DesignatorMax; 1058 | public short DataIndexMin; 1059 | public short DataIndexMax; 1060 | } 1061 | 1062 | [StructLayout(LayoutKind.Sequential)] 1063 | public struct HidP_NotRange 1064 | { 1065 | public short Usage; 1066 | public short Reserved1; 1067 | public short StringIndex; 1068 | public short Reserved2; 1069 | public short DesignatorIndex; 1070 | public short Reserved3; 1071 | public short DataIndex; 1072 | public short Reserved4; 1073 | } 1074 | 1075 | [StructLayout(LayoutKind.Explicit)] 1076 | public struct HidP_Value_Caps 1077 | { 1078 | [FieldOffset(0)] 1079 | public ushort UsagePage; 1080 | [FieldOffset(2)] 1081 | public byte ReportID; 1082 | [FieldOffset(3), MarshalAs(UnmanagedType.U1)] 1083 | public bool IsAlias; 1084 | [FieldOffset(4)] 1085 | public ushort BitField; 1086 | [FieldOffset(6)] 1087 | public ushort LinkCollection; 1088 | [FieldOffset(8)] 1089 | public ushort LinkUsage; 1090 | [FieldOffset(10)] 1091 | public ushort LinkUsagePage; 1092 | [FieldOffset(12), MarshalAs(UnmanagedType.U1)] 1093 | public bool IsRange; 1094 | [FieldOffset(13), MarshalAs(UnmanagedType.U1)] 1095 | public bool IsStringRange; 1096 | [FieldOffset(14), MarshalAs(UnmanagedType.U1)] 1097 | public bool IsDesignatorRange; 1098 | [FieldOffset(15), MarshalAs(UnmanagedType.U1)] 1099 | public bool IsAbsolute; 1100 | [FieldOffset(16), MarshalAs(UnmanagedType.U1)] 1101 | public bool HasNull; 1102 | [FieldOffset(17)] 1103 | public byte Reserved; 1104 | [FieldOffset(18)] 1105 | public short BitSize; 1106 | [FieldOffset(20)] 1107 | public short ReportCount; 1108 | [FieldOffset(22)] 1109 | public ushort Reserved2a; 1110 | [FieldOffset(24)] 1111 | public ushort Reserved2b; 1112 | [FieldOffset(26)] 1113 | public ushort Reserved2c; 1114 | [FieldOffset(28)] 1115 | public ushort Reserved2d; 1116 | [FieldOffset(30)] 1117 | public ushort Reserved2e; 1118 | [FieldOffset(32)] 1119 | public int UnitsExp; 1120 | [FieldOffset(36)] 1121 | public int Units; 1122 | [FieldOffset(40)] 1123 | public int LogicalMin; 1124 | [FieldOffset(44)] 1125 | public int LogicalMax; 1126 | [FieldOffset(48)] 1127 | public int PhysicalMin; 1128 | [FieldOffset(52)] 1129 | public int PhysicalMax; 1130 | 1131 | [FieldOffset(56)] 1132 | public HidP_Range Range; 1133 | [FieldOffset(56)] 1134 | public HidP_NotRange NotRange; 1135 | } 1136 | 1137 | [UnmanagedFunctionPointer(CallingConvention.StdCall, CharSet = CharSet.Unicode, SetLastError = true)] 1138 | delegate int HidP_GetValueCaps_Delegate(HIDP_REPORT_TYPE reportType, ref Byte valueCaps, ref short valueCapsLength, IntPtr preparsedData); 1139 | 1140 | [DllImport("hid.dll", CharSet = CharSet.Unicode, SetLastError = true)] 1141 | static extern int HidP_GetValueCaps(HIDP_REPORT_TYPE reportType, ref Byte valueCaps, ref short valueCapsLength, IntPtr preparsedData); 1142 | 1143 | int HidP_GetValueCaps_Hook(HIDP_REPORT_TYPE reportType, ref Byte valueCaps, ref short valueCapsLength, IntPtr preparsedData) 1144 | { 1145 | int result = 0; 1146 | 1147 | try 1148 | { 1149 | if (_server.ShouldEmulateController()) 1150 | { 1151 | // SPOOF 1152 | result = 0x110000; 1153 | unsafe 1154 | { 1155 | fixed (byte* p = &valueCaps) 1156 | { 1157 | IntPtr ptr = (IntPtr)p; 1158 | byte[] managedArray = ToManagedArray(ptr, 3456); 1159 | managedArray[0] = 0x0; 1160 | managedArray[1] = 0xFF; 1161 | managedArray[2] = 0x4; 1162 | managedArray[3] = 0x0; 1163 | managedArray[4] = 0x2; 1164 | managedArray[5] = 0x0; 1165 | managedArray[6] = 0x0; 1166 | managedArray[7] = 0x0; 1167 | managedArray[8] = 0x5; 1168 | managedArray[9] = 0x0; 1169 | managedArray[10] = 0x1; 1170 | managedArray[11] = 0x0; 1171 | managedArray[12] = 0x0; 1172 | managedArray[13] = 0x0; 1173 | managedArray[14] = 0x0; 1174 | managedArray[15] = 0x1; 1175 | managedArray[16] = 0x0; 1176 | //managedArray[17] = managedArray[17]; 1177 | managedArray[18] = 0x8; 1178 | managedArray[19] = 0x0; 1179 | managedArray[20] = 0x24; 1180 | managedArray[21] = 0x0; 1181 | //managedArray[22] = managedArray[22]; 1182 | //managedArray[23] = managedArray[23]; 1183 | //managedArray[24] = managedArray[24]; 1184 | //managedArray[25] = managedArray[25]; 1185 | //managedArray[26] = managedArray[26]; 1186 | //managedArray[27] = managedArray[27]; 1187 | //managedArray[28] = managedArray[28]; 1188 | //managedArray[29] = managedArray[29]; 1189 | //managedArray[30] = managedArray[30]; 1190 | //managedArray[31] = managedArray[31]; 1191 | managedArray[32] = 0x0; 1192 | managedArray[33] = 0x0; 1193 | managedArray[34] = 0x0; 1194 | managedArray[35] = 0x0; 1195 | managedArray[36] = 0x0; 1196 | managedArray[37] = 0x0; 1197 | managedArray[38] = 0x0; 1198 | managedArray[39] = 0x0; 1199 | managedArray[40] = 0x0; 1200 | managedArray[41] = 0x0; 1201 | managedArray[42] = 0x0; 1202 | managedArray[43] = 0x0; 1203 | managedArray[44] = 0xFF; 1204 | managedArray[45] = 0x0; 1205 | managedArray[46] = 0x0; 1206 | managedArray[47] = 0x0; 1207 | 1208 | managedArray[48] = 0x0; 1209 | managedArray[49] = 0x0; 1210 | managedArray[50] = 0x0; 1211 | managedArray[51] = 0x0; 1212 | managedArray[52] = 0x3B; 1213 | managedArray[53] = 0x01; 1214 | managedArray[54] = 0x0; 1215 | managedArray[55] = 0x23; 1216 | managedArray[56] = 0x0; 1217 | managedArray[57] = 0x23; 1218 | managedArray[58] = 0x0; 1219 | managedArray[59] = 0x0; 1220 | managedArray[60] = 0x0; 1221 | managedArray[61] = 0x0; 1222 | managedArray[62] = 0x0; 1223 | managedArray[63] = 0x0; 1224 | managedArray[64] = 0x0; 1225 | managedArray[65] = 0x0; 1226 | managedArray[66] = 0x0; 1227 | managedArray[67] = 0x0; 1228 | 1229 | //managedArray[72] = 0x80; // a3 1230 | //managedArray[128] = 0x43; // a4 1231 | //managedArray[74] = 0xA3; // a5 1232 | //managedArray[92] = 0x30; // a6 1233 | 1234 | var i = 72 + 20; 1235 | while (i + 36 < 3456) 1236 | { 1237 | managedArray[i - 20] = 0x80; 1238 | managedArray[i + 36] = 0x43; 1239 | managedArray[i] = 0x30; 1240 | managedArray[i - 18] = 0xA3; 1241 | managedArray[i - 19] = 0xFF; 1242 | 1243 | i += 72; 1244 | } 1245 | 1246 | RestoreUnmanagedArray(ptr, managedArray.Length, managedArray); 1247 | } 1248 | } 1249 | } 1250 | else 1251 | { 1252 | // Call original first so we get the result 1253 | result = HidP_GetValueCaps_Hook(reportType, ref valueCaps, ref valueCapsLength, preparsedData); 1254 | } 1255 | } 1256 | catch 1257 | { 1258 | // swallow exceptions so that any issues caused by this code do not crash target process 1259 | } 1260 | 1261 | return result; 1262 | } 1263 | #endregion 1264 | } 1265 | } 1266 | --------------------------------------------------------------------------------