From 22da0c2e21eb61d8a26d501bac73cf9b622b57b1 Mon Sep 17 00:00:00 2001 From: David Korth Date: Mon, 9 Jun 2025 18:37:47 -0400 Subject: [PATCH] [libromdata] EXE: Load icons from Windows 1.x/2.x executables. Windows 1.x/2.x does not have RT_GROUP_ICON. For NE executables, check for RT_GROUP_ICON, and use it if found. Otherwise, check for RT_ICON and use that if found. IResourceReader: Add a function has_resource_type() to check if the executable has any resources of the specified type. [librptexture] ICO: Handle RT_ICON and RT_CURSOR as Windows 1.x/2.x. TODO: Check the header to verify? --- src/libromdata/Other/EXE.cpp | 23 +++++++- src/libromdata/disc/NEResourceReader.cpp | 15 +++++ src/libromdata/disc/NEResourceReader.hpp | 7 +++ src/libromdata/disc/PEResourceReader.cpp | 11 ++++ src/libromdata/disc/PEResourceReader.hpp | 7 +++ src/librpbase/disc/IResourceReader.hpp | 7 +++ src/librptexture/fileformat/ICO.cpp | 74 +++++++++++++++++++----- 7 files changed, 126 insertions(+), 18 deletions(-) diff --git a/src/libromdata/Other/EXE.cpp b/src/libromdata/Other/EXE.cpp index fc2bb7d6c..9be6a5dc3 100644 --- a/src/libromdata/Other/EXE.cpp +++ b/src/libromdata/Other/EXE.cpp @@ -394,7 +394,26 @@ rp_image_const_ptr EXEPrivate::loadSpecificIcon(int iconindex) int ret = loadResourceReader(); if (ret != 0 || !rsrcReader) { // No resources available. - return 0; + return {}; + } + + uint16_t type = RT_GROUP_ICON; + if (exeType == ExeType::NE || exeType == ExeType::COM_NE) { + // Windows 1.x/2.x executables don't have RT_GROUP_ICON, + // but do have RT_ICON. If this is Win16, check for + // RT_GROUP_ICON first, then try RT_ICON. + // NOTE: Can't simply check based on if it's a 1.x/2.x + // executable because some EXEs converted to 3.x will + // still show up as 1.x/2.x. + if (rsrcReader->has_resource_type(RT_GROUP_ICON)) { + // We have RT_GROUP_ICON. + } else if (rsrcReader->has_resource_type(RT_ICON)) { + // We have RT_ICON. + type = RT_ICON; + } else { + // No icons... + return {}; + } } // Get the resource ID. @@ -417,7 +436,7 @@ rp_image_const_ptr EXEPrivate::loadSpecificIcon(int iconindex) } // Attempt to load the default icon. - unique_ptr ico(new ICO(rsrcReader, RT_GROUP_ICON, resID, -1)); + unique_ptr ico(new ICO(rsrcReader, type, resID, -1)); if (!ico->isValid()) { // Unable to load the default icon. return {}; diff --git a/src/libromdata/disc/NEResourceReader.cpp b/src/libromdata/disc/NEResourceReader.cpp index 914e8f661..5135755d2 100644 --- a/src/libromdata/disc/NEResourceReader.cpp +++ b/src/libromdata/disc/NEResourceReader.cpp @@ -838,4 +838,19 @@ int NEResourceReader::lookup_resource_ID(int type, int index) return type_dir[index].id; } +/** + * Do we have any resources of the specified type? + * @param type [in] Resource type ID + * @return True if we have these resources; false if we don't. + */ +bool NEResourceReader::has_resource_type(int type) +{ + // NOTE: Type and resource IDs have the high bit set for integers. + // We're only supporting integer IDs, so set the high bits here. + type |= 0x8000; + + RP_D(const NEResourceReader); + return (d->res_types.find(type) != d->res_types.end()); +} + } diff --git a/src/libromdata/disc/NEResourceReader.hpp b/src/libromdata/disc/NEResourceReader.hpp index 63a7d4c8a..b29976f8c 100644 --- a/src/libromdata/disc/NEResourceReader.hpp +++ b/src/libromdata/disc/NEResourceReader.hpp @@ -126,6 +126,13 @@ public: * @return Resource ID, or negative POSIX error code on error. */ int lookup_resource_ID(int type, int index) final; + + /** + * Do we have any resources of the specified type? + * @param type [in] Resource type ID + * @return True if we have these resources; false if we don't. + */ + bool has_resource_type(int type) final; }; } diff --git a/src/libromdata/disc/PEResourceReader.cpp b/src/libromdata/disc/PEResourceReader.cpp index 1936f11b1..26fe13233 100644 --- a/src/libromdata/disc/PEResourceReader.cpp +++ b/src/libromdata/disc/PEResourceReader.cpp @@ -892,4 +892,15 @@ int PEResourceReader::lookup_resource_ID(int type, int index) return type_dir->at(index).id; } +/** + * Do we have any resources of the specified type? + * @param type [in] Resource type ID + * @return True if we have these resources; false if we don't. + */ +bool PEResourceReader::has_resource_type(int type) +{ + RP_D(PEResourceReader); + return (d->getTypeDir(type) != nullptr); +} + } diff --git a/src/libromdata/disc/PEResourceReader.hpp b/src/libromdata/disc/PEResourceReader.hpp index cc9dd9c9d..49c7b97e6 100644 --- a/src/libromdata/disc/PEResourceReader.hpp +++ b/src/libromdata/disc/PEResourceReader.hpp @@ -127,6 +127,13 @@ public: * @return Resource ID, or negative POSIX error code on error. */ int lookup_resource_ID(int type, int index) final; + + /** + * Do we have any resources of the specified type? + * @param type [in] Resource type ID + * @return True if we have these resources; false if we don't. + */ + bool has_resource_type(int type) final; }; } diff --git a/src/librpbase/disc/IResourceReader.hpp b/src/librpbase/disc/IResourceReader.hpp index c4c64d828..d950baea5 100644 --- a/src/librpbase/disc/IResourceReader.hpp +++ b/src/librpbase/disc/IResourceReader.hpp @@ -129,6 +129,13 @@ public: * @return Resource ID, or negative POSIX error code on error. */ virtual int lookup_resource_ID(int type, int index) = 0; + + /** + * Do we have any resources of the specified type? + * @param type [in] Resource type ID + * @return True if we have these resources; false if we don't. + */ + virtual bool has_resource_type(int type) = 0; }; typedef std::shared_ptr IResourceReaderPtr; diff --git a/src/librptexture/fileformat/ICO.cpp b/src/librptexture/fileformat/ICO.cpp index c9135a453..1bc55ed1b 100644 --- a/src/librptexture/fileformat/ICO.cpp +++ b/src/librptexture/fileformat/ICO.cpp @@ -63,9 +63,13 @@ public: Icon_Win3 = 2, Cursor_Win3 = 3, + // Win1.x resources (RT_ICON, RT_CURSOR) + IconRes_Win1 = 4, + CursorRes_Win1 = 5, + // Win3.x resources (RT_GROUP_ICON, RT_GROUP_CURSOR) - IconRes_Win3 = 4, - CursorRes_Win3 = 5, + IconRes_Win3 = 6, + CursorRes_Win3 = 7, Max }; @@ -85,7 +89,7 @@ public: */ inline bool isResource(void) const { - return (iconType >= IconType::IconRes_Win3) && (iconType <= IconType::CursorRes_Win3); + return (iconType >= IconType::IconRes_Win1) && (iconType <= IconType::CursorRes_Win3); } /** @@ -95,8 +99,10 @@ public: inline uint16_t imageResType(void) const { switch (iconType) { + case IconType::IconRes_Win1: case IconType::IconRes_Win3: return RT_ICON; + case IconType::CursorRes_Win1: case IconType::CursorRes_Win3: return RT_CURSOR; default: @@ -265,15 +271,28 @@ ICOPrivate::ICOPrivate(ICO *q, const IResourceReaderPtr &resReader, uint16_t typ memset(&icoHeader, 0, sizeof(icoHeader)); // Determine the icon type here. - assert(type == RT_GROUP_ICON || type == RT_GROUP_CURSOR); - if (type == RT_GROUP_ICON) { - iconType = IconType::IconRes_Win3; - } else if (type == RT_GROUP_CURSOR) { - iconType = IconType::CursorRes_Win3; - } else { - // Unrecognized? - dir.v = nullptr; - return; + assert(type == RT_ICON || type == RT_CURSOR || type == RT_GROUP_ICON || type == RT_GROUP_CURSOR); + switch (type) { + default: + assert(!"Unsupported resource type"); + dir.v = nullptr; + return; + + // NOTE: Assuming individual icon/cursor is Windows 1.x/2.x format. + // TODO: Check the header to verify? + case RT_ICON: + iconType = IconType::IconRes_Win1; + break; + case RT_CURSOR: + iconType = IconType::CursorRes_Win1; + break; + + case RT_GROUP_ICON: + iconType = IconType::IconRes_Win3; + break; + case RT_GROUP_CURSOR: + iconType = IconType::CursorRes_Win3; + break; } // Initialize the icon directory union. @@ -314,7 +333,7 @@ int ICOPrivate::loadIconDirectory_Win3(void) // Open the RT_GROUP_ICON / RT_GROUP_CURSOR resource. auto &res = *(dir.res); - IRpFilePtr f_icondir = res.resReader->open(res.type, res.id, res.lang); + IRpFilePtr f_icondir = res.resReader->open(rt, res.id, res.lang); if (!f_icondir) { // Unable to open the RT_GROUP_ICON / RT_GROUP_CURSOR. return -ENOENT; @@ -502,7 +521,26 @@ rp_image_const_ptr ICOPrivate::loadImage_Win1(void) // Load the icon data. rp::uvector icon_data; icon_data.resize(icon_size * 2); - size_t size = file->seekAndRead(sizeof(icoHeader.win1), icon_data.data(), icon_size * 2); + + // Is this from a file or a resource? + size_t size; + const uint16_t rt = imageResType(); + if (rt != 0) { + // Open the resource. + const auto &res = *(dir.res); + IRpFilePtr f_icon = res.resReader->open(rt, res.id, res.lang); + if (!f_icon) { + // Unable to open the resource. + return {}; + } + + // Read from the resource. + size = f_icon->seekAndRead(sizeof(icoHeader.win1), icon_data.data(), icon_size * 2); + } else { + // Read from the file. + size = file->seekAndRead(sizeof(icoHeader.win1), icon_data.data(), icon_size * 2); + } + if (size != icon_size * 2) { // Seek and/or read error. return {}; @@ -873,6 +911,8 @@ rp_image_const_ptr ICOPrivate::loadImage(void) case IconType::Icon_Win1: case IconType::Cursor_Win1: + case IconType::IconRes_Win1: + case IconType::CursorRes_Win1: // Windows 1.0 icon or cursor return loadImage_Win1(); @@ -918,7 +958,7 @@ ICO::ICO(const IRpFilePtr &file) * NOTE: Check isValid() to determine if this is a valid ROM. * * @param resReader [in] IResourceReader - * @param type [in] Resource type ID (RT_GROUP_ICON or RT_GROUP_CURSOR) + * @param type [in] Resource type ID * @param id [in] Resource ID (-1 for "first entry") * @param lang [in] Language ID (-1 for "first entry") */ @@ -946,7 +986,7 @@ void ICO::init(bool res) const auto &res = *(d->dir.res); IRpFilePtr f_icondir = res.resReader->open(res.type, res.id, res.lang); if (!f_icondir) { - // Unable to open the RT_GROUP_ICON / RT_GROUP_CURSOR. + // Unable to open the icon or cursor. d->file.reset(); delete d->dir.res; d->dir.res = nullptr; @@ -1059,6 +1099,8 @@ void ICO::init(bool res) case ICOPrivate::IconType::Icon_Win1: case ICOPrivate::IconType::Cursor_Win1: + case ICOPrivate::IconType::IconRes_Win1: + case ICOPrivate::IconType::CursorRes_Win1: d->dimensions[0] = le16_to_cpu(d->icoHeader.win1.width); d->dimensions[1] = le16_to_cpu(d->icoHeader.win1.height); break;