mirror of
https://github.com/GerbilSoft/rom-properties.git
synced 2025-06-18 11:35:38 -04:00
[libromdata] NintendoDS_BNR: Split icon/title data parsing out of NintendoDS.
This will allow thumbnailing of standalone NDS banners. TODO: Validate CRC16s to reduce false-positives.
This commit is contained in:
parent
67d3f4048c
commit
f2565965b2
3
NEWS.md
3
NEWS.md
@ -25,6 +25,9 @@
|
||||
* rp-config now has an option to disable directory thumbnailing,
|
||||
since it can slow down file browsing. Disabling directory
|
||||
thumbnailing will disable Wii U NUS package thumbnailing.
|
||||
* NintendoDS_BNR: Split out icon/title parser from NintendoDS.
|
||||
Allows thumbnailing of individual Nintendo DS .bnr files in addition
|
||||
to .nds ROM images.
|
||||
|
||||
* New parser features:
|
||||
* EXE: Use numeric sorting for ordinals and hints.
|
||||
|
@ -59,6 +59,7 @@ SET(${PROJECT_NAME}_SRCS
|
||||
Handheld/Nintendo3DS_SMDH.cpp
|
||||
Handheld/NintendoDS.cpp
|
||||
Handheld/NintendoDS_ops.cpp
|
||||
Handheld/NintendoDS_BNR.cpp
|
||||
Handheld/PalmOS.cpp
|
||||
Handheld/PokemonMini.cpp
|
||||
Handheld/PSP.cpp
|
||||
@ -227,6 +228,7 @@ SET(${PROJECT_NAME}_H
|
||||
Handheld/Nintendo3DS_SMDH.hpp
|
||||
Handheld/NintendoDS.hpp
|
||||
Handheld/NintendoDS_p.hpp
|
||||
Handheld/NintendoDS_BNR.hpp
|
||||
Handheld/PalmOS.hpp
|
||||
Handheld/PokemonMini.hpp
|
||||
Handheld/PSP.hpp
|
||||
|
@ -17,7 +17,6 @@
|
||||
// Other rom-properties libraries
|
||||
#include "librpbase/config/Config.hpp"
|
||||
#include "librpbase/SystemRegion.hpp"
|
||||
#include "librptexture/decoder/ImageDecoder_NDS.hpp"
|
||||
using namespace LibRpBase;
|
||||
using namespace LibRpFile;
|
||||
using namespace LibRpText;
|
||||
@ -25,6 +24,7 @@ using namespace LibRpTexture;
|
||||
|
||||
// C++ STL classes
|
||||
using std::array;
|
||||
using std::shared_ptr;
|
||||
using std::string;
|
||||
using std::vector;
|
||||
|
||||
@ -65,17 +65,21 @@ const RomDataInfo NintendoDSPrivate::romDataInfo = {
|
||||
NintendoDSPrivate::NintendoDSPrivate(const IRpFilePtr &file, bool cia)
|
||||
: super(file, &romDataInfo)
|
||||
, romType(RomType::Unknown)
|
||||
, nds_icon_title(nullptr)
|
||||
, romSize(0)
|
||||
, secData(0)
|
||||
, secArea(NDS_SECAREA_UNKNOWN)
|
||||
, nds_icon_title_loaded(false)
|
||||
, cia(cia)
|
||||
, fieldIdx_secData(-1)
|
||||
, fieldIdx_secArea(-1)
|
||||
{
|
||||
// Clear the various structs.
|
||||
memset(&romHeader, 0, sizeof(romHeader));
|
||||
memset(&nds_icon_title, 0, sizeof(nds_icon_title));
|
||||
}
|
||||
|
||||
NintendoDSPrivate::~NintendoDSPrivate()
|
||||
{
|
||||
delete nds_icon_title;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -86,7 +90,7 @@ int NintendoDSPrivate::loadIconTitleData(void)
|
||||
{
|
||||
assert(this->file != nullptr);
|
||||
|
||||
if (nds_icon_title_loaded) {
|
||||
if ((bool)nds_icon_title) {
|
||||
// Icon/title data is already loaded.
|
||||
return 0;
|
||||
}
|
||||
@ -99,257 +103,25 @@ int NintendoDSPrivate::loadIconTitleData(void)
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
// Create a DiscReader for the icon/title.
|
||||
IDiscReaderPtr discReader = std::make_shared<DiscReader>(this->file, icon_offset, sizeof(NDS_IconTitleData));
|
||||
if (!discReader->isOpen()) {
|
||||
// Failed to open the DiscReader.
|
||||
return -discReader->lastError();
|
||||
}
|
||||
// Read the icon/title data.
|
||||
size_t size = this->file->seekAndRead(icon_offset, &nds_icon_title, sizeof(nds_icon_title));
|
||||
|
||||
// Make sure we have the correct size based on the version.
|
||||
if (size < sizeof(nds_icon_title.version)) {
|
||||
// Couldn't even load the version number...
|
||||
NintendoDS_BNR *const bnrFile = new NintendoDS_BNR(discReader);
|
||||
if (!bnrFile->isValid()) {
|
||||
// Failed to open the NintendoDS_BNR.
|
||||
delete bnrFile;
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
unsigned int req_size;
|
||||
switch (le16_to_cpu(nds_icon_title.version)) {
|
||||
case NDS_ICON_VERSION_ORIGINAL:
|
||||
req_size = NDS_ICON_SIZE_ORIGINAL;
|
||||
break;
|
||||
case NDS_ICON_VERSION_HANS:
|
||||
req_size = NDS_ICON_SIZE_HANS;
|
||||
break;
|
||||
case NDS_ICON_VERSION_HANS_KO:
|
||||
req_size = NDS_ICON_SIZE_HANS_KO;
|
||||
break;
|
||||
case NDS_ICON_VERSION_DSi:
|
||||
req_size = NDS_ICON_SIZE_DSi;
|
||||
break;
|
||||
default:
|
||||
// Invalid version number.
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
if (size < req_size) {
|
||||
// Error reading the icon data.
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
// Icon data loaded.
|
||||
nds_icon_title_loaded = true;
|
||||
// Save the banner file.
|
||||
this->nds_icon_title = bnrFile;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the ROM image's icon.
|
||||
* @return Icon, or nullptr on error.
|
||||
*/
|
||||
rp_image_const_ptr NintendoDSPrivate::loadIcon(void)
|
||||
{
|
||||
if (icon_first_frame) {
|
||||
// Icon has already been loaded.
|
||||
return icon_first_frame;
|
||||
} else if (!this->isValid || !this->file) {
|
||||
// Can't load the icon.
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Attempt to load the icon/title data.
|
||||
int ret = loadIconTitleData();
|
||||
if (ret != 0) {
|
||||
// Error loading the icon/title data.
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Load the icon data.
|
||||
// TODO: Only read the first frame unless specifically requested?
|
||||
this->iconAnimData = std::make_shared<IconAnimData>();
|
||||
iconAnimData->count = 0;
|
||||
|
||||
// Check if a DSi animated icon is present.
|
||||
// TODO: Some configuration option to return the standard
|
||||
// NDS icon for the standard icon instead of the first frame
|
||||
// of the animated DSi icon? (Except for DSiWare...)
|
||||
if (le16_to_cpu(nds_icon_title.version) < NDS_ICON_VERSION_DSi ||
|
||||
(nds_icon_title.dsi_icon_seq[0] & cpu_to_le16(0xFF)) == 0)
|
||||
{
|
||||
// Either this isn't a DSi icon/title struct (pre-v0103),
|
||||
// or the animated icon sequence is invalid.
|
||||
|
||||
// Convert the NDS icon to rp_image.
|
||||
iconAnimData->frames[0] = ImageDecoder::fromNDS_CI4(32, 32,
|
||||
nds_icon_title.icon_data, sizeof(nds_icon_title.icon_data),
|
||||
nds_icon_title.icon_pal, sizeof(nds_icon_title.icon_pal));
|
||||
iconAnimData->count = 1;
|
||||
} else {
|
||||
// Animated icon is present.
|
||||
|
||||
// Maximum number of combinations based on bitmap index,
|
||||
// palette index, and flip bits is 256. We don't want to
|
||||
// reserve 256 images, so we'll use a map to determine
|
||||
// which combinations go to which bitmap.
|
||||
|
||||
// dsi_icon_seq is limited to 64, so there's still a maximum
|
||||
// of 64 possible bitmaps.
|
||||
|
||||
// Index: High byte of token.
|
||||
// Value: Bitmap index. (0xFF for unused)
|
||||
array<uint8_t, 256> arr_bmpUsed;
|
||||
arr_bmpUsed.fill(0xFF);
|
||||
|
||||
// Parse the icon sequence.
|
||||
uint8_t bmp_idx = 0;
|
||||
int seq_idx;
|
||||
for (seq_idx = 0; seq_idx < ARRAY_SIZE_I(nds_icon_title.dsi_icon_seq); seq_idx++) {
|
||||
const uint16_t seq = le16_to_cpu(nds_icon_title.dsi_icon_seq[seq_idx]);
|
||||
const int delay = (seq & 0xFF);
|
||||
if (delay == 0) {
|
||||
// End of sequence.
|
||||
break;
|
||||
}
|
||||
|
||||
// Token format: (bits)
|
||||
// - 15: V flip (1=yes, 0=no) [TODO]
|
||||
// - 14: H flip (1=yes, 0=no) [TODO]
|
||||
// - 13-11: Palette index.
|
||||
// - 10-8: Bitmap index.
|
||||
// - 7-0: Frame duration. (units of 60 Hz)
|
||||
|
||||
// NOTE: IconAnimData doesn't support arbitrary combinations
|
||||
// of palette and bitmap. As a workaround, we'll make each
|
||||
// combination a unique bitmap, which means we have a maximum
|
||||
// of 64 bitmaps.
|
||||
const uint8_t high_token = (seq >> 8);
|
||||
if (arr_bmpUsed[high_token] == 0xFF) {
|
||||
// Not used yet. Create the bitmap.
|
||||
const uint8_t bmp = (high_token & 7);
|
||||
const uint8_t pal = (high_token >> 3) & 7;
|
||||
rp_image_ptr img = ImageDecoder::fromNDS_CI4(32, 32,
|
||||
nds_icon_title.dsi_icon_data[bmp],
|
||||
sizeof(nds_icon_title.dsi_icon_data[bmp]),
|
||||
nds_icon_title.dsi_icon_pal[pal],
|
||||
sizeof(nds_icon_title.dsi_icon_pal[pal]));
|
||||
if (high_token & (3U << 6)) {
|
||||
// At least one flip bit is set.
|
||||
rp_image::FlipOp flipOp = rp_image::FLIP_NONE;
|
||||
if (high_token & (1U << 6)) {
|
||||
// H-flip
|
||||
flipOp = rp_image::FLIP_H;
|
||||
}
|
||||
if (high_token & (1U << 7)) {
|
||||
// V-flip
|
||||
flipOp = static_cast<rp_image::FlipOp>(flipOp | rp_image::FLIP_V);
|
||||
}
|
||||
const rp_image_ptr flipimg = img->flip(flipOp);
|
||||
if (flipimg && flipimg->isValid()) {
|
||||
img = flipimg;
|
||||
}
|
||||
}
|
||||
iconAnimData->frames[bmp_idx] = img;
|
||||
arr_bmpUsed[high_token] = bmp_idx;
|
||||
bmp_idx++;
|
||||
}
|
||||
iconAnimData->seq_index[seq_idx] = arr_bmpUsed[high_token];
|
||||
iconAnimData->delays[seq_idx].numer = static_cast<uint16_t>(delay);
|
||||
iconAnimData->delays[seq_idx].denom = 60;
|
||||
iconAnimData->delays[seq_idx].ms = delay * 1000 / 60;
|
||||
}
|
||||
iconAnimData->count = bmp_idx;
|
||||
iconAnimData->seq_count = seq_idx;
|
||||
}
|
||||
|
||||
// NOTE: We're not deleting iconAnimData even if we only have
|
||||
// a single icon because iconAnimData() will call loadIcon()
|
||||
// if iconAnimData is nullptr.
|
||||
|
||||
// Return a pointer to the first frame.
|
||||
icon_first_frame = iconAnimData->frames[iconAnimData->seq_index[0]];
|
||||
return icon_first_frame;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the maximum supported language for an icon/title version.
|
||||
* @param version Icon/title version.
|
||||
* @return Maximum supported language.
|
||||
*/
|
||||
NDS_Language_ID NintendoDSPrivate::getMaxSupportedLanguage(uint16_t version)
|
||||
{
|
||||
NDS_Language_ID maxID;
|
||||
if (version >= NDS_ICON_VERSION_HANS_KO) {
|
||||
maxID = NDS_LANG_KOREAN;
|
||||
} else if (version >= NDS_ICON_VERSION_HANS) {
|
||||
maxID = NDS_LANG_CHINESE_SIMP;
|
||||
} else {
|
||||
maxID = NDS_LANG_SPANISH;
|
||||
}
|
||||
return maxID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the language ID to use for the title fields.
|
||||
* @return NDS language ID.
|
||||
*/
|
||||
NDS_Language_ID NintendoDSPrivate::getLanguageID(void) const
|
||||
{
|
||||
if (!nds_icon_title_loaded) {
|
||||
// Attempt to load the icon/title data.
|
||||
if (const_cast<NintendoDSPrivate*>(this)->loadIconTitleData() != 0) {
|
||||
// Error loading the icon/title data.
|
||||
return (NDS_Language_ID)-1;
|
||||
}
|
||||
|
||||
// Make sure it was actually loaded.
|
||||
if (!nds_icon_title_loaded) {
|
||||
// Icon/title data was not loaded.
|
||||
return (NDS_Language_ID)-1;
|
||||
}
|
||||
}
|
||||
|
||||
// Version number check is required for ZH and KO.
|
||||
const uint16_t version = le16_to_cpu(nds_icon_title.version);
|
||||
NDS_Language_ID langID = (NDS_Language_ID)NintendoLanguage::getNDSLanguage(version);
|
||||
|
||||
// Check that the field is valid.
|
||||
if (nds_icon_title.title[langID][0] == cpu_to_le16('\0')) {
|
||||
// Not valid. Check English.
|
||||
if (nds_icon_title.title[NDS_LANG_ENGLISH][0] != cpu_to_le16('\0')) {
|
||||
// English is valid.
|
||||
langID = NDS_LANG_ENGLISH;
|
||||
} else {
|
||||
// Not valid. Check Japanese.
|
||||
if (nds_icon_title.title[NDS_LANG_JAPANESE][0] != cpu_to_le16('\0')) {
|
||||
// Japanese is valid.
|
||||
langID = NDS_LANG_JAPANESE;
|
||||
} else {
|
||||
// Not valid...
|
||||
// Default to English anyway.
|
||||
langID = NDS_LANG_ENGLISH;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return langID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the default language code for the multi-string fields.
|
||||
* @return Language code, e.g. 'en' or 'es'.
|
||||
*/
|
||||
uint32_t NintendoDSPrivate::getDefaultLC(void) const
|
||||
{
|
||||
// Get the system language.
|
||||
// TODO: Verify against the game's region code?
|
||||
const NDS_Language_ID langID = getLanguageID();
|
||||
|
||||
// Version number check is required for ZH and KO.
|
||||
const NDS_Language_ID maxID = getMaxSupportedLanguage(
|
||||
le16_to_cpu(nds_icon_title.version));
|
||||
uint32_t lc = NintendoLanguage::getNDSLanguageCode(langID, maxID);
|
||||
if (lc == 0) {
|
||||
// Invalid language code...
|
||||
// Default to English.
|
||||
lc = 'en';
|
||||
}
|
||||
return lc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a Nintendo DS(i) region value to a GameTDB language code.
|
||||
* @param ndsRegion Nintendo DS region.
|
||||
@ -840,15 +612,12 @@ uint32_t NintendoDS::imgpf(ImageType imageType) const
|
||||
uint32_t ret = 0;
|
||||
switch (imageType) {
|
||||
case IMG_INT_ICON:
|
||||
// Use nearest-neighbor scaling when resizing.
|
||||
// Also, need to check if this is an animated icon.
|
||||
const_cast<NintendoDSPrivate*>(d)->loadIcon();
|
||||
if (d->iconAnimData && d->iconAnimData->count > 1) {
|
||||
// Animated icon.
|
||||
ret = IMGPF_RESCALE_NEAREST | IMGPF_ICON_ANIMATED;
|
||||
} else {
|
||||
// Not animated.
|
||||
ret = IMGPF_RESCALE_NEAREST;
|
||||
// Wrapper function around NintendoDS_BNR.
|
||||
if (!d->nds_icon_title) {
|
||||
const_cast<NintendoDSPrivate*>(d)->loadIconTitleData();
|
||||
}
|
||||
if (d->nds_icon_title) {
|
||||
ret = d->nds_icon_title->imgpf(imageType);
|
||||
}
|
||||
break;
|
||||
|
||||
@ -937,56 +706,16 @@ int NintendoDS::loadFieldData(void)
|
||||
latin1_to_utf8(romHeader->title, ARRAY_SIZE_I(romHeader->title)),
|
||||
RomFields::STRF_TRIM_END);
|
||||
|
||||
if (!d->nds_icon_title_loaded) {
|
||||
// Attempt to load the icon/title data.
|
||||
if (!d->nds_icon_title) {
|
||||
const_cast<NintendoDSPrivate*>(d)->loadIconTitleData();
|
||||
}
|
||||
if (d->nds_icon_title_loaded) {
|
||||
// Full title: Check if English is valid.
|
||||
// If it is, we'll de-duplicate fields.
|
||||
const bool dedupe_titles = (d->nds_icon_title.title[NDS_LANG_ENGLISH][0] != cpu_to_le16(0));
|
||||
|
||||
// Full title field.
|
||||
RomFields::StringMultiMap_t *const pMap_full_title = new RomFields::StringMultiMap_t();
|
||||
const NDS_Language_ID maxID = d->getMaxSupportedLanguage(
|
||||
le16_to_cpu(d->nds_icon_title.version));
|
||||
for (int langID = 0; langID <= maxID; langID++) {
|
||||
// Check for empty strings first.
|
||||
if (d->nds_icon_title.title[langID][0] == 0) {
|
||||
// Strings are empty.
|
||||
continue;
|
||||
}
|
||||
|
||||
if (dedupe_titles && langID != NDS_LANG_ENGLISH) {
|
||||
// Check if the title matches English.
|
||||
// NOTE: Not converting to host-endian first, since
|
||||
// u16_strncmp() checks for equality and for 0.
|
||||
if (!u16_strncmp(d->nds_icon_title.title[langID],
|
||||
d->nds_icon_title.title[NDS_LANG_ENGLISH],
|
||||
ARRAY_SIZE(d->nds_icon_title.title[NDS_LANG_ENGLISH])))
|
||||
{
|
||||
// Full title field matches English.
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
const uint32_t lc = NintendoLanguage::getNDSLanguageCode(langID, maxID);
|
||||
assert(lc != 0);
|
||||
if (lc == 0)
|
||||
continue;
|
||||
|
||||
if (d->nds_icon_title.title[langID][0] != cpu_to_le16('\0')) {
|
||||
pMap_full_title->emplace(lc, utf16le_to_utf8(
|
||||
d->nds_icon_title.title[langID],
|
||||
ARRAY_SIZE(d->nds_icon_title.title[langID])));
|
||||
}
|
||||
}
|
||||
|
||||
if (!pMap_full_title->empty()) {
|
||||
const uint32_t def_lc = d->getDefaultLC();
|
||||
d->fields.addField_string_multi(C_("Nintendo", "Full Title"), pMap_full_title, def_lc);
|
||||
} else {
|
||||
delete pMap_full_title;
|
||||
if (d->nds_icon_title) {
|
||||
// Full title
|
||||
const RomFields *const other = d->nds_icon_title->fields();
|
||||
assert(other != nullptr);
|
||||
if (other) {
|
||||
// TODO: Verify that this has Full Title?
|
||||
d->fields.addFields_romFields(other, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1303,46 +1032,28 @@ int NintendoDS::loadMetaData(void)
|
||||
const NDS_RomHeader *const romHeader = &d->romHeader;
|
||||
|
||||
// Title
|
||||
string s_title;
|
||||
if (!d->nds_icon_title_loaded) {
|
||||
// Attempt to load the icon/title data.
|
||||
bool has_full_title = false;
|
||||
if (!d->nds_icon_title) {
|
||||
const_cast<NintendoDSPrivate*>(d)->loadIconTitleData();
|
||||
}
|
||||
if (d->nds_icon_title_loaded) {
|
||||
// Full title.
|
||||
// TODO: Use the default LC if it's available.
|
||||
// For now, default to English.
|
||||
if (d->nds_icon_title.title[NDS_LANG_ENGLISH][0] != cpu_to_le16(0)) {
|
||||
s_title = utf16le_to_utf8(d->nds_icon_title.title[NDS_LANG_ENGLISH],
|
||||
ARRAY_SIZE(d->nds_icon_title.title[NDS_LANG_ENGLISH]));
|
||||
|
||||
// Adjust the title based on the number of lines.
|
||||
const size_t nl_1 = s_title.find('\n');
|
||||
if (nl_1 != string::npos) {
|
||||
// Found the first newline.
|
||||
const size_t nl_2 = s_title.find('\n', nl_1+1);
|
||||
if (nl_2 != string::npos) {
|
||||
// Found the second newline.
|
||||
// Change the first to a space, and remove the third line.
|
||||
s_title[nl_1] = ' ';
|
||||
s_title.resize(nl_2);
|
||||
} else {
|
||||
// Only two lines.
|
||||
// Remove the second line.
|
||||
s_title.resize(nl_1);
|
||||
}
|
||||
}
|
||||
if (d->nds_icon_title) {
|
||||
// Full title
|
||||
const RomMetaData *const other = d->nds_icon_title->metaData();
|
||||
assert(other != nullptr);
|
||||
if (other) {
|
||||
d->metaData->addMetaData_metaData(other);
|
||||
has_full_title = true; // TODO: Verify?
|
||||
}
|
||||
}
|
||||
|
||||
if (s_title.empty()) {
|
||||
if (!has_full_title) {
|
||||
// Full title is not available.
|
||||
// Use the short title from the NDS header.
|
||||
s_title = latin1_to_utf8(romHeader->title, ARRAY_SIZE_I(romHeader->title));
|
||||
d->metaData->addMetaData_string(Property::Title,
|
||||
latin1_to_utf8(romHeader->title, ARRAY_SIZE_I(romHeader->title)),
|
||||
RomMetaData::STRF_TRIM_END);
|
||||
}
|
||||
|
||||
d->metaData->addMetaData_string(Property::Title, s_title, RomMetaData::STRF_TRIM_END);
|
||||
|
||||
// Publisher
|
||||
// TODO: Use publisher from the full title?
|
||||
const char *const publisher = NintendoPublishers::lookup(romHeader->company);
|
||||
@ -1364,14 +1075,17 @@ int NintendoDS::loadMetaData(void)
|
||||
int NintendoDS::loadInternalImage(ImageType imageType, rp_image_const_ptr &pImage)
|
||||
{
|
||||
ASSERT_loadInternalImage(imageType, pImage);
|
||||
|
||||
// Wrapper function around NintendoDS_BNR.
|
||||
RP_D(NintendoDS);
|
||||
ROMDATA_loadInternalImage_single(
|
||||
IMG_INT_ICON, // ourImageType
|
||||
d->file, // file
|
||||
d->isValid, // isValid
|
||||
d->romType, // romType
|
||||
d->icon_first_frame, // imgCache
|
||||
d->loadIcon); // func
|
||||
if (!d->nds_icon_title) {
|
||||
if (d->loadIconTitleData() != 0) {
|
||||
// Error loading the icon/title data.
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
return d->nds_icon_title->loadInternalImage(imageType, pImage);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1384,26 +1098,16 @@ int NintendoDS::loadInternalImage(ImageType imageType, rp_image_const_ptr &pImag
|
||||
*/
|
||||
IconAnimDataConstPtr NintendoDS::iconAnimData(void) const
|
||||
{
|
||||
// Wrapper function around NintendoDS_BNR.
|
||||
RP_D(const NintendoDS);
|
||||
if (!d->iconAnimData) {
|
||||
// Load the icon.
|
||||
if (!const_cast<NintendoDSPrivate*>(d)->loadIcon()) {
|
||||
// Error loading the icon.
|
||||
return nullptr;
|
||||
}
|
||||
if (!d->iconAnimData) {
|
||||
// Still no icon...
|
||||
return nullptr;
|
||||
if (!d->nds_icon_title) {
|
||||
if (const_cast<NintendoDSPrivate*>(d)->loadIconTitleData() != 0) {
|
||||
// Error loading the icon/title data.
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
if (d->iconAnimData->count <= 1) {
|
||||
// Not an animated icon.
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Return the icon animation data.
|
||||
return d->iconAnimData;
|
||||
return d->nds_icon_title->iconAnimData();
|
||||
}
|
||||
|
||||
/**
|
||||
|
701
src/libromdata/Handheld/NintendoDS_BNR.cpp
Normal file
701
src/libromdata/Handheld/NintendoDS_BNR.cpp
Normal file
@ -0,0 +1,701 @@
|
||||
/***************************************************************************
|
||||
* ROM Properties Page shell extension. (libromdata) *
|
||||
* NintendoDS_BNR.cpp: Nintendo DS icon/title data reader. *
|
||||
* Handles BNR files and icon/title sections. *
|
||||
* *
|
||||
* Copyright (c) 2016-2024 by David Korth. *
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later *
|
||||
***************************************************************************/
|
||||
|
||||
#include "stdafx.h"
|
||||
|
||||
#include "NintendoDS_BNR.hpp"
|
||||
#include "nds_structs.h"
|
||||
#include "data/NintendoLanguage.hpp"
|
||||
|
||||
// Other rom-properties libraries
|
||||
#include "librptexture/decoder/ImageDecoder_NDS.hpp"
|
||||
#include "librpbase/SystemRegion.hpp"
|
||||
using namespace LibRpBase;
|
||||
using namespace LibRpFile;
|
||||
using namespace LibRpText;
|
||||
using namespace LibRpTexture;
|
||||
|
||||
// C++ STL classes
|
||||
using std::array;
|
||||
using std::string;
|
||||
using std::vector;
|
||||
|
||||
namespace LibRomData {
|
||||
|
||||
// Workaround for RP_D() expecting the no-underscore naming convention.
|
||||
#define NintendoDS_BNRPrivate NintendoDS_BNR_Private
|
||||
|
||||
class NintendoDS_BNR_Private final : public RomDataPrivate
|
||||
{
|
||||
public:
|
||||
NintendoDS_BNR_Private(const IRpFilePtr &file);
|
||||
|
||||
private:
|
||||
typedef RomDataPrivate super;
|
||||
RP_DISABLE_COPY(NintendoDS_BNR_Private)
|
||||
|
||||
public:
|
||||
/** RomDataInfo **/
|
||||
static const char *const exts[];
|
||||
static const char *const mimeTypes[];
|
||||
static const RomDataInfo romDataInfo;
|
||||
|
||||
public:
|
||||
// Icon/title data from the ROM header.
|
||||
// NOTE: *NOT* byteswapped!
|
||||
NDS_IconTitleData nds_icon_title;
|
||||
|
||||
// Animated icon data
|
||||
// This class owns all of the icons in here, so we
|
||||
// must delete all of them.
|
||||
LibRpBase::IconAnimDataPtr iconAnimData;
|
||||
|
||||
// Pointer to the first frame in iconAnimData.
|
||||
// Used when showing a static icon.
|
||||
rp_image_const_ptr icon_first_frame;
|
||||
|
||||
/**
|
||||
* Load the ROM image's icon.
|
||||
* @return Icon, or nullptr on error.
|
||||
*/
|
||||
rp_image_const_ptr loadIcon(void);
|
||||
|
||||
/**
|
||||
* Get the maximum supported language for an icon/title version.
|
||||
* @param version Icon/title version.
|
||||
* @return Maximum supported language.
|
||||
*/
|
||||
static constexpr NDS_Language_ID getMaxSupportedLanguage(uint16_t version)
|
||||
{
|
||||
if (version >= NDS_ICON_VERSION_HANS_KO) {
|
||||
return NDS_LANG_KOREAN;
|
||||
} else if (version >= NDS_ICON_VERSION_HANS) {
|
||||
return NDS_LANG_CHINESE_SIMP;
|
||||
} else {
|
||||
return NDS_LANG_SPANISH;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the language ID to use for the title fields.
|
||||
* @return NDS language ID.
|
||||
*/
|
||||
NDS_Language_ID getLanguageID(void) const;
|
||||
|
||||
/**
|
||||
* Get the default language code for the multi-string fields.
|
||||
* @return Language code, e.g. 'en' or 'es'.
|
||||
*/
|
||||
uint32_t getDefaultLC(void) const;
|
||||
};
|
||||
|
||||
ROMDATA_IMPL(NintendoDS_BNR)
|
||||
ROMDATA_IMPL_IMG_TYPES(NintendoDS_BNR)
|
||||
ROMDATA_IMPL_IMG_SIZES(NintendoDS_BNR)
|
||||
|
||||
/** NintendoDS_BNR_Private **/
|
||||
|
||||
/* RomDataInfo */
|
||||
// NOTE: Using the same image settings as Nintendo3DS.
|
||||
const char *const NintendoDS_BNR_Private::exts[] = {
|
||||
".bnr", // Banner file
|
||||
|
||||
nullptr
|
||||
};
|
||||
const char *const NintendoDS_BNR_Private::mimeTypes[] = {
|
||||
// Unofficial MIME types.
|
||||
// TODO: Get these upstreamed on FreeDesktop.org.
|
||||
"application/x-nintendo-ds-bnr",
|
||||
|
||||
nullptr
|
||||
};
|
||||
const RomDataInfo NintendoDS_BNR_Private::romDataInfo = {
|
||||
"Nintendo3DS", exts, mimeTypes
|
||||
};
|
||||
|
||||
NintendoDS_BNR_Private::NintendoDS_BNR_Private(const IRpFilePtr &file)
|
||||
: super(file, &romDataInfo)
|
||||
{
|
||||
// Clear the icon/title data struct.
|
||||
memset(&nds_icon_title, 0, sizeof(nds_icon_title));
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the ROM image's icon.
|
||||
* @return Icon, or nullptr on error.
|
||||
*/
|
||||
rp_image_const_ptr NintendoDS_BNR_Private::loadIcon(void)
|
||||
{
|
||||
if (icon_first_frame) {
|
||||
// Icon has already been loaded.
|
||||
return icon_first_frame;
|
||||
} else if (!this->isValid || !this->file) {
|
||||
// Can't load the icon.
|
||||
return {};
|
||||
}
|
||||
|
||||
// Load the icon data.
|
||||
// TODO: Only read the first frame unless specifically requested?
|
||||
this->iconAnimData = std::make_shared<IconAnimData>();
|
||||
iconAnimData->count = 0;
|
||||
|
||||
// Check if a DSi animated icon is present.
|
||||
// TODO: Some configuration option to return the standard
|
||||
// NDS icon for the standard icon instead of the first frame
|
||||
// of the animated DSi icon? (Except for DSiWare...)
|
||||
if (le16_to_cpu(nds_icon_title.version) < NDS_ICON_VERSION_DSi ||
|
||||
(nds_icon_title.dsi_icon_seq[0] & cpu_to_le16(0xFF)) == 0)
|
||||
{
|
||||
// Either this isn't a DSi icon/title struct (pre-v0103),
|
||||
// or the animated icon sequence is invalid.
|
||||
|
||||
// Convert the NDS icon to rp_image.
|
||||
iconAnimData->frames[0] = ImageDecoder::fromNDS_CI4(32, 32,
|
||||
nds_icon_title.icon_data, sizeof(nds_icon_title.icon_data),
|
||||
nds_icon_title.icon_pal, sizeof(nds_icon_title.icon_pal));
|
||||
iconAnimData->count = 1;
|
||||
} else {
|
||||
// Animated icon is present.
|
||||
|
||||
// Maximum number of combinations based on bitmap index,
|
||||
// palette index, and flip bits is 256. We don't want to
|
||||
// reserve 256 images, so we'll use a map to determine
|
||||
// which combinations go to which bitmap.
|
||||
|
||||
// dsi_icon_seq is limited to 64, so there's still a maximum
|
||||
// of 64 possible bitmaps.
|
||||
|
||||
// Index: High byte of token.
|
||||
// Value: Bitmap index. (0xFF for unused)
|
||||
array<uint8_t, 256> arr_bmpUsed;
|
||||
arr_bmpUsed.fill(0xFF);
|
||||
|
||||
// Parse the icon sequence.
|
||||
uint8_t bmp_idx = 0;
|
||||
int seq_idx;
|
||||
for (seq_idx = 0; seq_idx < ARRAY_SIZE_I(nds_icon_title.dsi_icon_seq); seq_idx++) {
|
||||
const uint16_t seq = le16_to_cpu(nds_icon_title.dsi_icon_seq[seq_idx]);
|
||||
const int delay = (seq & 0xFF);
|
||||
if (delay == 0) {
|
||||
// End of sequence.
|
||||
break;
|
||||
}
|
||||
|
||||
// Token format: (bits)
|
||||
// - 15: V flip (1=yes, 0=no) [TODO]
|
||||
// - 14: H flip (1=yes, 0=no) [TODO]
|
||||
// - 13-11: Palette index.
|
||||
// - 10-8: Bitmap index.
|
||||
// - 7-0: Frame duration. (units of 60 Hz)
|
||||
|
||||
// NOTE: IconAnimData doesn't support arbitrary combinations
|
||||
// of palette and bitmap. As a workaround, we'll make each
|
||||
// combination a unique bitmap, which means we have a maximum
|
||||
// of 64 bitmaps.
|
||||
const uint8_t high_token = (seq >> 8);
|
||||
if (arr_bmpUsed[high_token] == 0xFF) {
|
||||
// Not used yet. Create the bitmap.
|
||||
const uint8_t bmp = (high_token & 7);
|
||||
const uint8_t pal = (high_token >> 3) & 7;
|
||||
rp_image_ptr img = ImageDecoder::fromNDS_CI4(32, 32,
|
||||
nds_icon_title.dsi_icon_data[bmp],
|
||||
sizeof(nds_icon_title.dsi_icon_data[bmp]),
|
||||
nds_icon_title.dsi_icon_pal[pal],
|
||||
sizeof(nds_icon_title.dsi_icon_pal[pal]));
|
||||
if (high_token & (3U << 6)) {
|
||||
// At least one flip bit is set.
|
||||
rp_image::FlipOp flipOp = rp_image::FLIP_NONE;
|
||||
if (high_token & (1U << 6)) {
|
||||
// H-flip
|
||||
flipOp = rp_image::FLIP_H;
|
||||
}
|
||||
if (high_token & (1U << 7)) {
|
||||
// V-flip
|
||||
flipOp = static_cast<rp_image::FlipOp>(flipOp | rp_image::FLIP_V);
|
||||
}
|
||||
const rp_image_ptr flipimg = img->flip(flipOp);
|
||||
if (flipimg && flipimg->isValid()) {
|
||||
img = flipimg;
|
||||
}
|
||||
}
|
||||
iconAnimData->frames[bmp_idx] = img;
|
||||
arr_bmpUsed[high_token] = bmp_idx;
|
||||
bmp_idx++;
|
||||
}
|
||||
iconAnimData->seq_index[seq_idx] = arr_bmpUsed[high_token];
|
||||
iconAnimData->delays[seq_idx].numer = static_cast<uint16_t>(delay);
|
||||
iconAnimData->delays[seq_idx].denom = 60;
|
||||
iconAnimData->delays[seq_idx].ms = delay * 1000 / 60;
|
||||
}
|
||||
iconAnimData->count = bmp_idx;
|
||||
iconAnimData->seq_count = seq_idx;
|
||||
}
|
||||
|
||||
// NOTE: We're not deleting iconAnimData even if we only have
|
||||
// a single icon because iconAnimData() will call loadIcon()
|
||||
// if iconAnimData is nullptr.
|
||||
|
||||
// Return a pointer to the first frame.
|
||||
icon_first_frame = iconAnimData->frames[iconAnimData->seq_index[0]];
|
||||
return icon_first_frame;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the language ID to use for the title fields.
|
||||
* @return NDS language ID.
|
||||
*/
|
||||
NDS_Language_ID NintendoDS_BNR_Private::getLanguageID(void) const
|
||||
{
|
||||
// Version number check is required for ZH and KO.
|
||||
const uint16_t version = le16_to_cpu(nds_icon_title.version);
|
||||
NDS_Language_ID langID = (NDS_Language_ID)NintendoLanguage::getNDSLanguage(version);
|
||||
|
||||
// Check that the field is valid.
|
||||
if (nds_icon_title.title[langID][0] == cpu_to_le16('\0')) {
|
||||
// Not valid. Check English.
|
||||
if (nds_icon_title.title[NDS_LANG_ENGLISH][0] != cpu_to_le16('\0')) {
|
||||
// English is valid.
|
||||
langID = NDS_LANG_ENGLISH;
|
||||
} else {
|
||||
// Not valid. Check Japanese.
|
||||
if (nds_icon_title.title[NDS_LANG_JAPANESE][0] != cpu_to_le16('\0')) {
|
||||
// Japanese is valid.
|
||||
langID = NDS_LANG_JAPANESE;
|
||||
} else {
|
||||
// Not valid...
|
||||
// Default to English anyway.
|
||||
langID = NDS_LANG_ENGLISH;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return langID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the default language code for the multi-string fields.
|
||||
* @return Language code, e.g. 'en' or 'es'.
|
||||
*/
|
||||
uint32_t NintendoDS_BNR_Private::getDefaultLC(void) const
|
||||
{
|
||||
// Get the system language.
|
||||
// TODO: Verify against the game's region code?
|
||||
const NDS_Language_ID langID = getLanguageID();
|
||||
|
||||
// Version number check is required for ZH and KO.
|
||||
const NDS_Language_ID maxID = getMaxSupportedLanguage(
|
||||
le16_to_cpu(nds_icon_title.version));
|
||||
uint32_t lc = NintendoLanguage::getNDSLanguageCode(langID, maxID);
|
||||
if (lc == 0) {
|
||||
// Invalid language code...
|
||||
// Default to English.
|
||||
lc = 'en';
|
||||
}
|
||||
return lc;
|
||||
}
|
||||
|
||||
/** NintendoDS_BNR **/
|
||||
|
||||
/**
|
||||
* Read a Nintendo DS BNR file and/or icon/title section.
|
||||
*
|
||||
* 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 disc image.
|
||||
*
|
||||
* To close the file, either delete this object or call close().
|
||||
*
|
||||
* NOTE: Check isValid() to determine if this is a valid ROM.
|
||||
*
|
||||
* @param file Open SMDH file and/or section.
|
||||
*/
|
||||
NintendoDS_BNR::NintendoDS_BNR(const IRpFilePtr &file)
|
||||
: super(new NintendoDS_BNR_Private(file))
|
||||
{
|
||||
// This class handles BNR files and/or icon/title sections only.
|
||||
// NOTE: Using the same image settings as Nintendo3DS.
|
||||
RP_D(NintendoDS_BNR);
|
||||
d->mimeType = "application/x-nintendo-ds-bnr"; // unofficial, not on fd.o
|
||||
d->fileType = FileType::IconFile;
|
||||
|
||||
if (!d->file) {
|
||||
// Could not ref() the file handle.
|
||||
return;
|
||||
}
|
||||
|
||||
// Read the icon/title data.
|
||||
d->file->rewind();
|
||||
size_t size = d->file->read(&d->nds_icon_title, sizeof(d->nds_icon_title));
|
||||
|
||||
// Make sure we have the correct size based on the version.
|
||||
if (size < sizeof(d->nds_icon_title.version)) {
|
||||
// Couldn't even load the version number...
|
||||
d->file.reset();
|
||||
return;
|
||||
}
|
||||
|
||||
unsigned int req_size;
|
||||
switch (le16_to_cpu(d->nds_icon_title.version)) {
|
||||
case NDS_ICON_VERSION_ORIGINAL:
|
||||
req_size = NDS_ICON_SIZE_ORIGINAL;
|
||||
break;
|
||||
case NDS_ICON_VERSION_HANS:
|
||||
req_size = NDS_ICON_SIZE_HANS;
|
||||
break;
|
||||
case NDS_ICON_VERSION_HANS_KO:
|
||||
req_size = NDS_ICON_SIZE_HANS_KO;
|
||||
break;
|
||||
case NDS_ICON_VERSION_DSi:
|
||||
req_size = NDS_ICON_SIZE_DSi;
|
||||
break;
|
||||
default:
|
||||
// Invalid version number.
|
||||
assert(!"NDS icon/title version number is invalid.");
|
||||
d->file.reset();
|
||||
return;
|
||||
}
|
||||
|
||||
if (size < req_size) {
|
||||
// Error reading the icon data.
|
||||
d->file.reset();
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: Verify CRC16s?
|
||||
d->isValid = true;
|
||||
}
|
||||
|
||||
/** ROM detection functions **/
|
||||
|
||||
/**
|
||||
* Is a ROM image supported by this class?
|
||||
* @param info DetectInfo containing ROM detection information.
|
||||
* @return Class-specific system ID (>= 0) if supported; -1 if not.
|
||||
*/
|
||||
int NintendoDS_BNR::isRomSupported_static(const DetectInfo *info)
|
||||
{
|
||||
assert(info != nullptr);
|
||||
assert(info->header.pData != nullptr);
|
||||
assert(info->header.addr == 0);
|
||||
if (!info || !info->header.pData ||
|
||||
info->header.addr != 0 ||
|
||||
info->header.size < sizeof(NDS_IconTitleData::version))
|
||||
{
|
||||
// Either no detection information was specified,
|
||||
// or the header is too small.
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Use heuristics to determine if this is valid.
|
||||
// TODO: Verify CRC16s?
|
||||
const NDS_IconTitleData *const nds_icon_title =
|
||||
reinterpret_cast<const NDS_IconTitleData*>(info->header.pData);
|
||||
|
||||
unsigned int req_size;
|
||||
switch (le16_to_cpu(nds_icon_title->version)) {
|
||||
case NDS_ICON_VERSION_ORIGINAL:
|
||||
req_size = NDS_ICON_SIZE_ORIGINAL;
|
||||
break;
|
||||
case NDS_ICON_VERSION_HANS:
|
||||
req_size = NDS_ICON_SIZE_HANS;
|
||||
break;
|
||||
case NDS_ICON_VERSION_HANS_KO:
|
||||
req_size = NDS_ICON_SIZE_HANS_KO;
|
||||
break;
|
||||
case NDS_ICON_VERSION_DSi:
|
||||
req_size = NDS_ICON_SIZE_DSi;
|
||||
break;
|
||||
default:
|
||||
// Invalid version number.
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (info->szFile < req_size) {
|
||||
// File is too small...
|
||||
return -1;
|
||||
}
|
||||
|
||||
// This is probably a supported BNR file.
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name of the system the loaded ROM is designed for.
|
||||
* @param type System name type. (See the SystemName enum.)
|
||||
* @return System name, or nullptr if type is invalid.
|
||||
*/
|
||||
const char *NintendoDS_BNR::systemName(unsigned int type) const
|
||||
{
|
||||
RP_D(const NintendoDS_BNR);
|
||||
if (!d->isValid || !isSystemNameTypeValid(type))
|
||||
return nullptr;
|
||||
|
||||
// NDS/DSi are mostly the same worldwide, except for China.
|
||||
// NOTE: We don't have region information here.
|
||||
// Assuming DSi if the version is >= NDS_ICON_VERSION_DSi.
|
||||
static_assert(SYSNAME_TYPE_MASK == 3,
|
||||
"NintendoDS::systemName() array index optimization needs to be updated.");
|
||||
|
||||
// Bits 0-1: Type. (long, short, abbreviation)
|
||||
// Bit 2: 0 for NDS, 1 for DSi-exclusive.
|
||||
static const array<const char*, 8> sysNames = {{
|
||||
"Nintendo DS", "Nintendo DS", "NDS", nullptr,
|
||||
"Nintendo DSi", "Nintendo DSi", "DSi", nullptr,
|
||||
}};
|
||||
|
||||
// "iQue" is only used if the localized system name is requested
|
||||
// *and* the ROM's region code is China only.
|
||||
unsigned int idx = (type & SYSNAME_TYPE_MASK);
|
||||
if (le16_to_cpu(d->nds_icon_title.version) >= NDS_ICON_VERSION_DSi) {
|
||||
// DSi-exclusive game.
|
||||
idx |= (1U << 2);
|
||||
}
|
||||
|
||||
return sysNames[idx];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a bitfield of image types this class can retrieve.
|
||||
* @return Bitfield of supported image types. (ImageTypesBF)
|
||||
*/
|
||||
uint32_t NintendoDS_BNR::supportedImageTypes_static(void)
|
||||
{
|
||||
return IMGBF_INT_ICON;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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> NintendoDS_BNR::supportedImageSizes_static(ImageType imageType)
|
||||
{
|
||||
ASSERT_supportedImageSizes(imageType);
|
||||
|
||||
if (imageType != IMG_INT_ICON) {
|
||||
// Only icons are supported.
|
||||
return {};
|
||||
}
|
||||
|
||||
return {{nullptr, 32, 32, 0}};
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 NintendoDS_BNR::imgpf(ImageType imageType) const
|
||||
{
|
||||
ASSERT_imgpf(imageType);
|
||||
|
||||
uint32_t ret = 0;
|
||||
switch (imageType) {
|
||||
case IMG_INT_ICON:
|
||||
// Use nearest-neighbor scaling.
|
||||
ret = IMGPF_RESCALE_NEAREST;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load field data.
|
||||
* Called by RomData::fields() if the field data hasn't been loaded yet.
|
||||
* @return Number of fields read on success; negative POSIX error code on error.
|
||||
*/
|
||||
int NintendoDS_BNR::loadFieldData(void)
|
||||
{
|
||||
RP_D(NintendoDS_BNR);
|
||||
if (!d->fields.empty()) {
|
||||
// Field data *has* been loaded...
|
||||
return 0;
|
||||
} else if (!d->file) {
|
||||
// No file.
|
||||
// A closed file is OK, since we already loaded the data.
|
||||
return -EBADF;
|
||||
} else if (!d->isValid) {
|
||||
// Banner file isn't valid.
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
// NOTE: Using "Nintendo3DS" as the localization context.
|
||||
|
||||
// Parse the icon/title data.
|
||||
const NDS_IconTitleData *const nds_icon_title = &d->nds_icon_title;
|
||||
d->fields.reserve(1); // Maximum of 1 field.
|
||||
|
||||
// Full title: Check if English is valid.
|
||||
// If it is, we'll de-duplicate fields.
|
||||
const bool dedupe_titles = (nds_icon_title->title[NDS_LANG_ENGLISH][0] != cpu_to_le16(0));
|
||||
|
||||
// Full title field.
|
||||
RomFields::StringMultiMap_t *const pMap_full_title = new RomFields::StringMultiMap_t();
|
||||
const NDS_Language_ID maxID = d->getMaxSupportedLanguage(
|
||||
le16_to_cpu(nds_icon_title->version));
|
||||
for (int langID = 0; langID <= maxID; langID++) {
|
||||
// Check for empty strings first.
|
||||
if (nds_icon_title->title[langID][0] == 0) {
|
||||
// Strings are empty.
|
||||
continue;
|
||||
}
|
||||
|
||||
if (dedupe_titles && langID != NDS_LANG_ENGLISH) {
|
||||
// Check if the title matches English.
|
||||
// NOTE: Not converting to host-endian first, since
|
||||
// u16_strncmp() checks for equality and for 0.
|
||||
if (!u16_strncmp(nds_icon_title->title[langID],
|
||||
nds_icon_title->title[NDS_LANG_ENGLISH],
|
||||
ARRAY_SIZE(nds_icon_title->title[NDS_LANG_ENGLISH])))
|
||||
{
|
||||
// Full title field matches English.
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
const uint32_t lc = NintendoLanguage::getNDSLanguageCode(langID, maxID);
|
||||
assert(lc != 0);
|
||||
if (lc == 0)
|
||||
continue;
|
||||
|
||||
if (nds_icon_title->title[langID][0] != cpu_to_le16('\0')) {
|
||||
pMap_full_title->emplace(lc, utf16le_to_utf8(
|
||||
nds_icon_title->title[langID],
|
||||
ARRAY_SIZE(nds_icon_title->title[langID])));
|
||||
}
|
||||
}
|
||||
|
||||
if (!pMap_full_title->empty()) {
|
||||
const uint32_t def_lc = d->getDefaultLC();
|
||||
d->fields.addField_string_multi(C_("Nintendo", "Full Title"), pMap_full_title, def_lc);
|
||||
} else {
|
||||
delete pMap_full_title;
|
||||
}
|
||||
|
||||
// Finished reading the field data.
|
||||
return static_cast<int>(d->fields.count());
|
||||
}
|
||||
|
||||
/**
|
||||
* Load metadata properties.
|
||||
* Called by RomData::metaData() if the metadata hasn't been loaded yet.
|
||||
* @return Number of metadata properties read on success; negative POSIX error code on error.
|
||||
*/
|
||||
int NintendoDS_BNR::loadMetaData(void)
|
||||
{
|
||||
RP_D(NintendoDS_BNR);
|
||||
if (d->metaData != nullptr) {
|
||||
// Metadata *has* been loaded...
|
||||
return 0;
|
||||
} else if (!d->file) {
|
||||
// No file.
|
||||
// A closed file is OK, since we already loaded the data.
|
||||
return -EBADF;
|
||||
} else if (!d->isValid) {
|
||||
// SMDH file isn't valid.
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
// Parse the icon/title data.
|
||||
const NDS_IconTitleData *const nds_icon_title = &d->nds_icon_title;
|
||||
|
||||
// Create the metadata object.
|
||||
d->metaData = new RomMetaData();
|
||||
d->metaData->reserve(1); // Maximum of 1 metadata property.
|
||||
|
||||
// Full title
|
||||
// TODO: Use the default LC if it's available.
|
||||
// For now, default to English.
|
||||
if (nds_icon_title->title[NDS_LANG_ENGLISH][0] != cpu_to_le16(0)) {
|
||||
string s_title = utf16le_to_utf8(nds_icon_title->title[NDS_LANG_ENGLISH],
|
||||
ARRAY_SIZE(nds_icon_title->title[NDS_LANG_ENGLISH]));
|
||||
|
||||
// Adjust the title based on the number of lines.
|
||||
const size_t nl_1 = s_title.find('\n');
|
||||
if (nl_1 != string::npos) {
|
||||
// Found the first newline.
|
||||
const size_t nl_2 = s_title.find('\n', nl_1+1);
|
||||
if (nl_2 != string::npos) {
|
||||
// Found the second newline.
|
||||
// Change the first to a space, and remove the third line.
|
||||
s_title[nl_1] = ' ';
|
||||
s_title.resize(nl_2);
|
||||
} else {
|
||||
// Only two lines.
|
||||
// Remove the second line.
|
||||
s_title.resize(nl_1);
|
||||
}
|
||||
}
|
||||
|
||||
d->metaData->addMetaData_string(Property::Title, s_title);
|
||||
}
|
||||
|
||||
// Finished reading the metadata.
|
||||
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 NintendoDS_BNR::loadInternalImage(ImageType imageType, rp_image_const_ptr &pImage)
|
||||
{
|
||||
ASSERT_loadInternalImage(imageType, pImage);
|
||||
RP_D(NintendoDS_BNR);
|
||||
ROMDATA_loadInternalImage_single(
|
||||
IMG_INT_ICON, // ourImageType
|
||||
d->file, // file
|
||||
d->isValid, // isValid
|
||||
0, // romType (not used here)
|
||||
d->icon_first_frame, // imgCache
|
||||
d->loadIcon); // func
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the animated icon data.
|
||||
*
|
||||
* Check imgpf for IMGPF_ICON_ANIMATED first to see if this
|
||||
* object has an animated icon.
|
||||
*
|
||||
* @return Animated icon data, or nullptr if no animated icon is present.
|
||||
*/
|
||||
IconAnimDataConstPtr NintendoDS_BNR::iconAnimData(void) const
|
||||
{
|
||||
RP_D(const NintendoDS_BNR);
|
||||
if (!d->iconAnimData) {
|
||||
// Load the icon.
|
||||
if (!const_cast<NintendoDS_BNR_Private*>(d)->loadIcon()) {
|
||||
// Error loading the icon.
|
||||
return nullptr;
|
||||
}
|
||||
if (!d->iconAnimData) {
|
||||
// Still no icon...
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
if (d->iconAnimData->count <= 1) {
|
||||
// Not an animated icon.
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Return the icon animation data.
|
||||
return d->iconAnimData;
|
||||
}
|
||||
|
||||
}
|
25
src/libromdata/Handheld/NintendoDS_BNR.hpp
Normal file
25
src/libromdata/Handheld/NintendoDS_BNR.hpp
Normal file
@ -0,0 +1,25 @@
|
||||
/***************************************************************************
|
||||
* ROM Properties Page shell extension. (libromdata) *
|
||||
* NintendoDS_BNR.hpp: Nintendo DS icon/title data reader. *
|
||||
* Handles BNR files and icon/title sections. *
|
||||
* *
|
||||
* Copyright (c) 2016-2024 by David Korth. *
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later *
|
||||
***************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "librpbase/RomData.hpp"
|
||||
|
||||
namespace LibRomData {
|
||||
|
||||
class NintendoDS_BNR_Private;
|
||||
ROMDATA_DECL_BEGIN(NintendoDS_BNR)
|
||||
ROMDATA_DECL_METADATA()
|
||||
ROMDATA_DECL_IMGSUPPORT()
|
||||
ROMDATA_DECL_IMGPF()
|
||||
ROMDATA_DECL_IMGINT()
|
||||
ROMDATA_DECL_ICONANIM()
|
||||
ROMDATA_DECL_END()
|
||||
|
||||
}
|
@ -14,6 +14,9 @@
|
||||
// C++ includes
|
||||
#include <vector>
|
||||
|
||||
// Icon/title data
|
||||
#include "NintendoDS_BNR.hpp"
|
||||
|
||||
// Other rom-properties libraries
|
||||
#include "librpbase/RomData_p.hpp"
|
||||
#include "librpbase/RomFields.hpp"
|
||||
@ -28,7 +31,7 @@ class NintendoDSPrivate final : public LibRpBase::RomDataPrivate
|
||||
{
|
||||
public:
|
||||
NintendoDSPrivate(const LibRpFile::IRpFilePtr &file, bool cia);
|
||||
~NintendoDSPrivate() final = default;
|
||||
~NintendoDSPrivate() final;;
|
||||
|
||||
private:
|
||||
typedef LibRpBase::RomDataPrivate super;
|
||||
@ -40,16 +43,6 @@ public:
|
||||
static const char *const mimeTypes[];
|
||||
static const LibRpBase::RomDataInfo romDataInfo;
|
||||
|
||||
public:
|
||||
// Animated icon data
|
||||
// This class owns all of the icons in here, so we
|
||||
// must delete all of them.
|
||||
LibRpBase::IconAnimDataPtr iconAnimData;
|
||||
|
||||
// Pointer to the first frame in iconAnimData.
|
||||
// Used when showing a static icon.
|
||||
rp_image_const_ptr icon_first_frame;
|
||||
|
||||
public:
|
||||
/** RomFields **/
|
||||
|
||||
@ -105,6 +98,9 @@ public:
|
||||
// NOTE: Must be byteswapped on access.
|
||||
NDS_RomHeader romHeader;
|
||||
|
||||
// Icon/title data
|
||||
NintendoDS_BNR *nds_icon_title;
|
||||
|
||||
// Cached ROM size to determine trimmed or untrimmed.
|
||||
off64_t romSize;
|
||||
|
||||
@ -112,10 +108,11 @@ public:
|
||||
uint32_t secData;
|
||||
NDS_SecureArea secArea;
|
||||
|
||||
// Icon/title data from the ROM header.
|
||||
// NOTE: Must be byteswapped on access.
|
||||
NDS_IconTitleData nds_icon_title;
|
||||
bool nds_icon_title_loaded;
|
||||
/**
|
||||
* Load the icon/title data.
|
||||
* @return 0 on success; negative POSIX error code on error.
|
||||
*/
|
||||
int loadIconTitleData(void);
|
||||
|
||||
// If true, this is an SRL in a 3DS CIA.
|
||||
// Some fields shouldn't be displayed.
|
||||
@ -125,18 +122,6 @@ public:
|
||||
int fieldIdx_secData; // "Security Data" (RFT_BITFIELD)
|
||||
int fieldIdx_secArea; // "Secure Area" (RFT_STRING)
|
||||
|
||||
/**
|
||||
* Load the icon/title data.
|
||||
* @return 0 on success; negative POSIX error code on error.
|
||||
*/
|
||||
int loadIconTitleData(void);
|
||||
|
||||
/**
|
||||
* Load the ROM image's icon.
|
||||
* @return Icon, or nullptr on error.
|
||||
*/
|
||||
rp_image_const_ptr loadIcon(void);
|
||||
|
||||
/**
|
||||
* Get the title index.
|
||||
* The title that most closely matches the
|
||||
@ -228,25 +213,6 @@ public:
|
||||
* @return DSi flags string vector.
|
||||
*/
|
||||
static LibRpBase::RomFields::ListData_t *getDSiFlagsStringVector(void);
|
||||
|
||||
/**
|
||||
* Get the maximum supported language for an icon/title version.
|
||||
* @param version Icon/title version.
|
||||
* @return Maximum supported language.
|
||||
*/
|
||||
static NDS_Language_ID getMaxSupportedLanguage(uint16_t version);
|
||||
|
||||
/**
|
||||
* Get the language ID to use for the title fields.
|
||||
* @return NDS language ID.
|
||||
*/
|
||||
NDS_Language_ID getLanguageID(void) const;
|
||||
|
||||
/**
|
||||
* Get the default language code for the multi-string fields.
|
||||
* @return Language code, e.g. 'en' or 'es'.
|
||||
*/
|
||||
uint32_t getDefaultLC(void) const;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -82,6 +82,7 @@ using std::vector;
|
||||
#include "Handheld/Nintendo3DSFirm.hpp"
|
||||
#include "Handheld/Nintendo3DS_SMDH.hpp"
|
||||
#include "Handheld/NintendoDS.hpp"
|
||||
#include "Handheld/NintendoDS_BNR.hpp"
|
||||
#include "Handheld/PalmOS.hpp"
|
||||
#include "Handheld/PokemonMini.hpp"
|
||||
#include "Handheld/PSP.hpp"
|
||||
@ -318,6 +319,10 @@ static const RomDataFns romDataFns_header[] = {
|
||||
// only validated by file size. (no magic numbers)
|
||||
GetRomDataFns(CBMDOS, ATTR_HAS_METADATA),
|
||||
|
||||
// Handhelds: NintendoDS_BNR
|
||||
// No magic number, but it has CRC16s.
|
||||
GetRomDataFns(NintendoDS_BNR, ATTR_HAS_THUMBNAIL | ATTR_HAS_METADATA),
|
||||
|
||||
// Headers with non-zero addresses.
|
||||
GetRomDataFns_addr(Sega8Bit, ATTR_HAS_METADATA, 0x7FE0, 0x20),
|
||||
GetRomDataFns_addr(PokemonMini, ATTR_HAS_METADATA, 0x2100, 0xD0),
|
||||
|
@ -100,6 +100,7 @@ application/x-ctr-smdh # Nintendo3DS_SMDH (Citra)
|
||||
application/x-nintendo-ds-rom # NintendoDS
|
||||
application/vnd.nintendo.nitro.rom # NintendoDS
|
||||
application/x-nintendo-dsi-rom # NintendoDS
|
||||
application/x-nintendo-ds-bnr # NintendoDS_BNR
|
||||
application/vnd.palm # Palm OS
|
||||
application/x-mobipocket-ebook # Palm OS (may take precedence on some systems)
|
||||
application/x-psp-ciso-image # PSP
|
||||
|
@ -806,6 +806,23 @@
|
||||
</magic>
|
||||
</mime-type>
|
||||
|
||||
<!-- NintendoDS_BNR -->
|
||||
<!-- FIXME: The magic number is too generic... -->
|
||||
<!--
|
||||
<mime-type type="application/x-nintendo-ds-bnr">
|
||||
<comment>Nintendo DS icon/title data</comment>
|
||||
<sub-class-of type="image/x-generic"/>
|
||||
<generic-icon name="image-x-generic"/>
|
||||
<glob pattern="*.bnr"/>
|
||||
<magic priority="50">
|
||||
<match value="0x0001" type="little16" offset="0"/>
|
||||
<match value="0x0002" type="little16" offset="0"/>
|
||||
<match value="0x0003" type="little16" offset="0"/>
|
||||
<match value="0x0103" type="little16" offset="0"/>
|
||||
</magic>
|
||||
</mime-type>
|
||||
-->
|
||||
|
||||
<!-- PSP -->
|
||||
<mime-type type="application/x-psp-ciso-image">
|
||||
<comment>PlayStation Portable CISO disc image</comment>
|
||||
|
Loading…
Reference in New Issue
Block a user