nxdumptool/source/core/nacp.c

1042 lines
43 KiB
C

/*
* nacp.c
*
* Copyright (c) 2020-2021, DarkMatterCore <pabloacurielz@gmail.com>.
*
* 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 <https://www.gnu.org/licenses/>.
*/
#include "utils.h"
#include "nacp.h"
#include "title.h"
/* 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"
};
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_nacpNacpRuntimeParameterDeliveryStrings[NacpRuntimeParameterDelivery_Count] = {
"Always",
"AlwaysIfUserStateMatched",
"OnRestart"
};
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_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_nacpJitConfigurationFlagStrings[NacpJitConfigurationFlag_Count] = {
"None",
"Enabled"
};
static const char *g_nacpPlayReportPermissionStrings[NacpPlayReportPermission_Count] = {
"None",
"TargetMarketing"
};
static const char *g_nacpCrashScreenshotForProdStrings[NacpCrashScreenshotForProd_Count] = {
"Deny",
"Allow"
};
static const char *g_nacpCrashScreenshotForDevStrings[NacpCrashScreenshotForDev_Count] = {
"Deny",
"Allow"
};
/* 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);
static bool nacpAddU16FieldToAuthoringToolXml(char **xml_buf, u64 *xml_buf_size, const char *tag_name, u16 value, bool hex);
static bool nacpAddU32FieldToAuthoringToolXml(char **xml_buf, u64 *xml_buf_size, const char *tag_name, u32 value, bool hex);
static bool nacpAddU64FieldToAuthoringToolXml(char **xml_buf, u64 *xml_buf_size, const char *tag_name, u64 value, bool hex);
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("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])))
{
LOG_MSG("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("Failed to retrieve file entry for \"control.nacp\" from RomFS!");
goto end;
}
//LOG_MSG("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("Invalid NACP size!");
goto end;
}
/* Allocate memory for the NACP data. */
if (!(out->data = malloc(sizeof(_NacpStruct))))
{
LOG_MSG("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("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("\"%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("\"%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("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("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("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("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)
{
if (!nacpIsValidContext(nacp_ctx))
{
LOG_MSG("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) 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;
/* 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("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("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("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("Invalid parameters!");
return false;
}
_NacpStruct *nacp = nacp_ctx->data;
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 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 (!utilsAppendFormattedStringToBuffer(&xml_buf, &xml_buf_size, \
"<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" \
"<Application>\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 (!utilsAppendFormattedStringToBuffer(&xml_buf, &xml_buf_size, \
" <Title>\n" \
" <Language>%s</Language>\n" \
" <Name>%s</Name>\n" \
" <Publisher>%s</Publisher>\n" \
" </Title>\n", \
nacpGetLanguageString(i), \
title->name, \
title->publisher)) goto end;
count++;
}
if (!count && !utilsAppendFormattedStringToBuffer(&xml_buf, &xml_buf_size, " <Title />\n")) goto end;
/* Isbn. */
if (!nacpAddStringFieldToAuthoringToolXml(&xml_buf, &xml_buf_size, "Isbn", nacp->isbn)) goto end;
/* StartupUserAccount. */
if (!nacpAddEnumFieldToAuthoringToolXml(&xml_buf, &xml_buf_size, "StartupUserAccount", nacp->startup_user_account, &nacpGetStartupUserAccountString)) goto end;
/* StartupUserAccountOption. */
if (!nacpAddBitflagFieldToAuthoringToolXml(&xml_buf, &xml_buf_size, "StartupUserAccountOption", &(nacp->startup_user_account_option), sizeof(nacp->startup_user_account_option), \
NacpStartupUserAccountOption_Count, &nacpGetStartupUserAccountOptionString)) goto end;
/* UserAccountSwitchLock. */
if (!nacpAddEnumFieldToAuthoringToolXml(&xml_buf, &xml_buf_size, "UserAccountSwitchLock", nacp->user_account_switch_lock, &nacpGetUserAccountSwitchLockString)) goto end;
/* Attribute. */
if (!nacpAddBitflagFieldToAuthoringToolXml(&xml_buf, &xml_buf_size, "Attribute", &(nacp->attribute), sizeof(nacp->attribute), NacpAttribute_Count, &nacpGetAttributeString)) goto end;
/* ParentalControl. */
if (!nacpAddBitflagFieldToAuthoringToolXml(&xml_buf, &xml_buf_size, "ParentalControl", &(nacp->parental_control), sizeof(nacp->parental_control), NacpParentalControl_Count, \
&nacpGetParentalControlString)) goto end;
/* SupportedLanguage. */
if (!nacpAddBitflagFieldToAuthoringToolXml(&xml_buf, &xml_buf_size, "SupportedLanguage", &(nacp->supported_language), sizeof(nacp->supported_language), NacpSupportedLanguage_Count, \
&nacpGetLanguageString)) goto end;
/* Screenshot. */
if (!nacpAddEnumFieldToAuthoringToolXml(&xml_buf, &xml_buf_size, "Screenshot", nacp->screenshot, &nacpGetScreenshotString)) goto end;
/* VideoCapture. */
if (!nacpAddEnumFieldToAuthoringToolXml(&xml_buf, &xml_buf_size, "VideoCapture", nacp->video_capture, &nacpGetVideoCaptureString)) goto end;
/* PresenceGroupId. */
if (!nacpAddU64FieldToAuthoringToolXml(&xml_buf, &xml_buf_size, "PresenceGroupId", nacp->presence_group_id, true)) goto end;
/* DisplayVersion. */
if (!nacpAddStringFieldToAuthoringToolXml(&xml_buf, &xml_buf_size, "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 (!utilsAppendFormattedStringToBuffer(&xml_buf, &xml_buf_size, \
" <Rating>\n" \
" <Organization>%s</Organization>\n" \
" <Age>%d</Age>\n" \
" </Rating>\n", \
nacpGetRatingAgeOrganizationString(i), \
age)) goto end;
count++;
}
if (!count && !utilsAppendFormattedStringToBuffer(&xml_buf, &xml_buf_size, " <Rating />\n")) goto end;
/* DataLossConfirmation. */
if (!nacpAddEnumFieldToAuthoringToolXml(&xml_buf, &xml_buf_size, "DataLossConfirmation", nacp->data_loss_confirmation, &nacpGetDataLossConfirmationString)) goto end;
/* PlayLogPolicy. */
if (!nacpAddEnumFieldToAuthoringToolXml(&xml_buf, &xml_buf_size, "PlayLogPolicy", nacp->play_log_policy, &nacpGetPlayLogPolicyString)) goto end;
/* SaveDataOwnerId. */
if (!nacpAddU64FieldToAuthoringToolXml(&xml_buf, &xml_buf_size, "SaveDataOwnerId", nacp->save_data_owner_id, true)) goto end;
/* UserAccountSaveDataSize. */
if (!nacpAddU64FieldToAuthoringToolXml(&xml_buf, &xml_buf_size, "UserAccountSaveDataSize", (u64)nacp->user_account_save_data_size, true)) goto end;
/* UserAccountSaveDataJournalSize. */
if (!nacpAddU64FieldToAuthoringToolXml(&xml_buf, &xml_buf_size, "UserAccountSaveDataJournalSize", (u64)nacp->user_account_save_data_journal_size, true)) goto end;
/* DeviceSaveDataSize. */
if (!nacpAddU64FieldToAuthoringToolXml(&xml_buf, &xml_buf_size, "DeviceSaveDataSize", (u64)nacp->device_save_data_size, true)) goto end;
/* DeviceSaveDataJournalSize. */
if (!nacpAddU64FieldToAuthoringToolXml(&xml_buf, &xml_buf_size, "DeviceSaveDataJournalSize", (u64)nacp->device_save_data_journal_size, true)) goto end;
/* BcatDeliveryCacheStorageSize. */
if (!nacpAddU64FieldToAuthoringToolXml(&xml_buf, &xml_buf_size, "BcatDeliveryCacheStorageSize", (u64)nacp->bcat_delivery_cache_storage_size, true)) goto end;
/* ApplicationErrorCodeCategory. */
if (!nacpAddStringFieldToAuthoringToolXml(&xml_buf, &xml_buf_size, "ApplicationErrorCodeCategory", nacp->application_error_code_category)) goto end;
/* AddOnContentBaseId. */
if (!nacpAddU64FieldToAuthoringToolXml(&xml_buf, &xml_buf_size, "AddOnContentBaseId", nacp->add_on_content_base_id, true)) goto end;
/* Version. */
if (!nacpAddU32FieldToAuthoringToolXml(&xml_buf, &xml_buf_size, "Version", version, false)) goto end;
/* ReleaseVersion and PrivateVersion. Unused but kept anyway. */
if (!utilsAppendFormattedStringToBuffer(&xml_buf, &xml_buf_size, \
" <ReleaseVersion />\n" \
" <PrivateVersion />\n")) goto end;
/* LogoType. */
if (!nacpAddEnumFieldToAuthoringToolXml(&xml_buf, &xml_buf_size, "LogoType", nacp->logo_type, &nacpGetLogoTypeString)) goto end;
/* RequiredSystemVersion. */
if (!nacpAddU32FieldToAuthoringToolXml(&xml_buf, &xml_buf_size, "RequiredSystemVersion", required_system_version, false)) goto end;
/* LocalCommunicationId. */
for(i = 0, count = 0; i < 0x8; i++)
{
if (!nacp->local_communication_id[i]) continue;
if (!nacpAddU64FieldToAuthoringToolXml(&xml_buf, &xml_buf_size, "LocalCommunicationId", nacp->local_communication_id[i], true)) goto end;
count++;
}
if (!count && !utilsAppendFormattedStringToBuffer(&xml_buf, &xml_buf_size, " <LocalCommunicationId />\n")) goto end;
/* LogoHandling. */
if (!nacpAddEnumFieldToAuthoringToolXml(&xml_buf, &xml_buf_size, "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, SHA256_HASH_SIZE + 1, icon_hash, SHA256_HASH_SIZE / 2);
/* Add XML element. */
if (!utilsAppendFormattedStringToBuffer(&xml_buf, &xml_buf_size, \
" <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 && !utilsAppendFormattedStringToBuffer(&xml_buf, &xml_buf_size, " <Icon />\n")) goto end;
/* HtmlDocumentPath, LegalInformationFilePath and AccessibleUrlsFilePath. Unused but kept anyway. */
if (!utilsAppendFormattedStringToBuffer(&xml_buf, &xml_buf_size, \
" <HtmlDocumentPath />\n" \
" <LegalInformationFilePath />\n" \
" <AccessibleUrlsFilePath />\n")) goto end;
/* SeedForPseudoDeviceId. */
if (!nacpAddU64FieldToAuthoringToolXml(&xml_buf, &xml_buf_size, "SeedForPseudoDeviceId", nacp->seed_for_pseudo_device_id, true)) goto end;
/* BcatPassphrase. */
if (!nacpAddStringFieldToAuthoringToolXml(&xml_buf, &xml_buf_size, "BcatPassphrase", nacp->bcat_passphrase)) goto end;
/* AddOnContentRegistrationType. */
if (!nacpAddEnumFieldToAuthoringToolXml(&xml_buf, &xml_buf_size, "AddOnContentRegistrationType", nacp->add_on_content_registration_type, &nacpGetAddOnContentRegistrationTypeString)) goto end;
/* UserAccountSaveDataSizeMax. */
if (!nacpAddU64FieldToAuthoringToolXml(&xml_buf, &xml_buf_size, "UserAccountSaveDataSizeMax", (u64)nacp->user_account_save_data_size_max, true)) goto end;
/* UserAccountSaveDataJournalSizeMax. */
if (!nacpAddU64FieldToAuthoringToolXml(&xml_buf, &xml_buf_size, "UserAccountSaveDataJournalSizeMax", (u64)nacp->user_account_save_data_journal_size_max, true)) goto end;
/* DeviceSaveDataSizeMax. */
if (!nacpAddU64FieldToAuthoringToolXml(&xml_buf, &xml_buf_size, "DeviceSaveDataSizeMax", (u64)nacp->device_save_data_size_max, true)) goto end;
/* DeviceSaveDataJournalSizeMax. */
if (!nacpAddU64FieldToAuthoringToolXml(&xml_buf, &xml_buf_size, "DeviceSaveDataJournalSizeMax", (u64)nacp->device_save_data_journal_size_max, true)) goto end;
/* TemporaryStorageSize. */
if (!nacpAddU64FieldToAuthoringToolXml(&xml_buf, &xml_buf_size, "TemporaryStorageSize", (u64)nacp->temporary_storage_size, true)) goto end;
/* CacheStorageSize. */
if (!nacpAddU64FieldToAuthoringToolXml(&xml_buf, &xml_buf_size, "CacheStorageSize", (u64)nacp->cache_storage_size, true)) goto end;
/* CacheStorageJournalSize. */
if (!nacpAddU64FieldToAuthoringToolXml(&xml_buf, &xml_buf_size, "CacheStorageJournalSize", (u64)nacp->cache_storage_journal_size, true)) goto end;
/* CacheStorageDataAndJournalSizeMax. */
if (!nacpAddU64FieldToAuthoringToolXml(&xml_buf, &xml_buf_size, "CacheStorageDataAndJournalSizeMax", (u64)nacp->cache_storage_data_and_journal_size_max, true)) goto end;
/* CacheStorageIndexMax. */
if (!nacpAddU16FieldToAuthoringToolXml(&xml_buf, &xml_buf_size, "CacheStorageIndexMax", nacp->cache_storage_index_max, true)) goto end;
/* Hdcp. */
if (!nacpAddEnumFieldToAuthoringToolXml(&xml_buf, &xml_buf_size, "Hdcp", nacp->hdcp, &nacpGetHdcpString)) goto end;
/* CrashReport. */
if (!nacpAddEnumFieldToAuthoringToolXml(&xml_buf, &xml_buf_size, "CrashReport", nacp->crash_report, &nacpGetCrashReportString)) goto end;
/* RuntimeAddOnContentInstall. */
if (!nacpAddEnumFieldToAuthoringToolXml(&xml_buf, &xml_buf_size, "RuntimeAddOnContentInstall", nacp->runtime_add_on_content_install, &nacpGetRuntimeAddOnContentInstallString)) goto end;
/* PlayLogQueryableApplicationId. */
for(i = 0, count = 0; i < 0x10; i++)
{
if (!nacp->play_log_queryable_application_id[i]) continue;
if (!nacpAddU64FieldToAuthoringToolXml(&xml_buf, &xml_buf_size, "PlayLogQueryableApplicationId", nacp->play_log_queryable_application_id[i], true)) goto end;
count++;
}
if (!count && !utilsAppendFormattedStringToBuffer(&xml_buf, &xml_buf_size, " <PlayLogQueryableApplicationId />\n")) goto end;
/* PlayLogQueryCapability. */
if (!nacpAddEnumFieldToAuthoringToolXml(&xml_buf, &xml_buf_size, "PlayLogQueryCapability", nacp->play_log_query_capability, &nacpGetPlayLogQueryCapabilityString)) goto end;
/* Repair. */
if (!nacpAddBitflagFieldToAuthoringToolXml(&xml_buf, &xml_buf_size, "Repair", &(nacp->repair), sizeof(nacp->repair), NacpRepair_Count, &nacpGetRepairString)) goto end;
/* ProgramIndex. */
if (!nacpAddU16FieldToAuthoringToolXml(&xml_buf, &xml_buf_size, "ProgramIndex", nacp->program_index, false)) goto end;
/* RequiredNetworkServiceLicenseOnLaunch. */
if (!nacpAddBitflagFieldToAuthoringToolXml(&xml_buf, &xml_buf_size, "RequiredNetworkServiceLicenseOnLaunch", &(nacp->required_network_service_license_on_launch), \
sizeof(nacp->required_network_service_license_on_launch), NacpRequiredNetworkServiceLicenseOnLaunch_Count, &nacpGetRequiredNetworkServiceLicenseOnLaunchString)) 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 (!utilsAppendFormattedStringToBuffer(&xml_buf, &xml_buf_size, " <NeighborDetectionClientConfiguration>\n")) goto end;
/* SendGroupConfiguration. */
utilsGenerateHexStringFromData(key_str, sizeof(key_str), ndcc->send_group_configuration.key, sizeof(ndcc->send_group_configuration.key));
if (!utilsAppendFormattedStringToBuffer(&xml_buf, &xml_buf_size, \
" <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));
if (!utilsAppendFormattedStringToBuffer(&xml_buf, &xml_buf_size, \
" <ReceivableGroupConfiguration>\n" \
" <GroupId>0x%016lx</GroupId>\n" \
" <Key>%s</Key>\n" \
" </ReceivableGroupConfiguration>\n", \
rgc->group_id, \
key_str)) goto end;
}
if (!utilsAppendFormattedStringToBuffer(&xml_buf, &xml_buf_size, " </NeighborDetectionClientConfiguration>\n")) goto end;
} else {
if (!utilsAppendFormattedStringToBuffer(&xml_buf, &xml_buf_size, " <NeighborDetectionClientConfiguration />\n")) goto end;
}
/* JitConfiguration. */
if (!utilsAppendFormattedStringToBuffer(&xml_buf, &xml_buf_size, \
" <JitConfiguration>\n" \
" <IsEnabled>%s</IsEnabled>\n" \
" <MemorySize>0x%016lx</MemorySize>\n" \
" </JitConfiguration>\n", \
nacpGetJitConfigurationFlagString(nacp->jit_configuration.jit_configuration_flag), \
nacp->jit_configuration.memory_size)) goto end;
/* RequiredAddOnContentsSet. */
for(i = 0, count = 0; i < 0x20; i++)
{
NacpDescriptors *descriptor = &(raocsbd->descriptors[i]);
if (!descriptor->index || !descriptor->continue_set) continue;
if (!utilsAppendFormattedStringToBuffer(&xml_buf, &xml_buf_size, \
" <RequiredAddOnContentsSet>\n" \
" <Index>%u</Index>\n" \
" </RequiredAddOnContentsSet>\n", \
descriptor->index)) goto end;
count++;
}
if (!count && !utilsAppendFormattedStringToBuffer(&xml_buf, &xml_buf_size, " <RequiredAddOnContentsSet />\n")) goto end;
/* PlayReportPermission. */
if (!nacpAddEnumFieldToAuthoringToolXml(&xml_buf, &xml_buf_size, "PlayReportPermission", nacp->play_report_permission, &nacpGetPlayReportPermissionString)) goto end;
/* CrashScreenshotForProd. */
if (!nacpAddEnumFieldToAuthoringToolXml(&xml_buf, &xml_buf_size, "CrashScreenshotForProd", nacp->crash_screenshot_for_prod, &nacpGetCrashScreenshotForProdString)) goto end;
/* CrashScreenshotForDev. */
if (!nacpAddEnumFieldToAuthoringToolXml(&xml_buf, &xml_buf_size, "CrashScreenshotForDev", nacp->crash_screenshot_for_dev, &nacpGetCrashScreenshotForDevString)) goto end;
/* AccessibleLaunchRequiredVersion. */
for(i = 0, count = 0; i < 0x8; i++)
{
if (!nacp->accessible_launch_required_version.application_id[i]) continue;
if (!utilsAppendFormattedStringToBuffer(&xml_buf, &xml_buf_size, \
" <AccessibleLaunchRequiredVersion>\n" \
" <ApplicationId>0x%016lx</ApplicationId>\n" \
" </AccessibleLaunchRequiredVersion>\n", \
nacp->accessible_launch_required_version.application_id[i])) goto end;
count++;
}
if (!count && !utilsAppendFormattedStringToBuffer(&xml_buf, &xml_buf_size, " <AccessibleLaunchRequiredVersion />\n")) goto end;
/* History. Unused but kept anyway. */
if (!utilsAppendFormattedStringToBuffer(&xml_buf, &xml_buf_size, " <History />\n")) goto end;
/* RuntimeParameterDelivery. */
if (!nacpAddEnumFieldToAuthoringToolXml(&xml_buf, &xml_buf_size, "RuntimeParameterDelivery", nacp->runtime_parameter_delivery, &nacpGetRuntimeParameterDeliveryString)) goto end;
/* ApplicationId. */
if (!nacpAddU64FieldToAuthoringToolXml(&xml_buf, &xml_buf_size, "ApplicationId", nacp_ctx->nca_ctx->header.program_id, true)) goto end;
/* FilterDescriptionFilePath. Unused but kept anyway. */
if (!utilsAppendFormattedStringToBuffer(&xml_buf, &xml_buf_size, " <FilterDescriptionFilePath />\n")) goto end;
if (!(success = utilsAppendFormattedStringToBuffer(&xml_buf, &xml_buf_size, "</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("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_nacpNacpRuntimeParameterDeliveryStrings[runtime_parameter_delivery] : 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 *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 *nacpGetJitConfigurationFlagString(u64 jig_configuration_flag)
{
return (jig_configuration_flag < NacpJitConfigurationFlag_Count ? g_nacpJitConfigurationFlagStrings[jig_configuration_flag] : g_unknownString);
}
const char *nacpGetPlayReportPermissionString(u8 play_report_permission)
{
return (play_report_permission < NacpPlayReportPermission_Count ? g_nacpPlayReportPermissionStrings[play_report_permission] : 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);
}
NX_INLINE bool nacpCheckBitflagField(const void *flag, u8 flag_bitcount, u8 idx)
{
if (!flag || !flag_bitcount || !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("Invalid parameters!");
return false;
}
return (*value ? utilsAppendFormattedStringToBuffer(xml_buf, xml_buf_size, " <%s>%s</%s>\n", tag_name, value, tag_name) : \
utilsAppendFormattedStringToBuffer(xml_buf, xml_buf_size, " <%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("Invalid parameters!");
return false;
}
return utilsAppendFormattedStringToBuffer(xml_buf, xml_buf_size, " <%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)
{
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("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 (!utilsAppendFormattedStringToBuffer(xml_buf, xml_buf_size, " <%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 && !utilsAppendFormattedStringToBuffer(xml_buf, xml_buf_size, " <%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)
{
if (!xml_buf || !xml_buf_size || !tag_name || !*tag_name)
{
LOG_MSG("Invalid parameters!");
return false;
}
const char *str = (hex ? " <%s>0x%04x</%s>\n" : " <%s>%u</%s>\n");
return utilsAppendFormattedStringToBuffer(xml_buf, xml_buf_size, str, tag_name, value, tag_name);
}
static bool nacpAddU32FieldToAuthoringToolXml(char **xml_buf, u64 *xml_buf_size, const char *tag_name, u32 value, bool hex)
{
if (!xml_buf || !xml_buf_size || !tag_name || !*tag_name)
{
LOG_MSG("Invalid parameters!");
return false;
}
const char *str = (hex ? " <%s>0x%08x</%s>\n" : " <%s>%u</%s>\n");
return utilsAppendFormattedStringToBuffer(xml_buf, xml_buf_size, str, tag_name, value, tag_name);
}
static bool nacpAddU64FieldToAuthoringToolXml(char **xml_buf, u64 *xml_buf_size, const char *tag_name, u64 value, bool hex)
{
if (!xml_buf || !xml_buf_size || !tag_name || !*tag_name)
{
LOG_MSG("Invalid parameters!");
return false;
}
const char *str = (hex ? " <%s>0x%016lx</%s>\n" : " <%s>%lu</%s>\n");
return utilsAppendFormattedStringToBuffer(xml_buf, xml_buf_size, str, tag_name, value, tag_name);
}