Merge branch 'feature/IsoPartition-readdir'
Some checks failed
Codecov / run (push) Has been cancelled
CodeQL / Analyze (cpp) (push) Has been cancelled

Includes:
- IsoPartition::readdir()
- ISO-9660 Joliet support
- ICO parser (for Win1.x and Win3.x)
- EXE thumbnailing (for Win1.x, Win3.x, and Win32/Win64)
- ISO thumbnailing via AUTORUN.INF
This commit is contained in:
David Korth 2025-06-09 23:48:08 -04:00
commit e2eede76f7
42 changed files with 3247 additions and 264 deletions

19
NEWS.md
View File

@ -2,6 +2,16 @@
## v2.6 (released 2025/??/??)
* New parsers:
* ICO: Windows icons and cursors. Supports most icons and cursors designed
for Windows 3.x and later (including Windows Vista PNG-format icons),
plus the old Windows 1.x format. Only the "best" version for each icon
is selected for thumbnailing. (Largest size and highest color depth.)
* Icon thumbnailing is not actually enabled on Windows and Linux systems
at the moment, since it may conflict with system icon handling.
It's mostly only usable for rpcli and for use as a subclass elsewhere.
* Partially fixes #170: Icon files: ICNS, ICO, EXE
* New parser features:
* Xbox360_STFS: Fix titles for some packages that were authored incorrectly
and have mojibake titles. Specifically, the titles were originally encoded
@ -9,6 +19,14 @@
cp1252 when being converted to UTF-16BE.
* Fixes #450: X360 - Non-Latin Titles appearing as mojibake
* Reported by @Masamune3210.
* EXE: The application icon can now be extracted using rpcli.
* ISO: AUTORUN.INF is now parsed. This includes a tab showing the contents
of AUTORUN.INF, as well as the disc icon from a .ico or .exe/.dll file.
* Fixes #232: ISO: Parse autorun.inf
* ISO: Joliet file systems are now partially supported. This was added to
handle older Windows disc images that use a long filename for the icon,
and the disc is authored with Joliet for long filenames but an old version
of ISO-9660, resulting in 8.3 filenames in the ISO-9660 directories.
* Bug fixes:
* Windows: Work around a potential libpng crash when attempting to read
@ -19,6 +37,7 @@
* Other changes:
* rpcli: Added more colorization for warning messages.
* rpcli: Refactored console handling into a separate library, libgsvt.
* IsoPartition: Implemented readdir(). Not currently used by anything, though.
## v2.5.1 (released 2025/05/10)

View File

@ -126,7 +126,6 @@ SET(${PROJECT_NAME}_SRCS
disc/GcnPartition_p.cpp
disc/GczReader.cpp
disc/GdiReader.cpp
disc/IResourceReader.cpp
disc/IsoPartition.cpp
disc/NASOSReader.cpp
disc/NCCHReader.cpp
@ -298,7 +297,6 @@ SET(${PROJECT_NAME}_H
Other/exe_mz_structs.h
Other/exe_ne_structs.h
Other/exe_pe_structs.h
Other/exe_res_structs.h
Other/macho_structs.h
Other/nfp_structs.h
@ -345,7 +343,6 @@ SET(${PROJECT_NAME}_H
disc/GcnPartition_p.hpp
disc/GczReader.hpp
disc/GdiReader.hpp
disc/IResourceReader.hpp
disc/IsoPartition.hpp
disc/MultiTrackSparseDiscReader.hpp
disc/NASOSReader.hpp

View File

@ -1550,10 +1550,11 @@ int GameCube::loadFieldData(void)
// - 21.29: IOS version. (21.29 == v5405)
IFst::Dir *const dirp = d->updatePartition->opendir("/_sys/");
if (dirp) {
IFst::DirEnt *dirent;
const IFst::DirEnt *dirent;
while ((dirent = d->updatePartition->readdir(dirp)) != nullptr) {
if (!dirent->name || dirent->type != DT_REG)
if (!dirent->name || dirent->type != DT_REG) {
continue;
}
// Check for a retail System Menu.
if (dirent->name[0] == 'R') {

View File

@ -213,15 +213,16 @@ int PlayStationDiscPrivate::loadSystemCnf(const IsoPartitionPtr &pt)
return ret;
}
// CNF file should be less than 2048 bytes.
// CNF file should be 2048 bytes or less.
static constexpr size_t SYSTEM_CNF_SIZE_MAX = 2048;
const off64_t fileSize = f_system_cnf->size();
if (fileSize > 2048) {
if (fileSize > static_cast<off64_t>(SYSTEM_CNF_SIZE_MAX)) {
return -ENOMEM;
}
// Read the entire file into memory.
char buf[2049];
size_t size = f_system_cnf->read(buf, 2048);
char buf[SYSTEM_CNF_SIZE_MAX + 1];
size_t size = f_system_cnf->read(buf, SYSTEM_CNF_SIZE_MAX);
if (size != static_cast<size_t>(fileSize)) {
// Short read.
return -EIO;

View File

@ -23,10 +23,20 @@ using namespace LibRpBase;
using namespace LibRpFile;
using namespace LibRpText;
// ISO-9660 file system access for AUTORUN.INF
#include "../disc/IsoPartition.hpp"
#include "ini.h"
// Windows icon handler
#include "../Other/EXE.hpp"
#include "librptexture/fileformat/ICO.hpp"
using namespace LibRpTexture;
// C++ STL classes
#include <typeinfo>
using std::array;
using std::map;
using std::string;
using std::unique_ptr;
using std::vector;
namespace LibRomData {
@ -79,9 +89,14 @@ public:
// actual sector information.
unsigned int sector_offset;
// UDF version
// TODO: Descriptors?
const char *s_udf_version;
// Joliet level
enum class JolietSVDType : uint8_t {
None = 0,
UCS2_Level1 = 1, // NOTE: UCS-2 BE
UCS2_Level2 = 2, // NOTE: UCS-2 BE
UCS2_Level3 = 3, // NOTE: UCS-2 BE
};
JolietSVDType jolietSVDType;
public:
// El Torito boot catalog LBA. (present if non-zero)
@ -95,6 +110,11 @@ public:
};
uint32_t boot_platforms;
public:
// UDF version
// TODO: Descriptors?
const char *s_udf_version;
public:
/**
* Check additional volume descirptors.
@ -189,6 +209,48 @@ public:
{
return (likely(discType != DiscType::CDi) ? lm32.he : be32_to_cpu(lm32.be));
}
public:
// Icon
rp_image_ptr img_icon;
// IsoPartition
IsoPartitionPtr isoPartition;
/**
* Open the ISO-9660 partition.
* @return 0 on success; negative POSIX error code on error.
*/
int openIsoPartition(void);
// AUTORUN.INF contents
// TODO: Concatenate the section and key names.
// For now, only handling the "[autorun]" section.
// Keys are stored as-is, without concatenation.
map<string, string> autorun_inf;
/**
* ini.h callback for parsing AUTORUN.INF.
* @param user [in] User data parameter (this)
* @param section [in] Section name
* @param name [in] Value name
* @param value [in] Value
* @return 0 to continue; 1 to stop.
*/
static int parse_autorun_inf(void *user, const char *section, const char *name, const char *value);
/**
* Load AUTORUN.INF.
* AUTORUN.INF will be loaded into autorun_inf.
* @return 0 on success; negative POSIX error code on error.
*/
int loadAutorunInf(void);
/**
* Load the icon.
* @return Icon, or nullptr on error.
*/
rp_image_const_ptr loadIcon(void);
};
ROMDATA_IMPL(ISO)
@ -227,9 +289,10 @@ ISOPrivate::ISOPrivate(const IRpFilePtr &file)
: super(file, &romDataInfo)
, discType(DiscType::Unknown)
, sector_offset(0)
, s_udf_version(nullptr)
, jolietSVDType(JolietSVDType::None)
, boot_catalog_LBA(0)
, boot_platforms(0)
, s_udf_version(nullptr)
{
// Clear the disc header structs.
memset(&pvd, 0, sizeof(pvd));
@ -274,6 +337,7 @@ void ISOPrivate::checkVolumeDescriptors(void)
// Found the terminator.
foundTerminator = true;
break;
case ISO_VDT_BOOT_RECORD:
if (boot_LBA != 0)
break;
@ -283,6 +347,42 @@ void ISOPrivate::checkVolumeDescriptors(void)
boot_LBA = le32_to_cpu(vd.boot.boot_catalog_addr);
}
break;
case ISO_VDT_SUPPLEMENTARY: {
if (vd.header.version == ISO_VD_VERSION) {
// Check the escape sequences.
// Escape sequence format: '%', '/', x
const char *const p_end = &vd.pri.svd_escape_sequences[sizeof(vd.pri.svd_escape_sequences)-3];
for (const char *p = vd.pri.svd_escape_sequences; p < p_end && *p != '\0'; p++) {
if (p[0] != '%' || p[1] != '/') {
continue;
}
// Check if this is a valid UCS-2 level seqeunce.
// NOTE: Using the highest level specified.
JolietSVDType newType = JolietSVDType::None;
switch (p[2]) {
case '@':
newType = JolietSVDType::UCS2_Level1;
break;
case 'C':
newType = JolietSVDType::UCS2_Level2;
break;
case 'E':
newType = JolietSVDType::UCS2_Level3;
break;
default:
break;
}
if (jolietSVDType < newType) {
jolietSVDType = newType;
}
}
}
break;
}
default:
break;
}
@ -573,6 +673,215 @@ void ISOPrivate::addPVDTimestamps_metaData(RomMetaData *metaData, const T *pvd)
pvd_time_to_unix_time(&pvd->btime));
}
/**
* Open the ISO-9660 partition.
* @return 0 on success; negative POSIX error code on error.
*/
int ISOPrivate::openIsoPartition(void)
{
if (isoPartition) {
// ISO-9660 partition is already open.
return 0;
} else if (!file) {
// File is not open.
return -EBADF;
}
isoPartition = std::make_shared<IsoPartition>(file, 0, 0);
if (!isoPartition->isOpen()) {
// Unable to open the ISO-9660 file system.
// TODO: Better error code?
isoPartition.reset();
return -EIO;
}
return 0;
}
/**
* ini.h callback for parsing AUTORUN.INF.
* @param user [in] User data parameter (this)
* @param section [in] Section name
* @param name [in] Value name
* @param value [in] Value
* @return 0 to continue; 1 to stop.
*/
int ISOPrivate::parse_autorun_inf(void *user, const char *section, const char *name, const char *value)
{
// TODO: Character encoding? Assuming ASCII.
// TODO: Concatenate the section and key names.
// For now, only handling the "[autorun]" section.
#if 0
string s_name;
if (section[0] != '\0') {
s_name = section;
s_name += '|';
}
s_name += name;
#endif
if (strcasecmp(section, "autorun") != 0) {
// Not "[autorun]".
return 0;
}
// Convert the name to lowercase.
// TODO: Store the original case elsewhere?
string s_name = name;
std::transform(s_name.begin(), s_name.end(), s_name.begin(),
[](char c) noexcept -> char { return std::tolower(c); });
// Save the value for later.
ISOPrivate *const d = static_cast<ISOPrivate*>(user);
auto ret = d->autorun_inf.emplace(std::move(s_name), value);
// NOTE: This will stop processing if a duplicate key is found.
return (ret.second ? 0 : 1);
}
/**
* Load AUTORUN.INF.
* AUTORUN.INF will be loaded into autorun_inf.
* @return 0 on success; negative POSIX error code on error.
*/
int ISOPrivate::loadAutorunInf(void)
{
if (!autorun_inf.empty()) {
// AUTORUN.INF is already loaded.
return 0;
}
// Make sure the ISO-9660 file system is open.
int ret = openIsoPartition();
if (ret != 0) {
return ret;
}
// Attempt to load AUTORUN.INF from the ISO-9660 file system.
IRpFilePtr f_autorun_inf = isoPartition->open("/AUTORUN.INF");
if (!f_autorun_inf) {
// Unable to open AUTORUN.INF.
return -isoPartition->lastError();
}
// AUTORUN.INF should be 2048 bytes or less.
static constexpr size_t AUTORUN_INF_SIZE_MAX = 2048;
const off64_t autorun_inf_size = f_autorun_inf->size();
if (autorun_inf_size > static_cast<off64_t>(AUTORUN_INF_SIZE_MAX)) {
// File is too big.
return -ENOMEM;
}
// Read the entire file into memory.
char buf[AUTORUN_INF_SIZE_MAX + 1];
size_t size = f_autorun_inf->read(buf, AUTORUN_INF_SIZE_MAX);
if (size != static_cast<size_t>(autorun_inf_size)) {
// Short read.
return {};
}
buf[static_cast<size_t>(autorun_inf_size)] = '\0';
// Parse AUTORUN.INF.
// TODO: Save other AUTORUN data for a tab?
ret = ini_parse_string(buf, parse_autorun_inf, this);
if (ret < 0) {
// Failed to parse AUTORUN.INF.
autorun_inf.clear();
return -EIO;
}
return 0;
}
/**
* Load the icon.
* @return Icon, or nullptr on error.
*/
rp_image_const_ptr ISOPrivate::loadIcon(void)
{
if (img_icon) {
// Icon has already been loaded.
return img_icon;
} else if (!this->isValid || static_cast<int>(this->discType) < 0) {
// Can't load the icon.
return {};
}
// Make sure the ISO-9660 file system is open.
int ret = openIsoPartition();
if (ret != 0) {
return {};
}
// Make sure AUTORUN.INF is loaded.
ret = loadAutorunInf();
if (ret != 0 || autorun_inf.empty()) {
// Unable to load AUTORUN.INF.
return {};
}
// Get the icon filename.
// TODO: Concatenate the section and key names.
// For now, only handling the "[autorun]" section.
auto iter = autorun_inf.find("icon");
if (iter == autorun_inf.end()) {
// No icon...
return {};
}
string icon_filename = iter->second;
// Check if there's an icon index specified.
// - Positive: Zero-based index
// - Negative: Resource ID
int iconindex = 0; // default is "first icon"
size_t dotpos = icon_filename.find_last_of('.');
size_t commapos = icon_filename.find_last_of(',');
if (commapos < (icon_filename.size()-1) && dotpos < commapos) {
// Found an icon index.
char *endptr = nullptr;
iconindex = strtol(&icon_filename[commapos+1], &endptr, 10);
if (*endptr == '\0') {
// Icon index is valid. Use it.
icon_filename.resize(commapos);
} else {
// Icon index is invalid.
iconindex = 0;
}
}
// Open the icon file from the disc.
IRpFilePtr f_file = isoPartition->open(icon_filename.c_str());
if (!f_file) {
// Unable to open the icon file.
return {};
}
// Use the file extension to determine the reader.
// NOTE: May be better to check the file header...
rp_image_const_ptr icon;
const size_t icon_filename_size = icon_filename.size();
if (icon_filename_size > 4) {
if (!strncasecmp(&icon_filename[icon_filename_size-4], ".exe", 4) ||
!strncasecmp(&icon_filename[icon_filename_size-4], ".dll", 4))
{
// EXE or DLL.
unique_ptr<EXE> exe(new EXE(f_file));
if (exe->isValid()) {
icon = exe->loadSpecificIcon(iconindex);
}
}
}
if (!icon) {
// No EXE or DLL. Try plain .ico.
unique_ptr<ICO> ico(new ICO(f_file));
if (ico->isValid()) {
icon = ico->image();
}
}
return icon;
}
/** ISO **/
/**
@ -662,7 +971,19 @@ ISO::ISO(const IRpFilePtr &file)
}
}
/** ROM detection functions. **/
/**
* Close the opened file.
*/
void ISO::close(void)
{
RP_D(ISO);
d->isoPartition.reset();
// Call the superclass function.
super::close();
}
/** ROM detection functions **/
/**
* Check for a valid PVD.
@ -774,6 +1095,92 @@ const char *ISO::systemName(unsigned int type) const
return sysNames[sysID][type & SYSNAME_TYPE_MASK];
}
/**
* Get a bitfield of image types this class can retrieve.
* @return Bitfield of supported image types. (ImageTypesBF)
*/
uint32_t ISO::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 ISO::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> ISO::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> ISO::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 ISO::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.
@ -793,7 +1200,7 @@ int ISO::loadFieldData(void)
return -EIO;
}
d->fields.reserve(18); // Maximum of 18 fields.
d->fields.reserve(19); // Maximum of 19 fields.
// NOTE: All fields are space-padded. (0x20, ' ')
// TODO: ascii_to_utf8()?
@ -839,7 +1246,7 @@ int ISO::loadFieldData(void)
d->fields.addField_string(C_("ISO", "Sector Format"), sector_format);
switch (d->discType) {
case ISOPrivate::DiscType::ISO9660:
case ISOPrivate::DiscType::ISO9660: {
// ISO-9660
d->fields.setTabName(0, C_("ISO", "ISO-9660 PVD"));
@ -867,7 +1274,17 @@ int ISO::loadFieldData(void)
v_boot_platforms_names, 0, d->boot_platforms);
}
// Joliet SVD
const char *const s_joliet_title = C_("ISO", "Joliet");
if (d->jolietSVDType == ISOPrivate::JolietSVDType::None) {
d->fields.addField_string(s_joliet_title, C_("ISO|JolietSVDType", "Not Present"));
} else {
d->fields.addField_string(s_joliet_title,
fmt::format(FRUN("UCS-2 Level {:d}"), static_cast<uint8_t>(d->jolietSVDType)));
}
break;
}
case ISOPrivate::DiscType::HighSierra:
// High Sierra
@ -911,6 +1328,16 @@ int ISO::loadFieldData(void)
d->s_udf_version);
}
// AUTORUN.INF
int ret = d->loadAutorunInf();
if (ret == 0) {
// Add the AUTORUN.INF fields as-is.
d->fields.addTab("AUTORUN.INF");
for (const auto &pair : d->autorun_inf) {
d->fields.addField_string(pair.first.c_str(), pair.second);
}
}
// Finished reading the field data.
return static_cast<int>(d->fields.count());
}
@ -955,6 +1382,26 @@ int ISO::loadMetaData(void)
return static_cast<int>(d->metaData.count());
}
/**
* 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 ISO::loadInternalImage(ImageType imageType, rp_image_const_ptr &pImage)
{
ASSERT_loadInternalImage(imageType, pImage);
RP_D(ISO);
ROMDATA_loadInternalImage_single(
IMG_INT_ICON, // ourImageType
d->file, // file
d->isValid, // isValid
d->discType, // romType
d->img_icon, // imgCache
d->loadIcon); // func
}
/**
* Check for "viewed" achievements.
*

View File

@ -19,6 +19,7 @@ namespace LibRpBase {
namespace LibRomData {
ROMDATA_DECL_BEGIN(ISO)
ROMDATA_DECL_CLOSE()
public:
/**
@ -38,6 +39,9 @@ public:
static void addMetaData_PVD(LibRpBase::RomMetaData *metaData, const struct _ISO_Primary_Volume_Descriptor *pvd);
ROMDATA_DECL_METADATA()
ROMDATA_DECL_IMGSUPPORT()
ROMDATA_DECL_IMGPF()
ROMDATA_DECL_IMGINT()
ROMDATA_DECL_VIEWED_ACHIEVEMENTS()
ROMDATA_DECL_END()

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,89 @@ void EXEPrivate::addFields_VS_VERSION_INFO(const VS_FIXEDFILEINFO *pVsFfi, const
fields.addField_listData("StringFileInfo", &params);
}
/**
* Load a specific icon by index.
* @param iconindex Icon index (positive for zero-based index; negative for resource ID)
* @return Icon, or nullptr if not found.
*/
rp_image_const_ptr EXEPrivate::loadSpecificIcon(int iconindex)
{
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 {};
}
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.
int resID;
if (iconindex == 0) {
// Default icon
resID = -1;
} else if (iconindex > 0) {
// Positive icon index
// This is a zero-based index into the RT_GROUP_ICON table.
resID = rsrcReader->lookup_resource_ID(RT_GROUP_ICON, iconindex);
if (resID < 0) {
// Not found.
return {};
}
} else {
// Negative icon index
// This is an actual resource ID.
resID = abs(iconindex);
}
// Attempt to load the default icon.
unique_ptr<ICO> ico(new ICO(rsrcReader, type, resID, -1));
if (!ico->isValid()) {
// Unable to load the default icon.
return {};
}
// Return the icon's image.
return ico->image();
}
/**
* 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;
}
// Load icon 0.
return loadSpecificIcon(0);
}
/** MZ-specific **/
/**
@ -936,6 +1055,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 +1252,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 +1353,51 @@ 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
}
/**
* Load a specific icon by index.
* @param iconindex Icon index (positive for zero-based index; negative for resource ID)
* @return Icon, or nullptr if not found.
*/
rp_image_const_ptr EXE::loadSpecificIcon(int iconindex)
{
RP_D(EXE);
if (iconindex == 0) {
// Main icon. See if it's already loaded.
if (d->img_icon) {
// Icon has already been loaded.
return d->img_icon;
}
}
rp_image_const_ptr icon = d->loadSpecificIcon(iconindex);
if (iconindex == 0) {
// Cache the main icon.
d->img_icon = icon;
}
return icon;
}
/**
* 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,18 @@ namespace LibRomData {
ROMDATA_DECL_BEGIN(EXE)
ROMDATA_DECL_DANGEROUS()
ROMDATA_DECL_METADATA()
ROMDATA_DECL_IMGSUPPORT()
ROMDATA_DECL_IMGPF()
ROMDATA_DECL_IMGINT()
public:
/**
* Load a specific icon by index.
* @param iconindex Icon index (positive for zero-based index; negative for resource ID)
* @return Icon, or nullptr if not found.
*/
LibRpTexture::rp_image_const_ptr loadSpecificIcon(int iconindex);
ROMDATA_DECL_VIEWED_ACHIEVEMENTS()
ROMDATA_DECL_END()

