1 //Simplified BSD License (BSD-2-Clause)
2 //
3 //Copyright (c) 2021, Marc Riera, The OpenBVE Project
4 //
5 //Redistribution and use in source and binary forms, with or without
6 //modification, are permitted provided that the following conditions are met:
7 //
8 //1. Redistributions of source code must retain the above copyright notice, this
9 //   list of conditions and the following disclaimer.
10 //2. Redistributions in binary form must reproduce the above copyright notice,
11 //   this list of conditions and the following disclaimer in the documentation
12 //   and/or other materials provided with the distribution.
13 //
14 //THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
15 //ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
16 //WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
17 //DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
18 //ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
19 //(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
20 //LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
21 //ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 //(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
23 //SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 
25 using LibUsbDotNet;
26 using LibUsbDotNet.Main;
27 using OpenBveApi;
28 using OpenBveApi.Interface;
29 using System;
30 using System.Collections.Generic;
31 using System.Globalization;
32 using System.Threading;
33 
34 namespace DenshaDeGoInput
35 {
36 	/// <summary>
37 	/// Class LibUsb-related functions.
38 	/// </summary>
39 	internal partial class LibUsb
40 	{
41 		/// <summary>
42 		/// Dictionary containing the supported USB controllers
43 		/// </summary>
44 		private static Dictionary<Guid, UsbController> supportedUsbControllers = new Dictionary<Guid, UsbController>();
45 
46 		/// <summary>
47 		/// GUID of the active controller
48 		/// </summary>
49 		private static Guid activeControllerGuid = new Guid();
50 
51 		/// <summary>
52 		/// The thread which spins to poll for LibUsb input
53 		/// </summary>
54 		internal static Thread LibUsbThread;
55 
56 		/// <summary>
57 		/// The control variable for the LibUsb input thread
58 		/// </summary>
59 		internal static bool LibUsbShouldLoop = true;
60 
61 		/// <summary>
62 		/// The setup packet needed to send data to the controller.
63 		/// </summary>
64 		private static UsbSetupPacket setupPacket = new UsbSetupPacket(0x40, 0x09, 0x0301, 0x0000, 0x0008);
65 
66 		/// <summary>
67 		/// Adds the supported controller models to the LibUsb list.
68 		/// </summary>
69 		/// <param name="ids">A list of VID+PID identifiers to search</param>
AddSupportedControllers(string[] ids)70 		internal static void AddSupportedControllers(string[] ids)
71 		{
72 			foreach (string id in ids)
73 			{
74 				int vid = int.Parse(id.Substring(0, 4), NumberStyles.HexNumber);
75 				int pid = int.Parse(id.Substring(5, 4), NumberStyles.HexNumber);
76 				Guid guid;
77 				switch (DenshaDeGoInput.CurrentHost.Platform)
78 				{
79 					case OpenBveApi.Hosts.HostPlatform.MicrosoftWindows:
80 						guid = new Guid(id.Substring(5, 4) + id.Substring(0, 4) + "-ffff-ffff-ffff-ffffffffffff");
81 						break;
82 					default:
83 						string vendor = id.Substring(2, 2) + id.Substring(0, 2);
84 						string product = id.Substring(7, 2) + id.Substring(5, 2);
85 						guid = new Guid("ffffffff-" + vendor + "-ffff-" + product + "-ffffffffffff");
86 						break;
87 				}
88 				UsbController controller = new UsbController(vid, pid);
89 				if (!supportedUsbControllers.ContainsKey(guid))
90 				{
91 					// Add new controller
92 					supportedUsbControllers.Add(guid, controller);
93 				}
94 				else
95 				{
96 					// Replace existing controller
97 					supportedUsbControllers[guid] = controller;
98 				}
99 			}
100 		}
101 
102 		/// <summary>
103 		/// Loop to be executed on a separate thread to handle LibUsb work.
104 		/// </summary>
LibUsbLoop()105 		internal static void LibUsbLoop()
106 		{
107 			while (LibUsbShouldLoop && !DenshaDeGoInput.LibUsbIssue)
108 			{
109 				// First, let's check which USB devices are connected
110 				CheckConnectedControllers();
111 
112 				if (activeControllerGuid != InputTranslator.ActiveControllerGuid)
113 				{
114 					if (supportedUsbControllers.ContainsKey(activeControllerGuid))
115 					{
116 						// If the selected controller has changed, unload the previous one
117 						supportedUsbControllers[activeControllerGuid].Unload();
118 					}
119 					activeControllerGuid = InputTranslator.ActiveControllerGuid;
120 				}
121 
122 				// If the current controller is a supported controller and is connected, poll it for input
123 				if (supportedUsbControllers.ContainsKey(activeControllerGuid) && supportedUsbControllers[activeControllerGuid].IsConnected)
124 				{
125 					supportedUsbControllers[activeControllerGuid].Poll();
126 				}
127 			}
128 
129 			foreach (var controller in supportedUsbControllers.Values)
130 			{
131 				controller.Unload();
132 			}
133 		}
134 
135 		/// <summary>
136 		/// Checks the connection status of supported LibUsb controllers.
137 		/// </summary>
CheckConnectedControllers()138 		private static void CheckConnectedControllers()
139 		{
140 			if (DenshaDeGoInput.LibUsbIssue)
141 			{
142 				return;
143 			}
144 			try
145 			{
146 				foreach (UsbController controller in supportedUsbControllers.Values)
147 				{
148 					if (controller.ControllerDevice == null || !controller.IsConnected)
149 					{
150 						// The device is not configured, try to find it
151 						controller.ControllerDevice = UsbDevice.OpenUsbDevice(new UsbDeviceFinder(controller.VendorID, controller.ProductID));
152 					}
153 					if (controller.ControllerDevice == null)
154 					{
155 						// The controller is not connected
156 						controller.IsConnected = false;
157 					}
158 					else
159 					{
160 						if (!controller.IsConnected)
161 						{
162 							// Open endpoint reader, if necessary
163 							controller.ControllerReader = controller.ControllerDevice.OpenEndpointReader(ReadEndpointID.Ep01);
164 						}
165 						// The controller is connected
166 						controller.IsConnected = true;
167 					}
168 				}
169 			}
170 			catch (Exception ex)
171 			{
172 				Console.WriteLine(ex.StackTrace);
173 				if (DenshaDeGoInput.CurrentHost.SimulationState == SimulationState.Running)
174 				{
175 					DenshaDeGoInput.CurrentHost.AddMessage(MessageType.Error, false, "The DenshaDeGo! Input Plugin encountered a critical error whilst attempting to update the connected controller list.");
176 				}
177 				//LibUsb isn't working right
178 				DenshaDeGoInput.LibUsbIssue = true;
179 			}
180 		}
181 
182 		/// <summary>
183 		/// Gets the list of supported controllers.
184 		/// </summary>
185 		/// <returns>The list of supported controllers.</returns>
GetSupportedControllers()186 		internal static Dictionary<Guid, UsbController> GetSupportedControllers()
187 		{
188 			return supportedUsbControllers;
189 		}
190 
191 		/// <summary>
192 		/// Syncs the read and input buffers for the controller with the specified GUID.
193 		/// </summary>
194 		/// <param name="guid">The GUID of the controller</param>
195 		/// <param name="read">An array containing the previous read buffer</param>
196 		/// <param name="write">The bytes to be sent to the controller</param>
197 		/// <returns>The bytes read from the controller.</returns>
SyncController(Guid guid, byte[] read, byte[] write)198 		internal static byte[] SyncController(Guid guid, byte[] read, byte[] write)
199 		{
200 			// If the read buffer's length is 0, copy the initial input bytes to get the required read length
201 			if (supportedUsbControllers[guid].ReadBuffer.Length == 0)
202 			{
203 				supportedUsbControllers[guid].ReadBuffer = read;
204 			}
205 			// Copy the output bytes to the write buffer
206 			supportedUsbControllers[guid].WriteBuffer = write;
207 			// If the length of the byte array to be sent to the controller when unloaded is 0, use the write buffer
208 			if (supportedUsbControllers[guid].UnloadBuffer.Length == 0)
209 			{
210 				supportedUsbControllers[guid].UnloadBuffer = write;
211 			}
212 			// Return the bytes from the read buffer
213 			return supportedUsbControllers[guid].ReadBuffer;
214 		}
215 
216 	}
217 }
218