[libromdata] EXE: Add icon thumbnailing using librptexture's ICO class.

Uses the first icon in the first available language.

New function loadResourceReader() which ensures rsrcReader is loaded.

[librptexture] ICO: Fix resource loading issues:

- Use the correct resource file pointer, not this->file.
- Add IconRes_Win3 and CursorRes_Win3 cases where appropriate.
- Ensure d->iconType doesn't get overwritten after it's set by the
  ICOPrivate constructor for resources.

FIXME: VBoxWindowsAdditions.exe's PNG icon loads fine, but the 128x128
"regular" icon is corrupted.
This commit is contained in:
David Korth 2025-06-08 11:40:59 -04:00
parent 9994c2df94
commit f5be3c81a4
4 changed files with 215 additions and 25 deletions

View File

@ -16,9 +16,14 @@ using namespace LibRpBase;
using namespace LibRpFile;
using namespace LibRpText;
// Windows icon handler
#include "librptexture/fileformat/ICO.hpp"
using namespace LibRpTexture;
// C++ STL classes
using std::array;
using std::string;
using std::unique_ptr;
using std::vector;
// EXE data
@ -96,6 +101,37 @@ EXEPrivate::EXEPrivate(const IRpFilePtr &file)
memset(&hdr, 0, sizeof(hdr));
}
/**
* Make sure the resource reader is loaded.
* @return 0 on success; negative POSIX error code on error.
*/
int EXEPrivate::loadResourceReader(void)
{
if (rsrcReader) {
// Resource reader is already loaded.
return 0;
}
int ret = -1;
switch (exeType) {
default:
// Unable to load resources from this type of executable.
return -ENOENT;
case EXEPrivate::ExeType::NE:
case EXEPrivate::ExeType::COM_NE:
ret = loadNEResourceTable();
break;
case EXEPrivate::ExeType::PE:
case EXEPrivate::ExeType::PE32PLUS:
ret = loadPEResourceTypes();
break;
}
return ret;
}
/**
* Add VS_VERSION_INFO fields.
*
@ -342,6 +378,38 @@ void EXEPrivate::addFields_VS_VERSION_INFO(const VS_FIXEDFILEINFO *pVsFfi, const
fields.addField_listData("StringFileInfo", &params);
}
/**
* Load the icon.
* @return Icon, or nullptr on error.
*/
rp_image_const_ptr EXEPrivate::loadIcon(void)
{
if (img_icon) {
// Icon has already been loaded.
return img_icon;
} else if (!this->isValid || static_cast<int>(this->exeType) < 0) {
// Can't load the icon.
return {};
}
// Make sure the the resource reader is loaded.
int ret = loadResourceReader();
if (ret != 0 || !rsrcReader) {
// No resources available.
return 0;
}
// Attempt to load the default icon.
unique_ptr<ICO> ico(new ICO(rsrcReader, RT_GROUP_ICON, -1, -1));
if (!ico->isValid()) {
// Unable to load the default icon.
return {};
}
// Return the icon's image.
return ico->image();
}
/** MZ-specific **/
/**
@ -936,6 +1004,92 @@ const char *EXE::systemName(unsigned int type) const
return C_("EXE", "Unknown EXE");
}
/**
* Get a bitfield of image types this class can retrieve.
* @return Bitfield of supported image types. (ImageTypesBF)
*/
uint32_t EXE::supportedImageTypes_static(void)
{
return IMGBF_INT_ICON;
}
/**
* Get a bitfield of image types this object can retrieve.
* @return Bitfield of supported image types. (ImageTypesBF)
*/
uint32_t EXE::supportedImageTypes(void) const
{
return supportedImageTypes_static();
}
/**
* Get a list of all available image sizes for the specified image type.
* @param imageType Image type.
* @return Vector of available image sizes, or empty vector if no images are available.
*/
vector<RomData::ImageSizeDef> EXE::supportedImageSizes_static(ImageType imageType)
{
ASSERT_supportedImageSizes(imageType);
switch (imageType) {
case IMG_INT_ICON:
// Assuming 32x32.
return {{nullptr, 32, 32, 0}};
default:
break;
}
// Unsupported image type.
return {};
}
/**
* Get a list of all available image sizes for the specified image type.
* @param imageType Image type.
* @return Vector of available image sizes, or empty vector if no images are available.
*/
vector<RomData::ImageSizeDef> EXE::supportedImageSizes(ImageType imageType) const
{
ASSERT_supportedImageSizes(imageType);
switch (imageType) {
case IMG_INT_ICON:
// Assuming 32x32.
// TODO: Load the icon and check?
return {{nullptr, 32, 32, 0}};
default:
break;
}
// Unsupported image type.
return {};
}
/**
* Get image processing flags.
*
* These specify post-processing operations for images,
* e.g. applying transparency masks.
*
* @param imageType Image type.
* @return Bitfield of ImageProcessingBF operations to perform.
*/
uint32_t EXE::imgpf(ImageType imageType) const
{
ASSERT_imgpf(imageType);
uint32_t ret = 0;
switch (imageType) {
case IMG_INT_ICON:
// TODO: Use nearest-neighbor for < 64x64.
break;
default:
break;
}
return ret;
}
/**
* Load field data.
* Called by RomData::fields() if the field data hasn't been loaded yet.
@ -1047,23 +1201,7 @@ int EXE::loadMetaData(void)
// We can parse fields for NE (Win16) and PE (Win32) executables,
// if they have a resource section.
int ret = -1;
switch (d->exeType) {
default:
// Cannot load any metadata...
return 0;
case EXEPrivate::ExeType::NE:
case EXEPrivate::ExeType::COM_NE:
ret = d->loadNEResourceTable();
break;
case EXEPrivate::ExeType::PE:
case EXEPrivate::ExeType::PE32PLUS:
ret = d->loadPEResourceTypes();
break;
}
int ret = d->loadResourceReader();
if (ret != 0 || !d->rsrcReader) {
// No resources available.
return 0;
@ -1164,6 +1302,26 @@ bool EXE::hasDangerousPermissions(void) const
#endif /* ENABLE_XML */
}
/**
* Load an internal image.
* Called by RomData::image().
* @param imageType [in] Image type to load.
* @param pImage [out] Reference to rp_image_const_ptr to store the image in.
* @return 0 on success; negative POSIX error code on error.
*/
int EXE::loadInternalImage(ImageType imageType, rp_image_const_ptr &pImage)
{
ASSERT_loadInternalImage(imageType, pImage);
RP_D(EXE);
ROMDATA_loadInternalImage_single(
IMG_INT_ICON, // ourImageType
d->file, // file
d->isValid, // isValid
d->exeType, // romType
d->img_icon, // imgCache
d->loadIcon); // func
}
/**
* Check for "viewed" achievements.
*

View File

@ -2,7 +2,7 @@
* ROM Properties Page shell extension. (libromdata) *
* EXE.hpp: DOS/Windows executable reader. *
* *
* Copyright (c) 2016-2023 by David Korth. *
* Copyright (c) 2016-2025 by David Korth. *
* SPDX-License-Identifier: GPL-2.0-or-later *
***************************************************************************/
@ -15,6 +15,9 @@ namespace LibRomData {
ROMDATA_DECL_BEGIN(EXE)
ROMDATA_DECL_DANGEROUS()
ROMDATA_DECL_METADATA()
ROMDATA_DECL_IMGSUPPORT()
ROMDATA_DECL_IMGPF()
ROMDATA_DECL_IMGINT()
ROMDATA_DECL_VIEWED_ACHIEVEMENTS()
ROMDATA_DECL_END()

View File

@ -102,6 +102,12 @@ public:
// Resource reader
IResourceReaderPtr rsrcReader;
/**
* Make sure the resource reader is loaded.
* @return 0 on success; negative POSIX error code on error.
*/
int loadResourceReader(void);
/**
* Add VS_VERSION_INFO fields.
*
@ -113,6 +119,15 @@ public:
*/
void addFields_VS_VERSION_INFO(const VS_FIXEDFILEINFO *pVsFfi, const IResourceReader::StringFileInfo *pVsSfi);
// Icon
LibRpTexture::rp_image_ptr img_icon;
/**
* Load the icon.
* @return Icon, or nullptr on error.
*/
LibRpTexture::rp_image_const_ptr loadIcon(void);
/** MZ-specific **/
/**

View File

@ -322,7 +322,7 @@ int ICOPrivate::loadIconDirectory_Win3(void)
const size_t fullsize = count * sizeof(GRPICONDIRENTRY);
auto &iconDirectory = res.iconDirectory;
iconDirectory.resize(count);
size_t size = file->seekAndRead(sizeof(GRPICONDIR), iconDirectory.data(), fullsize);
size_t size = f_icondir->seekAndRead(sizeof(GRPICONDIR), iconDirectory.data(), fullsize);
if (size != fullsize) {
// Seek and/or read error.
iconDirectory.clear();
@ -341,7 +341,7 @@ int ICOPrivate::loadIconDirectory_Win3(void)
return -ENOENT;
}
size_t size = file->read(p, sizeof(*p));
size_t size = f_icon->read(p, sizeof(*p));
if (size != sizeof(*p)) {
// Short read.
iconDirectory.clear();
@ -877,6 +877,8 @@ rp_image_const_ptr ICOPrivate::loadImage(void)
case IconType::Icon_Win3:
case IconType::Cursor_Win3:
case IconType::IconRes_Win3:
case IconType::CursorRes_Win3:
// Windows 3.x icon or cursor
return loadImage_Win3();
}
@ -949,6 +951,7 @@ void ICO::init(bool res)
d->dir.res = nullptr;
return;
}
size_t size = f_icondir->read(&d->icoHeader, sizeof(d->icoHeader));
if (size != sizeof(d->icoHeader)) {
d->file.reset();
@ -966,6 +969,8 @@ void ICO::init(bool res)
}
// Determine the icon type.
// NOTE: d->iconType is already set if loading from a Windows resource.
// Only set it if it's still ICOPrivate::IconType::Unknown.
switch (le16_to_cpu(d->icoHeader.win1.format)) {
default:
// Not recognized...
@ -987,12 +992,16 @@ void ICO::init(bool res)
}
return;
case ICO_WIN3_TYPE_ICON:
d->iconType = ICOPrivate::IconType::Icon_Win3;
if (d->iconType == ICOPrivate::IconType::Unknown) {
d->iconType = ICOPrivate::IconType::Icon_Win3;
}
d->mimeType = "image/vnd.microsoft.icon";
d->textureFormatName = "Windows 3.x Icon";
break;
case ICO_WIN3_TYPE_CURSOR:
d->iconType = ICOPrivate::IconType::Cursor_Win3;
if (d->iconType == ICOPrivate::IconType::Unknown) {
d->iconType = ICOPrivate::IconType::Cursor_Win3;
}
d->mimeType = "image/vnd.microsoft.cursor";
d->textureFormatName = "Windows 3.x Icon";
break;
@ -1015,7 +1024,9 @@ void ICO::init(bool res)
case ICO_WIN1_FORMAT_ICON_DIB:
case ICO_WIN1_FORMAT_ICON_DDB:
case ICO_WIN1_FORMAT_ICON_BOTH:
d->iconType = ICOPrivate::IconType::Icon_Win1;
if (d->iconType == ICOPrivate::IconType::Unknown) {
d->iconType = ICOPrivate::IconType::Icon_Win1;
}
// TODO: Different MIME type for Windows 1.x?
d->mimeType = "image/vnd.microsoft.icon";
d->textureFormatName = "Windows 1.x Icon";
@ -1024,7 +1035,9 @@ void ICO::init(bool res)
case ICO_WIN1_FORMAT_CURSOR_DIB:
case ICO_WIN1_FORMAT_CURSOR_DDB:
case ICO_WIN1_FORMAT_CURSOR_BOTH:
d->iconType = ICOPrivate::IconType::Cursor_Win1;
if (d->iconType == ICOPrivate::IconType::Unknown) {
d->iconType = ICOPrivate::IconType::Cursor_Win1;
}
// TODO: Different MIME type for Windows 1.x?
d->mimeType = "image/vnd.microsoft.cursor";
d->textureFormatName = "Windows 1.x Cursor";
@ -1051,7 +1064,8 @@ void ICO::init(bool res)
case ICOPrivate::IconType::Icon_Win3:
case ICOPrivate::IconType::Cursor_Win3:
// TODO: Need to check BITMAPINFOHEADER, BITMAPCOREHEADER, or PNG header.
case ICOPrivate::IconType::IconRes_Win3:
case ICOPrivate::IconType::CursorRes_Win3:
if (!d->dir.ico->pBestIcon) {
// No "best" icon...
d->file.reset();