View File

@ -17,6 +17,8 @@
#include "exe_le_structs.h"
#include "disc/PEResourceReader.hpp"
using LibRpBase::IResourceReader;
using LibRpBase::IResourceReaderPtr;
// Uninitialized vector class
#include "uvector.h"
@ -100,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.
*
@ -111,6 +119,22 @@ public:
*/
void addFields_VS_VERSION_INFO(const VS_FIXEDFILEINFO *pVsFfi, const IResourceReader::StringFileInfo *pVsSfi);
// Icon
LibRpTexture::rp_image_const_ptr img_icon;
/**
* Load a specific icon by index.
* @param iconindex Icon index (positive for zero-based index; negative for resource ID)
* @return Icon, or nullptr if not found.
*/
LibRpTexture::rp_image_const_ptr loadSpecificIcon(int iconindex);
/**
* Load the icon.
* @return Icon, or nullptr on error.
*/
LibRpTexture::rp_image_const_ptr loadIcon(void);
/** MZ-specific **/
/**

View File

@ -306,7 +306,8 @@ static const array<RomDataFns, 38> romDataFns_header = {{
// so they should go at the end of the address=0 section.
#ifdef _WIN32
// NOTE: Windows provides its own thumbnail and metadata extraction for EXEs.
GetRomDataFns(EXE, ATTR_HAS_DPOVERLAY),
// NOTE 2: EXE class does support thumbnailing now, but it shouldn't be registered as such.
GetRomDataFns(EXE, /*ATTR_HAS_THUMBNAIL |*/ ATTR_HAS_DPOVERLAY),
#else /* !_WIN32 */
GetRomDataFns(EXE, ATTR_HAS_DPOVERLAY | ATTR_HAS_METADATA), // TODO: Thumbnailing on non-Windows platforms.
#endif /* _WIN32 */
@ -1153,15 +1154,19 @@ static void init_supportedFileExtensions(void)
static constexpr unsigned int FFF_ATTRS = ATTR_HAS_THUMBNAIL | ATTR_HAS_METADATA;
const vector<const char*> &vec_exts_fileFormat = FileFormatFactory::supportedFileExtensions();
for (const char *ext : vec_exts_fileFormat) {
// Explicitly prevent thumbnailing of ".ico" and ".cur" on Windows.
const bool block_thumbnail = !strcmp(ext, ".ico") || !strcmp(ext, ".cur");
unsigned int attrs = (unlikely(block_thumbnail)) ? ATTR_HAS_METADATA : FFF_ATTRS;
auto iter = map_exts.find(ext);
if (iter != map_exts.end()) {
// We already had this extension.
// Update its attributes.
iter->second |= FFF_ATTRS;
iter->second |= attrs;
} else {
// First time encountering this extension.
map_exts[ext] = FFF_ATTRS;
vec_exts.emplace_back(ext, FFF_ATTRS);
map_exts[ext] = attrs;
vec_exts.emplace_back(ext, attrs);
}
}

View File

