mirror of
https://github.com/GerbilSoft/rom-properties.git
synced 2025-06-18 11:35:38 -04:00
[librptexture] ICO: Rework to support loading from Windows resources.
Some checks are pending
Codecov / run (push) Waiting to run
Some checks are pending
Codecov / run (push) Waiting to run
NOT TESTED; need to add it to the EXE class to test it. (.ico files still work as they did before.) Added a constructor that takes an IResourceReaderPtr and the type/id/lang of the icon. Type should be RT_GROUP_ICON or RT_GROUP_CURSOR. These are all saved in icodir_res. The dir union is now initialized in the ICOPrivate constructors, and is only reset in the ICOPrivate destructor. loadImage_Win3(): Use a new IRpFilePtr for the icon data. - For .ico, it's a copy of this->file. - For Windows executables, it's opened from the IResourceReader. - Starting address is the icon bitmap address for .ico and 0 for IResourceReader.
This commit is contained in:
parent
d0fb8dc73e
commit
8c5888e9e5
@ -37,6 +37,7 @@ class ICOPrivate final : public FileFormatPrivate
|
||||
{
|
||||
public:
|
||||
ICOPrivate(ICO *q, const IRpFilePtr &file);
|
||||
ICOPrivate(ICO *q, const IResourceReaderPtr &resReader, uint16_t type, int id, int lang);
|
||||
~ICOPrivate();
|
||||
|
||||
private:
|
||||
@ -73,7 +74,7 @@ public:
|
||||
// ICO header
|
||||
union {
|
||||
ICO_Win1_Header win1;
|
||||
ICONHEADER win3;
|
||||
ICONDIR win3; // ICONDIR and GRPICONDIR are essentially the same
|
||||
} icoHeader;
|
||||
|
||||
/** Win3.x icon stuff **/
|
||||
@ -87,6 +88,22 @@ public:
|
||||
return (iconType >= IconType::IconRes_Win3) && (iconType <= IconType::CursorRes_Win3);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the resource type for the individual icon or cursor.
|
||||
* @return RT_ICON or RT_CURSOR, or 0 if this is a standalone .ico/.cur file.
|
||||
*/
|
||||
inline uint16_t imageResType(void) const
|
||||
{
|
||||
switch (iconType) {
|
||||
case IconType::IconRes_Win3:
|
||||
return RT_ICON;
|
||||
case IconType::CursorRes_Win3:
|
||||
return RT_CURSOR;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: ICONDIRENTRY (for .ico files) and GRPICONDIRENTRY (for resources)
|
||||
// are different sizes. Hence, we have to use this union of struct pointers hack.
|
||||
struct icodir_ico {
|
||||
@ -113,8 +130,20 @@ public:
|
||||
// NOTE: *Not* byteswapped.
|
||||
const GRPICONDIRENTRY *pBestIcon;
|
||||
|
||||
icodir_res()
|
||||
// IResourceReader for loading icons from Windows executables
|
||||
IResourceReaderPtr resReader;
|
||||
|
||||
// Resource information
|
||||
uint16_t type;
|
||||
int id;
|
||||
int lang;
|
||||
|
||||
icodir_res(const IResourceReaderPtr &resReader, uint16_t type, int id, int lang)
|
||||
: pBestIcon(nullptr)
|
||||
, resReader(resReader)
|
||||
, type(type)
|
||||
, id(id)
|
||||
, lang(lang)
|
||||
{ }
|
||||
};
|
||||
|
||||
@ -124,18 +153,6 @@ public:
|
||||
icodir_res *res;
|
||||
} dir;
|
||||
|
||||
void reset_dir_union(void)
|
||||
{
|
||||
if (dir.v) {
|
||||
if (isResource()) {
|
||||
delete dir.res;
|
||||
} else {
|
||||
delete dir.ico;
|
||||
}
|
||||
dir.v = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
// Icon bitmap header
|
||||
union IconBitmapHeader_t {
|
||||
uint32_t size;
|
||||
@ -235,13 +252,42 @@ ICOPrivate::ICOPrivate(ICO *q, const IRpFilePtr &file)
|
||||
// Clear the ICO header union.
|
||||
memset(&icoHeader, 0, sizeof(icoHeader));
|
||||
|
||||
// Clear the icon directory union.
|
||||
dir.v = nullptr;
|
||||
// Initialize the icon directory union.
|
||||
dir.ico = new icodir_ico();
|
||||
}
|
||||
|
||||
ICOPrivate::ICOPrivate(ICO *q, const IResourceReaderPtr &resReader, uint16_t type, int id, int lang)
|
||||
: super(q, resReader, &textureInfo)
|
||||
, iconType(IconType::Unknown)
|
||||
, pIconHeader(nullptr)
|
||||
{
|
||||
// Clear the ICO header union.
|
||||
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;
|
||||
}
|
||||
|
||||
// Initialize the icon directory union.
|
||||
dir.res = new icodir_res(resReader, type, id, lang);
|
||||
}
|
||||
|
||||
ICOPrivate::~ICOPrivate()
|
||||
{
|
||||
reset_dir_union();
|
||||
if (dir.v) {
|
||||
if (isResource()) {
|
||||
delete dir.res;
|
||||
} else {
|
||||
delete dir.ico;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -261,34 +307,78 @@ int ICOPrivate::loadIconDirectory_Win3(void)
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
reset_dir_union();
|
||||
const size_t fullsize = count * sizeof(ICONDIRENTRY);
|
||||
dir.ico = new icodir_ico();
|
||||
auto &iconDirectory = dir.ico->iconDirectory;
|
||||
iconDirectory.resize(count);
|
||||
size_t size = file->seekAndRead(sizeof(ICONHEADER), iconDirectory.data(), fullsize);
|
||||
if (size != fullsize) {
|
||||
// Seek and/or read error.
|
||||
reset_dir_union();
|
||||
return -EIO;
|
||||
}
|
||||
const uint16_t rt = imageResType();
|
||||
if (rt != 0) {
|
||||
// Icon/cursor resource from a Windows executable.
|
||||
|
||||
// Load all of the icon image headers.
|
||||
iconBitmapHeaders.resize(count);
|
||||
IconBitmapHeader_t *p = iconBitmapHeaders.data();
|
||||
for (auto iter = iconDirectory.cbegin(); iter != iconDirectory.cend(); ++iter, p++) {
|
||||
unsigned int addr = le32_to_cpu(iter->dwImageOffset);
|
||||
size_t size = file->seekAndRead(addr, p, sizeof(*p));
|
||||
if (size != sizeof(*p)) {
|
||||
// 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);
|
||||
if (!f_icondir) {
|
||||
// Unable to open the RT_GROUP_ICON / RT_GROUP_CURSOR.
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
const size_t fullsize = count * sizeof(GRPICONDIRENTRY);
|
||||
auto &iconDirectory = res.iconDirectory;
|
||||
iconDirectory.resize(count);
|
||||
size_t size = file->seekAndRead(sizeof(GRPICONDIR), iconDirectory.data(), fullsize);
|
||||
if (size != fullsize) {
|
||||
// Seek and/or read error.
|
||||
reset_dir_union();
|
||||
iconBitmapHeaders.clear();
|
||||
iconDirectory.clear();
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
// Load all of the icon image headers.
|
||||
iconBitmapHeaders.resize(count);
|
||||
IconBitmapHeader_t *p = iconBitmapHeaders.data();
|
||||
for (auto iter = iconDirectory.cbegin(); iter != iconDirectory.cend(); ++iter, p++) {
|
||||
IRpFilePtr f_icon = res.resReader->open(rt, le16_to_cpu(iter->nID), res.lang);
|
||||
if (!f_icon) {
|
||||
// Unable to open the resource.
|
||||
iconDirectory.clear();
|
||||
iconBitmapHeaders.clear();
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
size_t size = file->read(p, sizeof(*p));
|
||||
if (size != sizeof(*p)) {
|
||||
// Short read.
|
||||
iconDirectory.clear();
|
||||
iconBitmapHeaders.clear();
|
||||
return -EIO;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Standalone .ico/.cur file.
|
||||
const size_t fullsize = count * sizeof(ICONDIRENTRY);
|
||||
auto &iconDirectory = dir.ico->iconDirectory;
|
||||
iconDirectory.resize(count);
|
||||
size_t size = file->seekAndRead(sizeof(ICONDIR), iconDirectory.data(), fullsize);
|
||||
if (size != fullsize) {
|
||||
// Seek and/or read error.
|
||||
iconDirectory.clear();
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
// Load all of the icon image headers.
|
||||
iconBitmapHeaders.resize(count);
|
||||
IconBitmapHeader_t *p = iconBitmapHeaders.data();
|
||||
for (auto iter = iconDirectory.cbegin(); iter != iconDirectory.cend(); ++iter, p++) {
|
||||
unsigned int addr = le32_to_cpu(iter->dwImageOffset);
|
||||
size_t size = file->seekAndRead(addr, p, sizeof(*p));
|
||||
if (size != sizeof(*p)) {
|
||||
// Seek and/or read error.
|
||||
iconDirectory.clear();
|
||||
iconBitmapHeaders.clear();
|
||||
return -EIO;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Go through the icon bitmap headers and figure out the "best" one.
|
||||
unsigned int width_best = 0, height_best = 0, bitcount_best = 0;
|
||||
int best_icon = -1;
|
||||
for (unsigned int i = 0; i < count; i++) {
|
||||
// Get the width, height, and color depth from this bitmap header.
|
||||
const IconBitmapHeader_t *const p = &iconBitmapHeaders[i];
|
||||
@ -367,7 +457,7 @@ int ICOPrivate::loadIconDirectory_Win3(void)
|
||||
|
||||
if (icon_is_better) {
|
||||
// This icon is better.
|
||||
dir.ico->pBestIcon = &iconDirectory[i];
|
||||
best_icon = static_cast<int>(i);
|
||||
pIconHeader = p;
|
||||
width_best = width;
|
||||
height_best = height;
|
||||
@ -375,7 +465,17 @@ int ICOPrivate::loadIconDirectory_Win3(void)
|
||||
}
|
||||
}
|
||||
|
||||
return (width_best > 0 && height_best > 0) ? 0 : -ENOENT;
|
||||
if (best_icon >= 0) {
|
||||
if (rt != 0) {
|
||||
dir.res->pBestIcon = &dir.res->iconDirectory[best_icon];
|
||||
} else {
|
||||
dir.ico->pBestIcon = &dir.ico->iconDirectory[best_icon];
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// No icons???
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -505,8 +605,26 @@ rp_image_const_ptr ICOPrivate::loadImage_Win3(void)
|
||||
// Mask row is 1bpp and must also be 32-bit aligned.
|
||||
unsigned int mask_stride = ALIGN_BYTES(4, width / 8);
|
||||
|
||||
const ICONDIRENTRY *const pBestIcon = dir.ico->pBestIcon;
|
||||
unsigned int addr = le32_to_cpu(pBestIcon->dwImageOffset) + header_size;
|
||||
// Icon file (this->file for .ico; IResourceReader::open() for .exe/.dll)
|
||||
IRpFilePtr f_icon;
|
||||
unsigned int addr;
|
||||
|
||||
const uint16_t rt = imageResType();
|
||||
if (rt != 0) {
|
||||
// Open the resource.
|
||||
const auto &res = *(dir.res);
|
||||
f_icon = res.resReader->open(rt, le16_to_cpu(res.pBestIcon->nID), res.lang);
|
||||
if (!f_icon) {
|
||||
// Unable to open the resource.
|
||||
return {};
|
||||
}
|
||||
addr = 0;
|
||||
} else {
|
||||
// Get the icon's starting address within the .ico file.
|
||||
const ICONDIRENTRY *const pBestIcon = dir.ico->pBestIcon;
|
||||
f_icon = this->file;
|
||||
addr = le32_to_cpu(pBestIcon->dwImageOffset) + header_size;
|
||||
}
|
||||
|
||||
// For 8bpp or less, a color table is present.
|
||||
// NOTE: Need to manually set the alpha channel to 0xFF.
|
||||
@ -515,7 +633,7 @@ rp_image_const_ptr ICOPrivate::loadImage_Win3(void)
|
||||
const unsigned int palette_count = (1U << bitcount);
|
||||
const size_t palette_size = palette_count * sizeof(uint32_t);
|
||||
pal_data.resize(palette_count);
|
||||
size_t size = file->seekAndRead(addr, pal_data.data(), palette_size);
|
||||
size_t size = f_icon->seekAndRead(addr, pal_data.data(), palette_size);
|
||||
if (size != palette_size) {
|
||||
// Seek and/or read error.
|
||||
return {};
|
||||
@ -536,7 +654,7 @@ rp_image_const_ptr ICOPrivate::loadImage_Win3(void)
|
||||
size_t biSizeImage = icon_size + mask_size;
|
||||
rp::uvector<uint8_t> img_data;
|
||||
img_data.resize(biSizeImage);
|
||||
size_t size = file->seekAndRead(addr, img_data.data(), biSizeImage);
|
||||
size_t size = f_icon->seekAndRead(addr, img_data.data(), biSizeImage);
|
||||
if (size != biSizeImage) {
|
||||
// Seek and/or read error.
|
||||
return {};
|
||||
@ -714,14 +832,22 @@ rp_image_const_ptr ICOPrivate::loadImage_Win3(void)
|
||||
rp_image_const_ptr ICOPrivate::loadImage_WinVista_PNG(void)
|
||||
{
|
||||
// Use RpPng to load a PNG image.
|
||||
// NOTE: PartitionFile only supports IDiscReader, so we'll need to
|
||||
// create a dummy DiscReader object.
|
||||
IRpFilePtr f_png;
|
||||
|
||||
IDiscReaderPtr discReader = std::make_shared<DiscReader>(file, 0, file->size());
|
||||
const ICONDIRENTRY *const pBestIcon = dir.ico->pBestIcon;
|
||||
PartitionFile *const partitionFile = new PartitionFile(discReader, pBestIcon->dwImageOffset, pBestIcon->dwBytesInRes);
|
||||
img = RpPng::load(partitionFile);
|
||||
delete partitionFile;
|
||||
const uint16_t rt = imageResType();
|
||||
if (rt != 0) {
|
||||
// Load the PNG from a resource.
|
||||
const auto &res = *(dir.res);
|
||||
f_png = res.resReader->open(rt, res.id, res.lang);
|
||||
} else {
|
||||
// NOTE: PartitionFile only supports IDiscReader, so we'll need to
|
||||
// create a dummy DiscReader object.
|
||||
IDiscReaderPtr discReader = std::make_shared<DiscReader>(file, 0, file->size());
|
||||
const ICONDIRENTRY *const pBestIcon = dir.ico->pBestIcon;
|
||||
f_png = std::make_shared<PartitionFile>(discReader, pBestIcon->dwImageOffset, pBestIcon->dwBytesInRes);
|
||||
}
|
||||
|
||||
img = RpPng::load(f_png);
|
||||
return img;
|
||||
}
|
||||
|
||||
@ -773,6 +899,37 @@ rp_image_const_ptr ICOPrivate::loadImage(void)
|
||||
*/
|
||||
ICO::ICO(const IRpFilePtr &file)
|
||||
: super(new ICOPrivate(this, file))
|
||||
{
|
||||
init(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read an icon or cursor from a Windows executable.
|
||||
*
|
||||
* A ROM image must be opened by the caller. The file handle
|
||||
* will be ref()'d and must be kept open in order to load
|
||||
* data from the ROM image.
|
||||
*
|
||||
* To close the file, either delete this object or call close().
|
||||
*
|
||||
* 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 id [in] Resource ID (-1 for "first entry")
|
||||
* @param lang [in] Language ID (-1 for "first entry")
|
||||
*/
|
||||
ICO::ICO(const LibRpBase::IResourceReaderPtr &resReader, uint16_t type, int id, int lang)
|
||||
: super(new ICOPrivate(this, resReader, type, id, lang))
|
||||
{
|
||||
init(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Common initialization function.
|
||||
* @param res True if the icon is in a Windows executable; false if not.
|
||||
*/
|
||||
void ICO::init(bool res)
|
||||
{
|
||||
RP_D(ICO);
|
||||
|
||||
@ -781,12 +938,31 @@ ICO::ICO(const IRpFilePtr &file)
|
||||
return;
|
||||
}
|
||||
|
||||
// Read the ICO header.
|
||||
d->file->rewind();
|
||||
size_t size = d->file->read(&d->icoHeader, sizeof(d->icoHeader));
|
||||
if (size != sizeof(d->icoHeader)) {
|
||||
d->file.reset();
|
||||
return;
|
||||
// Read the ICONDIR.
|
||||
if (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.
|
||||
d->file.reset();
|
||||
delete d->dir.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();
|
||||
delete d->dir.res;
|
||||
d->dir.res = nullptr;
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
d->file->rewind();
|
||||
size_t size = d->file->read(&d->icoHeader, sizeof(d->icoHeader));
|
||||
if (size != sizeof(d->icoHeader)) {
|
||||
d->file.reset();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Determine the icon type.
|
||||
@ -794,6 +970,10 @@ ICO::ICO(const IRpFilePtr &file)
|
||||
default:
|
||||
// Not recognized...
|
||||
d->file.reset();
|
||||
if (res) {
|
||||
delete d->dir.res;
|
||||
d->dir.res = nullptr;
|
||||
}
|
||||
return;
|
||||
|
||||
case ICO_WIN1_FORMAT_MAYBE_WIN3: {
|
||||
@ -801,6 +981,10 @@ ICO::ICO(const IRpFilePtr &file)
|
||||
default:
|
||||
// Not recognized...
|
||||
d->file.reset();
|
||||
if (res) {
|
||||
delete d->dir.res;
|
||||
d->dir.res = nullptr;
|
||||
}
|
||||
return;
|
||||
case ICO_WIN3_TYPE_ICON:
|
||||
d->iconType = ICOPrivate::IconType::Icon_Win3;
|
||||
@ -819,6 +1003,10 @@ ICO::ICO(const IRpFilePtr &file)
|
||||
if (ret != 0) {
|
||||
// Failed to load the icon directory.
|
||||
d->file.reset();
|
||||
if (res) {
|
||||
delete d->dir.res;
|
||||
d->dir.res = nullptr;
|
||||
}
|
||||
return;
|
||||
}
|
||||
break;
|
||||
@ -849,6 +1037,10 @@ ICO::ICO(const IRpFilePtr &file)
|
||||
// Shouldn't get here...
|
||||
assert(!"Invalid case!");
|
||||
d->file.reset();
|
||||
if (res) {
|
||||
delete d->dir.res;
|
||||
d->dir.res = nullptr;
|
||||
}
|
||||
return;
|
||||
|
||||
case ICOPrivate::IconType::Icon_Win1:
|
||||
@ -863,6 +1055,10 @@ ICO::ICO(const IRpFilePtr &file)
|
||||
if (!d->dir.ico->pBestIcon) {
|
||||
// No "best" icon...
|
||||
d->file.reset();
|
||||
if (res) {
|
||||
delete d->dir.res;
|
||||
d->dir.res = nullptr;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@ -871,6 +1067,10 @@ ICO::ICO(const IRpFilePtr &file)
|
||||
default:
|
||||
// Not supported...
|
||||
d->file.reset();
|
||||
if (res) {
|
||||
delete d->dir.res;
|
||||
d->dir.res = nullptr;
|
||||
}
|
||||
return;
|
||||
|
||||
case BITMAPCOREHEADER_SIZE:
|
||||
|
@ -10,9 +10,39 @@
|
||||
|
||||
#include "FileFormat.hpp"
|
||||
|
||||
// IResourceReader for loading icons from .exe/.dll files.
|
||||
#include "librpbase/disc/IResourceReader.hpp"
|
||||
|
||||
namespace LibRpTexture {
|
||||
|
||||
FILEFORMAT_DECL_BEGIN(ICO)
|
||||
|
||||
public:
|
||||
/**
|
||||
* Read an icon or cursor from a Windows executable.
|
||||
*
|
||||
* A ROM image must be opened by the caller. The file handle
|
||||
* will be ref()'d and must be kept open in order to load
|
||||
* data from the ROM image.
|
||||
*
|
||||
* To close the file, either delete this object or call close().
|
||||
*
|
||||
* 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 id [in] Resource ID (-1 for "first entry")
|
||||
* @param lang [in] Language ID (-1 for "first entry")
|
||||
*/
|
||||
explicit ICO(const LibRpBase::IResourceReaderPtr &resReader, uint16_t type, int id, int lang);
|
||||
|
||||
private:
|
||||
/**
|
||||
* Common initialization function.
|
||||
* @param res True if the icon is in a Windows executable; false if not.
|
||||
*/
|
||||
void init(bool res);
|
||||
|
||||
FILEFORMAT_DECL_END()
|
||||
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user