mirror of
https://github.com/GerbilSoft/rom-properties.git
synced 2025-06-18 11:35:38 -04:00
Merge branch 'feature/IsoPartition-readdir'
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:
commit
e2eede76f7
19
NEWS.md
19
NEWS.md
@ -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)
|
||||
|
||||
|
@ -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
|
||||
|
@ -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') {
|
||||
|
@ -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;
|
||||
|
@ -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.
|
||||
*
|
||||
|
@ -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()
|
||||
|
||||
|
@ -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", ¶ms);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
|
@ -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()
|
||||
|
||||
|
@ -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 **/
|
||||
|
||||
/**
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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.
|
||||
|
@ -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) {
|
||||
|
@ -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.
|
||||
|
@ -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)
|
||||
|
@ -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);
|
||||
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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.
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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 ";
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -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!");
|
||||
|
@ -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.
|
||||
|
@ -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.
|
@ -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;
|
@ -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
|
||||
}
|
@ -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
|
||||
|
@ -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))
|
||||
{
|
||||
|
@ -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) {
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
} }
|
||||
|
@ -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);
|
||||
|
||||
} }
|
||||
|
@ -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 \
|
||||
|
1254
src/librptexture/fileformat/ICO.cpp
Normal file
1254
src/librptexture/fileformat/ICO.cpp
Normal file
File diff suppressed because it is too large
Load Diff
48
src/librptexture/fileformat/ICO.hpp
Normal file
48
src/librptexture/fileformat/ICO.hpp
Normal 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()
|
||||
|
||||
}
|
236
src/librptexture/fileformat/ico_structs.h
Normal file
236
src/librptexture/fileformat/ico_structs.h
Normal 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
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user