1 // Copyright 2008 Dolphin Emulator Project
2 // Licensed under GPLv2+
3 // Refer to the license.txt file included.
4 
5 #include "Core/HW/EXI/BBA/TAP_Win32.h"
6 #include "Common/Assert.h"
7 #include "Common/Logging/Log.h"
8 #include "Common/MsgHandler.h"
9 #include "Common/StringUtil.h"
10 #include "Core/HW/EXI/EXI_Device.h"
11 #include "Core/HW/EXI/EXI_DeviceEthernet.h"
12 
13 namespace Win32TAPHelper
14 {
IsTAPDevice(const TCHAR * guid)15 bool IsTAPDevice(const TCHAR* guid)
16 {
17   HKEY netcard_key;
18   LONG status;
19   DWORD len;
20   int i = 0;
21 
22   status = RegOpenKeyEx(HKEY_LOCAL_MACHINE, ADAPTER_KEY, 0, KEY_READ, &netcard_key);
23 
24   if (status != ERROR_SUCCESS)
25     return false;
26 
27   for (;;)
28   {
29     TCHAR enum_name[256];
30     TCHAR unit_string[256];
31     HKEY unit_key;
32     TCHAR component_id_string[] = _T("ComponentId");
33     TCHAR component_id[256];
34     TCHAR net_cfg_instance_id_string[] = _T("NetCfgInstanceId");
35     TCHAR net_cfg_instance_id[256];
36     DWORD data_type;
37 
38     len = _countof(enum_name);
39     status = RegEnumKeyEx(netcard_key, i, enum_name, &len, nullptr, nullptr, nullptr, nullptr);
40 
41     if (status == ERROR_NO_MORE_ITEMS)
42       break;
43     else if (status != ERROR_SUCCESS)
44       return false;
45 
46     _sntprintf(unit_string, _countof(unit_string), _T("%s\\%s"), ADAPTER_KEY, enum_name);
47     unit_string[_countof(unit_string) - 1] = _T('\0');
48 
49     status = RegOpenKeyEx(HKEY_LOCAL_MACHINE, unit_string, 0, KEY_READ, &unit_key);
50 
51     if (status != ERROR_SUCCESS)
52     {
53       return false;
54     }
55     else
56     {
57       len = sizeof(component_id);
58       status = RegQueryValueEx(unit_key, component_id_string, nullptr, &data_type,
59                                (LPBYTE)component_id, &len);
60 
61       if (!(status != ERROR_SUCCESS || data_type != REG_SZ))
62       {
63         len = sizeof(net_cfg_instance_id);
64         status = RegQueryValueEx(unit_key, net_cfg_instance_id_string, nullptr, &data_type,
65                                  (LPBYTE)net_cfg_instance_id, &len);
66 
67         if (status == ERROR_SUCCESS && data_type == REG_SZ)
68         {
69           if (!_tcscmp(component_id, TAP_COMPONENT_ID) && !_tcscmp(net_cfg_instance_id, guid))
70           {
71             RegCloseKey(unit_key);
72             RegCloseKey(netcard_key);
73             return true;
74           }
75         }
76       }
77       RegCloseKey(unit_key);
78     }
79     ++i;
80   }
81 
82   RegCloseKey(netcard_key);
83   return false;
84 }
85 
GetGUIDs(std::vector<std::basic_string<TCHAR>> & guids)86 bool GetGUIDs(std::vector<std::basic_string<TCHAR>>& guids)
87 {
88   LONG status;
89   HKEY control_net_key;
90   DWORD len;
91   DWORD cSubKeys = 0;
92 
93   status = RegOpenKeyEx(HKEY_LOCAL_MACHINE, NETWORK_CONNECTIONS_KEY, 0, KEY_READ | KEY_QUERY_VALUE,
94                         &control_net_key);
95 
96   if (status != ERROR_SUCCESS)
97     return false;
98 
99   status = RegQueryInfoKey(control_net_key, nullptr, nullptr, nullptr, &cSubKeys, nullptr, nullptr,
100                            nullptr, nullptr, nullptr, nullptr, nullptr);
101 
102   if (status != ERROR_SUCCESS)
103     return false;
104 
105   for (DWORD i = 0; i < cSubKeys; i++)
106   {
107     TCHAR enum_name[256];
108     TCHAR connection_string[256];
109     HKEY connection_key;
110     TCHAR name_data[256];
111     DWORD name_type;
112     const TCHAR name_string[] = _T("Name");
113 
114     len = _countof(enum_name);
115     status = RegEnumKeyEx(control_net_key, i, enum_name, &len, nullptr, nullptr, nullptr, nullptr);
116 
117     if (status != ERROR_SUCCESS)
118       continue;
119 
120     _sntprintf(connection_string, _countof(connection_string), _T("%s\\%s\\Connection"),
121                NETWORK_CONNECTIONS_KEY, enum_name);
122     connection_string[_countof(connection_string) - 1] = _T('\0');
123 
124     status = RegOpenKeyEx(HKEY_LOCAL_MACHINE, connection_string, 0, KEY_READ, &connection_key);
125 
126     if (status == ERROR_SUCCESS)
127     {
128       len = sizeof(name_data);
129       status = RegQueryValueEx(connection_key, name_string, nullptr, &name_type, (LPBYTE)name_data,
130                                &len);
131 
132       if (status != ERROR_SUCCESS || name_type != REG_SZ)
133       {
134         continue;
135       }
136       else
137       {
138         if (IsTAPDevice(enum_name))
139         {
140           guids.push_back(enum_name);
141         }
142       }
143 
144       RegCloseKey(connection_key);
145     }
146   }
147 
148   RegCloseKey(control_net_key);
149 
150   return !guids.empty();
151 }
152 
OpenTAP(HANDLE & adapter,const std::basic_string<TCHAR> & device_guid)153 bool OpenTAP(HANDLE& adapter, const std::basic_string<TCHAR>& device_guid)
154 {
155   auto const device_path = USERMODEDEVICEDIR + device_guid + TAPSUFFIX;
156 
157   adapter = CreateFile(device_path.c_str(), GENERIC_READ | GENERIC_WRITE, 0, nullptr, OPEN_EXISTING,
158                        FILE_ATTRIBUTE_SYSTEM | FILE_FLAG_OVERLAPPED, nullptr);
159 
160   if (adapter == INVALID_HANDLE_VALUE)
161   {
162     INFO_LOG(SP1, "Failed to open TAP at %s", device_path.c_str());
163     return false;
164   }
165   return true;
166 }
167 
168 }  // namespace Win32TAPHelper
169 
170 namespace ExpansionInterface
171 {
Activate()172 bool CEXIETHERNET::TAPNetworkInterface::Activate()
173 {
174   if (IsActivated())
175     return true;
176 
177   DWORD len;
178   std::vector<std::basic_string<TCHAR>> device_guids;
179 
180   if (!Win32TAPHelper::GetGUIDs(device_guids))
181   {
182     ERROR_LOG(SP1, "Failed to find a TAP GUID");
183     return false;
184   }
185 
186   for (size_t i = 0; i < device_guids.size(); i++)
187   {
188     if (Win32TAPHelper::OpenTAP(mHAdapter, device_guids.at(i)))
189     {
190       INFO_LOG(SP1, "OPENED %s", device_guids.at(i).c_str());
191       break;
192     }
193   }
194   if (mHAdapter == INVALID_HANDLE_VALUE)
195   {
196     PanicAlert("Failed to open any TAP");
197     return false;
198   }
199 
200   /* get driver version info */
201   ULONG info[3]{};
202   if (DeviceIoControl(mHAdapter, TAP_IOCTL_GET_VERSION, &info, sizeof(info), &info, sizeof(info),
203                       &len, nullptr))
204   {
205     INFO_LOG(SP1, "TAP-Win32 Driver Version %d.%d %s", info[0], info[1], info[2] ? "(DEBUG)" : "");
206   }
207   if (!(info[0] > TAP_WIN32_MIN_MAJOR ||
208         (info[0] == TAP_WIN32_MIN_MAJOR && info[1] >= TAP_WIN32_MIN_MINOR)))
209   {
210     PanicAlertT("ERROR: This version of Dolphin requires a TAP-Win32 driver"
211                 " that is at least version %d.%d -- If you recently upgraded your Dolphin"
212                 " distribution, a reboot is probably required at this point to get"
213                 " Windows to see the new driver.",
214                 TAP_WIN32_MIN_MAJOR, TAP_WIN32_MIN_MINOR);
215     return false;
216   }
217 
218   /* set driver media status to 'connected' */
219   ULONG status = TRUE;
220   if (!DeviceIoControl(mHAdapter, TAP_IOCTL_SET_MEDIA_STATUS, &status, sizeof(status), &status,
221                        sizeof(status), &len, nullptr))
222   {
223     ERROR_LOG(SP1, "WARNING: The TAP-Win32 driver rejected a"
224                    "TAP_IOCTL_SET_MEDIA_STATUS DeviceIoControl call.");
225     return false;
226   }
227 
228   /* initialize read/write events */
229   mReadOverlapped.hEvent = CreateEvent(nullptr, TRUE, FALSE, nullptr);
230   mWriteOverlapped.hEvent = CreateEvent(nullptr, TRUE, FALSE, nullptr);
231   if (mReadOverlapped.hEvent == nullptr || mWriteOverlapped.hEvent == nullptr)
232     return false;
233 
234   mWriteBuffer.reserve(1518);
235   return RecvInit();
236 }
237 
Deactivate()238 void CEXIETHERNET::TAPNetworkInterface::Deactivate()
239 {
240   if (!IsActivated())
241     return;
242 
243   // Signal read thread to exit.
244   readEnabled.Clear();
245   readThreadShutdown.Set();
246 
247   // Cancel any outstanding requests from both this thread (writes), and the read thread.
248   CancelIoEx(mHAdapter, nullptr);
249 
250   // Wait for read thread to exit.
251   if (readThread.joinable())
252     readThread.join();
253 
254   // Clean-up handles
255   CloseHandle(mReadOverlapped.hEvent);
256   CloseHandle(mWriteOverlapped.hEvent);
257   CloseHandle(mHAdapter);
258   mHAdapter = INVALID_HANDLE_VALUE;
259   memset(&mReadOverlapped, 0, sizeof(mReadOverlapped));
260   memset(&mWriteOverlapped, 0, sizeof(mWriteOverlapped));
261 }
262 
IsActivated()263 bool CEXIETHERNET::TAPNetworkInterface::IsActivated()
264 {
265   return mHAdapter != INVALID_HANDLE_VALUE;
266 }
267 
ReadThreadHandler(TAPNetworkInterface * self)268 void CEXIETHERNET::TAPNetworkInterface::ReadThreadHandler(TAPNetworkInterface* self)
269 {
270   while (!self->readThreadShutdown.IsSet())
271   {
272     DWORD transferred;
273 
274     // Read from TAP into internal buffer.
275     if (ReadFile(self->mHAdapter, self->m_eth_ref->mRecvBuffer.get(), BBA_RECV_SIZE, &transferred,
276                  &self->mReadOverlapped))
277     {
278       // Returning immediately is not likely to happen, but if so, reset the event state manually.
279       ResetEvent(self->mReadOverlapped.hEvent);
280     }
281     else
282     {
283       // IO should be pending.
284       if (GetLastError() != ERROR_IO_PENDING)
285       {
286         ERROR_LOG(SP1, "ReadFile failed (err=0x%X)", GetLastError());
287         continue;
288       }
289 
290       // Block until the read completes.
291       if (!GetOverlappedResult(self->mHAdapter, &self->mReadOverlapped, &transferred, TRUE))
292       {
293         // If CancelIO was called, we should exit (the flag will be set).
294         if (GetLastError() == ERROR_OPERATION_ABORTED)
295           continue;
296 
297         // Something else went wrong.
298         ERROR_LOG(SP1, "GetOverlappedResult failed (err=0x%X)", GetLastError());
299         continue;
300       }
301     }
302 
303     // Copy to BBA buffer, and fire interrupt if enabled.
304     DEBUG_LOG(SP1, "Received %u bytes:\n %s", transferred,
305               ArrayToString(self->m_eth_ref->mRecvBuffer.get(), transferred, 0x10).c_str());
306     if (self->readEnabled.IsSet())
307     {
308       self->m_eth_ref->mRecvBufferLength = transferred;
309       self->m_eth_ref->RecvHandlePacket();
310     }
311   }
312 }
313 
SendFrame(const u8 * frame,u32 size)314 bool CEXIETHERNET::TAPNetworkInterface::SendFrame(const u8* frame, u32 size)
315 {
316   DEBUG_LOG(SP1, "SendFrame %u bytes:\n%s", size, ArrayToString(frame, size, 0x10).c_str());
317 
318   // Check for a background write. We can't issue another one until this one has completed.
319   DWORD transferred;
320   if (mWritePending)
321   {
322     // Wait for previous write to complete.
323     if (!GetOverlappedResult(mHAdapter, &mWriteOverlapped, &transferred, TRUE))
324       ERROR_LOG(SP1, "GetOverlappedResult failed (err=0x%X)", GetLastError());
325   }
326 
327   // Copy to write buffer.
328   mWriteBuffer.assign(frame, frame + size);
329   mWritePending = true;
330 
331   // Queue async write.
332   if (WriteFile(mHAdapter, mWriteBuffer.data(), size, &transferred, &mWriteOverlapped))
333   {
334     // Returning immediately is not likely to happen, but if so, reset the event state manually.
335     ResetEvent(mWriteOverlapped.hEvent);
336   }
337   else
338   {
339     // IO should be pending.
340     if (GetLastError() != ERROR_IO_PENDING)
341     {
342       ERROR_LOG(SP1, "WriteFile failed (err=0x%X)", GetLastError());
343       ResetEvent(mWriteOverlapped.hEvent);
344       mWritePending = false;
345       return false;
346     }
347   }
348 
349   // Always report the packet as being sent successfully, even though it might be a lie
350   m_eth_ref->SendComplete();
351   return true;
352 }
353 
RecvInit()354 bool CEXIETHERNET::TAPNetworkInterface::RecvInit()
355 {
356   readThread = std::thread(ReadThreadHandler, this);
357   return true;
358 }
359 
RecvStart()360 void CEXIETHERNET::TAPNetworkInterface::RecvStart()
361 {
362   readEnabled.Set();
363 }
364 
RecvStop()365 void CEXIETHERNET::TAPNetworkInterface::RecvStop()
366 {
367   readEnabled.Clear();
368 }
369 }  // namespace ExpansionInterface
370