You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

557 rivejä
17 KiB

  1. using System;
  2. using System.Drawing;
  3. using System.Threading;
  4. using RenderHookAPI.Hook.Common;
  5. namespace RenderHookAPI.Interface
  6. {
  7. [Serializable]
  8. public delegate void RecordingStartedEvent(CaptureConfig config);
  9. [Serializable]
  10. public delegate void RecordingStoppedEvent();
  11. [Serializable]
  12. public delegate void MessageReceivedEvent(MessageReceivedEventArgs message);
  13. [Serializable]
  14. public delegate void ScreenshotReceivedEvent(ScreenshotReceivedEventArgs response);
  15. [Serializable]
  16. public delegate void DisconnectedEvent();
  17. [Serializable]
  18. public delegate void ScreenshotRequestedEvent(ScreenshotRequest request);
  19. [Serializable]
  20. public delegate void DisplayTextEvent(DisplayTextEventArgs args);
  21. [Serializable]
  22. public delegate void DrawOverlayEvent(DrawOverlayEventArgs args);
  23. [Serializable]
  24. public class CaptureInterface : MarshalByRefObject
  25. {
  26. /// <summary>
  27. /// The client process Id
  28. /// </summary>
  29. public int ProcessId { get; set; }
  30. #region Events
  31. #region Server-side Events
  32. /// <summary>
  33. /// Server event for sending debug and error information from the client to server
  34. /// </summary>
  35. public event MessageReceivedEvent RemoteMessage;
  36. /// <summary>
  37. /// Server event for receiving screenshot image data
  38. /// </summary>
  39. public event ScreenshotReceivedEvent ScreenshotReceived;
  40. #endregion
  41. #region Client-side Events
  42. /// <summary>
  43. /// Client event used to communicate to the client that it is time to start recording
  44. /// </summary>
  45. public event RecordingStartedEvent RecordingStarted;
  46. /// <summary>
  47. /// Client event used to communicate to the client that it is time to stop recording
  48. /// </summary>
  49. public event RecordingStoppedEvent RecordingStopped;
  50. /// <summary>
  51. /// Client event used to communicate to the client that it is time to create a screenshot
  52. /// </summary>
  53. public event ScreenshotRequestedEvent ScreenshotRequested;
  54. /// <summary>
  55. /// Client event used to notify the hook to exit
  56. /// </summary>
  57. public event DisconnectedEvent Disconnected;
  58. /// <summary>
  59. /// Client event used to display a piece of text in-game
  60. /// </summary>
  61. public event DisplayTextEvent DisplayText;
  62. /// <summary>
  63. /// Client event used to (re-)draw an overlay in-game.
  64. /// </summary>
  65. public event DrawOverlayEvent DrawOverlay;
  66. #endregion
  67. #endregion
  68. public bool IsRecording { get; set; }
  69. #region Public Methods
  70. #region Video Capture
  71. /// <summary>
  72. /// If not <see cref="IsRecording"/> will invoke the <see cref="RecordingStarted"/> event, starting a new recording.
  73. /// </summary>
  74. /// <param name="config">The configuration for the recording</param>
  75. /// <remarks>Handlers in the server and remote process will be be invoked.</remarks>
  76. public void StartRecording(CaptureConfig config)
  77. {
  78. if (IsRecording)
  79. return;
  80. SafeInvokeRecordingStarted(config);
  81. IsRecording = true;
  82. }
  83. /// <summary>
  84. /// If <see cref="IsRecording"/>, will invoke the <see cref="RecordingStopped"/> event, finalising any existing recording.
  85. /// </summary>
  86. /// <remarks>Handlers in the server and remote process will be be invoked.</remarks>
  87. public void StopRecording()
  88. {
  89. if (!IsRecording)
  90. return;
  91. SafeInvokeRecordingStopped();
  92. IsRecording = false;
  93. }
  94. #endregion
  95. #region Still image Capture
  96. object _lock = new object();
  97. Guid? _requestId = null;
  98. Action<Screenshot> _completeScreenshot = null;
  99. ManualResetEvent _wait = new ManualResetEvent(false);
  100. /// <summary>
  101. /// Get a fullscreen screenshot with the default timeout of 2 seconds
  102. /// </summary>
  103. public Screenshot GetScreenshot()
  104. {
  105. return GetScreenshot(Rectangle.Empty, new TimeSpan(0, 0, 2), null, ImageFormat.Bitmap);
  106. }
  107. /// <summary>
  108. /// Get a screenshot of the specified region
  109. /// </summary>
  110. /// <param name="region">the region to capture (x=0,y=0 is top left corner)</param>
  111. /// <param name="timeout">maximum time to wait for the screenshot</param>
  112. public Screenshot GetScreenshot(Rectangle region, TimeSpan timeout, Size? resize, ImageFormat format)
  113. {
  114. lock (_lock)
  115. {
  116. Screenshot result = null;
  117. _requestId = Guid.NewGuid();
  118. _wait.Reset();
  119. SafeInvokeScreenshotRequested(new ScreenshotRequest(_requestId.Value, region)
  120. {
  121. Format = format,
  122. Resize = resize,
  123. });
  124. _completeScreenshot = (sc) =>
  125. {
  126. try
  127. {
  128. Interlocked.Exchange(ref result, sc);
  129. }
  130. catch
  131. {
  132. }
  133. _wait.Set();
  134. };
  135. _wait.WaitOne(timeout);
  136. _completeScreenshot = null;
  137. return result;
  138. }
  139. }
  140. public IAsyncResult BeginGetScreenshot(Rectangle region, TimeSpan timeout, AsyncCallback callback = null, Size? resize = null, ImageFormat format = ImageFormat.Bitmap)
  141. {
  142. Func<Rectangle, TimeSpan, Size?, ImageFormat, Screenshot> getScreenshot = GetScreenshot;
  143. return getScreenshot.BeginInvoke(region, timeout, resize, format, callback, getScreenshot);
  144. }
  145. public Screenshot EndGetScreenshot(IAsyncResult result)
  146. {
  147. Func<Rectangle, TimeSpan, Size?, ImageFormat, Screenshot> getScreenshot = result.AsyncState as Func<Rectangle, TimeSpan, Size?, ImageFormat, Screenshot>;
  148. if (getScreenshot != null)
  149. {
  150. return getScreenshot.EndInvoke(result);
  151. }
  152. else
  153. return null;
  154. }
  155. public void SendScreenshotResponse(Screenshot screenshot)
  156. {
  157. if (_requestId != null && screenshot != null && screenshot.RequestId == _requestId.Value)
  158. {
  159. if (_completeScreenshot != null)
  160. {
  161. _completeScreenshot(screenshot);
  162. }
  163. }
  164. }
  165. #endregion
  166. /// <summary>
  167. /// Tell the client process to disconnect
  168. /// </summary>
  169. public void Disconnect()
  170. {
  171. SafeInvokeDisconnected();
  172. }
  173. /// <summary>
  174. /// Send a message to all handlers of <see cref="CaptureInterface.RemoteMessage"/>.
  175. /// </summary>
  176. /// <param name="messageType"></param>
  177. /// <param name="format"></param>
  178. /// <param name="args"></param>
  179. public void Message(MessageType messageType, string format, params object[] args)
  180. {
  181. Message(messageType, String.Format(format, args));
  182. }
  183. public void Message(MessageType messageType, string message)
  184. {
  185. SafeInvokeMessageRecevied(new MessageReceivedEventArgs(messageType, message));
  186. }
  187. /// <summary>
  188. /// Display text in-game for the default duration of 5 seconds
  189. /// </summary>
  190. /// <param name="text"></param>
  191. public void DisplayInGameText(string text)
  192. {
  193. DisplayInGameText(text, new TimeSpan(0, 0, 5));
  194. }
  195. /// <summary>
  196. ///
  197. /// </summary>
  198. /// <param name="text"></param>
  199. /// <param name="duration"></param>
  200. public void DisplayInGameText(string text, TimeSpan duration)
  201. {
  202. if (duration.TotalMilliseconds <= 0)
  203. throw new ArgumentException("Duration must be larger than 0", "duration");
  204. SafeInvokeDisplayText(new DisplayTextEventArgs(text, duration));
  205. }
  206. /// <summary>
  207. /// Replace the in-game overlay with the one provided.
  208. ///
  209. /// Note: this is not designed for fast updates (i.e. only a couple of times per second)
  210. /// </summary>
  211. /// <param name="overlay"></param>
  212. public void DrawOverlayInGame(IOverlay overlay)
  213. {
  214. SafeInvokeDrawOverlay(new DrawOverlayEventArgs()
  215. {
  216. Overlay = overlay
  217. });
  218. }
  219. #endregion
  220. #region Private: Invoke message handlers
  221. private void SafeInvokeRecordingStarted(CaptureConfig config)
  222. {
  223. if (RecordingStarted == null)
  224. return; //No Listeners
  225. RecordingStartedEvent listener = null;
  226. Delegate[] dels = RecordingStarted.GetInvocationList();
  227. foreach (Delegate del in dels)
  228. {
  229. try
  230. {
  231. listener = (RecordingStartedEvent)del;
  232. listener.Invoke(config);
  233. }
  234. catch (Exception)
  235. {
  236. //Could not reach the destination, so remove it
  237. //from the list
  238. RecordingStarted -= listener;
  239. }
  240. }
  241. }
  242. private void SafeInvokeRecordingStopped()
  243. {
  244. if (RecordingStopped == null)
  245. return; //No Listeners
  246. RecordingStoppedEvent listener = null;
  247. Delegate[] dels = RecordingStopped.GetInvocationList();
  248. foreach (Delegate del in dels)
  249. {
  250. try
  251. {
  252. listener = (RecordingStoppedEvent)del;
  253. listener.Invoke();
  254. }
  255. catch (Exception)
  256. {
  257. //Could not reach the destination, so remove it
  258. //from the list
  259. RecordingStopped -= listener;
  260. }
  261. }
  262. }
  263. private void SafeInvokeMessageRecevied(MessageReceivedEventArgs eventArgs)
  264. {
  265. if (RemoteMessage == null)
  266. return; //No Listeners
  267. MessageReceivedEvent listener = null;
  268. Delegate[] dels = RemoteMessage.GetInvocationList();
  269. foreach (Delegate del in dels)
  270. {
  271. try
  272. {
  273. listener = (MessageReceivedEvent)del;
  274. listener.Invoke(eventArgs);
  275. }
  276. catch (Exception)
  277. {
  278. //Could not reach the destination, so remove it
  279. //from the list
  280. RemoteMessage -= listener;
  281. }
  282. }
  283. }
  284. private void SafeInvokeScreenshotRequested(ScreenshotRequest eventArgs)
  285. {
  286. if (ScreenshotRequested == null)
  287. return; //No Listeners
  288. ScreenshotRequestedEvent listener = null;
  289. Delegate[] dels = ScreenshotRequested.GetInvocationList();
  290. foreach (Delegate del in dels)
  291. {
  292. try
  293. {
  294. listener = (ScreenshotRequestedEvent)del;
  295. listener.Invoke(eventArgs);
  296. }
  297. catch (Exception)
  298. {
  299. //Could not reach the destination, so remove it
  300. //from the list
  301. ScreenshotRequested -= listener;
  302. }
  303. }
  304. }
  305. private void SafeInvokeScreenshotReceived(ScreenshotReceivedEventArgs eventArgs)
  306. {
  307. if (ScreenshotReceived == null)
  308. return; //No Listeners
  309. ScreenshotReceivedEvent listener = null;
  310. Delegate[] dels = ScreenshotReceived.GetInvocationList();
  311. foreach (Delegate del in dels)
  312. {
  313. try
  314. {
  315. listener = (ScreenshotReceivedEvent)del;
  316. listener.Invoke(eventArgs);
  317. }
  318. catch (Exception)
  319. {
  320. //Could not reach the destination, so remove it
  321. //from the list
  322. ScreenshotReceived -= listener;
  323. }
  324. }
  325. }
  326. private void SafeInvokeDisconnected()
  327. {
  328. if (Disconnected == null)
  329. return; //No Listeners
  330. DisconnectedEvent listener = null;
  331. Delegate[] dels = Disconnected.GetInvocationList();
  332. foreach (Delegate del in dels)
  333. {
  334. try
  335. {
  336. listener = (DisconnectedEvent)del;
  337. listener.Invoke();
  338. }
  339. catch (Exception)
  340. {
  341. //Could not reach the destination, so remove it
  342. //from the list
  343. Disconnected -= listener;
  344. }
  345. }
  346. }
  347. private void SafeInvokeDisplayText(DisplayTextEventArgs displayTextEventArgs)
  348. {
  349. if (DisplayText == null)
  350. return; //No Listeners
  351. DisplayTextEvent listener = null;
  352. Delegate[] dels = DisplayText.GetInvocationList();
  353. foreach (Delegate del in dels)
  354. {
  355. try
  356. {
  357. listener = (DisplayTextEvent)del;
  358. listener.Invoke(displayTextEventArgs);
  359. }
  360. catch (Exception)
  361. {
  362. //Could not reach the destination, so remove it
  363. //from the list
  364. DisplayText -= listener;
  365. }
  366. }
  367. }
  368. private void SafeInvokeDrawOverlay(DrawOverlayEventArgs drawOverlayEventArgs)
  369. {
  370. if (DrawOverlay == null)
  371. return; //No Listeners
  372. DrawOverlayEvent listener = null;
  373. var dels = DrawOverlay.GetInvocationList();
  374. foreach (var del in dels)
  375. {
  376. try
  377. {
  378. listener = (DrawOverlayEvent)del;
  379. listener.Invoke(drawOverlayEventArgs);
  380. }
  381. catch (Exception)
  382. {
  383. //Could not reach the destination, so remove it
  384. //from the list
  385. DrawOverlay -= listener;
  386. }
  387. }
  388. }
  389. #endregion
  390. /// <summary>
  391. /// Used to confirm connection to IPC server channel
  392. /// </summary>
  393. public DateTime Ping()
  394. {
  395. return DateTime.Now;
  396. }
  397. }
  398. /// <summary>
  399. /// Client event proxy for marshalling event handlers
  400. /// </summary>
  401. public class ClientCaptureInterfaceEventProxy : MarshalByRefObject
  402. {
  403. #region Event Declarations
  404. /// <summary>
  405. /// Client event used to communicate to the client that it is time to start recording
  406. /// </summary>
  407. public event RecordingStartedEvent RecordingStarted;
  408. /// <summary>
  409. /// Client event used to communicate to the client that it is time to stop recording
  410. /// </summary>
  411. public event RecordingStoppedEvent RecordingStopped;
  412. /// <summary>
  413. /// Client event used to communicate to the client that it is time to create a screenshot
  414. /// </summary>
  415. public event ScreenshotRequestedEvent ScreenshotRequested;
  416. /// <summary>
  417. /// Client event used to notify the hook to exit
  418. /// </summary>
  419. public event DisconnectedEvent Disconnected;
  420. /// <summary>
  421. /// Client event used to display in-game text
  422. /// </summary>
  423. public event DisplayTextEvent DisplayText;
  424. /// <summary>
  425. /// Client event used to (re-)draw an overlay in-game.
  426. /// </summary>
  427. public event DrawOverlayEvent DrawOverlay;
  428. #endregion
  429. #region Lifetime Services
  430. public override object InitializeLifetimeService()
  431. {
  432. //Returning null holds the object alive
  433. //until it is explicitly destroyed
  434. return null;
  435. }
  436. #endregion
  437. public void RecordingStartedProxyHandler(CaptureConfig config)
  438. {
  439. if (RecordingStarted != null)
  440. RecordingStarted(config);
  441. }
  442. public void RecordingStoppedProxyHandler()
  443. {
  444. if (RecordingStopped != null)
  445. RecordingStopped();
  446. }
  447. public void DisconnectedProxyHandler()
  448. {
  449. if (Disconnected != null)
  450. Disconnected();
  451. }
  452. public void ScreenshotRequestedProxyHandler(ScreenshotRequest request)
  453. {
  454. if (ScreenshotRequested != null)
  455. ScreenshotRequested(request);
  456. }
  457. public void DisplayTextProxyHandler(DisplayTextEventArgs args)
  458. {
  459. if (DisplayText != null)
  460. DisplayText(args);
  461. }
  462. public void DrawOverlayProxyHandler(DrawOverlayEventArgs args)
  463. {
  464. if (DrawOverlay != null)
  465. DrawOverlay(args);
  466. }
  467. }
  468. }