1 #include "pch.h"
2 #include "PPSSPP_UWPMain.h"
3
4 #include <mutex>
5
6 #include "Common/File/FileUtil.h"
7 #include "Common/Net/HTTPClient.h"
8 #include "Common/Net/Resolve.h"
9 #include "Common/GPU/thin3d_create.h"
10
11 #include "Common/Common.h"
12 #include "Common/Input/InputState.h"
13 #include "Common/File/VFS/VFS.h"
14 #include "Common/File/VFS/AssetReader.h"
15 #include "Common/Thread/ThreadUtil.h"
16 #include "Common/Data/Encoding/Utf8.h"
17 #include "Common/DirectXHelper.h"
18 #include "Common/File/FileUtil.h"
19 #include "Common/Log.h"
20 #include "Common/LogManager.h"
21 #include "Common/TimeUtil.h"
22 #include "Common/StringUtils.h"
23 #include "Common/System/Display.h"
24 #include "Common/System/NativeApp.h"
25 #include "Common/System/System.h"
26
27 #include "Core/System.h"
28 #include "Core/Loaders.h"
29 #include "Core/Config.h"
30
31 #include "NKCodeFromWindowsSystem.h"
32 #include "XAudioSoundStream.h"
33 #include "UWPHost.h"
34 #include "UWPUtil.h"
35 #include "StorageFileLoader.h"
36 #include "App.h"
37
38 using namespace UWP;
39 using namespace Windows::Foundation;
40 using namespace Windows::Storage;
41 using namespace Windows::Storage::Streams;
42 using namespace Windows::System::Threading;
43 using namespace Windows::ApplicationModel::DataTransfer;
44 using namespace Windows::Devices::Enumeration;
45 using namespace Concurrency;
46
47 // UGLY!
48 PPSSPP_UWPMain *g_main;
49 extern WindowsAudioBackend *winAudioBackend;
50 std::string langRegion;
51 // TODO: Use Microsoft::WRL::ComPtr<> for D3D11 objects?
52 // TODO: See https://github.com/Microsoft/Windows-universal-samples/tree/master/Samples/WindowsAudioSession for WASAPI with UWP
53 // TODO: Low latency input: https://github.com/Microsoft/Windows-universal-samples/tree/master/Samples/LowLatencyInput/cpp
54
55 // Loads and initializes application assets when the application is loaded.
56 PPSSPP_UWPMain::PPSSPP_UWPMain(App ^app, const std::shared_ptr<DX::DeviceResources>& deviceResources) :
57 app_(app),
58 m_deviceResources(deviceResources)
59 {
60 g_main = this;
61
62 net::Init();
63
64 host = new UWPHost();
65 // Register to be notified if the Device is lost or recreated
66 m_deviceResources->RegisterDeviceNotify(this);
67
68 // create_task(KnownFolders::GetFolderForUserAsync(nullptr, KnownFolderId::RemovableDevices)).then([this](StorageFolder ^));
69
70 // TODO: Change the timer settings if you want something other than the default variable timestep mode.
71 // e.g. for 60 FPS fixed timestep update logic, call:
72 /*
73 m_timer.SetFixedTimeStep(true);
74 m_timer.SetTargetElapsedSeconds(1.0 / 60);
75 */
76
77 ctx_.reset(new UWPGraphicsContext(deviceResources));
78
79 const Path &exePath = File::GetExeDirectory();
80 VFSRegister("", new DirectoryAssetReader(exePath / "Content"));
81 VFSRegister("", new DirectoryAssetReader(exePath));
82
83 wchar_t lcCountry[256];
84
85 if (0 != GetLocaleInfoEx(LOCALE_NAME_USER_DEFAULT, LOCALE_SNAME, lcCountry, 256)) {
86 langRegion = ConvertWStringToUTF8(lcCountry);
87 for (size_t i = 0; i < langRegion.size(); i++) {
88 if (langRegion[i] == '-')
89 langRegion[i] = '_';
90 }
91 } else {
92 langRegion = "en_US";
93 }
94
95 std::wstring memstickFolderW = ApplicationData::Current->LocalFolder->Path->Data();
96 g_Config.memStickDirectory = Path(memstickFolderW);
97
98 // On Win32 it makes more sense to initialize the system directories here
99 // because the next place it was called was in the EmuThread, and it's too late by then.
100 InitSysDirectories();
101
102 LogManager::Init(&g_Config.bEnableLogging);
103
104 // Load config up here, because those changes below would be overwritten
105 // if it's not loaded here first.
106 g_Config.SetSearchPath(GetSysDirectory(DIRECTORY_SYSTEM));
107 g_Config.Load();
108
109 bool debugLogLevel = false;
110
111 g_Config.iGPUBackend = (int)GPUBackend::DIRECT3D11;
112
113 if (debugLogLevel) {
114 LogManager::GetInstance()->SetAllLogLevels(LogTypes::LDEBUG);
115 }
116
117 const char *argv[2] = { "fake", nullptr };
118
119
120 std::string cacheFolder = ConvertWStringToUTF8(ApplicationData::Current->LocalFolder->Path->Data());
121
122 NativeInit(1, argv, "", "", cacheFolder.c_str());
123
124 NativeInitGraphics(ctx_.get());
125 NativeResized();
126
127 int width = m_deviceResources->GetScreenViewport().Width;
128 int height = m_deviceResources->GetScreenViewport().Height;
129
130 ctx_->GetDrawContext()->HandleEvent(Draw::Event::GOT_BACKBUFFER, width, height, m_deviceResources->GetBackBufferRenderTargetView());
131 InputDevice::BeginPolling();
132 }
133
~PPSSPP_UWPMain()134 PPSSPP_UWPMain::~PPSSPP_UWPMain() {
135 InputDevice::StopPolling();
136 ctx_->GetDrawContext()->HandleEvent(Draw::Event::LOST_BACKBUFFER, 0, 0, nullptr);
137 NativeShutdownGraphics();
138 NativeShutdown();
139
140 // Deregister device notification
141 m_deviceResources->RegisterDeviceNotify(nullptr);
142 net::Shutdown();
143 }
144
145 // Updates application state when the window size changes (e.g. device orientation change)
CreateWindowSizeDependentResources()146 void PPSSPP_UWPMain::CreateWindowSizeDependentResources() {
147 ctx_->GetDrawContext()->HandleEvent(Draw::Event::LOST_BACKBUFFER, 0, 0, nullptr);
148
149 NativeResized();
150
151 int width = m_deviceResources->GetScreenViewport().Width;
152 int height = m_deviceResources->GetScreenViewport().Height;
153 ctx_->GetDrawContext()->HandleEvent(Draw::Event::GOT_BACKBUFFER, width, height, m_deviceResources->GetBackBufferRenderTargetView());
154 }
155
156 // Renders the current frame according to the current application state.
157 // Returns true if the frame was rendered and is ready to be displayed.
Render()158 bool PPSSPP_UWPMain::Render() {
159 ctx_->GetDrawContext()->HandleEvent(Draw::Event::PRESENTED, 0, 0, nullptr, nullptr);
160 NativeUpdate();
161
162 static bool hasSetThreadName = false;
163 if (!hasSetThreadName) {
164 SetCurrentThreadName("UWPRenderThread");
165 hasSetThreadName = true;
166 }
167
168 auto context = m_deviceResources->GetD3DDeviceContext();
169
170 switch (m_deviceResources->ComputeDisplayRotation()) {
171 case DXGI_MODE_ROTATION_IDENTITY: g_display_rotation = DisplayRotation::ROTATE_0; break;
172 case DXGI_MODE_ROTATION_ROTATE90: g_display_rotation = DisplayRotation::ROTATE_90; break;
173 case DXGI_MODE_ROTATION_ROTATE180: g_display_rotation = DisplayRotation::ROTATE_180; break;
174 case DXGI_MODE_ROTATION_ROTATE270: g_display_rotation = DisplayRotation::ROTATE_270; break;
175 }
176 // Not super elegant but hey.
177 memcpy(&g_display_rot_matrix, &m_deviceResources->GetOrientationTransform3D(), sizeof(float) * 16);
178
179 // Reset the viewport to target the whole screen.
180 auto viewport = m_deviceResources->GetScreenViewport();
181
182 pixel_xres = viewport.Width;
183 pixel_yres = viewport.Height;
184
185 if (g_display_rotation == DisplayRotation::ROTATE_90 || g_display_rotation == DisplayRotation::ROTATE_270) {
186 // We need to swap our width/height.
187 std::swap(pixel_xres, pixel_yres);
188 }
189
190 g_dpi = m_deviceResources->GetActualDpi();
191
192 if (System_GetPropertyInt(SYSPROP_DEVICE_TYPE) == DEVICE_TYPE_MOBILE) {
193 // Boost DPI a bit to look better.
194 g_dpi *= 96.0f / 136.0f;
195 }
196 g_dpi_scale_x = 96.0f / g_dpi;
197 g_dpi_scale_y = 96.0f / g_dpi;
198
199 pixel_in_dps_x = 1.0f / g_dpi_scale_x;
200 pixel_in_dps_y = 1.0f / g_dpi_scale_y;
201
202 dp_xres = pixel_xres * g_dpi_scale_x;
203 dp_yres = pixel_yres * g_dpi_scale_y;
204
205 context->RSSetViewports(1, &viewport);
206
207 NativeRender(ctx_.get());
208 return true;
209 }
210
211 // Notifies renderers that device resources need to be released.
OnDeviceLost()212 void PPSSPP_UWPMain::OnDeviceLost() {
213 ctx_->GetDrawContext()->HandleEvent(Draw::Event::LOST_DEVICE, 0, 0, nullptr);
214 }
215
216 // Notifies renderers that device resources may now be recreated.
OnDeviceRestored()217 void PPSSPP_UWPMain::OnDeviceRestored() {
218 CreateWindowSizeDependentResources();
219
220 ctx_->GetDrawContext()->HandleEvent(Draw::Event::GOT_DEVICE, 0, 0, nullptr);
221 }
222
OnKeyDown(int scanCode,Windows::System::VirtualKey virtualKey,int repeatCount)223 void PPSSPP_UWPMain::OnKeyDown(int scanCode, Windows::System::VirtualKey virtualKey, int repeatCount) {
224 auto iter = virtualKeyCodeToNKCode.find(virtualKey);
225 if (iter != virtualKeyCodeToNKCode.end()) {
226 KeyInput key{};
227 key.deviceId = DEVICE_ID_KEYBOARD;
228 key.keyCode = iter->second;
229 key.flags = KEY_DOWN | (repeatCount > 1 ? KEY_IS_REPEAT : 0);
230 NativeKey(key);
231 }
232 }
233
OnKeyUp(int scanCode,Windows::System::VirtualKey virtualKey)234 void PPSSPP_UWPMain::OnKeyUp(int scanCode, Windows::System::VirtualKey virtualKey) {
235 auto iter = virtualKeyCodeToNKCode.find(virtualKey);
236 if (iter != virtualKeyCodeToNKCode.end()) {
237 KeyInput key{};
238 key.deviceId = DEVICE_ID_KEYBOARD;
239 key.keyCode = iter->second;
240 key.flags = KEY_UP;
241 NativeKey(key);
242 }
243 }
244
OnMouseWheel(float delta)245 void PPSSPP_UWPMain::OnMouseWheel(float delta) {
246 int key = NKCODE_EXT_MOUSEWHEEL_UP;
247 if (delta < 0) {
248 key = NKCODE_EXT_MOUSEWHEEL_DOWN;
249 } else if (delta == 0) {
250 return;
251 }
252
253 KeyInput keyInput{};
254 keyInput.keyCode = key;
255 keyInput.deviceId = DEVICE_ID_MOUSE;
256 keyInput.flags = KEY_DOWN | KEY_UP;
257 NativeKey(keyInput);
258 }
259
OnHardwareButton(HardwareButton button)260 bool PPSSPP_UWPMain::OnHardwareButton(HardwareButton button) {
261 KeyInput keyInput{};
262 keyInput.deviceId = DEVICE_ID_KEYBOARD;
263 keyInput.flags = KEY_DOWN | KEY_UP;
264 switch (button) {
265 case HardwareButton::BACK:
266 keyInput.keyCode = NKCODE_BACK;
267 return NativeKey(keyInput);
268 default:
269 return false;
270 }
271 }
272
OnTouchEvent(int touchEvent,int touchId,float x,float y,double timestamp)273 void PPSSPP_UWPMain::OnTouchEvent(int touchEvent, int touchId, float x, float y, double timestamp) {
274 // We get the coordinate in Windows' device independent pixels already. So let's undo that,
275 // and then apply our own "dpi".
276 float dpiFactor_x = m_deviceResources->GetActualDpi() / 96.0f;
277 float dpiFactor_y = dpiFactor_x;
278 dpiFactor_x /= pixel_in_dps_x;
279 dpiFactor_y /= pixel_in_dps_y;
280
281 TouchInput input{};
282 input.id = touchId;
283 input.x = x * dpiFactor_x;
284 input.y = y * dpiFactor_y;
285 input.flags = touchEvent;
286 input.timestamp = timestamp;
287 NativeTouch(input);
288
289 KeyInput key{};
290 key.deviceId = DEVICE_ID_MOUSE;
291 if (touchEvent & TOUCH_DOWN) {
292 key.keyCode = NKCODE_EXT_MOUSEBUTTON_1;
293 key.flags = KEY_DOWN;
294 NativeKey(key);
295 }
296 if (touchEvent & TOUCH_UP) {
297 key.keyCode = NKCODE_EXT_MOUSEBUTTON_1;
298 key.flags = KEY_UP;
299 NativeKey(key);
300 }
301 }
302
OnSuspend()303 void PPSSPP_UWPMain::OnSuspend() {
304 // TODO
305 }
306
307 void PPSSPP_UWPMain::LoadStorageFile(StorageFile ^file) {
308 std::unique_ptr<FileLoaderFactory> factory(new StorageFileLoaderFactory(file, IdentifiedFileType::PSP_ISO));
309 RegisterFileLoaderFactory("override://", std::move(factory));
310 NativeMessageReceived("boot", "override://file");
311 }
312
UWPGraphicsContext(std::shared_ptr<DX::DeviceResources> resources)313 UWPGraphicsContext::UWPGraphicsContext(std::shared_ptr<DX::DeviceResources> resources) {
314 std::vector<std::string> adapterNames;
315
316 draw_ = Draw::T3DCreateD3D11Context(
317 resources->GetD3DDevice(), resources->GetD3DDeviceContext(), resources->GetD3DDevice(), resources->GetD3DDeviceContext(), resources->GetDeviceFeatureLevel(), 0, adapterNames);
318 bool success = draw_->CreatePresets();
319 _assert_(success);
320 }
321
Shutdown()322 void UWPGraphicsContext::Shutdown() {
323 delete draw_;
324 }
325
SwapInterval(int interval)326 void UWPGraphicsContext::SwapInterval(int interval) {
327
328 }
329
System_GetProperty(SystemProperty prop)330 std::string System_GetProperty(SystemProperty prop) {
331 static bool hasCheckedGPUDriverVersion = false;
332 switch (prop) {
333 case SYSPROP_NAME:
334 return "Windows 10 Universal";
335 case SYSPROP_LANGREGION:
336 return langRegion;
337 case SYSPROP_CLIPBOARD_TEXT:
338 /* TODO: Need to either change this API or do this on a thread in an ugly fashion.
339 DataPackageView ^view = Clipboard::GetContent();
340 if (view) {
341 string text = await view->GetTextAsync();
342 }
343 */
344 return "";
345 case SYSPROP_GPUDRIVER_VERSION:
346 return "";
347 default:
348 return "";
349 }
350 }
351
System_GetPropertyStringVec(SystemProperty prop)352 std::vector<std::string> System_GetPropertyStringVec(SystemProperty prop) {
353 std::vector<std::string> result;
354 switch (prop) {
355 case SYSPROP_TEMP_DIRS:
356 {
357 std::wstring tempPath(MAX_PATH, '\0');
358 size_t sz = GetTempPath((DWORD)tempPath.size(), &tempPath[0]);
359 if (sz >= tempPath.size()) {
360 tempPath.resize(sz);
361 sz = GetTempPath((DWORD)tempPath.size(), &tempPath[0]);
362 }
363 // Need to resize off the null terminator either way.
364 tempPath.resize(sz);
365 result.push_back(ConvertWStringToUTF8(tempPath));
366
367 if (getenv("TMPDIR") && strlen(getenv("TMPDIR")) != 0)
368 result.push_back(getenv("TMPDIR"));
369 if (getenv("TMP") && strlen(getenv("TMP")) != 0)
370 result.push_back(getenv("TMP"));
371 if (getenv("TEMP") && strlen(getenv("TEMP")) != 0)
372 result.push_back(getenv("TEMP"));
373 return result;
374 }
375
376 default:
377 return result;
378 }
379 }
380
System_GetPropertyInt(SystemProperty prop)381 int System_GetPropertyInt(SystemProperty prop) {
382 switch (prop) {
383 case SYSPROP_AUDIO_SAMPLE_RATE:
384 return winAudioBackend ? winAudioBackend->GetSampleRate() : -1;
385 case SYSPROP_DEVICE_TYPE:
386 {
387 auto ver = Windows::System::Profile::AnalyticsInfo::VersionInfo;
388 if (ver->DeviceFamily == "Windows.Mobile") {
389 return DEVICE_TYPE_MOBILE;
390 } else if (ver->DeviceFamily == "Windows.Xbox") {
391 return DEVICE_TYPE_TV;
392 } else {
393 return DEVICE_TYPE_DESKTOP;
394 }
395 }
396 default:
397 return -1;
398 }
399 }
400
System_GetPropertyFloat(SystemProperty prop)401 float System_GetPropertyFloat(SystemProperty prop) {
402 switch (prop) {
403 case SYSPROP_DISPLAY_REFRESH_RATE:
404 return 60.f;
405 case SYSPROP_DISPLAY_SAFE_INSET_LEFT:
406 case SYSPROP_DISPLAY_SAFE_INSET_RIGHT:
407 case SYSPROP_DISPLAY_SAFE_INSET_TOP:
408 case SYSPROP_DISPLAY_SAFE_INSET_BOTTOM:
409 return 0.0f;
410 default:
411 return -1;
412 }
413 }
414
VulkanMayBeAvailable()415 bool VulkanMayBeAvailable() {
416 return false;
417 }
418
System_GetPropertyBool(SystemProperty prop)419 bool System_GetPropertyBool(SystemProperty prop) {
420 switch (prop) {
421 case SYSPROP_HAS_FILE_BROWSER:
422 return true;
423 case SYSPROP_HAS_FOLDER_BROWSER:
424 return false; // at least I don't know a usable one
425 case SYSPROP_HAS_IMAGE_BROWSER:
426 return false;
427 case SYSPROP_HAS_BACK_BUTTON:
428 return true;
429 case SYSPROP_APP_GOLD:
430 #ifdef GOLD
431 return true;
432 #else
433 return false;
434 #endif
435 case SYSPROP_CAN_JIT:
436 return true;
437 default:
438 return false;
439 }
440 }
441
System_SendMessage(const char * command,const char * parameter)442 void System_SendMessage(const char *command, const char *parameter) {
443 using namespace concurrency;
444
445 if (!strcmp(command, "finish")) {
446 // Not really supposed to support this under UWP.
447 } else if (!strcmp(command, "browse_file")) {
448 auto picker = ref new Windows::Storage::Pickers::FileOpenPicker();
449 picker->ViewMode = Pickers::PickerViewMode::List;
450
451 // These are single files that can be loaded directly using StorageFileLoader.
452 picker->FileTypeFilter->Append(".cso");
453 picker->FileTypeFilter->Append(".iso");
454
455 // Can't load these this way currently, they require mounting the underlying folder.
456 // picker->FileTypeFilter->Append(".bin");
457 // picker->FileTypeFilter->Append(".elf");
458 picker->SuggestedStartLocation = Pickers::PickerLocationId::DocumentsLibrary;
459
460 create_task(picker->PickSingleFileAsync()).then([](StorageFile ^file){
461 if (file) {
462 g_main->LoadStorageFile(file);
463 }
464 });
465 } else if (!strcmp(command, "toggle_fullscreen")) {
466 auto view = Windows::UI::ViewManagement::ApplicationView::GetForCurrentView();
467 bool flag = !view->IsFullScreenMode;
468 if (strcmp(parameter, "0") == 0) {
469 flag = false;
470 } else if (strcmp(parameter, "1") == 0){
471 flag = true;
472 }
473 if (flag) {
474 view->TryEnterFullScreenMode();
475 } else {
476 view->ExitFullScreenMode();
477 }
478 }
479 }
480
OpenDirectory(const char * path)481 void OpenDirectory(const char *path) {
482 // Unsupported
483 }
484
LaunchBrowser(const char * url)485 void LaunchBrowser(const char *url) {
486 auto uri = ref new Windows::Foundation::Uri(ToPlatformString(url));
487
488 create_task(Windows::System::Launcher::LaunchUriAsync(uri)).then([](bool b) {});
489 }
490
Vibrate(int length_ms)491 void Vibrate(int length_ms) {
492 #if _M_ARM
493 if (length_ms == -1 || length_ms == -3)
494 length_ms = 50;
495 else if (length_ms == -2)
496 length_ms = 25;
497 else
498 return;
499
500 auto timeSpan = Windows::Foundation::TimeSpan();
501 timeSpan.Duration = length_ms * 10000;
502 // TODO: Can't use this?
503 // Windows::Phone::Devices::Notification::VibrationDevice::GetDefault()->Vibrate(timeSpan);
504 #endif
505 }
506
System_AskForPermission(SystemPermission permission)507 void System_AskForPermission(SystemPermission permission) {
508 // Do nothing
509 }
510
System_GetPermissionStatus(SystemPermission permission)511 PermissionStatus System_GetPermissionStatus(SystemPermission permission) {
512 return PERMISSION_STATUS_GRANTED;
513 }
514
System_InputBoxGetString(const std::string & title,const std::string & defaultValue,std::function<void (bool,const std::string &)> cb)515 void System_InputBoxGetString(const std::string &title, const std::string &defaultValue, std::function<void(bool, const std::string &)> cb) {
516 // TODO
517 cb(false, "");
518 }
519
GetCPUBrandString()520 std::string GetCPUBrandString() {
521 Platform::String^ cpu_id = nullptr;
522 Platform::String^ cpu_name = nullptr;
523
524 // GUID_DEVICE_PROCESSOR: {97FADB10-4E33-40AE-359C-8BEF029DBDD0}
525 Platform::String^ if_filter = L"System.Devices.InterfaceClassGuid:=\"{97FADB10-4E33-40AE-359C-8BEF029DBDD0}\"";
526
527 // Enumerate all CPU DeviceInterfaces, and get DeviceInstanceID of the first one.
528 auto if_task = create_task(
529 DeviceInformation::FindAllAsync(if_filter)).then([&](DeviceInformationCollection ^ collection) {
530 if (collection->Size > 0) {
531 auto cpu = collection->GetAt(0);
532 auto id = cpu->Properties->Lookup(L"System.Devices.DeviceInstanceID");
533 cpu_id = dynamic_cast<Platform::String^>(id);
534 }
535 });
536
537 try {
538 if_task.wait();
539 }
540 catch (const std::exception & e) {
541 const char* what = e.what();
542 INFO_LOG(SYSTEM, "%s", what);
543 }
544
545 if (cpu_id != nullptr) {
546 // Get the Device with the same ID as the DeviceInterface
547 // Then get the name (description) of that Device
548 // We have to do this because the DeviceInterface we get doesn't have a proper description.
549 Platform::String^ dev_filter = L"System.Devices.DeviceInstanceID:=\"" + cpu_id + L"\"";
550
551 auto dev_task = create_task(
552 DeviceInformation::FindAllAsync(dev_filter, {}, DeviceInformationKind::Device)).then(
553 [&](DeviceInformationCollection ^ collection) {
554 if (collection->Size > 0) {
555 cpu_name = collection->GetAt(0)->Name;
556 }
557 });
558
559 try {
560 dev_task.wait();
561 }
562 catch (const std::exception & e) {
563 const char* what = e.what();
564 INFO_LOG(SYSTEM, "%s", what);
565 }
566 }
567
568 if (cpu_name != nullptr) {
569 return FromPlatformString(cpu_name);
570 } else {
571 return "Unknown";
572 }
573 }
574
575 // Emulation of TlsAlloc for Windows 10. Used by glslang. Doesn't actually seem to work, other than fixing the linking errors?
576
577 extern "C" {
__imp_TlsAlloc()578 DWORD WINAPI __imp_TlsAlloc() {
579 return FlsAlloc(nullptr);
580 }
__imp_TlsFree(DWORD index)581 BOOL WINAPI __imp_TlsFree(DWORD index) {
582 return FlsFree(index);
583 }
__imp_TlsSetValue(DWORD dwTlsIndex,LPVOID lpTlsValue)584 BOOL WINAPI __imp_TlsSetValue(DWORD dwTlsIndex, LPVOID lpTlsValue) {
585 return FlsSetValue(dwTlsIndex, lpTlsValue);
586 }
__imp_TlsGetValue(DWORD dwTlsIndex)587 LPVOID WINAPI __imp_TlsGetValue(DWORD dwTlsIndex) {
588 return FlsGetValue(dwTlsIndex);
589 }
590 }
591