1 // Copyright (c) 2012- PPSSPP Project.
2
3 // This program is free software: you can redistribute it and/or modify
4 // it under the terms of the GNU General Public License as published by
5 // the Free Software Foundation, version 2.0 or later versions.
6
7 // This program is distributed in the hope that it will be useful,
8 // but WITHOUT ANY WARRANTY; without even the implied warranty of
9 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 // GNU General Public License 2.0 for more details.
11
12 // A copy of the GPL 2.0 should have been included with the program.
13 // If not, see http://www.gnu.org/licenses/
14
15 // Official git repository and contact information can be found at
16 // https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
17
18 #include "ppsspp_config.h"
19
20 #include <algorithm>
21
22 // For shell links
23 #include "Common/CommonWindows.h"
24 #include "winnls.h"
25 #include "shobjidl.h"
26 #include "objbase.h"
27 #include "objidl.h"
28 #include "shlguid.h"
29 #pragma warning(push)
30 #pragma warning(disable:4091) // workaround bug in VS2015 headers
31 #include "shlobj.h"
32 #pragma warning(pop)
33
34 // native stuff
35 #include "Common/System/Display.h"
36 #include "Common/System/NativeApp.h"
37 #include "Common/Input/InputState.h"
38 #include "Common/Input/KeyCodes.h"
39 #include "Common/Data/Encoding/Utf8.h"
40 #include "Common/File/DirListing.h"
41 #include "Common/StringUtils.h"
42
43 #include "Core/Core.h"
44 #include "Core/Config.h"
45 #include "Core/ConfigValues.h"
46 #include "Core/CoreParameter.h"
47 #include "Core/System.h"
48 #include "Core/Debugger/SymbolMap.h"
49 #include "Core/Instance.h"
50
51 #include "Windows/EmuThread.h"
52 #include "Windows/WindowsAudio.h"
53 #include "Windows/WindowsHost.h"
54 #include "Windows/MainWindow.h"
55
56 #if PPSSPP_API(ANY_GL)
57 #include "Windows/GPU/WindowsGLContext.h"
58 #endif
59 #include "Windows/GPU/WindowsVulkanContext.h"
60 #include "Windows/GPU/D3D9Context.h"
61 #include "Windows/GPU/D3D11Context.h"
62
63 #include "Windows/Debugger/DebuggerShared.h"
64 #include "Windows/Debugger/Debugger_Disasm.h"
65 #include "Windows/Debugger/Debugger_MemoryDlg.h"
66
67 #ifndef _M_ARM
68 #include "Windows/DinputDevice.h"
69 #endif
70 #include "Windows/XinputDevice.h"
71
72 #include "Windows/main.h"
73 #include "UI/OnScreenDisplay.h"
74
75 float g_mouseDeltaX = 0;
76 float g_mouseDeltaY = 0;
77
PostDialogMessage(Dialog * dialog,UINT message,WPARAM wParam=0,LPARAM lParam=0)78 static BOOL PostDialogMessage(Dialog *dialog, UINT message, WPARAM wParam = 0, LPARAM lParam = 0) {
79 return PostMessage(dialog->GetDlgHandle(), message, wParam, lParam);
80 }
81
WindowsHost(HINSTANCE hInstance,HWND mainWindow,HWND displayWindow)82 WindowsHost::WindowsHost(HINSTANCE hInstance, HWND mainWindow, HWND displayWindow)
83 : hInstance_(hInstance),
84 displayWindow_(displayWindow),
85 mainWindow_(mainWindow)
86 {
87 g_mouseDeltaX = 0;
88 g_mouseDeltaY = 0;
89
90 //add first XInput device to respond
91 input.push_back(std::make_unique<XinputDevice>());
92 #ifndef _M_ARM
93 //find all connected DInput devices of class GamePad
94 numDinputDevices_ = DinputDevice::getNumPads();
95 for (size_t i = 0; i < numDinputDevices_; i++) {
96 input.push_back(std::make_unique<DinputDevice>(static_cast<int>(i)));
97 }
98 #endif
99 SetConsolePosition();
100 }
101
SetConsolePosition()102 void WindowsHost::SetConsolePosition() {
103 HWND console = GetConsoleWindow();
104 if (console != NULL && g_Config.iConsoleWindowX != -1 && g_Config.iConsoleWindowY != -1) {
105 SetWindowPos(console, NULL, g_Config.iConsoleWindowX, g_Config.iConsoleWindowY, 0, 0, SWP_NOSIZE | SWP_NOZORDER);
106 }
107 }
108
UpdateConsolePosition()109 void WindowsHost::UpdateConsolePosition() {
110 RECT rc;
111 HWND console = GetConsoleWindow();
112 if (console != NULL && GetWindowRect(console, &rc) && !IsIconic(console)) {
113 g_Config.iConsoleWindowX = rc.left;
114 g_Config.iConsoleWindowY = rc.top;
115 }
116 }
117
InitGraphics(std::string * error_message,GraphicsContext ** ctx)118 bool WindowsHost::InitGraphics(std::string *error_message, GraphicsContext **ctx) {
119 WindowsGraphicsContext *graphicsContext = nullptr;
120 switch (g_Config.iGPUBackend) {
121 #if PPSSPP_API(ANY_GL)
122 case (int)GPUBackend::OPENGL:
123 graphicsContext = new WindowsGLContext();
124 break;
125 #endif
126 case (int)GPUBackend::DIRECT3D9:
127 graphicsContext = new D3D9Context();
128 break;
129 case (int)GPUBackend::DIRECT3D11:
130 graphicsContext = new D3D11Context();
131 break;
132 case (int)GPUBackend::VULKAN:
133 graphicsContext = new WindowsVulkanContext();
134 break;
135 default:
136 return false;
137 }
138
139 if (graphicsContext->Init(hInstance_, displayWindow_, error_message)) {
140 *ctx = graphicsContext;
141 gfx_ = graphicsContext;
142 return true;
143 } else {
144 delete graphicsContext;
145 *ctx = nullptr;
146 gfx_ = nullptr;
147 return false;
148 }
149 }
150
ShutdownGraphics()151 void WindowsHost::ShutdownGraphics() {
152 gfx_->Shutdown();
153 delete gfx_;
154 gfx_ = nullptr;
155 }
156
SetWindowTitle(const char * message)157 void WindowsHost::SetWindowTitle(const char *message) {
158 #ifdef GOLD
159 const char *name = "PPSSPP Gold ";
160 #else
161 const char *name = "PPSSPP ";
162 #endif
163 std::wstring winTitle = ConvertUTF8ToWString(std::string(name) + PPSSPP_GIT_VERSION);
164 if (message != nullptr) {
165 winTitle.append(ConvertUTF8ToWString(" - "));
166 winTitle.append(ConvertUTF8ToWString(message));
167 }
168 #ifdef _DEBUG
169 winTitle.append(L" (debug)");
170 #endif
171 lastTitle_ = winTitle;
172
173 MainWindow::SetWindowTitle(winTitle.c_str());
174 PostMessage(mainWindow_, MainWindow::WM_USER_WINDOW_TITLE_CHANGED, 0, 0);
175 }
176
InitSound()177 void WindowsHost::InitSound() {
178 }
179
180 // UGLY!
181 extern WindowsAudioBackend *winAudioBackend;
182
UpdateSound()183 void WindowsHost::UpdateSound() {
184 if (winAudioBackend)
185 winAudioBackend->Update();
186 }
187
ShutdownSound()188 void WindowsHost::ShutdownSound() {
189 }
190
UpdateUI()191 void WindowsHost::UpdateUI() {
192 PostMessage(mainWindow_, MainWindow::WM_USER_UPDATE_UI, 0, 0);
193
194 int peers = GetInstancePeerCount();
195 if (PPSSPP_ID >= 1 && peers != lastNumInstances_) {
196 lastNumInstances_ = peers;
197 PostMessage(mainWindow_, MainWindow::WM_USER_WINDOW_TITLE_CHANGED, 0, 0);
198 }
199 }
200
UpdateMemView()201 void WindowsHost::UpdateMemView() {
202 if (memoryWindow)
203 PostDialogMessage(memoryWindow, WM_DEB_UPDATE);
204 }
205
UpdateDisassembly()206 void WindowsHost::UpdateDisassembly() {
207 if (disasmWindow)
208 PostDialogMessage(disasmWindow, WM_DEB_UPDATE);
209 }
210
SetDebugMode(bool mode)211 void WindowsHost::SetDebugMode(bool mode) {
212 if (disasmWindow)
213 PostDialogMessage(disasmWindow, WM_DEB_SETDEBUGLPARAM, 0, (LPARAM)mode);
214 }
215
PollControllers()216 void WindowsHost::PollControllers() {
217 static int checkCounter = 0;
218 static const int CHECK_FREQUENCY = 71;
219 if (checkCounter++ > CHECK_FREQUENCY) {
220 #ifndef _M_ARM
221 size_t newCount = DinputDevice::getNumPads();
222 if (newCount > numDinputDevices_) {
223 INFO_LOG(SYSTEM, "New controller device detected");
224 for (size_t i = numDinputDevices_; i < newCount; i++) {
225 input.push_back(std::make_unique<DinputDevice>(static_cast<int>(i)));
226 }
227 numDinputDevices_ = newCount;
228 }
229 #endif
230 checkCounter = 0;
231 }
232
233 for (const auto &device : input) {
234 if (device->UpdateState() == InputDevice::UPDATESTATE_SKIP_PAD)
235 break;
236 }
237
238 // Disabled by default, needs a workaround to map to psp keys.
239 if (g_Config.bMouseControl) {
240 float scaleFactor_x = g_dpi_scale_x * 0.1 * g_Config.fMouseSensitivity;
241 float scaleFactor_y = g_dpi_scale_y * 0.1 * g_Config.fMouseSensitivity;
242
243 float mx = std::max(-1.0f, std::min(1.0f, g_mouseDeltaX * scaleFactor_x));
244 float my = std::max(-1.0f, std::min(1.0f, g_mouseDeltaY * scaleFactor_y));
245 AxisInput axisX, axisY;
246 axisX.axisId = JOYSTICK_AXIS_MOUSE_REL_X;
247 axisX.deviceId = DEVICE_ID_MOUSE;
248 axisX.value = mx;
249 axisY.axisId = JOYSTICK_AXIS_MOUSE_REL_Y;
250 axisY.deviceId = DEVICE_ID_MOUSE;
251 axisY.value = my;
252
253 if (GetUIState() == UISTATE_INGAME || g_Config.bMapMouse) {
254 NativeAxis(axisX);
255 NativeAxis(axisY);
256 }
257 }
258
259 g_mouseDeltaX *= g_Config.fMouseSmoothing;
260 g_mouseDeltaY *= g_Config.fMouseSmoothing;
261 }
262
BootDone()263 void WindowsHost::BootDone() {
264 if (g_symbolMap)
265 g_symbolMap->SortSymbols();
266 PostMessage(mainWindow_, WM_USER + 1, 0, 0);
267
268 SetDebugMode(!g_Config.bAutoRun);
269 }
270
SymbolMapFilename(const Path & currentFilename,const char * ext)271 static Path SymbolMapFilename(const Path ¤tFilename, const char *ext) {
272 File::FileInfo info;
273 // can't fail, definitely exists if it gets this far
274 File::GetFileInfo(currentFilename, &info);
275 if (info.isDirectory) {
276 return currentFilename / (std::string(".ppsspp-symbols") + ext);
277 }
278 return currentFilename.WithReplacedExtension(ext);
279 }
280
AttemptLoadSymbolMap()281 bool WindowsHost::AttemptLoadSymbolMap() {
282 if (!g_symbolMap)
283 return false;
284 bool result1 = g_symbolMap->LoadSymbolMap(SymbolMapFilename(PSP_CoreParameter().fileToStart, ".ppmap"));
285 // Load the old-style map file.
286 if (!result1)
287 result1 = g_symbolMap->LoadSymbolMap(SymbolMapFilename(PSP_CoreParameter().fileToStart, ".map"));
288 bool result2 = g_symbolMap->LoadNocashSym(SymbolMapFilename(PSP_CoreParameter().fileToStart, ".sym"));
289 return result1 || result2;
290 }
291
SaveSymbolMap()292 void WindowsHost::SaveSymbolMap() {
293 if (g_symbolMap)
294 g_symbolMap->SaveSymbolMap(SymbolMapFilename(PSP_CoreParameter().fileToStart, ".ppmap"));
295 }
296
NotifySymbolMapUpdated()297 void WindowsHost::NotifySymbolMapUpdated() {
298 if (g_symbolMap)
299 g_symbolMap->SortSymbols();
300 PostMessage(mainWindow_, WM_USER + 1, 0, 0);
301 }
302
IsDebuggingEnabled()303 bool WindowsHost::IsDebuggingEnabled() {
304 #ifdef _DEBUG
305 return true;
306 #else
307 return false;
308 #endif
309 }
310
311 // http://msdn.microsoft.com/en-us/library/aa969393.aspx
CreateLink(LPCWSTR lpszPathObj,LPCWSTR lpszArguments,LPCWSTR lpszPathLink,LPCWSTR lpszDesc)312 HRESULT CreateLink(LPCWSTR lpszPathObj, LPCWSTR lpszArguments, LPCWSTR lpszPathLink, LPCWSTR lpszDesc) {
313 HRESULT hres;
314 IShellLink* psl;
315 CoInitializeEx(NULL, COINIT_MULTITHREADED);
316
317 // Get a pointer to the IShellLink interface. It is assumed that CoInitialize
318 // has already been called.
319 hres = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink, (LPVOID*)&psl);
320 if (SUCCEEDED(hres)) {
321 IPersistFile* ppf;
322
323 // Set the path to the shortcut target and add the description.
324 psl->SetPath(lpszPathObj);
325 psl->SetArguments(lpszArguments);
326 psl->SetDescription(lpszDesc);
327
328 // Query IShellLink for the IPersistFile interface, used for saving the
329 // shortcut in persistent storage.
330 hres = psl->QueryInterface(IID_IPersistFile, (LPVOID*)&ppf);
331
332 if (SUCCEEDED(hres)) {
333 // Save the link by calling IPersistFile::Save.
334 hres = ppf->Save(lpszPathLink, TRUE);
335 ppf->Release();
336 }
337 psl->Release();
338 }
339 CoUninitialize();
340
341 return hres;
342 }
343
CanCreateShortcut()344 bool WindowsHost::CanCreateShortcut() {
345 return false; // Turn on when below function fixed
346 }
347
CreateDesktopShortcut(std::string argumentPath,std::string gameTitle)348 bool WindowsHost::CreateDesktopShortcut(std::string argumentPath, std::string gameTitle) {
349 // TODO: not working correctly
350 return false;
351
352
353 // Get the desktop folder
354 // TODO: Not long path safe.
355 wchar_t *pathbuf = new wchar_t[MAX_PATH + gameTitle.size() + 100];
356 SHGetFolderPath(0, CSIDL_DESKTOPDIRECTORY, NULL, SHGFP_TYPE_CURRENT, pathbuf);
357
358 // Sanitize the game title for banned characters.
359 const char bannedChars[] = "<>:\"/\\|?*";
360 for (size_t i = 0; i < gameTitle.size(); i++) {
361 for (char c : bannedChars) {
362 if (gameTitle[i] == c) {
363 gameTitle[i] = '_';
364 break;
365 }
366 }
367 }
368
369 wcscat(pathbuf, L"\\");
370 wcscat(pathbuf, ConvertUTF8ToWString(gameTitle).c_str());
371
372 std::wstring moduleFilename;
373 size_t sz;
374 do {
375 moduleFilename.resize(moduleFilename.size() + MAX_PATH);
376 // On failure, this will return the same value as passed in, but success will always be one lower.
377 sz = GetModuleFileName(nullptr, &moduleFilename[0], (DWORD)moduleFilename.size());
378 } while (sz >= moduleFilename.size());
379 moduleFilename.resize(sz);
380
381 CreateLink(moduleFilename.c_str(), ConvertUTF8ToWString(argumentPath).c_str(), pathbuf, ConvertUTF8ToWString(gameTitle).c_str());
382
383 delete [] pathbuf;
384 return false;
385 }
386
ToggleDebugConsoleVisibility()387 void WindowsHost::ToggleDebugConsoleVisibility() {
388 MainWindow::ToggleDebugConsoleVisibility();
389 }
390
NotifyUserMessage(const std::string & message,float duration,u32 color,const char * id)391 void WindowsHost::NotifyUserMessage(const std::string &message, float duration, u32 color, const char *id) {
392 osm.Show(message, duration, color, -1, true, id);
393 }
394
SendUIMessage(const std::string & message,const std::string & value)395 void WindowsHost::SendUIMessage(const std::string &message, const std::string &value) {
396 NativeMessageReceived(message.c_str(), value.c_str());
397 }
398
NotifySwitchUMDUpdated()399 void WindowsHost::NotifySwitchUMDUpdated() {
400 PostMessage(mainWindow_, MainWindow::WM_USER_SWITCHUMD_UPDATED, 0, 0);
401 }
402