├── 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 | [](https://nuget.org/packages/PS4RemotePlayInterceptor)
4 | [](https://nuget.org/packages/PS4RemotePlayInterceptor)
5 | [](http://paypal.me/Komefai)
6 | [](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($"{containerTypeName}>");
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 |
--------------------------------------------------------------------------------