@ -422,14 +422,15 @@ IFst::Dir *GcnFst::opendir(const char *path)
return nullptr;
}
IFst::Dir *dirp = new IFst::Dir(this);
d->fstDirCount++;
// TODO: Better way to get dir_idx?
dirp->dir_idx = static_cast<int>(fst_entry - d->fstData);
const int dir_idx = static_cast<int>(fst_entry - d->fstData);
IFst::Dir *dirp = new IFst::Dir(this, dir_idx);
d->fstDirCount++;
// Initialize the entry to this directory.
// readdir() will automatically seek to the next entry.
dirp->entry.ptnum = 0; // not used for GCN/Wii
dirp->entry.extra = nullptr; // not used for GCN/Wii
dirp->entry.ptnum = 0; // not used for GCN/Wii
dirp->entry.idx = dirp->dir_idx;
dirp->entry.type = DT_DIR;
dirp->entry.name = d->entry_name(fst_entry);
@ -447,7 +448,7 @@ IFst::Dir *GcnFst::opendir(const char *path)
* @return IFst::DirEnt*, or nullptr if end of directory or on error.
* (End of directory does not set lastError; an error does.)
*/
IFst::DirEnt *GcnFst::readdir(IFst::Dir *dirp)
const IFst::DirEnt *GcnFst::readdir(IFst::Dir *dirp)
{
assert(dirp != nullptr);
assert(dirp->parent == this);

View File

@ -68,7 +68,7 @@ public:
* @return DirEnt*, or nullptr if end of directory or on error.
* (TODO: Add lastError()?)
*/
DirEnt *readdir(Dir *dirp) final;
const DirEnt *readdir(Dir *dirp) final;
/**
* Close an opened directory.

View File

@ -2,7 +2,7 @@
* ROM Properties Page shell extension. (libromdata) *
* GcnPartition.cpp: GameCube partition reader. *
* *
* Copyright (c) 2016-2024 by David Korth. *
* Copyright (c) 2016-2025 by David Korth. *
* SPDX-License-Identifier: GPL-2.0-or-later *
***************************************************************************/
@ -217,7 +217,7 @@ IFst::Dir *GcnPartition::opendir(const char *path)
* @return IFst::DirEnt*, or nullptr if end of directory or on error.
* (TODO: Add lastError()?)
*/
IFst::DirEnt *GcnPartition::readdir(IFst::Dir *dirp)
const IFst::DirEnt *GcnPartition::readdir(IFst::Dir *dirp)
{
RP_D(GcnPartition);
if (!d->fst) {

View File

@ -2,7 +2,7 @@
* ROM Properties Page shell extension. (libromdata) *
* GcnPartition.hpp: GameCube partition reader. *
* *
* Copyright (c) 2016-2023 by David Korth. *
* Copyright (c) 2016-2025 by David Korth. *
* SPDX-License-Identifier: GPL-2.0-or-later *
***************************************************************************/
@ -107,7 +107,7 @@ public:
* @return IFst::DirEnt, or nullptr if end of directory or on error.
* (TODO: Add lastError()?)
*/
LibRpBase::IFst::DirEnt *readdir(LibRpBase::IFst::Dir *dirp) final;
const LibRpBase::IFst::DirEnt *readdir(LibRpBase::IFst::Dir *dirp) final;
/**
* Close an opened directory.

View File

@ -19,12 +19,15 @@ using namespace LibRpText;
using std::string;
using std::unordered_map;
// TODO: HSFS/CDI support?
namespace LibRomData {
class IsoPartitionPrivate
{
public:
IsoPartitionPrivate(IsoPartition *q, off64_t partition_offset, int iso_start_offset);
~IsoPartitionPrivate();
private:
RP_DISABLE_COPY(IsoPartitionPrivate)
@ -36,8 +39,15 @@ public:
off64_t partition_offset;
off64_t partition_size; // Calculated partition size
// ISO primary volume descriptor
ISO_Primary_Volume_Descriptor pvd;
// ISO volume descriptors
ISO_Primary_Volume_Descriptor pvd, svd;
enum class JolietSVDType : uint8_t {
None = 0,
UCS2_Level1 = 1, // NOTE: UCS-2 BE
UCS2_Level2 = 2, // NOTE: UCS-2 BE
UCS2_Level3 = 3, // NOTE: UCS-2 BE
};
JolietSVDType jolietSVDType;
// Directories
// - Key: Directory name, WITHOUT leading slash. (Root == empty string) [cp1252]
@ -51,23 +61,70 @@ public:
// -1 == unknown
int iso_start_offset;
// IFst::Dir* reference counter
int fstDirCount;
/**
* Is this character a slash or backslash?
* @return True if it is; false if it isn't.
*/
static inline bool is_slash(char c)
{
return (c == '/') || (c == '\\');
}
private:
/**
* Find the last slash or backslash in a path.
* @param path Path.
* (Internal function!)
* @param path Path
* @param size Size of path
* @return Last slash or backslash, or nullptr if not found.
*/
inline const char *findLastSlash(const char *path)
static inline const char *findLastSlash(const char *path, size_t size)
{
const char *sl = strrchr(path, '/');
const char *const bs = strrchr(path, '\\');
if (sl && bs) {
if (bs > sl) {
sl = bs;
const char *p = path + size - 1;
for (; size > 0; size--, p--) {
if (is_slash(*p)) {
return p;
}
}
return (sl ? sl : bs);
return nullptr;
}
public:
/**
* Find the last slash or backslash in a path.
* @param path Path
* @return Last slash or backslash, or nullptr if not found.
*/
static inline const char *findLastSlash(const char *path)
{
return findLastSlash(path, strlen(path));
}
/**
* Find the last slash or backslash in a path.
* @param path Path
* @return Last slash or backslash, or nullptr if not found.
*/
static inline const char *findLastSlash(const string &path)
{
return findLastSlash(path.c_str(), path.size());
}
/**
* Sanitize an incoming path for ISO-9660 lookup.
*
* This function does the following:
* - Converts the path from UTF-8 to cp1252.
* - Removes leading and trailing slashes.
*
* @param path Path to sanitize
* @return Sanitized path (empty string for "/")
*/
static std::string sanitize_path(const char *path);
/**
* Look up a directory entry from a base filename and directory.
* @param pDir [in] Directory
@ -88,16 +145,16 @@ public:
/**
* Look up a directory entry from a filename.
* @param filename Filename [UTF-8]
* @return ISO directory entry.
* @return ISO directory entry
*/
const ISO_DirEntry *lookup(const char *filename);
/**
* Parse an ISO-9660 timestamp.
* @param isofiletime File timestamp.
* @return Unix time.
* @param isofiletime File timestamp
* @return Unix time
*/
time_t parseTimestamp(const ISO_Dir_DateTime_t *isofiletime);
static time_t parseTimestamp(const ISO_Dir_DateTime_t *isofiletime);
};
/** IsoPartitionPrivate **/
@ -107,10 +164,13 @@ IsoPartitionPrivate::IsoPartitionPrivate(IsoPartition *q,
: q_ptr(q)
, partition_offset(partition_offset)
, partition_size(0)
, jolietSVDType(JolietSVDType::None)
, iso_start_offset(iso_start_offset)
, fstDirCount(0)
{
// Clear the PVD struct.
// Clear the Volume Descriptor structs.
memset(&pvd, 0, sizeof(pvd));
memset(&svd, 0, sizeof(svd));
if (!q->m_file) {
q->m_lastError = EIO;
@ -146,10 +206,87 @@ IsoPartitionPrivate::IsoPartitionPrivate(IsoPartition *q,
return;
}
// Attempt to load the Supplementary Volume Descriptor.
// TODO: Keep loading VDs until we reach 0xFF?
size = q->m_file->seekAndRead(partition_offset + ISO_SVD_ADDRESS_2048, &svd, sizeof(svd));
// Verify the signature and volume descriptor type.
if (size == sizeof(svd) &&
svd.header.type == ISO_VDT_SUPPLEMENTARY && svd.header.version == ISO_VD_VERSION &&
!memcmp(svd.header.identifier, ISO_VD_MAGIC, sizeof(svd.header.identifier)))
{
// This is a supplementary volume descriptor.
// Check the escape sequences.
// Escape sequence format: '%', '/', x
const char *const p_end = &svd.svd_escape_sequences[sizeof(svd.svd_escape_sequences)-3];
for (const char *p = svd.svd_escape_sequences; p < p_end && *p != '\0'; p++) {
if (p[0] != '%' || p[1] != '/') {
continue;
}
// Check if this is a valid UCS-2 level seqeunce.
// NOTE: Using the highest level specified.
JolietSVDType newType = JolietSVDType::None;
switch (p[2]) {
case '@':
newType = JolietSVDType::UCS2_Level1;
break;
case 'C':
newType = JolietSVDType::UCS2_Level2;
break;
case 'E':
newType = JolietSVDType::UCS2_Level3;
break;
default:
break;
}
if (jolietSVDType < newType) {
jolietSVDType = newType;
}
}
}
// Load the root directory.
getDirectory("/");
}
IsoPartitionPrivate::~IsoPartitionPrivate()
{
assert(fstDirCount == 0);
}
/**
* Sanitize an incoming path for ISO-9660 lookup.
*
* This function does the following:
* - Converts the path from UTF-8 to cp1252.
* - Removes leading and trailing slashes.
*
* @param path Path to sanitize
* @return Sanitized path (empty string for "/")
*/
std::string IsoPartitionPrivate::sanitize_path(const char *path)
{
// Remove leading slashes.
while (is_slash(*path)) {
path++;
}
if (*path == '\0') {
// Nothing but slashes?
return {};
}
// Convert to cp1252, then remove trailing slashes.
string s_path = utf8_to_cp1252(path, -1);
size_t s_path_len = s_path.size();
while (s_path_len > 0 && is_slash(s_path[s_path_len-1])) {
s_path_len--;
}
s_path.resize(s_path_len);
return s_path;
}
/**
* Look up a directory entry from a base filename and directory.
* @param pDir [in] Directory
@ -167,23 +304,70 @@ const ISO_DirEntry *IsoPartitionPrivate::lookup_int(const DirData_t *pDir, const
const ISO_DirEntry *dirEntry_found = nullptr;
const uint8_t *p = pDir->data();
const uint8_t *const p_end = p + pDir->size();
while (p < p_end) {
// Temporary buffer for converting Joliet UCS-2 filenames to cp1252.
char joliet_cp1252_buf[128];
while ((p + sizeof(ISO_DirEntry)) < p_end) {
const ISO_DirEntry *dirEntry = reinterpret_cast<const ISO_DirEntry*>(p);
if (dirEntry->entry_length < sizeof(*dirEntry)) {
// End of directory.
if (dirEntry->entry_length == 0) {
// Directory entries cannot span multiple sectors in
// multi-sector directories, so if needed, the rest
// of the sector is padded with 00.
// Find the next non-zero byte.
for (p++; p < p_end; p++) {
if (*p != '\0') {
// Found a non-zero byte.
break;
}
}
if (p >= p_end) {
// No more non-zero bytes.
break;
}
continue;
} else if (dirEntry->entry_length < sizeof(*dirEntry)) {
// Invalid directory entry?
break;
}
const char *const entry_filename = reinterpret_cast<const char*>(p) + sizeof(*dirEntry);
const char *entry_filename = reinterpret_cast<const char*>(p) + sizeof(*dirEntry);
if (entry_filename + dirEntry->filename_length > reinterpret_cast<const char*>(p_end)) {
// Filename is out of bounds.
break;
}
// Skip subdirectories with names "\x00" and "\x01".
// These are "special directory identifiers", representing "." and "..", respectively.
if ((dirEntry->flags & ISO_FLAG_DIRECTORY) && dirEntry->filename_length == 1) {
if (static_cast<uint8_t>(entry_filename[0]) <= 0x01) {
// Skip this filename.
p += dirEntry->entry_length;
continue;
}
}
// If using Joliet, the filename is encoded as UCS-2 (UTF-16).
// Use a quick-and-dirty (and not necessarily accurate) conversion to cp1252.
// FIXME: Proper conversion?
uint8_t dirEntry_filename_len = dirEntry->filename_length;
if (jolietSVDType > JolietSVDType::None) {
// dirEntry_filename_len is in bytes, which means it's double
// the number of UCS-2 code points.
// NOTE: UCS-2 *Big-Endian*.
dirEntry_filename_len /= 2;
unsigned int i = 0;
for (; i < dirEntry_filename_len; i++) {
joliet_cp1252_buf[i] = entry_filename[(i * 2) + 1];
}
joliet_cp1252_buf[i] = '\0';
entry_filename = joliet_cp1252_buf;
}
// Check the filename.
// 1990s and early 2000s CD-ROM games usually have
// ";1" filenames, so check for that first.
if (dirEntry->filename_length == filename_len + 2) {
if (dirEntry_filename_len == filename_len + 2) {
// +2 length match.
// This might have ";1".
if (!strncasecmp(entry_filename, filename, filename_len)) {
@ -205,11 +389,19 @@ const ISO_DirEntry *IsoPartitionPrivate::lookup_int(const DirData_t *pDir, const
break;
}
}
} else if (dirEntry->filename_length == filename_len) {
} else if (dirEntry_filename_len == filename_len) {
// Exact length match.
if (!strncasecmp(entry_filename, filename, filename_len)) {
// Found it!
dirEntry_found = dirEntry;
// Verify directory vs. file.
const bool isDir = !!(dirEntry->flags & ISO_FLAG_DIRECTORY);
if (isDir == bFindDir) {
// Directory attribute matches.
dirEntry_found = dirEntry;
} else {
// Not a match.
err = (isDir ? EISDIR : ENOTDIR);
}
break;
}
}
@ -273,7 +465,10 @@ const IsoPartitionPrivate::DirData_t *IsoPartitionPrivate::getDirectory(const ch
// Loading the root directory.
// Check the root directory entry.
const ISO_DirEntry *const rootdir = &pvd.dir_entry_root;
const ISO_DirEntry *const rootdir = (jolietSVDType > JolietSVDType::None)
? &svd.dir_entry_root
: &pvd.dir_entry_root;
if (rootdir->size.he > 16*1024*1024) {
// Root directory is too big.
q->m_lastError = EIO;
@ -299,6 +494,7 @@ const IsoPartitionPrivate::DirData_t *IsoPartitionPrivate::getDirectory(const ch
// in which case, we'll need to assume that the
// root directory starts at block 20.
// TODO: Better heuristics.
// TODO: Find the block that starts with "CD001" instead of this heuristic.
if (rootdir->block.he < 20) {
// Starting block is invalid.
q->m_lastError = EIO;
@ -328,7 +524,7 @@ const IsoPartitionPrivate::DirData_t *IsoPartitionPrivate::getDirectory(const ch
if (!pDir) {
// Can't find the parent directory.
// getDirectory() already set q->lastError().
// getDirectory() already set q->m_lastError().
return nullptr;
}
@ -336,7 +532,11 @@ const IsoPartitionPrivate::DirData_t *IsoPartitionPrivate::getDirectory(const ch
const ISO_DirEntry *const entry = lookup_int(pDir, path, true);
if (!entry) {
// Not found.
// lookup_int() already set q->lastError().
// lookup_int() already set q->m_lastError().
return nullptr;
} else if (!(entry->flags & ISO_FLAG_DIRECTORY)) {
// Entry found, but it's a directory.
q->m_lastError = ENOTDIR;
return nullptr;
}
@ -369,34 +569,27 @@ const IsoPartitionPrivate::DirData_t *IsoPartitionPrivate::getDirectory(const ch
/**
* Look up a directory entry from a filename.
* @param filename Filename [UTF-8]
* @return ISO directory entry.
* @return ISO directory entry
*/
const ISO_DirEntry *IsoPartitionPrivate::lookup(const char *filename)
{
assert(filename != nullptr);
assert(filename[0] != '\0');
RP_Q(IsoPartition);
// Remove leading slashes.
while (*filename == '/') {
filename++;
}
if (filename[0] == 0) {
// Nothing but slashes...
q->m_lastError = EINVAL;
return nullptr;
}
// Sanitize the filename.
// If the return value is an empty string, that means root directory.
string s_filename = sanitize_path(filename);
// TODO: Which encoding?
// Assuming cp1252...
const DirData_t *pDir;
// Is this file in a subdirectory?
const char *const sl = findLastSlash(filename);
const char *const sl = findLastSlash(s_filename);
if (sl) {
// This file is in a subdirectory.
const string s_parentDir = utf8_to_cp1252(filename, static_cast<int>(sl - filename));
filename = sl + 1;
const string s_parentDir = s_filename.substr(0, sl - s_filename.c_str());
s_filename.assign(sl + 1);
pDir = getDirectory(s_parentDir.c_str());
} else {
// Not in a subdirectory.
@ -406,19 +599,18 @@ const ISO_DirEntry *IsoPartitionPrivate::lookup(const char *filename)
if (!pDir) {
// Error getting the directory.
// getDirectory() has already set q->lastError.
// getDirectory() has already set q->m_lastError.
return nullptr;
}
// Find the file in the directory.
const string s_filename = utf8_to_cp1252(filename, -1);
return lookup_int(pDir, s_filename.c_str(), false);
}
/**
* Parse an ISO-9660 timestamp.
* @param isofiletime File timestamp.
* @return Unix time.
* @param isofiletime File timestamp
* @return Unix time
*/
time_t IsoPartitionPrivate::parseTimestamp(const ISO_Dir_DateTime_t *isofiletime)
{
@ -467,7 +659,7 @@ time_t IsoPartitionPrivate::parseTimestamp(const ISO_Dir_DateTime_t *isofiletime
IsoPartition::IsoPartition(const IRpFilePtr &discReader, off64_t partition_offset, int iso_start_offset)
: super(discReader)
, d_ptr(new IsoPartitionPrivate(this, partition_offset, iso_start_offset))
{}
{ }
IsoPartition::~IsoPartition()
{
@ -587,68 +779,205 @@ off64_t IsoPartition::partition_size_used(void) const
/** IsoPartition **/
/** GcnFst wrapper functions. **/
/** IFst wrapper functions **/
// TODO
#if 0
/**
* Open a directory.
* @param path [in] Directory path.
* @param path [in] Directory path
* @return IFst::Dir*, or nullptr on error.
*/
IFst::Dir *IsoPartition::opendir(const char *path)
{
RP_D(IsoPartition);
if (!d->fst) {
// FST isn't loaded.
if (d->loadFst() != 0) {
// FST load failed.
// TODO: Errors?
return nullptr;
}
// Sanitize the path.
// If the return value is an empty string, that means root directory.
string s_path = d->sanitize_path(path);
const IsoPartitionPrivate::DirData_t *const pDir = d->getDirectory(s_path.c_str());
if (!pDir) {
// Path not found.
// TODO: Return an error code?
return nullptr;
}
return d->fst->opendir(path);
// FIXME: Create an IsoFst class? Cannot pass `this` as IFst*.
IFst::Dir *const dirp = new IFst::Dir(nullptr, (void*)pDir);
d->fstDirCount++;
// Initialize the entry to this directory.
// readdir() will automatically seek to the next entry.
dirp->entry.extra = nullptr; // temporary filename storage
dirp->entry.ptnum = 0; // not used for ISO
dirp->entry.idx = 0;
dirp->entry.type = DT_UNKNOWN;
dirp->entry.name = nullptr;
// offset and size are not valid for directories.
dirp->entry.offset = 0;
dirp->entry.size = 0;
// Return the IFst::Dir*.
return dirp;
}
/**
* Read a directory entry.
* @param dirp FstDir pointer.
* @param dirp FstDir pointer
* @return IFst::DirEnt*, or nullptr if end of directory or on error.
* (TODO: Add lastError()?)
*/
IFst::DirEnt *IsoPartition::readdir(IFst::Dir *dirp)
const IFst::DirEnt *IsoPartition::readdir(IFst::Dir *dirp)
{
RP_D(IsoPartition);
if (!d->fst) {
// TODO: Errors?
assert(dirp != nullptr);
//assert(dirp->parent == this);
if (!dirp /*|| dirp->parent != this*/) {
// No directory pointer, or the dirp
// doesn't belong to this IFst.
return nullptr;
}
return d->fst->readdir(dirp);
const IsoPartitionPrivate::DirData_t *const pDir =
reinterpret_cast<const IsoPartitionPrivate::DirData_t*>(dirp->dir_idx);
const uint8_t *p = pDir->data();
const uint8_t *const p_end = p + pDir->size();
p += dirp->entry.idx;
// NOTE: Using a loop in order to skip files that aren't really files.
const ISO_DirEntry *dirEntry = nullptr;
const char *entry_filename = nullptr;
while (p < p_end) {
dirEntry = reinterpret_cast<const ISO_DirEntry*>(p);
if (dirEntry->entry_length == 0) {
// Directory entries cannot span multiple sectors in
// multi-sector directories, so if needed, the rest
// of the sector is padded with 00.
// Find the next non-zero byte.
for (p++; p < p_end; p++) {
if (*p != '\0') {
// Found a non-zero byte.
dirp->entry.idx = static_cast<int>(p - pDir->data());
break;
}
}
if (p >= p_end) {
// No more non-zero bytes.
dirp->entry.idx = static_cast<int>(pDir->size());
return nullptr;
}
continue;
} else if (dirEntry->entry_length < sizeof(*dirEntry)) {
// Invalid directory entry?
return nullptr;
}
entry_filename = reinterpret_cast<const char*>(p) + sizeof(*dirEntry);
if (entry_filename + dirEntry->filename_length > reinterpret_cast<const char*>(p_end)) {
// Filename is out of bounds.
return nullptr;
}
// Skip subdirectories with names "\x00" and "\x01".
// These are "special directory identifiers", representing "." and "..", respectively.
if ((dirEntry->flags & ISO_FLAG_DIRECTORY) && dirEntry->filename_length == 1) {
if (static_cast<uint8_t>(entry_filename[0]) <= 0x01) {
// Skip this filename.
dirp->entry.idx += dirEntry->entry_length;
p += dirEntry->entry_length;
continue;
}
}
// Found a valid file.
break;
}
if (!dirEntry) {
// Could not find a valid file. (End of directory?)
return nullptr;
}
const bool isDir = !!(dirEntry->flags & ISO_FLAG_DIRECTORY);
if (isDir) {
// Technically, offset/size are valid for directories on ISO-9660,
// but we're going to set them to 0.
dirp->entry.type = DT_DIR;
dirp->entry.offset = 0;
dirp->entry.size = 0;
} else {
RP_D(IsoPartition);
const unsigned int block_size = d->pvd.logical_block_size.he;
dirp->entry.type = DT_REG;
dirp->entry.offset = static_cast<off64_t>(dirEntry->block.he) * block_size;
dirp->entry.size = dirEntry->size.he;
}
// NOTE: Need to copy the filename in order to have NULL-termination.
// TODO: Remove ";1" from the filename, if present?
char *extra = static_cast<char*>(dirp->entry.extra);
delete[] extra;
// If using Joliet, the filename is encoded as UCS-2 (UTF-16).
// Use a quick-and-dirty (and not necessarily accurate) conversion to cp1252.
// FIXME: Proper conversion?
// TODO: Convert to UTF-8 for readdir()?
RP_D(IsoPartition);
uint8_t dirEntry_filename_len = dirEntry->filename_length;
if (d->jolietSVDType > IsoPartitionPrivate::JolietSVDType::None) {
// dirEntry_filename_len is in bytes, which means it's double
// the number of UCS-2 code points.
// NOTE: UCS-2 *Big-Endian*.
dirEntry_filename_len /= 2;
extra = new char[dirEntry_filename_len + 1];
unsigned int i = 0;
for (; i < dirEntry_filename_len; i++) {
extra[i] = entry_filename[(i * 2) + 1];
}
extra[i] = '\0';
} else {
// TODO: Convert from cp1252 to UTF-8 for readdir()?
extra = new char[dirEntry_filename_len + 1];
memcpy(extra, entry_filename, dirEntry_filename_len);
extra[dirEntry_filename_len] = '\0';
}
dirp->entry.name = extra;
dirp->entry.extra = extra;
// Next file entry.
dirp->entry.idx += dirEntry->entry_length;
return &dirp->entry;
}
/**
* Close an opened directory.
* @param dirp FstDir pointer.
* @param dirp FstDir pointer
* @return 0 on success; negative POSIX error code on error.
*/
int IsoPartition::closedir(IFst::Dir *dirp)
{
RP_D(IsoPartition);
if (!d->fst) {
// TODO: Errors?
return -EBADF;
}
assert(dirp != nullptr);
//assert(dirp->parent == this);
if (!dirp) {
// No directory pointer.
// In release builds, this is a no-op.
return 0;
} /*else if (dirp->parent != this) {
// The dirp doesn't belong to this IFst.
return -EINVAL;
}*/
return d->fst->closedir(dirp);
RP_D(IsoPartition);
assert(d->fstDirCount > 0);
if (dirp->entry.extra) {
delete[] static_cast<char*>(dirp->entry.extra);
}
delete dirp;
d->fstDirCount--;
return 0;
}
#endif
/**
* Open a file. (read-only)
* @param filename Filename.
* @param filename Filename
* @return IRpFile*, or nullptr on error.
*/
IRpFilePtr IsoPartition::open(const char *filename)
@ -673,6 +1002,7 @@ IRpFilePtr IsoPartition::open(const char *filename)
const ISO_DirEntry *const dirEntry = d->lookup(filename);
if (!dirEntry) {
// Not found.
m_lastError = ENOENT;
return nullptr;
}
@ -705,9 +1035,11 @@ IRpFilePtr IsoPartition::open(const char *filename)
return std::make_shared<PartitionFile>(this->shared_from_this(), file_addr, dirEntry->size.he);
}
/** IsoPartition-specific functions **/
/**
* Get a file's timestamp.
* @param filename Filename.
* @param filename Filename
* @return Timestamp, or -1 on error.
*/
time_t IsoPartition::get_mtime(const char *filename)

View File

@ -90,41 +90,41 @@ public:
public:
/** IFst wrapper functions **/
// TODO
#if 0
/**
* Open a directory.
* @param path [in] Directory path.
* @param path [in] Directory path
* @return IFst::Dir*, or nullptr on error.
*/
LibRpBase::IFst::Dir *opendir(const char *path) final;
/**
* Read a directory entry.
* @param dirp IFst::Dir pointer.
* @param dirp IFst::Dir pointer
* @return IFst::DirEnt, or nullptr if end of directory or on error.
* (TODO: Add lastError()?)
*/
LibRpBase::IFst::DirEnt *readdir(LibRpBase::IFst::Dir *dirp) final;
const LibRpBase::IFst::DirEnt *readdir(LibRpBase::IFst::Dir *dirp) final;
/**
* Close an opened directory.
* @param dirp IFst::Dir pointer.
* @param dirp IFst::Dir pointer
* @return 0 on success; negative POSIX error code on error.
*/
int closedir(LibRpBase::IFst::Dir *dirp) final;
#endif
/**
* Open a file. (read-only)
* @param filename Filename.
* @param filename Filename
* @return IRpFile*, or nullptr on error.
*/
LibRpFile::IRpFilePtr open(const char *filename) final;
public:
/** IsoPartition-specific functions **/
/**
* Get a file's timestamp.
* @param filename Filename.
* @param filename Filename
* @return Timestamp, or -1 on error.
*/
time_t get_mtime(const char *filename);

View File

@ -190,13 +190,14 @@ int NEResourceReaderPrivate::loadResTbl(void)
int ret = -EIO;
while (pos < rsrc_tbl_size) {
// Read the next type ID.
if ((pos + 2) >= rsrc_tbl_size) {
if ((pos + 2) > rsrc_tbl_size) {
// I/O error; should be at least 2 bytes left...
break;
}
const NE_TYPEINFO *typeInfo = reinterpret_cast<const NE_TYPEINFO*>(&rsrcTblData[pos]);
const uint16_t rtTypeID = le16_to_cpu(typeInfo->rtTypeID);
if (rtTypeID == 0) {
// typeInfo is actually pointing to rscEndTypes.
// End of rscTypes[].
ret = 0;
break;
@ -244,11 +245,27 @@ int NEResourceReaderPrivate::loadResTbl(void)
const NE_NAMEINFO *nameInfo = reinterpret_cast<const NE_NAMEINFO*>(&rsrcTblData[pos]);
pos += sizeof(NE_NAMEINFO);
/**
* NOTE: If the ID doesn't have 0x8000 set, the name is a string.
* The ID is the offset of the string, in bytes relative to the
* beginning of the resource table. The sting is a Pascal string;
* one byte indicating length, followed by the string data.
* ***NOT NULL-TERMINATED!***
*
* We'll allow it for now as-is, since CALC.EXE, and probably
* other Windows 3.1 programs, have RT_GROUP_ICONs that use
* string names.
*
* TODO: How does Windows 3.1 select the application icon if
* the icons have names?
*/
const uint16_t rnID = le16_to_cpu(nameInfo->rnID);
#if 0
if (!(rnID & 0x8000)) {
// Resource name is a string. Not supported.
continue;
}
#endif
// Add the resource information.
auto &entry = dir[entriesRead];
@ -260,8 +277,9 @@ int NEResourceReaderPrivate::loadResTbl(void)
entry.len = le16_to_cpu(nameInfo->rnLength) << rscAlignShift;
entriesRead++;
}
if (isErr)
if (isErr) {
break;
}
// Shrink the vector in case we skipped some entries.
dir.resize(entriesRead);
@ -646,11 +664,13 @@ IRpFilePtr NEResourceReader::open(uint16_t type, int id, int lang)
// Get the directory for the specified type.
auto iter_dir = d->res_types.find(type);
if (iter_dir == d->res_types.end())
if (iter_dir == d->res_types.end()) {
return nullptr;
}
auto &dir = iter_dir->second;
if (dir.empty())
if (dir.empty()) {
return nullptr;
}
const NEResourceReaderPrivate::ResTblEntry *entry = nullptr;
if (id == -1) {
@ -781,4 +801,56 @@ int NEResourceReader::load_VS_VERSION_INFO(int id, int lang, VS_FIXEDFILEINFO *p
return 0;
}
/**
* Look up a resource ID given a zero-based index.
* Mostly useful for icon indexes.
*
* @param type [in] Resource type ID
* @param index [in] Zero-based index
* @param lang [in] Language ID (-1 for "first entry")
* @return Resource ID, or negative POSIX error code on error.
*/
int NEResourceReader::lookup_resource_ID(int type, int index)
{
if (index < 0) {
return -EINVAL;
}
// 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;
// Get the resource directory for this type.
RP_D(const NEResourceReader);
auto iter_find = d->res_types.find(type);
if (iter_find == d->res_types.end()) {
// Not found.
return -ENOENT;
}
const NEResourceReaderPrivate::rsrc_dir_t &type_dir = iter_find->second;
if (index >= static_cast<int>(type_dir.size())) {
// Zero-based index is out of bounds.
return -ENOENT;
}
// Return the ID at this 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());
}
}

View File

@ -2,18 +2,18 @@
* ROM Properties Page shell extension. (libromdata) *
* NEResourceReader.hpp: New Executable resource reader. *
* *
* Copyright (c) 2016-2023 by David Korth. *
* Copyright (c) 2016-2025 by David Korth. *
* SPDX-License-Identifier: GPL-2.0-or-later *
***************************************************************************/
#pragma once
#include "IResourceReader.hpp"
#include "librpbase/disc/IResourceReader.hpp"
namespace LibRomData {
class NEResourceReaderPrivate;
class NEResourceReader final : public IResourceReader
class NEResourceReader final : public LibRpBase::IResourceReader
{
public:
/**
@ -30,7 +30,7 @@ public:
~NEResourceReader() final;
private:
typedef IResourceReader super;
typedef LibRpBase::IResourceReader super;
RP_DISABLE_COPY(NEResourceReader)
protected:
friend class NEResourceReaderPrivate;
@ -92,9 +92,9 @@ public:
/**
* Open a resource.
* @param type Resource type ID.
* @param id Resource ID. (-1 for "first entry")
* @param lang Language ID. (-1 for "first entry")
* @param type [in] Resource type ID
* @param id [in] Resource ID (-1 for "first entry")
* @param lang [in] Language ID (-1 for "first entry")
* @return IRpFile*, or nullptr on error.
*/
LibRpFile::IRpFilePtr open(uint16_t type, int id, int lang) final;
@ -116,6 +116,23 @@ public:
* @return 0 on success; non-zero on error.
*/
int load_VS_VERSION_INFO(int id, int lang, VS_FIXEDFILEINFO *pVsFfi, StringFileInfo *pVsSfi) final;
/**
* Look up a resource ID given a zero-based index.
* Mostly useful for icon indexes.
*
* @param type [in] Resource type ID
* @param index [in] Zero-based index
* @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;
};
}

View File

@ -861,4 +861,46 @@ int PEResourceReader::load_VS_VERSION_INFO(int id, int lang, VS_FIXEDFILEINFO *p
return 0;
}
/**
* Look up a resource ID given a zero-based index.
* Mostly useful for icon indexes.
*
* @param type [in] Resource type ID
* @param index [in] Zero-based index
* @param lang [in] Language ID (-1 for "first entry")
* @return Resource ID, or negative POSIX error code on error.
*/
int PEResourceReader::lookup_resource_ID(int type, int index)
{
if (index < 0) {
return -EINVAL;
}
// Get the resource directory for this type.
RP_D(PEResourceReader);
const PEResourceReaderPrivate::rsrc_dir_t *type_dir = d->getTypeDir(type);
if (!type_dir) {
return -ENOENT;
}
if (index >= static_cast<int>(type_dir->size())) {
// Zero-based index is out of bounds.
return -ENOENT;
}
// Return the ID at this 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);
}
}

View File

@ -2,18 +2,18 @@
* ROM Properties Page shell extension. (libromdata) *
* PEResourceReader.hpp: Portable Executable resource reader. *
* *
* Copyright (c) 2016-2023 by David Korth. *
* Copyright (c) 2016-2025 by David Korth. *
* SPDX-License-Identifier: GPL-2.0-or-later *
***************************************************************************/
#pragma once
#include "IResourceReader.hpp"
#include "librpbase/disc/IResourceReader.hpp"
namespace LibRomData {
class PEResourceReaderPrivate;
class PEResourceReader final : public IResourceReader
class PEResourceReader final : public LibRpBase::IResourceReader
{
public:
/**
@ -31,7 +31,7 @@ public:
~PEResourceReader() final;
private:
typedef IResourceReader super;
typedef LibRpBase::IResourceReader super;
RP_DISABLE_COPY(PEResourceReader)
protected:
friend class PEResourceReaderPrivate;
@ -93,9 +93,9 @@ public:
/**
* Open a resource.
* @param type Resource type ID.
* @param id Resource ID. (-1 for "first entry")
* @param lang Language ID. (-1 for "first entry")
* @param type [in] Resource type ID
* @param id [in] Resource ID (-1 for "first entry")
* @param lang [in] Language ID (-1 for "first entry")
* @return IRpFile*, or nullptr on error.
*/
LibRpFile::IRpFilePtr open(uint16_t type, int id, int lang) final;
@ -110,13 +110,30 @@ public:
/**
* Load a VS_VERSION_INFO resource.
* Data will be byteswapped to host-endian if necessary.
* @param id [in] Resource ID. (-1 for "first entry")
* @param lang [in] Language ID. (-1 for "first entry")
* @param id [in] Resource ID (-1 for "first entry")
* @param lang [in] Language ID (-1 for "first entry")
* @param pVsFfi [out] VS_FIXEDFILEINFO (host-endian)
* @param pVsSfi [out] StringFileInfo section.
* @return 0 on success; non-zero on error.
*/
int load_VS_VERSION_INFO(int id, int lang, VS_FIXEDFILEINFO *pVsFfi, StringFileInfo *pVsSfi) final;
/**
* Look up a resource ID given a zero-based index.
* Mostly useful for icon indexes.
*
* @param type [in] Resource type ID
* @param index [in] Zero-based index
* @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;
};
}

View File

@ -467,13 +467,14 @@ IFst::Dir *WiiUFst::opendir(const char *path)
return nullptr;
}
IFst::Dir *dirp = new IFst::Dir(this);
d->fstDirCount++;
// TODO: Better way to get dir_idx?
dirp->dir_idx = static_cast<int>(fst_entry - d->fstEntries);
const int dir_idx = static_cast<int>(fst_entry - d->fstEntries);
IFst::Dir *dirp = new IFst::Dir(this, dir_idx);
d->fstDirCount++;
// Initialize the entry to this directory.
// readdir() will automatically seek to the next entry.
dirp->entry.extra = nullptr; // not used for Wii U
dirp->entry.ptnum = be16_to_cpu(fst_entry->storage_cluster_index);
dirp->entry.idx = dirp->dir_idx;
dirp->entry.type = DT_DIR;
@ -492,7 +493,7 @@ IFst::Dir *WiiUFst::opendir(const char *path)
* @return IFst::DirEnt*, or nullptr if end of directory or on error.
* (End of directory does not set lastError; an error does.)
*/
IFst::DirEnt *WiiUFst::readdir(IFst::Dir *dirp)
const IFst::DirEnt *WiiUFst::readdir(IFst::Dir *dirp)
{
RP_D(WiiUFst);
assert(dirp != nullptr);

View File

@ -67,7 +67,7 @@ public:
* @return DirEnt*, or nullptr if end of directory or on error.
* (TODO: Add lastError()?)
*/
DirEnt *readdir(Dir *dirp) final;
const DirEnt *readdir(Dir *dirp) final;
/**
* Close an opened directory.

View File

@ -192,16 +192,22 @@ ASSERT_STRUCT(ISO_Boot_Volume_Descriptor, ISO_SECTOR_SIZE_MODE1_COOKED);
* Primary volume descriptor.
*
* NOTE: All fields are space-padded. (0x20, ' ')
*
* NOTE 2: SVD fields are only valid in Supplementary Volume Descriptors.
* In PVDs, these fields should be all zero.
*/
typedef struct _ISO_Primary_Volume_Descriptor {
ISO_Volume_Descriptor_Header header;
uint8_t reserved1; // [0x007] 0x00
uint8_t svd_volume_flags; // [0x007] Bit 0, if clear, indicates escape sequences *only* has
// valid sequences from ISO 2375. If set, it has sequences
// that aren't in ISO 2375.
char sysID[32]; // [0x008] (strA) System identifier.
char volID[32]; // [0x028] (strD) Volume identifier.
uint8_t reserved2[8]; // [0x048] All zeroes.
uint32_lsb_msb_t volume_space_size; // [0x050] Size of volume, in blocks.
uint8_t reserved3[32]; // [0x058] All zeroes.
char svd_escape_sequences[32]; // [0x058] SVD: Escape sequences (indicates character sets)
uint16_lsb_msb_t volume_set_size; // [0x078] Size of the logical volume. (number of discs)
uint16_lsb_msb_t volume_seq_number; // [0x07C] Disc number in the volume set.
uint16_lsb_msb_t logical_block_size; // [0x080] Logical block size. (usually 2048)
@ -245,14 +251,22 @@ ASSERT_STRUCT(ISO_Primary_Volume_Descriptor, ISO_SECTOR_SIZE_MODE1_COOKED);
/**
* Volume descriptor.
*
* Primary volume descriptor is located at sector 0x10. (0x8000)
* Primary Volume Descriptor is located at sector 0x10. (0x8000)
* Supplementary Volume Descriptor is usually located at sector 0x11, if present. (0x8800)
*/
#define ISO_VD_MAGIC "CD001"
#define ISO_VD_VERSION 0x01
#define ISO_PVD_LBA 0x10
#define ISO_PVD_ADDRESS_2048 (ISO_PVD_LBA * ISO_SECTOR_SIZE_MODE1_COOKED)
#define ISO_PVD_ADDRESS_2352 (ISO_PVD_LBA * ISO_SECTOR_SIZE_MODE1_RAW)
#define ISO_PVD_ADDRESS_2448 (ISO_PVD_LBA * ISO_SECTOR_SIZE_MODE1_RAW_SUBCHAN)
#define ISO_SVD_LBA 0x11
#define ISO_SVD_ADDRESS_2048 (ISO_SVD_LBA * ISO_SECTOR_SIZE_MODE1_COOKED)
#define ISO_SVD_ADDRESS_2352 (ISO_SVD_LBA * ISO_SECTOR_SIZE_MODE1_RAW)
#define ISO_SVD_ADDRESS_2448 (ISO_SVD_LBA * ISO_SECTOR_SIZE_MODE1_RAW_SUBCHAN)
typedef union _ISO_Volume_Descriptor {
ISO_Volume_Descriptor_Header header;

View File

@ -71,7 +71,7 @@ static int fstPrint(IFst *fst, ostream &os, const string &path,
}
// Read the directory entries.
IFst::DirEnt *dirent = fst->readdir(dirp);
const IFst::DirEnt *dirent = fst->readdir(dirp);
while (dirent != nullptr) {
if (!dirent->name || dirent->name[0] == 0) {
// Empty name...
@ -79,8 +79,7 @@ static int fstPrint(IFst *fst, ostream &os, const string &path,
}
// Print the tree lines.
for (int i = 0; i < level; i++)
{
for (int i = 0; i < level; i++) {
if (tree_lines[i]) {
// Directory tree exists for this segment.
os << "\xE2\x94\x82 ";

View File

@ -357,7 +357,7 @@ void GcnFstTest::checkNoDuplicateFilenames(const char *subdir)
ASSERT_TRUE(dirp != nullptr) <<
"Failed to open directory '" << subdir << "'.";
IFst::DirEnt *dirent = m_fst->readdir(dirp);
const IFst::DirEnt *dirent = m_fst->readdir(dirp);
while (dirent != nullptr) {
// Make sure we haven't seen this filename in
// the current subdirectory yet.

View File

@ -47,6 +47,7 @@ SET(${PROJECT_NAME}_SRCS
disc/PartitionFile.cpp
disc/SparseDiscReader.cpp
disc/CBCReader.cpp
disc/IResourceReader.cpp
crypto/KeyManager.cpp
config/ConfReader.cpp
config/Config.cpp
@ -74,6 +75,8 @@ SET(${PROJECT_NAME}_H
disc/SparseDiscReader.hpp
disc/SparseDiscReader_p.hpp
disc/CBCReader.hpp
disc/IResourceReader.hpp
disc/exe_res_structs.h
crypto/KeyManager.hpp
config/ConfReader.hpp
config/Config.hpp

View File

@ -2,7 +2,7 @@
* ROM Properties Page shell extension. (librpbase) *
* IFst.hpp: File System Table interface. *
* *
* Copyright (c) 2016-2024 by David Korth. *
* Copyright (c) 2016-2025 by David Korth. *
* SPDX-License-Identifier: GPL-2.0-or-later *
***************************************************************************/
@ -25,92 +25,99 @@ namespace LibRpBase {
class NOVTABLE IFst
{
protected:
IFst() = default;
public:
virtual ~IFst() = 0;
protected:
IFst() = default;
public:
virtual ~IFst() = 0;
private:
RP_DISABLE_COPY(IFst)
private:
RP_DISABLE_COPY(IFst)
public:
// TODO: Base class?
public:
// TODO: Base class?
/**
* Is the FST open?
* @return True if open; false if not.
*/
virtual bool isOpen(void) const = 0;
/**
* Is the FST open?
* @return True if open; false if not.
*/
virtual bool isOpen(void) const = 0;
/**
* Have any errors been detected in the FST?
* @return True if yes; false if no.
*/
virtual bool hasErrors(void) const = 0;
/**
* Have any errors been detected in the FST?
* @return True if yes; false if no.
*/
virtual bool hasErrors(void) const = 0;
public:
/** opendir() interface **/
public:
/** opendir() interface **/
struct DirEnt {
off64_t offset; // Starting address
off64_t size; // File size
const char *name; // Filename
struct DirEnt {
off64_t offset; // Starting address
off64_t size; // File size
const char *name; // Filename
// TODO: Additional placeholders?
unsigned int ptnum; // Partition or content number
int idx; // File index
uint8_t type; // File type (See d_type.h)
};
// TODO: Additional placeholders?
void *extra; // Extra data
unsigned int ptnum; // Partition or content number
int idx; // File index
uint8_t type; // File type (See d_type.h)
};
struct Dir {
IFst *const parent; // IFst that owns this Dir
int dir_idx; // Directory index in the FST
DirEnt entry; // Current DirEnt
struct Dir {
IFst *const parent; // IFst that owns this Dir
intptr_t dir_idx; // Directory index in the FST
DirEnt entry; // Current DirEnt
explicit Dir(IFst *parent)
: parent(parent)
{}
};
explicit Dir(IFst *parent, intptr_t dir_idx)
: parent(parent)
, dir_idx(dir_idx)
{}
/**
* Open a directory.
* @param path [in] Directory path.
* @return Dir*, or nullptr on error.
*/
virtual Dir *opendir(const char *path) = 0;
explicit Dir(IFst *parent, void *dir_idx)
: parent(parent)
, dir_idx(reinterpret_cast<intptr_t>(dir_idx))
{}
};
/**
* Open a directory.
* @param path [in] Directory path.
* @return Dir*, or nullptr on error.
*/
inline Dir *opendir(const std::string &path)
{
return opendir(path.c_str());
}
/**
* Open a directory.
* @param path [in] Directory path.
* @return Dir*, or nullptr on error.
*/
virtual Dir *opendir(const char *path) = 0;
/**
* Read a directory entry.
* @param dirp Dir pointer.
* @return DirEnt*, or nullptr if end of directory or on error.
* (TODO: Add lastError()?)
*/
virtual DirEnt *readdir(Dir *dirp) = 0;
/**
* Open a directory.
* @param path [in] Directory path.
* @return Dir*, or nullptr on error.
*/
inline Dir *opendir(const std::string &path)
{
return opendir(path.c_str());
}
/**
* Close an opened directory.
* @param dirp Dir pointer.
* @return 0 on success; negative POSIX error code on error.
*/
virtual int closedir(Dir *dirp) = 0;
/**
* Read a directory entry.
* @param dirp Dir pointer.
* @return DirEnt*, or nullptr if end of directory or on error.
* (TODO: Add lastError()?)
*/
virtual const DirEnt *readdir(Dir *dirp) = 0;
/**
* Get the directory entry for the specified file.
* @param filename [in] Filename.
* @param dirent [out] Pointer to DirEnt buffer.
* @return 0 on success; negative POSIX error code on error.
*/
virtual int find_file(const char *filename, DirEnt *dirent) = 0;
/**
* Close an opened directory.
* @param dirp Dir pointer.
* @return 0 on success; negative POSIX error code on error.
*/
virtual int closedir(Dir *dirp) = 0;
/**
* Get the directory entry for the specified file.
* @param filename [in] Filename.
* @param dirent [out] Pointer to DirEnt buffer.
* @return 0 on success; negative POSIX error code on error.
*/
virtual int find_file(const char *filename, DirEnt *dirent) = 0;
};
/**

View File

@ -2,7 +2,7 @@
* ROM Properties Page shell extension. (librpbase) *
* IPartition.cpp: Partition reader interface. *
* *
* Copyright (c) 2016-2024 by David Korth. *
* Copyright (c) 2016-2025 by David Korth. *
* SPDX-License-Identifier: GPL-2.0-or-later *
***************************************************************************/
@ -38,7 +38,7 @@ IFst::Dir *IPartition::opendir(const char *path)
* @return IFst::DirEnt, or nullptr if end of directory or on error.
* (TODO: Add lastError()?)
*/
IFst::DirEnt *IPartition::readdir(IFst::Dir *dirp)
const IFst::DirEnt *IPartition::readdir(IFst::Dir *dirp)
{
RP_UNUSED(dirp);
assert(!"IFst wrapper functions are not implemented for this class!");

View File

@ -2,7 +2,7 @@
* ROM Properties Page shell extension. (librpbase) *
* IPartition.hpp: Partition reader interface. *
* *
* Copyright (c) 2016-2024 by David Korth. *
* Copyright (c) 2016-2025 by David Korth. *
* SPDX-License-Identifier: GPL-2.0-or-later *
***************************************************************************/
@ -87,7 +87,7 @@ public:
* @return IFst::DirEnt, or nullptr if end of directory or on error.
* (TODO: Add lastError()?)
*/
virtual IFst::DirEnt *readdir(IFst::Dir *dirp);
virtual const IFst::DirEnt *readdir(IFst::Dir *dirp);
/**
* Close an opened directory.

View File

@ -1,15 +1,15 @@
/***************************************************************************
* ROM Properties Page shell extension. (libromdata) *
* ROM Properties Page shell extension. (librpbase) *
* IResourceReader.cpp: Interface for Windows resource readers. *
* *
* Copyright (c) 2016-2024 by David Korth. *
* Copyright (c) 2016-2025 by David Korth. *
* SPDX-License-Identifier: GPL-2.0-or-later *
***************************************************************************/
#include "stdafx.h"
#include "IResourceReader.hpp"
namespace LibRomData {
namespace LibRpBase {
/**
* DWORD alignment function.

View File

@ -1,16 +1,15 @@
/***************************************************************************
* ROM Properties Page shell extension. (libromdata) *
* ROM Properties Page shell extension. (librpbase) *
* IResourceReader.hpp: Interface for Windows resource readers. *
* *
* Copyright (c) 2016-2024 by David Korth. *
* Copyright (c) 2016-2025 by David Korth. *
* SPDX-License-Identifier: GPL-2.0-or-later *
***************************************************************************/
#pragma once
#include "../Other/exe_res_structs.h"
#include "exe_res_structs.h"
// librpbase
#include "librpbase/disc/IPartition.hpp"
// C++ includes
@ -29,9 +28,9 @@ typedef WCHAR *LPWSTR;
typedef const WCHAR *LPCWSTR;
#endif /* _WIN32 && !_WINNT_ */
namespace LibRomData {
namespace LibRpBase {
class NOVTABLE IResourceReader : public LibRpBase::IPartition
class NOVTABLE IResourceReader : public IPartition
{
protected:
IResourceReader(const LibRpFile::IRpFilePtr &file)
@ -41,7 +40,7 @@ public:
~IResourceReader() override = 0;
private:
typedef LibRpBase::IPartition super;
typedef IPartition super;
RP_DISABLE_COPY(IResourceReader)
public:
@ -67,9 +66,9 @@ private:
public:
/**
* Open a resource.
* @param type Resource type ID
* @param id Resource ID (-1 for "first entry")
* @param lang Language ID (-1 for "first entry")
* @param type [in] Resource type ID
* @param id [in] Resource ID (-1 for "first entry")
* @param lang [in] Language ID (-1 for "first entry")
* @return IRpFile*, or nullptr on error.
*/
virtual LibRpFile::IRpFilePtr open(uint16_t type, int id, int lang) = 0;
@ -113,13 +112,30 @@ public:
* Load a VS_VERSION_INFO resource.
* Data will be byteswapped to host-endian if necessary.
*
* @param id [in] Resource ID. (-1 for "first entry")
* @param lang [in] Language ID. (-1 for "first entry")
* @param id [in] Resource ID (-1 for "first entry")
* @param lang [in] Language ID (-1 for "first entry")
* @param pVsFfi [out] VS_FIXEDFILEINFO (host-endian)
* @param pVsSfi [out] StringFileInfo section.
* @return 0 on success; non-zero on error.
*/
virtual int load_VS_VERSION_INFO(int id, int lang, VS_FIXEDFILEINFO *pVsFfi, StringFileInfo *pVsSfi) = 0;
/**
* Look up a resource ID given a zero-based index.
* Mostly useful for icon indexes.
*
* @param type [in] Resource type ID
* @param index [in] Zero-based index
* @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<IResourceReader> IResourceReaderPtr;

View File

@ -1,8 +1,8 @@
/***************************************************************************
* ROM Properties Page shell extension. (libromdata) *
* ROM Properties Page shell extension. (librpbase) *
* exe_res_structs.h: DOS/Windows executable structures. (resources) *
* *
* Copyright (c) 2017-2024 by David Korth. *
* Copyright (c) 2017-2025 by David Korth. *
* SPDX-License-Identifier: GPL-2.0-or-later *
***************************************************************************/
@ -57,6 +57,7 @@ typedef enum {
/** Version resource **/
#ifndef VER_H
//#define VS_FILE_INFO RT_VERSION // TODO
#define VS_VERSION_INFO 1
#define VS_USER_DEFINED 100
@ -151,6 +152,7 @@ typedef struct _VS_FIXEDFILEINFO {
uint32_t dwFileDateLS;
} VS_FIXEDFILEINFO;
ASSERT_STRUCT(VS_FIXEDFILEINFO, 13*sizeof(uint32_t));
#endif /* VER_H */
#ifdef __cplusplus
}

View File

@ -44,6 +44,7 @@ SET(${PROJECT_NAME}_SRCS
fileformat/DidjTex.cpp
fileformat/DirectDrawSurface.cpp
fileformat/GodotSTEX.cpp
fileformat/ICO.cpp
fileformat/KhronosKTX.cpp
fileformat/KhronosKTX2.cpp
fileformat/PalmOS_Tbmp.cpp
@ -91,6 +92,7 @@ SET(${PROJECT_NAME}_H
fileformat/DidjTex.hpp
fileformat/DirectDrawSurface.hpp
fileformat/GodotSTEX.hpp
fileformat/ICO.hpp
fileformat/KhronosKTX.hpp
fileformat/KhronosKTX2.hpp
fileformat/PalmOS_Tbmp.hpp
@ -106,6 +108,7 @@ SET(${PROJECT_NAME}_H
fileformat/dds_structs.h
fileformat/didj_tex_structs.h
fileformat/godot_stex_structs.h
fileformat/ico_structs.h
fileformat/ktx_structs.h
fileformat/ktx_structs.h
fileformat/palmos_tbmp_structs.h

View File

@ -32,6 +32,7 @@ using std::vector;
#include "fileformat/DidjTex.hpp"
#include "fileformat/DirectDrawSurface.hpp"
#include "fileformat/GodotSTEX.hpp"
#include "fileformat/ICO.hpp"
#include "fileformat/KhronosKTX.hpp"
#include "fileformat/KhronosKTX2.hpp"
#include "fileformat/PowerVR3.hpp"
@ -178,6 +179,10 @@ FileFormatPtr create(const IRpFilePtr &file)
}
}
// Check for specific file extensions.
// - ICO, CUR: Windows icons and cursors don't have a useful magic number.
// - TGA: There's no header magic number, but there *is* some information.
// Use some heuristics to check for TGA files.
// Based on heuristics from `file`.
// TGA 2.0 has an identifying footer as well.
@ -185,28 +190,45 @@ FileFormatPtr create(const IRpFilePtr &file)
// conflicts with "WWF Raw" on SNES.
const char *const filename = file->filename();
const char *const ext = FileSystem::file_ext(filename);
bool ext_ok = false;
bool is_ico = false, maybe_tga = false;
if (!ext || ext[0] == '\0') {
// No extension. Check for TGA anyway.
ext_ok = true;
maybe_tga = true;
} else if (!strcasecmp(ext, ".tga")) {
// TGA extension.
ext_ok = true;
maybe_tga = true;
} else if (!strcasecmp(ext, ".ico") || !strcasecmp(ext, ".cur")) {
// ICO or CUR extension.
is_ico = true;
} else if (!strcasecmp(ext, ".gz")) {
// Check if it's ".tga.gz".
const size_t filename_len = strlen(filename);
if (filename_len >= 7) {
if (!strncasecmp(&filename[filename_len-7], ".tga", 4)) {
// It's ".tga.gz".
ext_ok = true;
maybe_tga = true;
} else if (!strncasecmp(&filename[filename_len-7], ".ico", 4) ||
!strncasecmp(&filename[filename_len-7], ".cur", 4))
{
// It's ".ico.gz" or ".cur.gz".
is_ico = true;
}
}
}
if (is_ico) {
// This might be a Windows icon or cursor.
FileFormatPtr fileFormat = std::make_shared<ICO>(file);
if (fileFormat->isValid()) {
// FileFormat subclass obtained.
return fileFormat;
}
}
// test of Color Map Type 0~no 1~color map
// and Image Type 1 2 3 9 10 11 32 33
// and Color Map Entry Size 0 15 16 24 32
if (ext_ok &&
if (maybe_tga &&
((magic.u32[0] & be32_to_cpu(0x00FEC400)) == 0) &&
((magic.u32[1] & be32_to_cpu(0x000000C0)) == 0))
{

View File

@ -309,6 +309,7 @@ rp_image_ptr fromLinearCI8(PixelFormat px_format,
}
// Verify palette size.
// TODO: Table to map PixelFormat to bpp?
switch (px_format) {
case PixelFormat::RGB888:
// 24-bit palette required.
@ -320,6 +321,9 @@ rp_image_ptr fromLinearCI8(PixelFormat px_format,
case PixelFormat::BGR888_ABGR7888:
case PixelFormat::Host_ARGB32:
case PixelFormat::Swap_ARGB32:
case PixelFormat::Host_xRGB32:
case PixelFormat::Swap_xRGB32:
// 32-bit palette required.
assert(pal_siz >= 256*4);
if (pal_siz < 256*4) {

View File

@ -17,6 +17,9 @@ namespace LibRpTexture { namespace ImageDecoder {
/**
* Convert a linear monochrome image to rp_image.
*
* 0 == white; 1 == black
*
* @param width [in] Image width
* @param height [in] Image height
* @param img_buf [in] Monochrome image buffer
@ -97,6 +100,9 @@ rp_image_ptr fromLinearMono(int width, int height,
/**
* Convert a linear 2-bpp grayscale image to rp_image.
*
* 0 == white; 3 == black
*
* @param width [in] Image width
* @param height [in] Image height
* @param img_buf [in] Monochrome image buffer
@ -180,4 +186,109 @@ rp_image_ptr fromLinearGray2bpp(int width, int height,
return img;
}
/**
* Convert a linear monochrome image to rp_image.
*
* Windows icons are handled a bit different compared to "regular" monochrome images:
* - Two images are required: icon and mask
* - Transparency is supported using the mask.
* - 0 == black; 1 == white
*
* @param width [in] Image width
* @param height [in] Image height
* @param img_buf [in] Monochrome image buffer
* @param img_siz [in] Size of image data [must be >= ((w*h)/8)]
* @param mask_buf [in] Mask buffer
* @param mask_siz [in] Size of mask buffer [must be == img_siz]
* @param stride [in,opt] Stride, in bytes (if 0, assumes width*bytespp)
* @return rp_image, or nullptr on error.
*/
ATTR_ACCESS_SIZE(read_only, 3, 4)
rp_image_ptr fromLinearMono_WinIcon(int width, int height,
const uint8_t *RESTRICT img_buf, size_t img_siz,
const uint8_t *RESTRICT mask_buf, size_t mask_siz, int stride)
{
// Verify parameters.
assert(img_buf != nullptr);
assert(width > 0);
assert(height > 0);
assert(img_siz >= (static_cast<size_t>(width) * static_cast<size_t>(height) / 8));
assert(img_siz == mask_siz);
if (!img_buf || width <= 0 || height <= 0 ||
img_siz < (static_cast<size_t>(width) * static_cast<size_t>(height) / 8) ||
img_siz != mask_siz)
{
return {};
}
// Source stride adjustment.
int src_stride_adj = 0;
assert(stride >= 0);
if (stride > 0) {
// Set src_stride_adj to the number of bytes we need to
// add to the end of each line to get to the next row.
src_stride_adj = stride - ((width / 8) + ((width % 8) > 0));
}
// Create an rp_image.
rp_image_ptr img = std::make_shared<rp_image>(width, height, rp_image::Format::CI8);
if (!img->isValid()) {
// Could not allocate the image.
return {};
}
const int dest_stride_adj = img->stride() - img->width();
// Set up a default monochrome palette.
// NOTE: Color 0 is used for transparency.
uint32_t *palette = img->palette();
palette[0] = 0x00000000U; // transparent
palette[1] = 0xFF000000U; // black
palette[2] = 0xFFFFFFFFU; // white
img->set_tr_idx(0);
// NOTE: rp_image initializes the palette to 0,
// so we don't need to clear the remaining colors.
// Convert one line at a time. (monochrome -> CI8)
uint8_t *px_dest = static_cast<uint8_t*>(img->bits());
for (unsigned int y = static_cast<unsigned int>(height); y > 0; y--) {
for (int x = width; x > 0; x -= 8) {
uint8_t pxMask = *mask_buf++;
uint8_t pxIcon = *img_buf++;
// For images where width is not a multiple of 8,
// we'll discard the remaining bits in the last byte.
const unsigned int max_bit = (x >= 8) ? 8 : static_cast<unsigned int>(x);
// TODO: Unroll this loop?
for (unsigned int bit = max_bit; bit > 0; bit--, px_dest++) {
// MSB == left-most pixel
if (pxMask & 0x80) {
// Mask bit is set: this is either screen or inverted.
// FIXME: Inverted doesn't work here. Will use white for inverted.
*px_dest = (pxIcon & 0x80) ? 2 : 0;
} else {
// Mask bit is clear: this is the image.
*px_dest = (pxIcon & 0x80) ? 2 : 1;
}
pxMask <<= 1;
pxIcon <<= 1;
}
}
img_buf += src_stride_adj;
px_dest += dest_stride_adj;
}
// Set the sBIT metadata.
// NOTE: Setting the grayscale value, though we're
// not saving grayscale PNGs at the moment.
// TODO: Don't set alpha if the icon mask doesn't have any set bits?
static const rp_image::sBIT_t sBIT = {1,1,1,1,1};
img->set_sBIT(&sBIT);
// Image has been converted.
return img;
}
} }

View File

@ -15,6 +15,9 @@ namespace LibRpTexture { namespace ImageDecoder {
/**
* Convert a linear monochrome image to rp_image.
*
* 0 == white; 1 == black
*
* @param width [in] Image width
* @param height [in] Image height
* @param img_buf [in] Monochrome image buffer
@ -28,6 +31,9 @@ rp_image_ptr fromLinearMono(int width, int height,
/**
* Convert a linear 2-bpp grayscale image to rp_image.
*
* 0 == white; 3 == black
*
* @param width [in] Image width
* @param height [in] Image height
* @param img_buf [in] Monochrome image buffer
@ -39,4 +45,27 @@ ATTR_ACCESS_SIZE(read_only, 3, 4)
rp_image_ptr fromLinearGray2bpp(int width, int height,
const uint8_t *RESTRICT img_buf, size_t img_siz, int stride = 0);
/**
* Convert a linear monochrome image to rp_image.
*
* Windows icons are handled a bit different compared to "regular" monochrome images:
* - Two images are required: icon and mask
* - Transparency is supported using the mask.
* - 0 == black; 1 == white
*
* @param width [in] Image width
* @param height [in] Image height
* @param img_buf [in] Monochrome image buffer
* @param img_siz [in] Size of image data [must be >= ((w*h)/8)]
* @param mask_buf [in] Mask buffer
* @param mask_siz [in] Size of mask buffer [must be == img_siz]
* @param stride [in,opt] Stride, in bytes (if 0, assumes width*bytespp)
* @return rp_image, or nullptr on error.
*/
ATTR_ACCESS_SIZE(read_only, 3, 4)
ATTR_ACCESS_SIZE(read_only, 5, 6)
rp_image_ptr fromLinearMono_WinIcon(int width, int height,
const uint8_t *RESTRICT img_buf, size_t img_siz,
const uint8_t *RESTRICT mask_buf, size_t mask_siz, int stride = 0);
} }

View File

@ -2,7 +2,7 @@
* ROM Properties Page shell extension. (librptexture) *
* FileFormat_decl.hpp: Texture file format base class. (Subclass macros) *
* *
* Copyright (c) 2016-2024 by David Korth. *
* Copyright (c) 2016-2025 by David Korth. *
* Copyright (c) 2016-2018 by Egor. *
* SPDX-License-Identifier: GPL-2.0-or-later *
***************************************************************************/
@ -58,7 +58,7 @@ private: \
#define FILEFORMAT_DECL_CTOR_DEFAULT(klass) \
public: \
/** \
* Read a texture file file. \
* Read a texture file. \
* \
* 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 \

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,48 @@
/***************************************************************************
* ROM Properties Page shell extension. (librptexture) *
* ICO.hpp: Windows icon and cursor image reader. *
* *
* Copyright (c) 2017-2025 by David Korth. *
* SPDX-License-Identifier: GPL-2.0-or-later *
***************************************************************************/
#pragma once
#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()
}

View File

@ -0,0 +1,236 @@
/***************************************************************************
* ROM Properties Page shell extension. (librptexture) *
* ico_structs.h: Windows icon and cursor format data structures. *
* *
* Copyright (c) 2019-2025 by David Korth. *
* SPDX-License-Identifier: GPL-2.0-or-later *
***************************************************************************/
#pragma once
#include <stdint.h>
#include "common.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* References:
* - http://justsolve.archiveteam.org/wiki/Windows_1.0_Icon
* - http://justsolve.archiveteam.org/wiki/Windows_1.0_Cursor
* - http://justsolve.archiveteam.org/wiki/ICO
* - http://justsolve.archiveteam.org/wiki/CUR
* - https://devblogs.microsoft.com/oldnewthing/20101018-00/?p=12513
*/
/**
* Windows 1.0: Icon (and cursor)
*
* All fields are in little-endian.
*/
typedef struct _ICO_Win1_Header {
uint16_t format; // [0x000] See ICO_Win1_Format_e
uint16_t hotX; // [0x002] Cursor hotspot X (cursors only)
uint16_t hotY; // [0x004] Cursor hotspot Y (cursors only)
uint16_t width; // [0x006] Width, in pixels
uint16_t height; // [0x008] Height, in pixels
uint16_t stride; // [0x00A] Row stride, in bytes
uint16_t color; // [0x00C] Cursor color
} ICO_Win1_Header;
ASSERT_STRUCT(ICO_Win1_Header, 14);
/**
* Windows 1.0: Icon format
*/
typedef enum {
ICO_WIN1_FORMAT_MAYBE_WIN3 = 0x0000U, // may be a Win3 icon/cursor
ICO_WIN1_FORMAT_ICON_DIB = 0x0001U,
ICO_WIN1_FORMAT_ICON_DDB = 0x0101U,
ICO_WIN1_FORMAT_ICON_BOTH = 0x0201U,
ICO_WIN1_FORMAT_CURSOR_DIB = 0x0003U,
ICO_WIN1_FORMAT_CURSOR_DDB = 0x0103U,
ICO_WIN1_FORMAT_CURSOR_BOTH = 0x0203U,
} ICO_Win1_Format_e;
/**
* Windows 3.x: Icon header
* Layout-compatible with the Windows 1.0 icon header.
*
* All fields are in little-endian.
*/
typedef struct _ICONDIR {
uint16_t idReserved; // [0x000] Zero for Win3.x icons
uint16_t idType; // [0x002] See ICO_Win3_idType_e
uint16_t idCount; // [0x004] Number of images
} ICONDIR, ICONHEADER;
ASSERT_STRUCT(ICONDIR, 6);
/**
* Windows 3.x: Icon header, RES version (RT_GROUP_ICON)
*
* All fields are in little-endian.
*/
typedef struct _GRPICONDIR {
uint16_t idReserved; // [0x000] Zero for Win3.x icons
uint16_t idType; // [0x002] See ICO_Win3_idType_e
uint16_t idCount; // [0x004] Number of images
} GRPICONDIR;
ASSERT_STRUCT(GRPICONDIR, 6);
/**
* Windows 3.x: Icon types
*/
typedef enum {
ICO_WIN3_TYPE_ICON = 0x0001U,
ICO_WIN3_TYPE_CURSOR = 0x0002U,
} ICO_Win3_IdType_e;
/**
* Windows 3.x: Icon directory entry
*
* All fields are in little-endian.
*/
typedef struct _ICONDIRENTRY {
uint8_t bWidth; // [0x000] Width (0 is actually 256)
uint8_t bHeight; // [0x001] Height (0 is actually 256)
uint8_t bColorCount; // [0x002] Color count
uint8_t bReserved; // [0x003]
uint16_t wPlanes; // [0x004] Bitplanes (if >1, multiply by wBitCount)
uint16_t wBitCount; // [0x006] Bitcount
uint32_t dwBytesInRes; // [0x008] Size
uint32_t dwImageOffset; // [0x00C] Offset
} ICONDIRENTRY;
ASSERT_STRUCT(ICONDIRENTRY, 16);
/**
* Windows 3.x: Icon directory entry, RES version (RT_GROUP_ICON)
*
* All fields are in little-endian.
*/
#pragma pack(2)
typedef struct RP_PACKED _GRPICONDIRENTRY {
uint8_t bWidth; // [0x000] Width (0 is actually 256)
uint8_t bHeight; // [0x001] Height (0 is actually 256)
uint8_t bColorCount; // [0x002] Color count
uint8_t bReserved; // [0x003]
uint16_t wPlanes; // [0x004] Bitplanes (if >1, multiply by wBitCount)
uint16_t wBitCount; // [0x006] Bitcount
uint32_t dwBytesInRes; // [0x008] Size
uint16_t nID; // [0x00C] RT_ICON ID
} GRPICONDIRENTRY;
ASSERT_STRUCT(GRPICONDIRENTRY, 14);
#pragma pack()
// Windows 3.x icons can either have BITMAPCOREHEADER, BITMAPINFOHEADER,
// or a raw PNG image (supported by Windows Vista and later).
// TODO: Combine with librpbase/tests/bmp.h?
/**
* BITMAPCOREHEADER
* All fields are little-endian.
* Reference: https://learn.microsoft.com/en-us/windows/win32/api/wingdi/ns-wingdi-bitmapcoreheader
*/
#define BITMAPCOREHEADER_SIZE 12U
typedef struct tagBITMAPCOREHEADER {
uint32_t bcSize;
uint16_t bcWidth;
uint16_t bcHeight;
uint16_t bcPlanes;
uint16_t bcBitCount;
} BITMAPCOREHEADER;
ASSERT_STRUCT(BITMAPCOREHEADER, BITMAPCOREHEADER_SIZE);
/**
* BITMAPINFOHEADER
* All fields are little-endian.
* Reference: https://learn.microsoft.com/en-us/windows/win32/api/wingdi/ns-wingdi-bitmapinfoheader
*/
#define BITMAPINFOHEADER_SIZE 40U
#define BITMAPV2INFOHEADER_SIZE 52U
#define BITMAPV3INFOHEADER_SIZE 56U
#define BITMAPV4HEADER_SIZE 108U
#define BITMAPV5HEADER_SIZE 124U
typedef struct tagBITMAPINFOHEADER {
uint32_t biSize; // [0x000] sizeof(BITMAPINFOHEADER)
int32_t biWidth; // [0x004] Width, in pixels
int32_t biHeight; // [0x008] Height, in pixels
uint16_t biPlanes; // [0x00C] Bitplanes
uint16_t biBitCount; // [0x00E] Bitcount
uint32_t biCompression; // [0x010] Compression (see BMP_Compression_e)
uint32_t biSizeImage;
int32_t biXPelsPerMeter;
int32_t biYPelsPerMeter;
uint32_t biClrUsed;
uint32_t biClrImportant;
} BITMAPINFOHEADER;
ASSERT_STRUCT(BITMAPINFOHEADER, BITMAPINFOHEADER_SIZE);
/**
* Bitmap compression
*
* NOTE: For Windows icons, only BI_RGB and BI_BITFIELDS are valid.
* For PNG, use a raw PNG without BITMAPINFOHEADER.
*/
typedef enum {
BI_RGB = 0U,
BI_RLE8 = 1U,
BI_RLE4 = 2U,
BI_BITFIELDS = 3U,
BI_JPEG = 4U,
BI_PNG = 5U,
} BMP_Compression_e;
/** PNG chunks (TODO: Combine with librpbase/tests/bmp.h?) **/
/* These describe the color_type field in png_info. */
/* color type masks */
#define PNG_COLOR_MASK_PALETTE 1
#define PNG_COLOR_MASK_COLOR 2
#define PNG_COLOR_MASK_ALPHA 4
/* color types. Note that not all combinations are legal */
#define PNG_COLOR_TYPE_GRAY 0
#define PNG_COLOR_TYPE_PALETTE (PNG_COLOR_MASK_COLOR | PNG_COLOR_MASK_PALETTE)
#define PNG_COLOR_TYPE_RGB (PNG_COLOR_MASK_COLOR)
#define PNG_COLOR_TYPE_RGB_ALPHA (PNG_COLOR_MASK_COLOR | PNG_COLOR_MASK_ALPHA)
#define PNG_COLOR_TYPE_GRAY_ALPHA (PNG_COLOR_MASK_ALPHA)
/* aliases */
#define PNG_COLOR_TYPE_RGBA PNG_COLOR_TYPE_RGB_ALPHA
#define PNG_COLOR_TYPE_GA PNG_COLOR_TYPE_GRAY_ALPHA
/**
* PNG IHDR chunk
*/
#pragma pack(1)
typedef struct RP_PACKED _PNG_IHDR_t {
uint32_t width; // BE32
uint32_t height; // BE32
uint8_t bit_depth;
uint8_t color_type;
uint8_t compression_method;
uint8_t filter_method;
uint8_t interlace_method;
} PNG_IHDR_t;
ASSERT_STRUCT(PNG_IHDR_t, 13);
#pragma pack()
/**
* PNG IHDR struct, with length, name, and CRC32.
*/
#pragma pack(1)
typedef struct RP_PACKED _PNG_IHDR_full_t {
uint32_t chunk_size; // BE32
char chunk_name[4]; // "IHDR"
PNG_IHDR_t data;
uint32_t crc32;
} PNG_IHDR_full_t;
ASSERT_STRUCT(PNG_IHDR_full_t, 25);
#pragma pack()
#ifdef __cplusplus
}
#endif

View File

@ -89,6 +89,15 @@ application/x-ms-wim # Wim
# Texture
# Formats listed here are widely supported by Linux desktop
# environments, but not other systems.
application/ico # ICO
image/ico # ICO
image/icon # ICO
image/x-cursor # ICO
image/x-ico # ICO
image/x-icon # ICO
image/vnd.microsoft.cursor # ICO
image/vnd.microsoft.icon # ICO
text/ico # ICO
application/tga # TGA
application/x-targa # TGA
application/x-tga # TGA