From 4d0f3183f29774377f32ca155a1d950ea63304eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Mon, 13 May 2024 01:37:53 +0200 Subject: [PATCH 1/2] Windows: When using "Create shortcut", use the game's icon instead of PPSSPP's. Since on Windows, shortcuts can't embed icons, we first save the game's icon .png as an .ico in the SAVESTATE folder (there might be a better place, but it also doesn't seem worth it to create a new folder for this). Part of #10885 (Android functionality still missing, for example). --- Windows/W32Util/ShellUtil.cpp | 69 +++++++++++++++++++++++++++++++++-- Windows/W32Util/ShellUtil.h | 26 +++++++------ Windows/main.cpp | 22 ++++++++++- 3 files changed, 101 insertions(+), 16 deletions(-) diff --git a/Windows/W32Util/ShellUtil.cpp b/Windows/W32Util/ShellUtil.cpp index 32c5d3b2723d..0a3621ad3036 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 = 32; + 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 fb0247e7f35b..8201ae691bdd 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, const Path &icoFile); +bool CreateICOFromPNGData(const uint8_t *imageData, size_t imageDataSize, const Path &icoPath); - bool CreateDesktopShortcut(std::string_view argumentPath, std::string_view gameTitle); -} // namespace +} // namespace diff --git a/Windows/main.cpp b/Windows/main.cpp index 48ca4c1d7383..73ac6ee78488 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); From bb7972c4372426ef1005ab981cc8fa043e8034a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Mon, 13 May 2024 01:48:15 +0200 Subject: [PATCH 2/2] Correct the wPlanes field in the ico format --- Windows/W32Util/ShellUtil.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Windows/W32Util/ShellUtil.cpp b/Windows/W32Util/ShellUtil.cpp index 0a3621ad3036..c0f3af4a4939 100644 --- a/Windows/W32Util/ShellUtil.cpp +++ b/Windows/W32Util/ShellUtil.cpp @@ -318,7 +318,7 @@ bool CreateICOFromPNGData(const uint8_t *imageData, size_t imageDataSize, const entry.bHeight = pngHeader.Height(); entry.bColorCount = 0; entry.dwBytesInRes = (DWORD)imageDataSize; - entry.wPlanes = 32; + entry.wPlanes = 1; entry.wBitCount = 32; entry.dwImageOffset = sizeof(hdr) + sizeof(entry);