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