From 2bea3021c3223238756ebdc139014b25957faf17 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Tue, 7 Jul 2020 21:25:15 +0200 Subject: [PATCH] Added proper emulator launch function for Windows and added logic for the new %EMUPATH% variable. --- es-app/src/FileData.cpp | 73 +++++++++++++++++++++++- es-app/src/SystemData.cpp | 6 +- es-app/src/VolumeControl.cpp | 4 +- es-core/src/Platform.cpp | 89 +++++++++++++++++++++++++++--- es-core/src/Platform.h | 8 ++- es-core/src/utils/FileSystemUtil.h | 2 +- es-core/src/utils/StringUtil.cpp | 18 ++++++ es-core/src/utils/StringUtil.h | 2 + 8 files changed, 187 insertions(+), 15 deletions(-) diff --git a/es-app/src/FileData.cpp b/es-app/src/FileData.cpp index 6bb0d2a2a..829431352 100644 --- a/es-app/src/FileData.cpp +++ b/es-app/src/FileData.cpp @@ -22,6 +22,7 @@ #include "SystemData.h" #include "VolumeControl.h" #include "Window.h" + #include FileData::FileData( @@ -452,18 +453,84 @@ void FileData::launchGame(Window* window) else command = mEnvData->mLaunchCommand; + std::string commandRaw = command; + const std::string rom = Utils::FileSystem::getEscapedPath(getPath()); const std::string basename = Utils::FileSystem::getStem(getPath()); const std::string rom_raw = Utils::FileSystem::getPreferredPath(getPath()); + const std::string emupath = Utils::FileSystem::getExePath(); command = Utils::String::replace(command, "%ROM%", rom); command = Utils::String::replace(command, "%BASENAME%", basename); command = Utils::String::replace(command, "%ROM_RAW%", rom_raw); - Scripting::fireEvent("game-start", rom, basename); + #ifdef _WIN64 + std::wstring commandWide = Utils::String::charToWideChar(command); + #endif - LOG(LogInfo) << " " << command; - int exitCode = runSystemCommand(command); + Scripting::fireEvent("game-start", rom, basename); + int exitCode = 0; + + if (command.find("%EMUPATH%") != std::string::npos) { + // Extract the emulator executable from the launch command string. This could either be + // just the program name, assuming the binary is in the PATH variable of the operating + // system, or it could be an absolute path to the emulator. (In the latter case, if + // there is a space in the the path, it needs to be enclosed by quotation marks in + // es_systems.cfg.) + std::string emuExecutable; + + // If the first character is a quotation mark, then we need to extract up to the + // next quotation mark, otherwise we'll extract up to the first space character. + if (command.front() == '\"') { + std::string emuTemp = command.substr(1, std::string::npos); + emuExecutable = emuTemp.substr(0, emuTemp.find('"')); + } + else { + emuExecutable = command.substr(0, command.find(' ')); + } + + // For Windows, we need to handle UTF-16 encoding. + #ifdef _WIN64 + std::wstring emuExecutableWide; + std::wstring emuPathWide; + + emuExecutableWide = Utils::String::charToWideChar(emuExecutable); + + // Search for the emulator using the PATH environmental variable. + DWORD size = SearchPathW(nullptr, emuExecutableWide.c_str(), L".exe", 0, nullptr, nullptr); + + if (size) { + std::vector pathBuffer(static_cast(size) + 1 ); + wchar_t* fileName = nullptr; + + SearchPathW(nullptr, emuExecutableWide.c_str(), L".exe", size + 1 , + pathBuffer.data(), &fileName); + std::wstring pathString = pathBuffer.data(); + + if (pathString.length()) { + emuPathWide = pathString.substr(0, pathString.size() - + std::wstring(fileName).size()); + emuPathWide.pop_back(); + auto stringPos = commandWide.find(L"%EMUPATH%"); + commandWide = commandWide.replace(stringPos, 9, emuPathWide); + } + } + #else + // TODO for Unix. + #endif + } + + LOG(LogInfo) << "Raw emulator launch command:"; + LOG(LogInfo) << commandRaw; + LOG(LogInfo) << "Expanded emulator launch command:"; + + #ifdef _WIN64 + LOG(LogInfo) << Utils::String::wideCharToChar(commandWide); + exitCode = launchEmulatorWindows(commandWide); + #else + LOG(LogInfo) << command; + exitCode = launchEmulatorUnix(command); + #endif if (exitCode != 0) { LOG(LogWarning) << "...launch terminated with nonzero exit code " << exitCode << "!"; diff --git a/es-app/src/SystemData.cpp b/es-app/src/SystemData.cpp index 4a87b8910..dab0443d7 100644 --- a/es-app/src/SystemData.cpp +++ b/es-app/src/SystemData.cpp @@ -235,7 +235,11 @@ bool SystemData::loadConfig() for (pugi::xml_node system = systemList.child("system"); system; system = system.next_sibling("system")) { - std::string name, fullname, path, cmd, themeFolder; + std::string name; + std::string fullname; + std::string path; + std::string cmd; + std::string themeFolder; name = system.child("name").text().get(); fullname = system.child("fullname").text().get(); diff --git a/es-app/src/VolumeControl.cpp b/es-app/src/VolumeControl.cpp index 967965365..fe944223e 100644 --- a/es-app/src/VolumeControl.cpp +++ b/es-app/src/VolumeControl.cpp @@ -312,8 +312,8 @@ int VolumeControl::getVolume() const float floatVolume = 0.0f; // 0-1 if (endpointVolume->GetMasterVolumeLevelScalar(&floatVolume) == S_OK) { volume = (int)Math::round(floatVolume * 100.0f); - LOG(LogInfo) << " getting volume as " << volume << - " ( from float " << floatVolume << ")"; + LOG(LogInfo) << "System audio volume is " << volume << + " (floating point value " << floatVolume << ")"; } else { LOG(LogError) << "VolumeControl::getVolume() - Failed to get master volume!"; diff --git a/es-core/src/Platform.cpp b/es-core/src/Platform.cpp index 498f7d806..5999e252d 100644 --- a/es-core/src/Platform.cpp +++ b/es-core/src/Platform.cpp @@ -5,6 +5,7 @@ // #include "Platform.h" +#include "utils/StringUtil.h" #if defined(__linux__) || defined(_WIN64) #include @@ -13,6 +14,7 @@ #endif #ifdef _WIN64 +#include #include #include #else @@ -42,16 +44,89 @@ int runPoweroffCommand() int runSystemCommand(const std::string& cmd_utf8) { -#ifdef _WIN64 + #ifdef _WIN64 // On Windows we use _wsystem to support non-ASCII paths - // which requires converting from UTF8 to a wstring. - typedef std::codecvt_utf8 convert_type; - std::wstring_convert converter; - std::wstring wchar_str = converter.from_bytes(cmd_utf8); + // which requires converting from UTF-8 to a wstring. + std::wstring wchar_str = Utils::String::charToWideChar(cmd_utf8); return _wsystem(wchar_str.c_str()); -#else + #else return system(cmd_utf8.c_str()); -#endif + #endif +} + +int runSystemCommand(const std::wstring& cmd_utf16) +{ + #ifdef _WIN64 + return _wsystem(cmd_utf16.c_str()); + #else + return 0; + #endif +} + +int launchEmulatorUnix(const std::string& cmd_utf8) +{ + #ifdef __unix__ + return system(cmd_utf8.c_str()); + #else + return 0; + #endif +} + +int launchEmulatorWindows(const std::wstring& cmd_utf16) +{ + #ifdef _WIN64 + STARTUPINFOW si {}; + PROCESS_INFORMATION pi; + + si.cb = sizeof(si); + bool processReturnValue = true; + DWORD errorCode = 0; + + processReturnValue = CreateProcessW( + nullptr, // No application name (use command line). + (wchar_t*) cmd_utf16.c_str(), // Command line. + nullptr, // Process attributes. + nullptr, // Thread attributes. + FALSE, // Handles inheritance. + 0, // Creation flags. + nullptr, // Use parent's environment block. + nullptr, // Use parent's starting directory. + &si, // Pointer to the STARTUPINFOW structure. + &pi ); // Pointer to the PROCESS_INFORMATION structure. + + // Wait for the child process to exit. + WaitForSingleObject(pi.hThread, INFINITE); + WaitForSingleObject(pi.hProcess, INFINITE); + + // If the return value is false, then something failed. + if (!processReturnValue) { + LPWSTR pBuffer = nullptr; + + FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER, + nullptr, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPWSTR)&pBuffer, 0, nullptr); + + errorCode = GetLastError(); + + std::string errorMessage = Utils::String::wideCharToChar(pBuffer); + // Remove trailing newline from the error message. + if (errorMessage.back() == '\n'); + errorMessage.pop_back(); + if (errorMessage.back() == '\r'); + errorMessage.pop_back(); + + LOG(LogError) << "Error - launchEmulatorWindows - system error code " << + errorCode << ": " << errorMessage; + } + + // Close process and thread handles. + CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); + + return errorCode; + #else // _WIN64 + return 0; + #endif } QuitMode quitMode = QuitMode::QUIT; diff --git a/es-core/src/Platform.h b/es-core/src/Platform.h index 6e9514187..40c1992e4 100644 --- a/es-core/src/Platform.h +++ b/es-core/src/Platform.h @@ -21,8 +21,14 @@ enum QuitMode { POWEROFF = 2 }; -// Run UTF-8 encoded in the shell (requires wstring conversion on Windows). +// Uses UTF-8 for Unix and does a UTF-16/wstring conversion for Windows. int runSystemCommand(const std::string& cmd_utf8); +// Windows specific UTF-16/wstring function. (FOR FUTURE USE) +int runSystemCommand(const std::wstring& cmd_utf16); + +int launchEmulatorUnix(const std::string& cmd_utf8); +int launchEmulatorWindows(const std::wstring& cmd_utf16); + int quitES(QuitMode mode = QuitMode::QUIT); void processQuitMode(); diff --git a/es-core/src/utils/FileSystemUtil.h b/es-core/src/utils/FileSystemUtil.h index c533409b5..a431afcdc 100644 --- a/es-core/src/utils/FileSystemUtil.h +++ b/es-core/src/utils/FileSystemUtil.h @@ -27,7 +27,7 @@ namespace Utils std::string getCWDPath(); void setExePath(const std::string& _path); std::string getExePath(); - std::string getProgramDataPath (); + std::string getProgramDataPath(); std::string getPreferredPath(const std::string& _path); std::string getGenericPath(const std::string& _path); std::string getEscapedPath(const std::string& _path); diff --git a/es-core/src/utils/StringUtil.cpp b/es-core/src/utils/StringUtil.cpp index 685b1be93..530be6ed5 100644 --- a/es-core/src/utils/StringUtil.cpp +++ b/es-core/src/utils/StringUtil.cpp @@ -8,6 +8,8 @@ #include "utils/StringUtil.h" #include +#include +#include #include namespace Utils @@ -185,6 +187,22 @@ namespace Utils return string; } + std::wstring charToWideChar(const std::string& _string) + { + typedef std::codecvt_utf8 convert_type; + std::wstring_convert stringConverter; + + return stringConverter.from_bytes(_string); + } + + std::string wideCharToChar(const std::wstring& _string) + { + typedef std::codecvt_utf8 convert_type; + std::wstring_convert stringConverter; + + return stringConverter.to_bytes(_string); + } + bool startsWith(const std::string& _string, const std::string& _start) { return (_string.find(_start) == 0); diff --git a/es-core/src/utils/StringUtil.h b/es-core/src/utils/StringUtil.h index c9241dcfb..5cb423268 100644 --- a/es-core/src/utils/StringUtil.h +++ b/es-core/src/utils/StringUtil.h @@ -28,6 +28,8 @@ namespace Utils std::string trim(const std::string& _string); std::string replace(const std::string& _string, const std::string& _replace, const std::string& _with); + std::wstring charToWideChar(const std::string& _string); + std::string wideCharToChar(const std::wstring& _string); bool startsWith(const std::string& _string, const std::string& _start); bool endsWith(const std::string& _string, const std::string& _end); std::string removeParenthesis(const std::string& _string);