Skip to content

Commit

Permalink
Merge pull request #19138 from hrydgard/shortcut-icons-windows
Browse files Browse the repository at this point in the history
Windows: When using "Create shortcut", use the game's icon instead of PPSSPP's
  • Loading branch information
hrydgard committed May 13, 2024
2 parents 0f15bf4 + bb7972c commit 9ab775f
Show file tree
Hide file tree
Showing 3 changed files with 101 additions and 16 deletions.
69 changes: 65 additions & 4 deletions Windows/W32Util/ShellUtil.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down Expand Up @@ -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);
Expand All @@ -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.
Expand All @@ -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);
Expand Down Expand Up @@ -264,12 +268,69 @@ 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.

delete[] pathbuf;
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
26 changes: 15 additions & 11 deletions Windows/W32Util/ShellUtil.h
Original file line number Diff line number Diff line change
Expand Up @@ -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, 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
22 changes: 21 additions & 1 deletion Windows/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down

0 comments on commit 9ab775f

Please sign in to comment.