/* * nacp.c * * Copyright (c) 2020-2022, DarkMatterCore . * * This file is part of nxdumptool (https://github.com/DarkMatterCore/nxdumptool). * * nxdumptool is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * nxdumptool is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "nxdt_utils.h" #include "nacp.h" #include "title.h" /* Helper macros. */ #define NACP_ADD_FMT_STR_T1(fmt, ...) utilsAppendFormattedStringToBuffer(&xml_buf, &xml_buf_size, fmt, ##__VA_ARGS__) #define NACP_ADD_FMT_STR_T2(fmt, ...) utilsAppendFormattedStringToBuffer(xml_buf, xml_buf_size, fmt, ##__VA_ARGS__) #define NACP_ADD_STR(tag_name, value) nacpAddStringFieldToAuthoringToolXml(&xml_buf, &xml_buf_size, tag_name, value) #define NACP_ADD_ENUM(tag_name, value, str_func) nacpAddEnumFieldToAuthoringToolXml(&xml_buf, &xml_buf_size, tag_name, value, \ &str_func) #define NACP_ADD_BITFLAG(tag_name, flag, flag_width, max_flag_idx, str_func, allow_empty_str) nacpAddBitflagFieldToAuthoringToolXml(&xml_buf, &xml_buf_size, tag_name, flag, \ flag_width, max_flag_idx, &(str_func), \ allow_empty_str) #define NACP_ADD_U16(tag_name, value, hex, prefix) nacpAddU16FieldToAuthoringToolXml(&xml_buf, &xml_buf_size, tag_name, value, hex, \ prefix) #define NACP_ADD_U32(tag_name, value, hex, prefix) nacpAddU32FieldToAuthoringToolXml(&xml_buf, &xml_buf_size, tag_name, value, hex, \ prefix) #define NACP_ADD_U64(tag_name, value, hex, prefix) nacpAddU64FieldToAuthoringToolXml(&xml_buf, &xml_buf_size, tag_name, value, hex, \ prefix) /* Type definitions. */ typedef const char *(*NacpStringFunction)(u8 value); /* Used while adding fields to the AuthoringTool-like XML. */ /* Global variables. */ static const char *g_unknownString = "Unknown"; static const char *g_nacpLanguageStrings[NacpLanguage_Count] = { "AmericanEnglish", "BritishEnglish", "Japanese", "French", "German", "LatinAmericanSpanish", "Spanish", "Italian", "Dutch", "CanadianFrench", "Portuguese", "Russian", "Korean", "TraditionalChinese", "SimplifiedChinese", "BrazilianPortuguese" }; static const char *g_nacpStartupUserAccountStrings[NacpStartupUserAccount_Count] = { "None", "Required", "RequiredWithNetworkServiceAccountAvailable" }; static const char *g_nacpUserAccountSwitchLockStrings[NacpUserAccountSwitchLock_Count] = { "Disable", "Enable" }; static const char *g_nacpAddOnContentRegistrationTypeStrings[NacpAddOnContentRegistrationType_Count] = { "AllOnLaunch", "OnDemand" }; static const char *g_nacpAttributeStrings[NacpAttribute_Count] = { "Demo", "RetailInteractiveDisplay", "DownloadPlay" }; static const char *g_nacpParentalControlStrings[NacpParentalControl_Count] = { "FreeCommunication" }; static const char *g_nacpScreenshotStrings[NacpScreenshot_Count] = { "Allow", "Deny" }; static const char *g_nacpVideoCaptureStrings[NacpVideoCapture_Count] = { "Disable", "Manual", "Enable" }; static const char *g_nacpDataLossConfirmationStrings[NacpDataLossConfirmation_Count] = { "None", "Required" }; static const char *g_nacpPlayLogPolicyStrings[NacpPlayLogPolicy_Count] = { "Open", "LogOnly", "None", "Closed" }; static const char *g_nacpRatingAgeOrganizationStrings[NacpRatingAgeOrganization_Count] = { "CERO", "GRACGCRB", "GSRMR", "ESRB", "ClassInd", "USK", "PEGI", "PEGIPortugal", "PEGIBBFC", "Russian", "ACB", "OFLC", "IARCGeneric" }; static const char *g_nacpLogoTypeStrings[NacpLogoType_Count] = { "LicensedByNintendo", "DistributedByNintendo", "Nintendo" }; static const char *g_nacpLogoHandlingStrings[NacpLogoHandling_Count] = { "Auto", "Manual" }; static const char *g_nacpRuntimeAddOnContentInstallStrings[NacpRuntimeAddOnContentInstall_Count] = { "Deny", "AllowAppend", "AllowAppendButDontDownloadWhenUsingNetwork" }; static const char *g_nacpRuntimeParameterDeliveryStrings[NacpRuntimeParameterDelivery_Count] = { "Always", "AlwaysIfUserStateMatched", "OnRestart" }; static const char *g_nacpAppropriateAgeForChinaStrings[NacpAppropriateAgeForChina_Count] = { "None", "Age8", "Age12", "Age16" }; static const char *g_nacpUndecidedParameter75b8bStrings[NacpUndecidedParameter75b8b_Count] = { "a", "b" }; static const char *g_nacpCrashReportStrings[NacpCrashReport_Count] = { "Deny", "Allow" }; static const char *g_nacpHdcpStrings[NacpHdcp_Count] = { "None", "Required" }; static const char *g_nacpStartupUserAccountOptionStrings[NacpStartupUserAccountOption_Count] = { "IsOptional" }; static const char *g_nacpRuntimeUpgradeStrings[NacpRuntimeUpgrade_Count] = { "Deny", "Allow" }; static const char *g_nacpPlayLogQueryCapabilityStrings[NacpPlayLogQueryCapability_Count] = { "None", "WhiteList", "All" }; static const char *g_nacpRepairStrings[NacpRepair_Count] = { "SuppressGameCardAccess" }; static const char *g_nacpRequiredNetworkServiceLicenseOnLaunchStrings[NacpRequiredNetworkServiceLicenseOnLaunch_Count] = { "Common" }; static const char *g_nacpCrashScreenshotForProdStrings[NacpCrashScreenshotForProd_Count] = { "Deny", "Allow" }; static const char *g_nacpCrashScreenshotForDevStrings[NacpCrashScreenshotForDev_Count] = { "Deny", "Allow" }; static const char *g_nacpContentsAvailabilityTransitionPolicyStrings[NacpContentsAvailabilityTransitionPolicy_Count] = { "NoPolicy", "Stable", "Changeable" }; /* Function prototypes. */ NX_INLINE bool nacpCheckBitflagField(const void *flag, u8 flag_bitcount, u8 idx); static bool nacpAddStringFieldToAuthoringToolXml(char **xml_buf, u64 *xml_buf_size, const char *tag_name, const char *value); static bool nacpAddEnumFieldToAuthoringToolXml(char **xml_buf, u64 *xml_buf_size, const char *tag_name, u8 value, NacpStringFunction str_func); static bool nacpAddBitflagFieldToAuthoringToolXml(char **xml_buf, u64 *xml_buf_size, const char *tag_name, const void *flag, u8 flag_width, u8 max_flag_idx, NacpStringFunction str_func, bool allow_empty_str); static bool nacpAddU16FieldToAuthoringToolXml(char **xml_buf, u64 *xml_buf_size, const char *tag_name, u16 value, bool hex, bool prefix); static bool nacpAddU32FieldToAuthoringToolXml(char **xml_buf, u64 *xml_buf_size, const char *tag_name, u32 value, bool hex, bool prefix); static bool nacpAddU64FieldToAuthoringToolXml(char **xml_buf, u64 *xml_buf_size, const char *tag_name, u64 value, bool hex, bool prefix); bool nacpInitializeContext(NacpContext *out, NcaContext *nca_ctx) { if (!out || !nca_ctx || !*(nca_ctx->content_id_str) || nca_ctx->content_type != NcmContentType_Control || nca_ctx->content_size < NCA_FULL_HEADER_LENGTH || \ (nca_ctx->storage_id != NcmStorageId_GameCard && !nca_ctx->ncm_storage) || (nca_ctx->storage_id == NcmStorageId_GameCard && !nca_ctx->gamecard_offset) || \ nca_ctx->header.content_type != NcaContentType_Control || nca_ctx->content_type_ctx || !out) { LOG_MSG_ERROR("Invalid parameters!"); return false; } const char *language_str = NULL; char icon_path[0x80] = {0}; RomFileSystemFileEntry *icon_entry = NULL; NacpIconContext *tmp_icon_ctx = NULL; bool success = false; /* Free output context beforehand. */ nacpFreeContext(out); /* Initialize RomFS context. */ if (!romfsInitializeContext(&(out->romfs_ctx), &(nca_ctx->fs_ctx[0]), NULL)) { LOG_MSG_ERROR("Failed to initialize RomFS context!"); goto end; } /* Retrieve RomFS file entry for 'control.nacp'. */ if (!(out->romfs_file_entry = romfsGetFileEntryByPath(&(out->romfs_ctx), "/control.nacp"))) { LOG_MSG_ERROR("Failed to retrieve file entry for \"control.nacp\" from RomFS!"); goto end; } LOG_MSG_INFO("Found 'control.nacp' entry in Control NCA \"%s\".", nca_ctx->content_id_str); /* Verify NACP size. */ if (out->romfs_file_entry->size != sizeof(_NacpStruct)) { LOG_MSG_ERROR("Invalid NACP size!"); goto end; } /* Allocate memory for the NACP data. */ if (!(out->data = malloc(sizeof(_NacpStruct)))) { LOG_MSG_ERROR("Failed to allocate memory for the NACP data!"); goto end; } /* Read NACP data into memory buffer. */ if (!romfsReadFileEntryData(&(out->romfs_ctx), out->romfs_file_entry, out->data, sizeof(_NacpStruct), 0)) { LOG_MSG_ERROR("Failed to read NACP data!"); goto end; } /* Calculate SHA-256 checksum for the whole NACP. */ sha256CalculateHash(out->data_hash, out->data, sizeof(_NacpStruct)); /* Retrieve NACP icon data. */ for(u8 i = 0; i < NacpSupportedLanguage_Count; i++) { NacpIconContext *icon_ctx = NULL; /* Get language string. */ language_str = nacpGetLanguageString(i); /* Check if the current language is supported. */ if (!nacpCheckBitflagField(&(out->data->supported_language), sizeof(out->data->supported_language) * 8, i)) { LOG_MSG_DEBUG("\"%s\" language not supported (flag 0x%08X, index %u).", language_str, out->data->supported_language, i); continue; } /* Generate icon path. */ sprintf(icon_path, "/icon_%s.dat", language_str); /* Retrieve RomFS file entry for this icon. */ if (!(icon_entry = romfsGetFileEntryByPath(&(out->romfs_ctx), icon_path))) { LOG_MSG_DEBUG("\"%s\" file entry not found (flag 0x%08X, index %u).", icon_path, out->data->supported_language, i); continue; } /* Check icon size. */ if (!icon_entry->size || icon_entry->size > NACP_MAX_ICON_SIZE) { LOG_MSG_ERROR("Invalid NACP icon size!"); goto end; } /* Reallocate icon context buffer. */ if (!(tmp_icon_ctx = realloc(out->icon_ctx, (out->icon_count + 1) * sizeof(NacpIconContext)))) { LOG_MSG_ERROR("Failed to reallocate NACP icon context buffer!"); goto end; } out->icon_ctx = tmp_icon_ctx; tmp_icon_ctx = NULL; icon_ctx = &(out->icon_ctx[out->icon_count]); memset(icon_ctx, 0, sizeof(NacpIconContext)); /* Allocate memory for this icon data. */ if (!(icon_ctx->icon_data = malloc(icon_entry->size))) { LOG_MSG_ERROR("Failed to allocate memory for NACP icon data!"); goto end; } /* Read icon data. */ if (!romfsReadFileEntryData(&(out->romfs_ctx), icon_entry, icon_ctx->icon_data, icon_entry->size, 0)) { LOG_MSG_ERROR("Failed to read NACP icon data!"); goto end; } /* Fill icon context. */ icon_ctx->language = i; icon_ctx->icon_size = icon_entry->size; /* Update icon count. */ out->icon_count++; } /* Update NCA context pointer in output context. */ out->nca_ctx = nca_ctx; /* Update content type context info in NCA context. */ nca_ctx->content_type_ctx = out; nca_ctx->content_type_ctx_patch = false; success = true; end: if (!success) nacpFreeContext(out); return success; } bool nacpGenerateNcaPatch(NacpContext *nacp_ctx, bool patch_sua, bool patch_screenshot, bool patch_video_capture, bool patch_hdcp) { if (!nacpIsValidContext(nacp_ctx)) { LOG_MSG_ERROR("Invalid parameters!"); return false; } _NacpStruct *data = nacp_ctx->data; u8 nacp_hash[SHA256_HASH_SIZE] = {0}; /* Check if we're not patching anything. */ if (!patch_sua && !patch_screenshot && !patch_video_capture && !patch_hdcp) return true; /* Patch StartupUserAccount, StartupUserAccountOption and UserAccountSwitchLock. */ if (patch_sua) { data->startup_user_account = NacpStartupUserAccount_None; data->startup_user_account_option &= ~NacpStartupUserAccountOption_IsOptional; data->user_account_switch_lock = NacpUserAccountSwitchLock_Disable; } /* Patch Screenshot. */ if (patch_screenshot) data->screenshot = NacpScreenshot_Allow; /* Patch VideoCapture. */ if (patch_video_capture) data->video_capture = NacpVideoCapture_Enable; /* Patch Hdcp. */ if (patch_hdcp) data->hdcp = NacpHdcp_None; /* Check if we really need to generate this patch. */ sha256CalculateHash(nacp_hash, data, sizeof(_NacpStruct)); if (!memcmp(nacp_hash, nacp_ctx->data_hash, sizeof(nacp_hash))) { LOG_MSG_INFO("Skipping NACP patching - no flags have changed."); return true; } /* Generate RomFS file entry patch. */ if (!romfsGenerateFileEntryPatch(&(nacp_ctx->romfs_ctx), nacp_ctx->romfs_file_entry, data, sizeof(_NacpStruct), 0, &(nacp_ctx->nca_patch))) { LOG_MSG_ERROR("Failed to generate RomFS file entry patch!"); return false; } /* Update NCA content type context patch status. */ nacp_ctx->nca_ctx->content_type_ctx_patch = true; return true; } void nacpWriteNcaPatch(NacpContext *nacp_ctx, void *buf, u64 buf_size, u64 buf_offset) { NcaContext *nca_ctx = NULL; RomFileSystemFileEntryPatch *nca_patch = (nacp_ctx ? &(nacp_ctx->nca_patch) : NULL); /* Using nacpIsValidContext() here would probably take up precious CPU cycles. */ if (!nca_patch || nca_patch->written || !(nca_ctx = nacp_ctx->nca_ctx) || nca_ctx->content_type != NcmContentType_Control || !nca_ctx->content_type_ctx_patch) return; /* Attempt to write RomFS file entry patch. */ romfsWriteFileEntryPatchToMemoryBuffer(&(nacp_ctx->romfs_ctx), nca_patch, buf, buf_size, buf_offset); /* Check if we need to update the NCA content type context patch status. */ if (nca_patch->written) { nca_ctx->content_type_ctx_patch = false; LOG_MSG_INFO("NACP RomFS file entry patch successfully written to NCA \"%s\"!", nca_ctx->content_id_str); } } bool nacpGenerateAuthoringToolXml(NacpContext *nacp_ctx, u32 version, u32 required_system_version) { if (!nacpIsValidContext(nacp_ctx)) { LOG_MSG_ERROR("Invalid parameters!"); return false; } _NacpStruct *nacp = nacp_ctx->data; Version app_ver = { .value = version }; u8 i = 0, count = 0; char *xml_buf = NULL; u64 xml_buf_size = 0; u8 icon_hash[SHA256_HASH_SIZE] = {0}; char icon_hash_str[SHA256_HASH_SIZE + 1] = {0}; u8 null_key[0x10] = {0}; char key_str[0x21] = {0}; bool ndcc_sgc_available = false, ndcc_rgc_available = false; NacpNeighborDetectionClientConfiguration *ndcc = &(nacp->neighbor_detection_client_configuration); NacpRequiredAddOnContentsSetBinaryDescriptor *raocsbd = &(nacp->required_add_on_contents_set_binary_descriptor); bool raocsbd_available = false; bool alrv_available = false; bool success = false; /* Free AuthoringTool-like XML data if needed. */ if (nacp_ctx->authoring_tool_xml) free(nacp_ctx->authoring_tool_xml); nacp_ctx->authoring_tool_xml = NULL; nacp_ctx->authoring_tool_xml_size = 0; if (!NACP_ADD_FMT_STR_T1("\n" \ "\n")) goto end; /* Title. */ for(i = 0, count = 0; i < NacpLanguage_Count; i++) { NacpTitle *title = &(nacp->title[i]); if (!*(title->name) || !*(title->publisher)) continue; if (!NACP_ADD_FMT_STR_T1(" \n" \ " <Language>%s</Language>\n" \ " <Name>%s</Name>\n" \ " <Publisher>%s</Publisher>\n" \ " \n", \ nacpGetLanguageString(i), \ title->name, \ title->publisher)) goto end; count++; } if (!count && !NACP_ADD_FMT_STR_T1(" \n")) goto end; /* Isbn. */ if (!NACP_ADD_STR("Isbn", nacp->isbn)) goto end; /* StartupUserAccount. */ if (!NACP_ADD_ENUM("StartupUserAccount", nacp->startup_user_account, nacpGetStartupUserAccountString)) goto end; /* StartupUserAccountOption. */ if (!NACP_ADD_BITFLAG("StartupUserAccountOption", &(nacp->startup_user_account_option), sizeof(nacp->startup_user_account_option), NacpStartupUserAccountOption_Count, \ nacpGetStartupUserAccountOptionString, true)) goto end; /* UserAccountSwitchLock. */ if (!NACP_ADD_ENUM("UserAccountSwitchLock", nacp->user_account_switch_lock, nacpGetUserAccountSwitchLockString)) goto end; /* Attribute. */ if (!NACP_ADD_BITFLAG("Attribute", &(nacp->attribute), sizeof(nacp->attribute), NacpAttribute_Count, nacpGetAttributeString, false)) goto end; /* ParentalControl. */ if (!NACP_ADD_BITFLAG("ParentalControl", &(nacp->parental_control), sizeof(nacp->parental_control), NacpParentalControl_Count, nacpGetParentalControlString, false)) goto end; /* SupportedLanguage. */ if (!NACP_ADD_BITFLAG("SupportedLanguage", &(nacp->supported_language), sizeof(nacp->supported_language), NacpSupportedLanguage_Count, nacpGetLanguageString, false)) goto end; /* Screenshot. */ if (!NACP_ADD_ENUM("Screenshot", nacp->screenshot, nacpGetScreenshotString)) goto end; /* VideoCapture. */ if (!NACP_ADD_ENUM("VideoCapture", nacp->video_capture, nacpGetVideoCaptureString)) goto end; /* PresenceGroupId. */ if (!NACP_ADD_U64("PresenceGroupId", nacp->presence_group_id, true, true)) goto end; /* DisplayVersion. */ if (!NACP_ADD_STR("DisplayVersion", nacp->display_version)) goto end; /* Rating. */ for(i = 0, count = 0; i < NacpRatingAgeOrganization_Count; i++) { s8 age = *(((s8*)&(nacp->rating_age)) + i); if (age < 0) continue; if (!NACP_ADD_FMT_STR_T1(" <Rating>\n" \ " <Organization>%s</Organization>\n" \ " <Age>%d</Age>\n" \ " </Rating>\n", \ nacpGetRatingAgeOrganizationString(i), \ age)) goto end; count++; } if (!count && !NACP_ADD_FMT_STR_T1(" <Rating />\n")) goto end; /* DataLossConfirmation. */ if (!NACP_ADD_ENUM("DataLossConfirmation", nacp->data_loss_confirmation, nacpGetDataLossConfirmationString)) goto end; /* PlayLogPolicy. */ if (!NACP_ADD_ENUM("PlayLogPolicy", nacp->play_log_policy, nacpGetPlayLogPolicyString)) goto end; /* SaveDataOwnerId. */ if (!NACP_ADD_U64("SaveDataOwnerId", nacp->save_data_owner_id, true, true)) goto end; /* UserAccountSaveDataSize. */ if (!NACP_ADD_U64("UserAccountSaveDataSize", (u64)nacp->user_account_save_data_size, true, true)) goto end; /* UserAccountSaveDataJournalSize. */ if (!NACP_ADD_U64("UserAccountSaveDataJournalSize", (u64)nacp->user_account_save_data_journal_size, true, true)) goto end; /* UserAccountSaveDataTotalSize. */ if (!NACP_ADD_U64("UserAccountSaveDataTotalSize", (u64)(nacp->user_account_save_data_size + nacp->user_account_save_data_journal_size), true, true)) goto end; /* DeviceSaveDataSize. */ if (!NACP_ADD_U64("DeviceSaveDataSize", (u64)nacp->device_save_data_size, true, true)) goto end; /* DeviceSaveDataJournalSize. */ if (!NACP_ADD_U64("DeviceSaveDataJournalSize", (u64)nacp->device_save_data_journal_size, true, true)) goto end; /* BcatDeliveryCacheStorageSize. */ if (!NACP_ADD_U64("BcatDeliveryCacheStorageSize", (u64)nacp->bcat_delivery_cache_storage_size, true, true)) goto end; /* ApplicationErrorCodeCategory. */ if (!NACP_ADD_STR("ApplicationErrorCodeCategory", nacp->application_error_code_category)) goto end; /* AddOnContentBaseId. */ if (!NACP_ADD_U64("AddOnContentBaseId", nacp->add_on_content_base_id, true, true)) goto end; /* Version. */ if (!NACP_ADD_U32("Version", version, false, false)) goto end; /* ReleaseVersion. */ if (!NACP_ADD_U32("ReleaseVersion", app_ver.application_version.release_ver, false, false)) goto end; /* PrivateVersion. */ if (!NACP_ADD_U32("PrivateVersion", app_ver.application_version.private_ver, false, false)) goto end; /* LogoType. */ if (!NACP_ADD_ENUM("LogoType", nacp->logo_type, nacpGetLogoTypeString)) goto end; /* RequiredSystemVersion. */ if (!NACP_ADD_U32("RequiredSystemVersion", required_system_version, false, false)) goto end; /* LocalCommunicationId. */ for(i = 0; i < 0x8; i++) { if (!nacp->local_communication_id[i]) continue; if (!NACP_ADD_U64("LocalCommunicationId", nacp->local_communication_id[i], true, true)) goto end; } /* LogoHandling. */ if (!NACP_ADD_ENUM("LogoHandling", nacp->logo_handling, nacpGetLogoHandlingString)) goto end; /* Icon. */ for(i = 0, count = 0; i < nacp_ctx->icon_count; i++) { NacpIconContext *icon_ctx = &(nacp_ctx->icon_ctx[i]); /* Calculate icon hash. */ sha256CalculateHash(icon_hash, icon_ctx->icon_data, icon_ctx->icon_size); /* Generate icon hash string. Only the first half from the hash is used. */ utilsGenerateHexStringFromData(icon_hash_str, sizeof(icon_hash_str), icon_hash, sizeof(icon_hash) / 2, false); /* Add XML element. */ if (!NACP_ADD_FMT_STR_T1(" <Icon>\n" \ " <Language>%s</Language>\n" \ /*" <IconPath />\n" \ " <NxIconPath />\n" \ " <RawIconHash />\n" \*/ " <NxIconHash>%s</NxIconHash>\n" \ " </Icon>\n", \ nacpGetLanguageString(icon_ctx->language), \ icon_hash_str)) goto end; count++; } if (!count && !NACP_ADD_FMT_STR_T1(" <Icon />\n")) goto end; /* HtmlDocumentPath, LegalInformationFilePath and AccessibleUrlsFilePath. Unused but kept anyway. */ if (!NACP_ADD_FMT_STR_T1(" <HtmlDocumentPath UseEnvironmentVariable=\"false\" />\n" \ " <LegalInformationFilePath UseEnvironmentVariable=\"false\" />\n" \ " <AccessibleUrlsFilePath UseEnvironmentVariable=\"false\" />\n")) goto end; /* SeedForPseudoDeviceId. */ if (!NACP_ADD_U64("SeedForPseudoDeviceId", nacp->seed_for_pseudo_device_id, true, false)) goto end; /* BcatPassphrase. */ if (!NACP_ADD_STR("BcatPassphrase", nacp->bcat_passphrase)) goto end; /* AddOnContentRegistrationType. */ if (!NACP_ADD_ENUM("AddOnContentRegistrationType", nacp->add_on_content_registration_type, nacpGetAddOnContentRegistrationTypeString)) goto end; /* UserAccountSaveDataSizeMax. */ if (!NACP_ADD_U64("UserAccountSaveDataSizeMax", (u64)nacp->user_account_save_data_size_max, true, true)) goto end; /* UserAccountSaveDataJournalSizeMax. */ if (!NACP_ADD_U64("UserAccountSaveDataJournalSizeMax", (u64)nacp->user_account_save_data_journal_size_max, true, true)) goto end; /* DeviceSaveDataSizeMax. */ if (!NACP_ADD_U64("DeviceSaveDataSizeMax", (u64)nacp->device_save_data_size_max, true, true)) goto end; /* DeviceSaveDataJournalSizeMax. */ if (!NACP_ADD_U64("DeviceSaveDataJournalSizeMax", (u64)nacp->device_save_data_journal_size_max, true, true)) goto end; /* TemporaryStorageSize. */ if (!NACP_ADD_U64("TemporaryStorageSize", (u64)nacp->temporary_storage_size, true, true)) goto end; /* CacheStorageSize. */ if (!NACP_ADD_U64("CacheStorageSize", (u64)nacp->cache_storage_size, true, true)) goto end; /* CacheStorageJournalSize. */ if (!NACP_ADD_U64("CacheStorageJournalSize", (u64)nacp->cache_storage_journal_size, true, true)) goto end; /* CacheStorageDataAndJournalSizeMax. */ if (!NACP_ADD_U64("CacheStorageDataAndJournalSizeMax", (u64)nacp->cache_storage_data_and_journal_size_max, true, true)) goto end; /* CacheStorageIndexMax. */ if (!NACP_ADD_U64("CacheStorageIndexMax", (u64)nacp->cache_storage_index_max, true, true)) goto end; /* Hdcp. */ if (!NACP_ADD_ENUM("Hdcp", nacp->hdcp, nacpGetHdcpString)) goto end; /* CrashReport. */ if (!NACP_ADD_ENUM("CrashReport", nacp->crash_report, nacpGetCrashReportString)) goto end; /* CrashScreenshotForProd. */ if (!NACP_ADD_ENUM("CrashScreenshotForProd", nacp->crash_screenshot_for_prod, nacpGetCrashScreenshotForProdString)) goto end; /* CrashScreenshotForDev. */ if (!NACP_ADD_ENUM("CrashScreenshotForDev", nacp->crash_screenshot_for_dev, nacpGetCrashScreenshotForDevString)) goto end; /* RuntimeAddOnContentInstall. */ if (!NACP_ADD_ENUM("RuntimeAddOnContentInstall", nacp->runtime_add_on_content_install, nacpGetRuntimeAddOnContentInstallString)) goto end; /* PlayLogQueryableApplicationId. */ for(i = 0; i < 0x10; i++) { if (!nacp->play_log_queryable_application_id[i]) continue; if (!NACP_ADD_U64("PlayLogQueryableApplicationId", nacp->play_log_queryable_application_id[i], true, true)) goto end; } /* PlayLogQueryCapability. */ if (!NACP_ADD_ENUM("PlayLogQueryCapability", nacp->play_log_query_capability, nacpGetPlayLogQueryCapabilityString)) goto end; /* Repair. */ if (!NACP_ADD_BITFLAG("Repair", &(nacp->repair), sizeof(nacp->repair), NacpRepair_Count, nacpGetRepairString, false)) goto end; /* ProgramIndex. */ if (!NACP_ADD_U16("ProgramIndex", nacp->program_index, false, false)) goto end; /* RequiredNetworkServiceLicenseOnLaunch. */ if (!NACP_ADD_BITFLAG("RequiredNetworkServiceLicenseOnLaunch", &(nacp->required_network_service_license_on_launch), sizeof(nacp->required_network_service_license_on_launch), \ NacpRequiredNetworkServiceLicenseOnLaunch_Count, nacpGetRequiredNetworkServiceLicenseOnLaunchString, false)) goto end; /* NeighborDetectionClientConfiguration. */ ndcc_sgc_available = (ndcc->send_group_configuration.group_id && memcmp(ndcc->send_group_configuration.key, null_key, sizeof(null_key))); for(i = 0; i < 0x10; i++) { ndcc_rgc_available = (ndcc->receivable_group_configurations[i].group_id && memcmp(ndcc->receivable_group_configurations[i].key, null_key, sizeof(null_key))); if (ndcc_rgc_available) break; } if (ndcc_sgc_available || ndcc_rgc_available) { if (!NACP_ADD_FMT_STR_T1(" <NeighborDetectionClientConfiguration>\n")) goto end; /* SendGroupConfiguration. */ utilsGenerateHexStringFromData(key_str, sizeof(key_str), ndcc->send_group_configuration.key, sizeof(ndcc->send_group_configuration.key), false); if (!NACP_ADD_FMT_STR_T1(" <SendGroupConfiguration>\n" \ " <GroupId>0x%016lx</GroupId>\n" \ " <Key>%s</Key>\n" \ " </SendGroupConfiguration>\n", \ ndcc->send_group_configuration.group_id, \ key_str)) goto end; /* ReceivableGroupConfiguration. */ for(i = 0; i < 0x10; i++) { NacpApplicationNeighborDetectionGroupConfiguration *rgc = &(ndcc->receivable_group_configurations[i]); utilsGenerateHexStringFromData(key_str, sizeof(key_str), rgc->key, sizeof(rgc->key), false); if (!NACP_ADD_FMT_STR_T1(" <ReceivableGroupConfiguration>\n" \ " <GroupId>0x%016lx</GroupId>\n" \ " <Key>%s</Key>\n" \ " </ReceivableGroupConfiguration>\n", \ rgc->group_id, \ key_str)) goto end; } if (!NACP_ADD_FMT_STR_T1(" </NeighborDetectionClientConfiguration>\n")) goto end; } /* JitConfiguration. */ if (!NACP_ADD_FMT_STR_T1(" <Jit>\n" \ " <IsEnabled>%s</IsEnabled>\n" \ " <MemorySize>%lu</MemorySize>\n" \ " </Jit>\n", \ (nacp->jit_configuration.jit_configuration_flag & NacpJitConfigurationFlag_Enabled) ? "true" : "false", \ nacp->jit_configuration.memory_size)) goto end; /* History. */ //if (!NACP_ADD_FMT_STR_T1(" <History />\n")) goto end; /* RuntimeParameterDelivery. */ if (!NACP_ADD_ENUM("RuntimeParameterDelivery", nacp->runtime_parameter_delivery, nacpGetRuntimeParameterDeliveryString)) goto end; /* AppropriateAgeForChina. */ if (!NACP_ADD_ENUM("AppropriateAgeForChina", nacp->appropriate_age_for_china, nacpGetAppropriateAgeForChina)) goto end; /* RequiredAddOnContentsSet. */ for(i = 0; i < 0x20; i++) { NacpRequiredAddOnContentsSetDescriptor *descriptor = &(raocsbd->descriptors[i]); if (descriptor->index) { raocsbd_available = true; break; } if (descriptor->flag != NacpRequiredAddOnContentsSetDescriptorFlag_Continue) break; } if (raocsbd_available) { if (!NACP_ADD_FMT_STR_T1(" <RequiredAddOnContentsSet>\n")) goto end; for(i = 0; i < 0x20; i++) { NacpRequiredAddOnContentsSetDescriptor *descriptor = &(raocsbd->descriptors[i]); if (!descriptor->index) continue; if (!NACP_ADD_FMT_STR_T1(" <Index>%u</Index>\n", descriptor->index)) goto end; if (descriptor->flag != NacpRequiredAddOnContentsSetDescriptorFlag_Continue) break; } if (!NACP_ADD_FMT_STR_T1(" </RequiredAddOnContentsSet>\n")) goto end; } /* PlayReportPermission. */ if (!NACP_ADD_FMT_STR_T1(" <PlayReportPermission>\n" \ " <TargetMarketing>%s</TargetMarketing>\n" \ " </PlayReportPermission>\n", \ (nacp->play_report_permission & NacpPlayReportPermission_TargetMarketing) ? "Allow" : "Deny")) goto end; /* AccessibleLaunchRequiredVersion. */ for(i = 0; i < 0x8; i++) { if (nacp->accessible_launch_required_version.application_id[i]) { alrv_available = true; break; } } if (alrv_available) { if (!NACP_ADD_FMT_STR_T1(" <AccessibleLaunchRequiredVersion>\n")) goto end; for(i = 0; i < 0x8; i++) { u64 id = nacp->accessible_launch_required_version.application_id[i]; if (!id) continue; if (!NACP_ADD_FMT_STR_T1(" <ApplicationId>0x%016lx</ApplicationId>\n", id)) goto end; } if (!NACP_ADD_FMT_STR_T1(" </AccessibleLaunchRequiredVersion>\n")) goto end; } else { if (!NACP_ADD_FMT_STR_T1(" <AccessibleLaunchRequiredVersion />\n")) goto end; } /* UndecidedParameter75b8b. */ if (!NACP_ADD_ENUM("UndecidedParameter75b8b", nacp->undecided_parameter_75b8b, nacpGetUndecidedParameter75b8bString)) goto end; /* ApplicationId. */ if (!NACP_ADD_U64("ApplicationId", nacp_ctx->nca_ctx->header.program_id, true, true)) goto end; /* FilterDescriptionFilePath and CompressionFileConfigurationFilePath. */ /*if (!NACP_ADD_FMT_STR_T1(" <FilterDescriptionFilePath />\n" \ " <CompressionFileConfigurationFilePath />\n")) goto end;*/ /* ContentsAvailabilityTransitionPolicy. */ if (!NACP_ADD_ENUM("ContentsAvailabilityTransitionPolicy", nacp->contents_availability_transition_policy, nacpGetContentsAvailabilityTransitionPolicyString)) goto end; /* LimitedLicenseSettings. */ if (!NACP_ADD_FMT_STR_T1(" <LimitedLicenseSettings>\n" \ " <RuntimeUpgrade>%s</RuntimeUpgrade>\n" \ " <SupportingLimitedLicenses>\n" \ " <LimitedLicense>%s</LimitedLicense>\n" \ " </SupportingLimitedLicenses>\n" \ " </LimitedLicenseSettings>\n", \ nacpGetRuntimeUpgradeString(nacp->runtime_upgrade), \ (nacp->supporting_limited_licenses & NacpSupportingLimitedLicenses_Demo) ? "Demo" : "None")) goto end; if (!(success = NACP_ADD_FMT_STR_T1("</Application>"))) goto end; /* Update NACP context. */ nacp_ctx->authoring_tool_xml = xml_buf; nacp_ctx->authoring_tool_xml_size = strlen(xml_buf); end: if (!success) { if (xml_buf) free(xml_buf); LOG_MSG_ERROR("Failed to generate NACP AuthoringTool XML!"); } return success; } const char *nacpGetLanguageString(u8 language) { return (language < NacpLanguage_Count ? g_nacpLanguageStrings[language] : g_unknownString); } const char *nacpGetStartupUserAccountString(u8 startup_user_account) { return (startup_user_account < NacpStartupUserAccount_Count ? g_nacpStartupUserAccountStrings[startup_user_account] : g_unknownString); } const char *nacpGetUserAccountSwitchLockString(u8 user_account_switch_lock) { return (user_account_switch_lock < NacpUserAccountSwitchLock_Count ? g_nacpUserAccountSwitchLockStrings[user_account_switch_lock] : g_unknownString); } const char *nacpGetAddOnContentRegistrationTypeString(u8 add_on_content_registration_type) { return (add_on_content_registration_type < NacpAddOnContentRegistrationType_Count ? g_nacpAddOnContentRegistrationTypeStrings[add_on_content_registration_type] : g_unknownString); } const char *nacpGetAttributeString(u8 attribute) { return (attribute < NacpAttribute_Count ? g_nacpAttributeStrings[attribute] : g_unknownString); } const char *nacpGetParentalControlString(u8 parental_control) { return (parental_control < NacpParentalControl_Count ? g_nacpParentalControlStrings[parental_control] : g_unknownString); } const char *nacpGetScreenshotString(u8 screenshot) { return (screenshot < NacpScreenshot_Count ? g_nacpScreenshotStrings[screenshot] : g_unknownString); } const char *nacpGetVideoCaptureString(u8 video_capture) { return (video_capture < NacpVideoCapture_Count ? g_nacpVideoCaptureStrings[video_capture] : g_unknownString); } const char *nacpGetDataLossConfirmationString(u8 data_loss_confirmation) { return (data_loss_confirmation < NacpDataLossConfirmation_Count ? g_nacpDataLossConfirmationStrings[data_loss_confirmation] : g_unknownString); } const char *nacpGetPlayLogPolicyString(u8 play_log_policy) { return (play_log_policy < NacpPlayLogPolicy_Count ? g_nacpPlayLogPolicyStrings[play_log_policy] : g_unknownString); } const char *nacpGetRatingAgeOrganizationString(u8 rating_age_organization) { return (rating_age_organization < NacpRatingAgeOrganization_Count ? g_nacpRatingAgeOrganizationStrings[rating_age_organization] : g_unknownString); } const char *nacpGetLogoTypeString(u8 logo_type) { return (logo_type < NacpLogoType_Count ? g_nacpLogoTypeStrings[logo_type] : g_unknownString); } const char *nacpGetLogoHandlingString(u8 logo_handling) { return (logo_handling < NacpLogoHandling_Count ? g_nacpLogoHandlingStrings[logo_handling] : g_unknownString); } const char *nacpGetRuntimeAddOnContentInstallString(u8 runtime_add_on_content_install) { return (runtime_add_on_content_install < NacpRuntimeAddOnContentInstall_Count ? g_nacpRuntimeAddOnContentInstallStrings[runtime_add_on_content_install] : g_unknownString); } const char *nacpGetRuntimeParameterDeliveryString(u8 runtime_parameter_delivery) { return (runtime_parameter_delivery < NacpRuntimeParameterDelivery_Count ? g_nacpRuntimeParameterDeliveryStrings[runtime_parameter_delivery] : g_unknownString); } const char *nacpGetAppropriateAgeForChina(u8 appropriate_age_for_china) { return (appropriate_age_for_china < NacpAppropriateAgeForChina_Count ? g_nacpAppropriateAgeForChinaStrings[appropriate_age_for_china] : g_unknownString); } const char *nacpGetUndecidedParameter75b8bString(u8 undecided_parameter_75b8b) { return (undecided_parameter_75b8b < NacpUndecidedParameter75b8b_Count ? g_nacpUndecidedParameter75b8bStrings[undecided_parameter_75b8b] : g_unknownString); } const char *nacpGetCrashReportString(u8 crash_report) { return (crash_report < NacpCrashReport_Count ? g_nacpCrashReportStrings[crash_report] : g_unknownString); } const char *nacpGetHdcpString(u8 hdcp) { return (hdcp < NacpHdcp_Count ? g_nacpHdcpStrings[hdcp] : g_unknownString); } const char *nacpGetStartupUserAccountOptionString(u8 startup_user_account_option) { return (startup_user_account_option < NacpStartupUserAccountOption_Count ? g_nacpStartupUserAccountOptionStrings[startup_user_account_option] : g_unknownString); } const char *nacpGetRuntimeUpgradeString(u8 runtime_upgrade) { return (runtime_upgrade < NacpRuntimeUpgrade_Count ? g_nacpRuntimeUpgradeStrings[runtime_upgrade] : g_unknownString); } const char *nacpGetPlayLogQueryCapabilityString(u8 play_log_query_capability) { return (play_log_query_capability < NacpPlayLogQueryCapability_Count ? g_nacpPlayLogQueryCapabilityStrings[play_log_query_capability] : g_unknownString); } const char *nacpGetRepairString(u8 repair) { return (repair < NacpRepair_Count ? g_nacpRepairStrings[repair] : g_unknownString); } const char *nacpGetRequiredNetworkServiceLicenseOnLaunchString(u8 required_network_service_license_on_launch) { return (required_network_service_license_on_launch < NacpRequiredNetworkServiceLicenseOnLaunch_Count ? \ g_nacpRequiredNetworkServiceLicenseOnLaunchStrings[required_network_service_license_on_launch] : g_unknownString); } const char *nacpGetCrashScreenshotForProdString(u8 crash_screenshot_for_prod) { return (crash_screenshot_for_prod < NacpCrashScreenshotForProd_Count ? g_nacpCrashScreenshotForProdStrings[crash_screenshot_for_prod] : g_unknownString); } const char *nacpGetCrashScreenshotForDevString(u8 crash_screenshot_for_dev) { return (crash_screenshot_for_dev < NacpCrashScreenshotForDev_Count ? g_nacpCrashScreenshotForDevStrings[crash_screenshot_for_dev] : g_unknownString); } const char *nacpGetContentsAvailabilityTransitionPolicyString(u8 contents_availability_transition_policy) { return (contents_availability_transition_policy < NacpContentsAvailabilityTransitionPolicy_Count ? \ g_nacpContentsAvailabilityTransitionPolicyStrings[contents_availability_transition_policy] : g_unknownString); } NX_INLINE bool nacpCheckBitflagField(const void *flag, u8 flag_bitcount, u8 idx) { if (!flag || !IS_POWER_OF_TWO(flag_bitcount) || idx >= flag_bitcount) return false; const u8 *flag_u8 = (const u8*)flag; u8 byte_idx = (idx >> 3); u8 bitmask = BIT(idx - ALIGN_DOWN(idx, 8)); return (flag_u8[byte_idx] & bitmask); } static bool nacpAddStringFieldToAuthoringToolXml(char **xml_buf, u64 *xml_buf_size, const char *tag_name, const char *value) { if (!xml_buf || !xml_buf_size || !tag_name || !*tag_name || !value) { LOG_MSG_ERROR("Invalid parameters!"); return false; } return (*value ? NACP_ADD_FMT_STR_T2(" <%s>%s</%s>\n", tag_name, value, tag_name) : NACP_ADD_FMT_STR_T2(" <%s />\n", tag_name)); } static bool nacpAddEnumFieldToAuthoringToolXml(char **xml_buf, u64 *xml_buf_size, const char *tag_name, u8 value, NacpStringFunction str_func) { if (!xml_buf || !xml_buf_size || !tag_name || !*tag_name || !str_func) { LOG_MSG_ERROR("Invalid parameters!"); return false; } return NACP_ADD_FMT_STR_T2(" <%s>%s</%s>\n", tag_name, str_func(value), tag_name); } static bool nacpAddBitflagFieldToAuthoringToolXml(char **xml_buf, u64 *xml_buf_size, const char *tag_name, const void *flag, u8 flag_width, u8 max_flag_idx, NacpStringFunction str_func, bool allow_empty_str) { u8 flag_bitcount = 0, i = 0, count = 0; const u8 *flag_u8 = (const u8*)flag; bool success = false, empty_flag = true; if (!xml_buf || !xml_buf_size || !tag_name || !*tag_name || !flag || !flag_width || (flag_width > 1 && !IS_POWER_OF_TWO(flag_width)) || flag_width > 0x10 || \ (flag_bitcount = (flag_width * 8)) < max_flag_idx || !str_func) { LOG_MSG_ERROR("Invalid parameters!"); return false; } for(i = 0; i < flag_width; i++) { if (flag_u8[i]) { empty_flag = false; break; } } if (!empty_flag) { for(i = 0; i < max_flag_idx; i++) { if (!nacpCheckBitflagField(flag, flag_bitcount, i)) continue; if (!NACP_ADD_FMT_STR_T2(" <%s>%s</%s>\n", tag_name, str_func(i), tag_name)) goto end; count++; } /* Edge case for new, unsupported flags. */ if (!count) empty_flag = true; } if (empty_flag && allow_empty_str && !NACP_ADD_FMT_STR_T2(" <%s />\n", tag_name)) goto end; success = true; end: return success; } static bool nacpAddU16FieldToAuthoringToolXml(char **xml_buf, u64 *xml_buf_size, const char *tag_name, u16 value, bool hex, bool prefix) { if (!xml_buf || !xml_buf_size || !tag_name || !*tag_name) { LOG_MSG_ERROR("Invalid parameters!"); return false; } const char *str = (hex ? (prefix ? " <%s>0x%04x</%s>\n" : " <%s>%04x</%s>\n") : " <%s>%u</%s>\n"); return NACP_ADD_FMT_STR_T2(str, tag_name, value, tag_name); } static bool nacpAddU32FieldToAuthoringToolXml(char **xml_buf, u64 *xml_buf_size, const char *tag_name, u32 value, bool hex, bool prefix) { if (!xml_buf || !xml_buf_size || !tag_name || !*tag_name) { LOG_MSG_ERROR("Invalid parameters!"); return false; } const char *str = (hex ? (prefix ? " <%s>0x%08x</%s>\n" : " <%s>%08x</%s>\n") : " <%s>%u</%s>\n"); return NACP_ADD_FMT_STR_T2(str, tag_name, value, tag_name); } static bool nacpAddU64FieldToAuthoringToolXml(char **xml_buf, u64 *xml_buf_size, const char *tag_name, u64 value, bool hex, bool prefix) { if (!xml_buf || !xml_buf_size || !tag_name || !*tag_name) { LOG_MSG_ERROR("Invalid parameters!"); return false; } const char *str = (hex ? (prefix ? " <%s>0x%016lx</%s>\n" : " <%s>%016lx</%s>\n") : " <%s>%lu</%s>\n"); return NACP_ADD_FMT_STR_T2(str, tag_name, value, tag_name); }