diff --git a/Windows/W32Util/ShellUtil.cpp b/Windows/W32Util/ShellUtil.cpp index 32c5d3b272..c0f3af4a49 100644 --- a/Windows/W32Util/ShellUtil.cpp +++ b/Windows/W32Util/ShellUtil.cpp @@ -6,6 +6,8 @@ #include #include "Common/Data/Encoding/Utf8.h" +#include "Common/File/FileUtil.h" +#include "Common/Data/Format/PNGLoad.h" #include "ShellUtil.h" #include @@ -188,7 +190,7 @@ namespace W32Util { // http://msdn.microsoft.com/en-us/library/aa969393.aspx -HRESULT CreateLink(LPCWSTR lpszPathObj, LPCWSTR lpszArguments, LPCWSTR lpszPathLink, LPCWSTR lpszDesc) { +static HRESULT CreateLink(LPCWSTR lpszPathObj, LPCWSTR lpszArguments, LPCWSTR lpszPathLink, LPCWSTR lpszDesc, LPCWSTR lpszIcon, int iconIndex) { HRESULT hres; IShellLink *psl = nullptr; hres = CoInitializeEx(NULL, COINIT_MULTITHREADED); @@ -205,7 +207,9 @@ HRESULT CreateLink(LPCWSTR lpszPathObj, LPCWSTR lpszArguments, LPCWSTR lpszPathL psl->SetPath(lpszPathObj); psl->SetArguments(lpszArguments); psl->SetDescription(lpszDesc); - // psl->SetIconLocation(..) + if (lpszIcon) { + psl->SetIconLocation(lpszIcon, iconIndex); + } // Query IShellLink for the IPersistFile interface, used for saving the // shortcut in persistent storage. @@ -223,7 +227,7 @@ HRESULT CreateLink(LPCWSTR lpszPathObj, LPCWSTR lpszArguments, LPCWSTR lpszPathL return hres; } -bool CreateDesktopShortcut(std::string_view argumentPath, std::string_view gameTitleStr) { +bool CreateDesktopShortcut(std::string_view argumentPath, std::string_view gameTitleStr, const Path &icoFile) { // Get the desktop folder wchar_t *pathbuf = new wchar_t[4096]; SHGetFolderPath(0, CSIDL_DESKTOPDIRECTORY, NULL, SHGFP_TYPE_CURRENT, pathbuf); @@ -264,7 +268,12 @@ bool CreateDesktopShortcut(std::string_view argumentPath, std::string_view gameT sanitizedArgument = "\"" + sanitizedArgument + "\""; - CreateLink(moduleFilename.c_str(), ConvertUTF8ToWString(sanitizedArgument).c_str(), pathbuf, ConvertUTF8ToWString(gameTitle).c_str()); + std::wstring icon; + if (!icoFile.empty()) { + icon = icoFile.ToWString(); + } + + CreateLink(moduleFilename.c_str(), ConvertUTF8ToWString(sanitizedArgument).c_str(), pathbuf, ConvertUTF8ToWString(gameTitle).c_str(), icon.empty() ? nullptr : icon.c_str(), 0); // TODO: Also extract the game icon and convert to .ico, put it somewhere under Memstick, and set it. @@ -272,4 +281,56 @@ bool CreateDesktopShortcut(std::string_view argumentPath, std::string_view gameT return false; } +// Function to create an icon file from PNG image data (these icons require Windows Vista). +// The Old New Thing comes to the rescue again! ChatGPT failed miserably. +// https://devblogs.microsoft.com/oldnewthing/20101018-00/?p=12513 +// https://devblogs.microsoft.com/oldnewthing/20101022-00/?p=12473 +bool CreateICOFromPNGData(const uint8_t *imageData, size_t imageDataSize, const Path &icoPath) { + if (imageDataSize <= sizeof(PNGHeaderPeek)) { + return false; + } + // Parse the PNG + PNGHeaderPeek pngHeader; + memcpy(&pngHeader, imageData, sizeof(PNGHeaderPeek)); + if (pngHeader.Width() > 256 || pngHeader.Height() > 256) { + // Reject the png as an icon. + return false; + } + + struct IconHeader { + uint16_t reservedZero; + uint16_t type; // should be 1 + uint16_t imageCount; + }; + IconHeader hdr{ 0, 1, 1 }; + struct IconDirectoryEntry { + BYTE bWidth; + BYTE bHeight; + BYTE bColorCount; + BYTE bReserved; + WORD wPlanes; + WORD wBitCount; + DWORD dwBytesInRes; + DWORD dwImageOffset; + }; + IconDirectoryEntry entry{}; + entry.bWidth = pngHeader.Width(); + entry.bHeight = pngHeader.Height(); + entry.bColorCount = 0; + entry.dwBytesInRes = (DWORD)imageDataSize; + entry.wPlanes = 1; + entry.wBitCount = 32; + entry.dwImageOffset = sizeof(hdr) + sizeof(entry); + + FILE *file = File::OpenCFile(icoPath, "wb"); + if (!file) { + return false; + } + fwrite(&hdr, sizeof(hdr), 1, file); + fwrite(&entry, sizeof(entry), 1, file); + fwrite(imageData, 1, imageDataSize, file); + fclose(file); + return true; +} + } // namespace diff --git a/Windows/W32Util/ShellUtil.h b/Windows/W32Util/ShellUtil.h index fb0247e7f3..8201ae691b 100644 --- a/Windows/W32Util/ShellUtil.h +++ b/Windows/W32Util/ShellUtil.h @@ -5,18 +5,22 @@ #include #include +class Path; + namespace W32Util { - // Can't make initialPath a string_view, need the null so might as well require it. - std::string BrowseForFolder(HWND parent, std::string_view title, std::string_view initialPath); - std::string BrowseForFolder(HWND parent, const wchar_t *title, std::string_view initialPath); - bool BrowseForFileName (bool _bLoad, HWND _hParent, const wchar_t*_pTitle, - const wchar_t *_pInitialFolder,const wchar_t *_pFilter,const wchar_t*_pExtension, - std::string& _strFileName); - std::vector BrowseForFileNameMultiSelect(bool _bLoad, HWND _hParent, const wchar_t*_pTitle, - const wchar_t*_pInitialFolder,const wchar_t*_pFilter,const wchar_t*_pExtension); +// Can't make initialPath a string_view, need the null so might as well require it. +std::string BrowseForFolder(HWND parent, std::string_view title, std::string_view initialPath); +std::string BrowseForFolder(HWND parent, const wchar_t *title, std::string_view initialPath); +bool BrowseForFileName(bool _bLoad, HWND _hParent, const wchar_t*_pTitle, + const wchar_t *_pInitialFolder, const wchar_t *_pFilter, const wchar_t*_pExtension, + std::string& _strFileName); +std::vector BrowseForFileNameMultiSelect(bool _bLoad, HWND _hParent, const wchar_t*_pTitle, + const wchar_t*_pInitialFolder, const wchar_t*_pFilter, const wchar_t*_pExtension); - std::string UserDocumentsPath(); +std::string UserDocumentsPath(); - bool CreateDesktopShortcut(std::string_view argumentPath, std::string_view gameTitle); -} // namespace +bool CreateDesktopShortcut(std::string_view argumentPath, std::string_view gameTitle, const Path &icoFile); +bool CreateICOFromPNGData(const uint8_t *imageData, size_t imageDataSize, const Path &icoPath); + +} // namespace diff --git a/Windows/main.cpp b/Windows/main.cpp index 48ca4c1d73..73ac6ee784 100644 --- a/Windows/main.cpp +++ b/Windows/main.cpp @@ -635,7 +635,27 @@ bool System_MakeRequest(SystemRequestType type, int requestId, const std::string return true; } case SystemRequestType::CREATE_GAME_SHORTCUT: - return W32Util::CreateDesktopShortcut(param1, param2); + { + // Get the game info to get our hands on the icon png + Path gamePath(param1); + std::shared_ptr info = g_gameInfoCache->GetInfo(nullptr, gamePath, GameInfoFlags::ICON); + Path icoPath; + if (info->icon.dataLoaded) { + // Write the icon png out as a .ICO file so the shortcut can point to it + + // Savestate seems like a good enough place to put ico files. + Path iconFolder = GetSysDirectory(PSPDirectories::DIRECTORY_SAVESTATE); + + icoPath = iconFolder / (info->id + ".ico"); + if (!File::Exists(icoPath)) { + if (!W32Util::CreateICOFromPNGData((const uint8_t *)info->icon.data.data(), info->icon.data.size(), icoPath)) { + ERROR_LOG(SYSTEM, "ICO creation failed"); + icoPath.clear(); + } + } + } + return W32Util::CreateDesktopShortcut(param1, param2, icoPath); + } case SystemRequestType::RUN_CALLBACK_IN_WNDPROC: { auto func = reinterpret_cast(param3);