Merge pull request #19138 from hrydgard/shortcut-icons-windows

Windows: When using "Create shortcut", use the game's icon instead of PPSSPP's
This commit is contained in:
Henrik Rydgård authored and GitHub committed 2024-05-13 13:42:25 +02:00
commit 9ab775fde1
3 files changed
+101 -16

No files matched your search

+65 -4
View File
@@ -6,6 +6,8 @@
#include <thread>
#include "Common/Data/Encoding/Utf8.h"
#include "Common/File/FileUtil.h"
#include "Common/Data/Format/PNGLoad.h"
#include "ShellUtil.h"
#include <shlobj.h>
@@ -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
+15 -11
View File
@@ -5,18 +5,22 @@
#include <vector>
#include <thread>
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<std::string> 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<std::string> 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
+21 -1
View File
@@ -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<GameInfo> 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<void (*)(void *window, void *userdata)>(param3);