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 &currentFilename, 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