-
Notifications
You must be signed in to change notification settings - Fork 175
/
EntryPoint.cs
303 lines (262 loc) · 12.8 KB
/
EntryPoint.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
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
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Capture.Hook;
using Capture.Interface;
using System.Threading.Tasks;
using System.Runtime.Remoting.Channels.Ipc;
namespace Capture
{
public class EntryPoint : EasyHook.IEntryPoint
{
List<IDXHook> _directXHooks = new List<IDXHook>();
IDXHook _directXHook = null;
private CaptureInterface _interface;
private System.Threading.ManualResetEvent _runWait;
ClientCaptureInterfaceEventProxy _clientEventProxy = new ClientCaptureInterfaceEventProxy();
IpcServerChannel _clientServerChannel = null;
public EntryPoint(
EasyHook.RemoteHooking.IContext context,
String channelName,
CaptureConfig config)
{
// Get reference to IPC to host application
// Note: any methods called or events triggered against _interface will execute in the host process.
_interface = EasyHook.RemoteHooking.IpcConnectClient<CaptureInterface>(channelName);
// We try to ping immediately, if it fails then injection fails
_interface.Ping();
#region Allow client event handlers (bi-directional IPC)
// Attempt to create a IpcServerChannel so that any event handlers on the client will function correctly
System.Collections.IDictionary properties = new System.Collections.Hashtable();
properties["name"] = channelName;
properties["portName"] = channelName + Guid.NewGuid().ToString("N"); // random portName so no conflict with existing channels of channelName
System.Runtime.Remoting.Channels.BinaryServerFormatterSinkProvider binaryProv = new System.Runtime.Remoting.Channels.BinaryServerFormatterSinkProvider();
binaryProv.TypeFilterLevel = System.Runtime.Serialization.Formatters.TypeFilterLevel.Full;
System.Runtime.Remoting.Channels.Ipc.IpcServerChannel _clientServerChannel = new System.Runtime.Remoting.Channels.Ipc.IpcServerChannel(properties, binaryProv);
System.Runtime.Remoting.Channels.ChannelServices.RegisterChannel(_clientServerChannel, false);
#endregion
}
public void Run(
EasyHook.RemoteHooking.IContext context,
String channelName,
CaptureConfig config)
{
// When not using GAC there can be issues with remoting assemblies resolving correctly
// this is a workaround that ensures that the current assembly is correctly associated
AppDomain currentDomain = AppDomain.CurrentDomain;
currentDomain.AssemblyResolve += (sender, args) =>
{
return this.GetType().Assembly.FullName == args.Name ? this.GetType().Assembly : null;
};
// NOTE: This is running in the target process
_interface.Message(MessageType.Information, "Injected into process Id:{0}.", EasyHook.RemoteHooking.GetCurrentProcessId());
_runWait = new System.Threading.ManualResetEvent(false);
_runWait.Reset();
try
{
// Initialise the Hook
if (!InitialiseDirectXHook(config))
{
return;
}
_interface.Disconnected += _clientEventProxy.DisconnectedProxyHandler;
// Important Note:
// accessing the _interface from within a _clientEventProxy event handler must always
// be done on a different thread otherwise it will cause a deadlock
_clientEventProxy.Disconnected += () =>
{
// We can now signal the exit of the Run method
_runWait.Set();
};
// We start a thread here to periodically check if the host is still running
// If the host process stops then we will automatically uninstall the hooks
StartCheckHostIsAliveThread();
// Wait until signaled for exit either when a Disconnect message from the host
// or if the the check is alive has failed to Ping the host.
_runWait.WaitOne();
// we need to tell the check host thread to exit (if it hasn't already)
StopCheckHostIsAliveThread();
// Dispose of the DXHook so any installed hooks are removed correctly
DisposeDirectXHook();
}
catch (Exception e)
{
_interface.Message(MessageType.Error, "An unexpected error occured: {0}", e.ToString());
}
finally
{
try
{
_interface.Message(MessageType.Information, "Disconnecting from process {0}", EasyHook.RemoteHooking.GetCurrentProcessId());
}
catch
{
}
// Remove the client server channel (that allows client event handlers)
System.Runtime.Remoting.Channels.ChannelServices.UnregisterChannel(_clientServerChannel);
// Always sleep long enough for any remaining messages to complete sending
System.Threading.Thread.Sleep(100);
}
}
private void DisposeDirectXHook()
{
if (_directXHooks != null)
{
try
{
_interface.Message(MessageType.Debug, "Disposing of hooks...");
}
catch (System.Runtime.Remoting.RemotingException) { } // Ignore channel remoting errors
// Dispose of the hooks so they are removed
foreach (var dxHook in _directXHooks)
dxHook.Dispose();
_directXHooks.Clear();
}
}
private bool InitialiseDirectXHook(CaptureConfig config)
{
Direct3DVersion version = config.Direct3DVersion;
List<Direct3DVersion> loadedVersions = new List<Direct3DVersion>();
bool isX64Process = EasyHook.RemoteHooking.IsX64Process(EasyHook.RemoteHooking.GetCurrentProcessId());
_interface.Message(MessageType.Information, "Remote process is a {0}-bit process.", isX64Process ? "64" : "32");
try
{
if (version == Direct3DVersion.AutoDetect || version == Direct3DVersion.Unknown)
{
// Attempt to determine the correct version based on loaded module.
// In most cases this will work fine, however it is perfectly ok for an application to use a D3D10 device along with D3D11 devices
// so the version might matched might not be the one you want to use
IntPtr d3D9Loaded = IntPtr.Zero;
IntPtr d3D10Loaded = IntPtr.Zero;
IntPtr d3D10_1Loaded = IntPtr.Zero;
IntPtr d3D11Loaded = IntPtr.Zero;
IntPtr d3D11_1Loaded = IntPtr.Zero;
int delayTime = 100;
int retryCount = 0;
while (d3D9Loaded == IntPtr.Zero && d3D10Loaded == IntPtr.Zero && d3D10_1Loaded == IntPtr.Zero && d3D11Loaded == IntPtr.Zero && d3D11_1Loaded == IntPtr.Zero)
{
retryCount++;
d3D9Loaded = NativeMethods.GetModuleHandle("d3d9.dll");
d3D10Loaded = NativeMethods.GetModuleHandle("d3d10.dll");
d3D10_1Loaded = NativeMethods.GetModuleHandle("d3d10_1.dll");
d3D11Loaded = NativeMethods.GetModuleHandle("d3d11.dll");
d3D11_1Loaded = NativeMethods.GetModuleHandle("d3d11_1.dll");
System.Threading.Thread.Sleep(delayTime);
if (retryCount * delayTime > 5000)
{
_interface.Message(MessageType.Error, "Unsupported Direct3D version, or Direct3D DLL not loaded within 5 seconds.");
return false;
}
}
version = Direct3DVersion.Unknown;
if (d3D11_1Loaded != IntPtr.Zero)
{
_interface.Message(MessageType.Debug, "Autodetect found Direct3D 11.1");
version = Direct3DVersion.Direct3D11_1;
loadedVersions.Add(version);
}
if (d3D11Loaded != IntPtr.Zero)
{
_interface.Message(MessageType.Debug, "Autodetect found Direct3D 11");
version = Direct3DVersion.Direct3D11;
loadedVersions.Add(version);
}
if (d3D10_1Loaded != IntPtr.Zero)
{
_interface.Message(MessageType.Debug, "Autodetect found Direct3D 10.1");
version = Direct3DVersion.Direct3D10_1;
loadedVersions.Add(version);
}
if (d3D10Loaded != IntPtr.Zero)
{
_interface.Message(MessageType.Debug, "Autodetect found Direct3D 10");
version = Direct3DVersion.Direct3D10;
loadedVersions.Add(version);
}
if (d3D9Loaded != IntPtr.Zero)
{
_interface.Message(MessageType.Debug, "Autodetect found Direct3D 9");
version = Direct3DVersion.Direct3D9;
loadedVersions.Add(version);
}
}
else
{
// If not autodetect, assume specified version is loaded
loadedVersions.Add(version);
}
foreach (var dxVersion in loadedVersions)
{
version = dxVersion;
switch (version)
{
case Direct3DVersion.Direct3D9:
_directXHook = new DXHookD3D9(_interface);
break;
case Direct3DVersion.Direct3D10:
_directXHook = new DXHookD3D10(_interface);
break;
case Direct3DVersion.Direct3D10_1:
_directXHook = new DXHookD3D10_1(_interface);
break;
case Direct3DVersion.Direct3D11:
_directXHook = new DXHookD3D11(_interface);
break;
//case Direct3DVersion.Direct3D11_1:
// _directXHook = new DXHookD3D11_1(_interface);
// return;
default:
_interface.Message(MessageType.Error, "Unsupported Direct3D version: {0}", version);
return false;
}
_directXHook.Config = config;
_directXHook.Hook();
_directXHooks.Add(_directXHook);
}
return true;
}
catch (Exception e)
{
// Notify the host/server application about this error
_interface.Message(MessageType.Error, "Error in InitialiseHook: {0}", e.ToString());
return false;
}
}
#region Check Host Is Alive
Task _checkAlive;
long _stopCheckAlive = 0;
/// <summary>
/// Begin a background thread to check periodically that the host process is still accessible on its IPC channel
/// </summary>
private void StartCheckHostIsAliveThread()
{
_checkAlive = new Task(() =>
{
try
{
while (System.Threading.Interlocked.Read(ref _stopCheckAlive) == 0)
{
System.Threading.Thread.Sleep(1000);
// .NET Remoting exceptions will throw RemotingException
_interface.Ping();
}
}
catch // We will assume that any exception means that the hooks need to be removed.
{
// Signal the Run method so that it can exit
_runWait.Set();
}
});
_checkAlive.Start();
}
/// <summary>
/// Tell the _checkAlive thread that it can exit if it hasn't already
/// </summary>
private void StopCheckHostIsAliveThread()
{
System.Threading.Interlocked.Increment(ref _stopCheckAlive);
}
#endregion
}
}