Skip to content

fix: update NACP language handling and sanitization logic - #348

Merged
NaGaa95 merged 1 commit into
NaGaa95:masterfrom
Insektaure:fix-title-ncap-indexing
Aug 17, 2026
Merged

fix: update NACP language handling and sanitization logic#348
NaGaa95 merged 1 commit into
NaGaa95:masterfrom
Insektaure:fix-title-ncap-indexing

Conversation

@Insektaure

Copy link
Copy Markdown
Contributor

Fix issue #347

Since commit db06528 ("handle localized game titles in export filenames"), a large number of games show up over MTP and FTP as a bare [0100XXXXXXXXXXXX] with no title, and their exported NSP/NCA/XCI filenames lose the title too.

The root cause is that GetEnglishTitleName() indexes the NACP language array with SetLanguage enum values, which is the wrong index space.

Two follow-on issues made the failure total instead of graceful: there was no fallback when the English name could not be resolved, and the browse listing was made to reuse the export filename, so a filesystem safety option started dictating how titles are displayed.

NACP language entries are not indexed by SetLanguage

NacpStruct::lang[] uses the NACP language ordering, not the SetLanguage ordering.
libnx keeps an explicit translation table for this in nx/source/runtime/nacp.c:

static u32 g_nacpLanguageTable[18] = {
    [SetLanguage_JA]   = 2,
    [SetLanguage_ENUS] = 0,
    [SetLanguage_ENGB] = 1,
    ...
    [SetLanguage_KO]   = 12,
};

The old code indexed the array directly with the enum:

for (const auto language : {SetLanguage_ENUS, SetLanguage_ENGB}) {
    const auto& entry = nacp.lang[language];

SetLanguage_ENUS is 1 and SetLanguage_ENGB is 12, so it actually read:

Intended Index read Language actually read
AmericanEnglish lang[1] BritishEnglish
BritishEnglish lang[12] Korean

AmericanEnglish, slot 0, was never checked.
That is the slot populated by most Nintendo of America releases (Dragon Quest for example), so those titles resolved to an empty English name.
Where a Korean entry happened to exist, the function returned Korean text, which the ASCII sanitiser then reduced to underscores.

Slot 0 is also where NormalizeNacpLangData() stores its fallback entry when it decompresses the newer NACP title format, so titles using that format were affected as well.

sphaira/source/title_info.cpp, GetEnglishTitleName() now uses the NACP ordering, AmericanEnglish first:

enum NacpLanguage {
    NacpLanguage_AmericanEnglish = 0,
    NacpLanguage_BritishEnglish = 1,
};

for (const auto language : {NacpLanguage_AmericanEnglish, NacpLanguage_BritishEnglish}) {

No fallback when the English name is unavailable

MakeExportTitleName() used the English name and nothing else when "Fix export filenames" was on, which is the default:

An empty result means the caller emits a title ID only.

MakeExportTitleName() now degrades instead of giving up. The sanitising step is factored into a lambda and applied:

  1. English name, ASCII only.
  2. Displayed (localised) name, ASCII only.
  3. Title ID, only if neither candidate has any ASCII safe character left.

With the option off, behaviour is unchanged: the displayed name with Unicode preserved.

This keeps the documented promise of the option, that filenames stay ASCII safe, while no longer throwing away a usable name.

Browse listing reused the export filename

commit db06528 made the MTP/FTP folder name and the exported NSP filename the same string.

Those serve different purposes.

The folder name is a browse convenience, the filename is what lands on the host filesystem.

Tying the folder name to the ASCII sanitiser meant any title with non ASCII characters, CJK text, a leading decorative character, or just a trademark or accented character, was displayed mangled into underscores or dropped to a bare title ID.

UTF-8 truncation

The old code truncated with a byte precision specifier:

std::snprintf(name, sizeof(name), "%.*s [%016lX]", name_max, entry.export_name.c_str(), entry.app_id);

That can cut a UTF-8 sequence in half. It was harmless while every name was forced to ASCII, but now that real titles reach the listing, a trailing partial sequence can make MTP clients bug or drop the entry.

A TruncateUtf8() helper was added to the anonymous namespace in devoptab_game.cpp.

It walks back off any continuation bytes so the name is always cut on a character boundary.

@NaGaa95
NaGaa95 merged commit a66eeb8 into NaGaa95:master Aug 17, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants