mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-10-01 14:58:14 +00:00
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:
3 files changed
+101
-16
No files matched your search
@@ -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
@@ -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
@@ -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);
|
||||
|
||||
Reference in new issue
Block a user