1
0
Fork 0
mirror of https://github.com/DarkMatterCore/nxdumptool.git synced 2024-11-08 11:51:48 +00:00

Small code refactor (part 3).

* Both gamecard header and decrypted CardInfo area are now retrieved upon gamecard insertion. LAFW version is checked against the CardInfo LAFW version right afterwards.

* Expanded GameCardStatus enum to add NoGameCardPatchEnabled and LotusAsicFirmwareUpdateRequired values.

* Updated utilsReplaceIllegalCharacters() to perform replacements on a per-codepoint basis, which means that invalid multibyte UTF-8 codepoints can now be replaced with a single ASCII underscore.

* Updated utilsGeneratePath() to truncate path elements that exceed 255 UTF-8 codepoints (safe limit for FAT and NTFS filesystems).

* Heavily simplified core logic in title functions by using newly defined TitleStorage elements (which hold the NCM database/storage handles, a TitleInfo array and a title counter) instead of the old, global index-based methods.

* Simplified background gamecard title thread logic by always returning duplicated TitleInfo data to the user.

* Update title API to account for the previously mentioned changes, including functions to free duplicated title data.

* Fallback gamecard filename string now also holds the gamecard package ID whenever possible.

* Implemented HDCP patching for Control NCAs.
This commit is contained in:
Pablo Curiel 2021-05-31 21:12:15 -04:00
parent 21d1b001ee
commit 04abb342bb
18 changed files with 1300 additions and 874 deletions

View file

@ -55,6 +55,7 @@ static options_t options[] = {
{ "disable linked account requirement", 0 },
{ "enable screenshots", 0 },
{ "enable video capture", 0 },
{ "disable hdcp", 0 },
{ "output device", 0 }
};
@ -135,7 +136,8 @@ static void nspDump(TitleInfo *title_info, u64 free_space)
bool patch_sua = (options[4].val == 1);
bool patch_screenshot = (options[5].val == 1);
bool patch_video_capture = (options[6].val == 1);
UsbHsFsDevice *ums_device = (options[7].val == 0 ? NULL : &(ums_devices[options[7].val - 1]));
bool patch_hdcp = (options[7].val == 1);
UsbHsFsDevice *ums_device = (options[8].val == 0 ? NULL : &(ums_devices[options[8].val - 1]));
bool success = false;
if (ums_device && ums_device->write_protect)
@ -321,7 +323,7 @@ static void nspDump(TitleInfo *title_info, u64 free_space)
goto end;
}
if (!nacpGenerateNcaPatch(cur_nacp_ctx, patch_sua, patch_screenshot, patch_video_capture))
if (!nacpGenerateNcaPatch(cur_nacp_ctx, patch_sua, patch_screenshot, patch_video_capture, patch_hdcp))
{
consolePrint("nacp nca patch failed (%s)\n", cur_nca_ctx->content_id_str);
goto end;
@ -1087,6 +1089,7 @@ int main(int argc, char *argv[])
} else {
selected_idx = (menu == 0 ? title_idx : type_idx);
scroll = (menu == 0 ? title_scroll : type_scroll);
if (menu == 0) titleFreeUserApplicationData(&user_app_data);
}
} else
if ((btn_down & (HidNpadButton_Left | HidNpadButton_Right)) && menu == 2 && selected_idx != 0)

View file

@ -62,7 +62,8 @@ static options_t options[] = {
{ "change acid rsa key/sig", false },
{ "disable linked account requirement", false },
{ "enable screenshots", false },
{ "enable video capture", false }
{ "enable video capture", false },
{ "disable hdcp", false }
};
static const u32 options_count = MAX_ELEMENTS(options);
@ -127,6 +128,7 @@ static void dump_thread_func(void *arg)
bool patch_sua = options[4].val;
bool patch_screenshot = options[5].val;
bool patch_video_capture = options[6].val;
bool patch_hdcp = options[7].val;
bool success = false;
u8 *buf = NULL;
@ -311,7 +313,7 @@ static void dump_thread_func(void *arg)
goto end;
}
if (!nacpGenerateNcaPatch(cur_nacp_ctx, patch_sua, patch_screenshot, patch_video_capture))
if (!nacpGenerateNcaPatch(cur_nacp_ctx, patch_sua, patch_screenshot, patch_video_capture, patch_hdcp))
{
consolePrint("nacp nca patch failed (%s)\n", cur_nca_ctx->content_id_str);
goto end;
@ -1189,6 +1191,7 @@ int main(int argc, char *argv[])
} else {
selected_idx = (menu == 0 ? title_idx : type_idx);
scroll = (menu == 0 ? title_scroll : type_scroll);
if (menu == 0) titleFreeUserApplicationData(&user_app_data);
}
} else
if ((btn_down & (HidNpadButton_Left | HidNpadButton_Right)) && menu == 2 && selected_idx != 0)

View file

@ -478,6 +478,7 @@ int main(int argc, char *argv[])
{
consolePrint("\nthe selected title doesn't have available base content.\n");
utilsSleep(3);
titleFreeUserApplicationData(&user_app_data);
continue;
}
@ -682,6 +683,8 @@ out2:
if (base_nca_ctx) free(base_nca_ctx);
titleFreeUserApplicationData(&user_app_data);
if (buf) free(buf);
if (app_metadata) free(app_metadata);

View file

@ -455,6 +455,7 @@ int main(int argc, char *argv[])
} else {
selected_idx = (menu == 0 ? title_idx : nca_idx);
scroll = (menu == 0 ? title_scroll : 0);
if (menu == 0) titleFreeTitleInfo(&cur_title_info);
}
}

View file

@ -371,17 +371,39 @@ static u32 menuGetElementCount(const Menu *menu)
return cnt;
}
static void waitForGameCardAndUsb(void)
static bool waitForGameCardAndUsb(void)
{
consoleClear();
consolePrint("waiting for gamecard...\n");
u8 status = GameCardStatus_NotInserted;
while(true)
{
if (gamecardGetStatus() == GameCardStatus_InsertedAndInfoLoaded) break;
status = gamecardGetStatus();
if (status > GameCardStatus_NotInserted) break;
}
titleIsGameCardInfoUpdated();
switch(status)
{
case GameCardStatus_NoGameCardPatchEnabled:
consolePrint("\"nogc\" patch enabled, please disable it and reboot your console\n");
break;
case GameCardStatus_LotusAsicFirmwareUpdateRequired:
consolePrint("gamecard controller firmware update required, please update your console\n");
break;
case GameCardStatus_InsertedAndInfoNotLoaded:
consolePrint("unexpected I/O error occurred, please check the logfile\n");
break;
default:
break;
}
if (status != GameCardStatus_InsertedAndInfoLoaded)
{
utilsWaitForButtonPress(0);
return false;
}
consolePrint("waiting for usb session...\n");
@ -389,6 +411,8 @@ static void waitForGameCardAndUsb(void)
{
if (usbIsReady()) break;
}
return true;
}
static bool sendFileData(const char *path, void *data, size_t data_size)
@ -434,7 +458,7 @@ static bool dumpGameCardKeyArea(GameCardKeyArea *out)
static bool sendGameCardKeyAreaViaUsb(void)
{
waitForGameCardAndUsb();
if (!waitForGameCardAndUsb()) return false;
utilsChangeHomeButtonBlockStatus(false);
@ -466,7 +490,7 @@ end:
static bool sendGameCardCertificateViaUsb(void)
{
waitForGameCardAndUsb();
if (!waitForGameCardAndUsb()) return false;
utilsChangeHomeButtonBlockStatus(true);
@ -504,7 +528,7 @@ end:
static bool sendGameCardImageViaUsb(void)
{
waitForGameCardAndUsb();
if (!waitForGameCardAndUsb()) return false;
utilsChangeHomeButtonBlockStatus(true);

View file

@ -451,6 +451,7 @@ int main(int argc, char *argv[])
{
consolePrint("\nthe selected title doesn't have available base content.\n");
utilsSleep(3);
titleFreeUserApplicationData(&user_app_data);
continue;
}
@ -677,6 +678,8 @@ out2:
if (base_nca_ctx) free(base_nca_ctx);
titleFreeUserApplicationData(&user_app_data);
if (buf) free(buf);
if (app_metadata) free(app_metadata);

View file

@ -177,6 +177,7 @@ int main(int argc, char *argv[])
{
consolePrint("\nthe selected title doesn't have available base content.\n");
utilsSleep(3);
titleFreeUserApplicationData(&user_app_data);
continue;
}
@ -445,6 +446,8 @@ out2:
if (nca_ctx) free(nca_ctx);
titleFreeUserApplicationData(&user_app_data);
if (app_metadata) free(app_metadata);
out:

View file

@ -190,9 +190,13 @@ typedef struct {
NXDT_ASSERT(GameCardHeader, 0x200);
typedef enum {
GameCardStatus_NotInserted = 0,
GameCardStatus_InsertedAndInfoNotLoaded = 1, ///< Most likely related to the "nogc" patch being enabled. Means nothing at all can be done with the inserted gamecard.
GameCardStatus_InsertedAndInfoLoaded = 2
GameCardStatus_NotInserted = 0, ///< No gamecard is inserted.
GameCardStatus_NoGameCardPatchEnabled = 1, ///< A gamecard has been inserted, but the running CFW enabled the "nogc" patch at boot.
///< This triggers an error whenever fsDeviceOperatorGetGameCardHandle is called. Nothing at all can be done with the inserted gamecard.
GameCardStatus_LotusAsicFirmwareUpdateRequired = 2, ///< A gamecard has been inserted, but a LAFW update is needed before being able to read the secure storage area.
///< Operations on the normal storage area are still possible, though.
GameCardStatus_InsertedAndInfoNotLoaded = 3, ///< A gamecard has been inserted, but an unexpected error unrelated to both "nogc" patch and LAFW version occurred.
GameCardStatus_InsertedAndInfoLoaded = 4 ///< A gamecard has been inserted and all required information could be successfully retrieved from it.
} GameCardStatus;
typedef enum {

View file

@ -103,10 +103,14 @@ typedef enum {
NacpSupportedLanguage_Portuguese = BIT(10),
NacpSupportedLanguage_Russian = BIT(11),
NacpSupportedLanguage_Korean = BIT(12),
NacpSupportedLanguage_TraditionalChinese = BIT(13), ///< Old: NacpSupportedLanguage_Taiwanese.
NacpSupportedLanguage_SimplifiedChinese = BIT(14), ///< Old: NacpSupportedLanguage_Chinese.
NacpSupportedLanguage_TraditionalChinese = BIT(13),
NacpSupportedLanguage_SimplifiedChinese = BIT(14),
NacpSupportedLanguage_BrazilianPortuguese = BIT(15),
NacpSupportedLanguage_Count = 16 ///< Total values supported by this enum. Should always match NacpLanguage_Count.
NacpSupportedLanguage_Count = 16, ///< Total values supported by this enum. Should always match NacpLanguage_Count.
///< Old.
NacpSupportedLanguage_Taiwanese = NacpSupportedLanguage_TraditionalChinese,
NacpSupportedLanguage_Chinese = NacpSupportedLanguage_SimplifiedChinese
} NacpSupportedLanguage;
typedef enum {
@ -390,7 +394,8 @@ bool nacpInitializeContext(NacpContext *out, NcaContext *nca_ctx);
/// If 'patch_sua' is true, StartupUserAccount is set to None, the IsOptional bit in StartupUserAccountOption is cleared and UserAccountSwitchLock is set to Disable.
/// If 'patch_screenshot' is true, Screenshot is set to Allow.
/// If 'patch_video_capture' is true, VideoCapture is set to Enable.
bool nacpGenerateNcaPatch(NacpContext *nacp_ctx, bool patch_sua, bool patch_screenshot, bool patch_video_capture);
/// If 'patch_hdcp' is true, Hdcp is set to None.
bool nacpGenerateNcaPatch(NacpContext *nacp_ctx, bool patch_sua, bool patch_screenshot, bool patch_video_capture, bool patch_hdcp);
/// Writes data from the RomFS file entry patch in the input NacpContext to the provided buffer.
void nacpWriteNcaPatch(NacpContext *nacp_ctx, void *buf, u64 buf_size, u64 buf_offset);

View file

@ -107,8 +107,9 @@ void utilsJoinThread(Thread *thread);
/// If the buffer isn't big enough to hold both its current contents and the new formatted string, it will be resized.
__attribute__((format(printf, 3, 4))) bool utilsAppendFormattedStringToBuffer(char **dst, size_t *dst_size, const char *fmt, ...);
/// Replaces illegal FAT characters in the provided string with underscores.
/// If 'ascii_only' is set to true, all characters outside the (0x20,0x7E] range will also be replaced with underscores.
/// Replaces illegal FAT characters in the provided UTF-8 string with underscores.
/// If 'ascii_only' is set to true, all codepoints outside the (0x20,0x7E] range will also be replaced with underscores.
/// Replacements are performed on a per-codepoint basis, which means the string length can be reduced by this function.
void utilsReplaceIllegalCharacters(char *str, bool ascii_only);
/// Trims whitespace characters from the provided string.
@ -139,7 +140,10 @@ bool utilsCreateConcatenationFile(const char *path);
/// If 'create_last_element' is true, the last element from the provided path will be created as well.
void utilsCreateDirectoryTree(const char *path, bool create_last_element);
/// Returns a pointer to a dynamically allocated string that holds the full path formed by the provided arguments.
/// Returns a pointer to a dynamically allocated string that holds the full path formed by the provided arguments. Both path prefix and file extension are optional.
/// If any elements from the generated path exceed safe filesystem limits, each exceeding element will be truncated. Truncations, if needed, are performed on a per-codepoint basis (UTF-8).
/// If an extension is provided, it will always be preserved, regardless of any possible truncations being carried out.
/// Furthermore, if the full length for the generated path is >= FS_MAX_PATH, NULL will be returned.
char *utilsGeneratePath(const char *prefix, const char *filename, const char *extension);
/// Simple wrapper to sleep the current thread for a specific number of full seconds.

View file

@ -45,7 +45,7 @@ typedef struct {
u8 *icon; ///< JPEG icon data.
} TitleApplicationMetadata;
/// Generated using ncm databases.
/// Generated using ncm calls.
typedef struct _TitleInfo {
u8 storage_id; ///< NcmStorageId.
NcmContentMetaKey meta_key; ///< Used with ncm calls.
@ -91,42 +91,49 @@ NcmContentMetaDatabase *titleGetNcmDatabaseByStorageId(u8 storage_id);
/// Returns a pointer to a ncm storage handle using a NcmStorageId value.
NcmContentStorage *titleGetNcmStorageByStorageId(u8 storage_id);
/// Returns a pointer to a dynamically allocated array of pointers to TitleApplicationMetadata entries, as well as their count. The allocated buffer must be freed by the calling function.
/// Returns a pointer to a dynamically allocated array of pointers to TitleApplicationMetadata entries, as well as their count. Returns NULL if an error occurs.
/// If 'is_system' is true, TitleApplicationMetadata entries from available system titles (NcmStorageId_BuiltInSystem) will be returned.
/// Otherwise, TitleApplicationMetadata entries from user applications with available content data (NcmStorageId_BuiltInUser, NcmStorageId_SdCard, NcmStorageId_GameCard) will be returned.
/// Returns NULL if an error occurs.
/// The allocated buffer must be freed by the calling function using free().
TitleApplicationMetadata **titleGetApplicationMetadataEntries(bool is_system, u32 *out_count);
/// Returns a pointer to a TitleInfo entry with a matching storage ID and title ID.
/// Returns a pointer to a dynamically allocated TitleInfo element with a matching storage ID and title ID. Returns NULL if an error occurs.
/// If NcmStorageId_Any is used, the first entry with a matching title ID is returned.
/// Returns NULL if an error occurs.
/// Use titleFreeTitleInfo() to free the returned data.
TitleInfo *titleGetInfoFromStorageByTitleId(u8 storage_id, u64 title_id);
/// Populates a TitleUserApplicationData element using a user application ID.
/// Frees a dynamically allocated TitleInfo element.
void titleFreeTitleInfo(TitleInfo **info);
/// Populates a TitleUserApplicationData element with dynamically allocated data using a user application ID.
/// Use titleFreeUserApplicationData() to free the populated data.
bool titleGetUserApplicationData(u64 app_id, TitleUserApplicationData *out);
/// Frees data populated by titleGetUserApplicationData().
void titleFreeUserApplicationData(TitleUserApplicationData *user_app_data);
/// Returns true if orphan titles are available.
/// Orphan titles are patches or add-on contents with no NsApplicationControlData available for their parent user application ID.
bool titleAreOrphanTitlesAvailable(void);
/// Returns a pointer to a dynamically allocated array of pointers to TitleInfo entries from orphan titles, as well as their count. The allocated buffer must be freed by the calling function.
/// Returns NULL if an error occurs.
TitleInfo **titleGetInfoFromOrphanTitles(u32 *out_count);
/// Returns a pointer to a dynamically allocated array of orphan TitleInfo entries, as well as their count. Returns NULL if an error occurs.
/// Use titleFreeOrphanTitles() to free the returned data.
TitleInfo **titleGetOrphanTitles(u32 *out_count);
/// Frees orphan title info data returned by titleGetInfoFromOrphanTitles().
void titleFreeOrphanTitles(TitleInfo ***orphan_info);
/// Checks if a gamecard status update has been detected by the background gamecard title info thread (e.g. after a new gamecard has been inserted, of after the current one has been taken out).
/// If so, gamecard title info entries will be updated or freed during this call, depending on the current gamecard status.
/// If this function returns true and titleGetApplicationMetadataEntries() or titleGetInfoFromOrphanTitles() have been previously called:
/// 1. Their returned buffers should be freed.
/// If this function returns true and functions such as titleGetInfoFromStorageByTitleId(), titleGetUserApplicationData() or titleGetInfoFromOrphanTitles() have been previously called:
/// 1. Their returned data must be freed.
/// 2. They must be called again.
bool titleIsGameCardInfoUpdated(void);
/// Returns a pointer to a dynamically allocated buffer that holds a filename string suitable for output title dumps.
/// Returns NULL if an error occurs.
/// Returns a pointer to a dynamically allocated buffer that holds a filename string suitable for output title dumps. Returns NULL if an error occurs.
char *titleGenerateFileName(const TitleInfo *title_info, u8 name_convention, u8 illegal_char_replace_type);
/// Returns a pointer to a dynamically allocated buffer that holds a filename string suitable for output gamecard dumps.
/// Returns a pointer to a dynamically allocated buffer that holds a filename string suitable for output gamecard dumps. Returns NULL if an error occurs.
/// A valid gamecard must be inserted, and title info must have been loaded from it accordingly.
/// Returns NULL if an error occurs.
char *titleGenerateGameCardFileName(u8 name_convention, u8 illegal_char_replace_type);
/// Returns a pointer to a string holding the name of the provided NcmContentType value. Returns NULL if the provided value is invalid.

View file

@ -97,7 +97,13 @@ bool bfttfInitialize(void)
}
/* Initialize NCA context. */
if (!ncaInitializeContext(nca_ctx, NcmStorageId_BuiltInSystem, 0, titleGetContentInfoByTypeAndIdOffset(title_info, NcmContentType_Data, 0), NULL))
bool nca_ctx_init = ncaInitializeContext(nca_ctx, NcmStorageId_BuiltInSystem, 0, titleGetContentInfoByTypeAndIdOffset(title_info, NcmContentType_Data, 0), NULL);
/* Free title info. */
titleFreeTitleInfo(&title_info);
/* Check if NCA context initialization succeeded. */
if (!nca_ctx_init)
{
LOG_MSG("Failed to initialize Data NCA context for %016lX!", font_info->title_id);
continue;

View file

@ -55,7 +55,7 @@ typedef enum {
} GameCardCapacity;
/// Only kept for documentation purposes, not really used.
/// A copy of the gamecard header without the RSA-2048 signature and a plaintext GameCardHeaderEncryptedArea precedes this struct in FS program memory.
/// A copy of the gamecard header without the RSA-2048 signature and a plaintext GameCardInfo precedes this struct in FS program memory.
typedef struct {
u32 memory_interface_mode;
u32 asic_status;
@ -114,16 +114,18 @@ static u64 g_lafwVersion = 0;
static Thread g_gameCardDetectionThread = {0};
static UEvent g_gameCardDetectionThreadExitEvent = {0}, g_gameCardStatusChangeEvent = {0};
static bool g_gameCardDetectionThreadCreated = false, g_gameCardInserted = false, g_gameCardInfoLoaded = false;
static bool g_gameCardDetectionThreadCreated = false;
static GameCardStatus g_gameCardStatus = GameCardStatus_NotInserted;
static FsGameCardHandle g_gameCardHandle = {0};
static FsStorage g_gameCardStorage = {0};
static u8 g_gameCardStorageCurrentArea = GameCardStorageArea_None;
static u8 g_gameCardCurrentStorageArea = GameCardStorageArea_None;
static u8 *g_gameCardReadBuf = NULL;
static GameCardHeader g_gameCardHeader = {0};
static GameCardInfo g_gameCardInfoArea = {0};
static u64 g_gameCardStorageNormalAreaSize = 0, g_gameCardStorageSecureAreaSize = 0, g_gameCardStorageTotalSize = 0;
static u64 g_gameCardNormalAreaSize = 0, g_gameCardSecureAreaSize = 0, g_gameCardTotalSize = 0;
static u64 g_gameCardCapacity = 0;
static u32 g_gameCardHfsCount = 0;
@ -156,7 +158,11 @@ static void gamecardDetectionThreadFunc(void *arg);
NX_INLINE bool gamecardIsInserted(void);
static void gamecardLoadInfo(void);
static void gamecardFreeInfo(void);
static void gamecardFreeInfo(bool clear_status);
static bool gamecardReadHeader(void);
static bool gamecardGetDecryptedCardInfoArea(void);
static bool gamecardReadInitialData(GameCardKeyArea *out);
@ -170,8 +176,6 @@ static void gamecardCloseStorageArea(void);
static bool gamecardGetStorageAreasSizes(void);
NX_INLINE u64 gamecardGetCapacityFromRomSizeValue(u8 rom_size);
static bool gamecardGetDecryptedCardInfoArea(void);
static HashFileSystemContext *gamecardInitializeHashFileSystemContext(const char *name, u64 offset, u64 size, u8 *hash, u64 hash_target_offset, u32 hash_target_size);
static HashFileSystemContext *_gamecardGetHashFileSystemContext(u8 hfs_partition_type);
@ -303,7 +307,7 @@ u8 gamecardGetStatus(void)
SCOPED_LOCK(&g_gameCardMutex)
{
if (g_gameCardInserted) status = (g_gameCardInfoLoaded ? GameCardStatus_InsertedAndInfoLoaded : GameCardStatus_InsertedAndInfoNotLoaded);
if (g_gameCardInterfaceInit) status = g_gameCardStatus;
}
return status;
@ -333,7 +337,7 @@ bool gamecardGetHeader(GameCardHeader *out)
SCOPED_LOCK(&g_gameCardMutex)
{
ret = (g_gameCardInserted && g_gameCardInfoLoaded && out);
ret = (g_gameCardInterfaceInit && g_gameCardStatus == GameCardStatus_InsertedAndInfoLoaded && out);
if (ret) memcpy(out, &g_gameCardHeader, sizeof(GameCardHeader));
}
@ -346,17 +350,11 @@ bool gamecardGetCertificate(FsGameCardCertificate *out)
SCOPED_LOCK(&g_gameCardMutex)
{
if (!g_gameCardInserted || !g_gameCardHandle.value || !out) break;
if (!g_gameCardInterfaceInit || g_gameCardStatus != GameCardStatus_InsertedAndInfoLoaded || !g_gameCardHandle.value || !out) break;
/* Read the gamecard certificate using the official IPC call. */
Result rc = fsDeviceOperatorGetGameCardDeviceCertificate(&g_deviceOperator, &g_gameCardHandle, out);
if (R_FAILED(rc))
{
LOG_MSG("fsDeviceOperatorGetGameCardDeviceCertificate failed! (0x%08X)", rc);
/* Attempt to manually read the gamecard certificate. */
if (gamecardReadStorageArea(out, sizeof(FsGameCardCertificate), GAMECARD_CERTIFICATE_OFFSET)) rc = 0;
}
if (R_FAILED(rc)) LOG_MSG("fsDeviceOperatorGetGameCardDeviceCertificate failed! (0x%08X)", rc);
ret = R_SUCCEEDED(rc);
}
@ -370,8 +368,8 @@ bool gamecardGetTotalSize(u64 *out)
SCOPED_LOCK(&g_gameCardMutex)
{
ret = (g_gameCardInserted && g_gameCardInfoLoaded && out);
if (ret) *out = g_gameCardStorageTotalSize;
ret = (g_gameCardInterfaceInit && g_gameCardStatus == GameCardStatus_InsertedAndInfoLoaded && out);
if (ret) *out = g_gameCardTotalSize;
}
return ret;
@ -383,7 +381,7 @@ bool gamecardGetTrimmedSize(u64 *out)
SCOPED_LOCK(&g_gameCardMutex)
{
ret = (g_gameCardInserted && g_gameCardInfoLoaded && out);
ret = (g_gameCardInterfaceInit && g_gameCardStatus == GameCardStatus_InsertedAndInfoLoaded && out);
if (ret) *out = (sizeof(GameCardHeader) + GAMECARD_PAGE_OFFSET(g_gameCardHeader.valid_data_end_address));
}
@ -396,7 +394,7 @@ bool gamecardGetRomCapacity(u64 *out)
SCOPED_LOCK(&g_gameCardMutex)
{
ret = (g_gameCardInserted && g_gameCardInfoLoaded && out);
ret = (g_gameCardInterfaceInit && g_gameCardStatus == GameCardStatus_InsertedAndInfoLoaded && out);
if (ret) *out = g_gameCardCapacity;
}
@ -409,7 +407,7 @@ bool gamecardGetBundledFirmwareUpdateVersion(VersionType1 *out)
SCOPED_LOCK(&g_gameCardMutex)
{
if (!g_gameCardInserted || !g_gameCardHandle.value || !out) break;
if (!g_gameCardInterfaceInit || g_gameCardStatus != GameCardStatus_InsertedAndInfoLoaded || !g_gameCardHandle.value || !out) break;
u64 update_id = 0;
u32 update_version = 0;
@ -544,7 +542,7 @@ static bool gamecardGetLotusAsicFirmwareVersion(void)
if (!found)
{
LOG_MSG("Unable to locate Lotus ReadFw blob in FS .data segment!");
LOG_MSG("Unable to locate Lotus %s blob in FS .data segment!", dev_unit ? "ReadDevFw" : "ReadFw");
goto end;
}
@ -602,12 +600,10 @@ static void gamecardDetectionThreadFunc(void *arg)
/* Load gamecard info right away if a gamecard is inserted, then signal the user mode gamecard status change event. */
SCOPED_LOCK(&g_gameCardMutex)
{
g_gameCardInserted = gamecardIsInserted();
if (g_gameCardInserted) gamecardLoadInfo();
if (gamecardIsInserted()) gamecardLoadInfo();
ueventSignal(&g_gameCardStatusChangeEvent);
}
ueventSignal(&g_gameCardStatusChangeEvent);
while(true)
{
/* Wait until an event is triggered. */
@ -620,12 +616,11 @@ static void gamecardDetectionThreadFunc(void *arg)
SCOPED_LOCK(&g_gameCardMutex)
{
/* Free gamecard info before proceeding. */
gamecardFreeInfo();
gamecardFreeInfo(true);
/* Retrieve current gamecard insertion status. */
/* Only proceed if we're dealing with a status change. */
g_gameCardInserted = gamecardIsInserted();
if (g_gameCardInserted)
if (gamecardIsInserted())
{
/* Don't access the gamecard immediately to avoid conflicts with HOS / sysmodules. */
utilsSleep(GAMECARD_ACCESS_WAIT_TIME);
@ -633,15 +628,14 @@ static void gamecardDetectionThreadFunc(void *arg)
/* Load gamecard info. */
gamecardLoadInfo();
}
/* Signal user mode gamecard status change event. */
ueventSignal(&g_gameCardStatusChangeEvent);
}
/* Signal user mode gamecard status change event. */
ueventSignal(&g_gameCardStatusChangeEvent);
}
/* Free gamecard info and close gamecard handle. */
gamecardFreeInfo();
g_gameCardInserted = false;
gamecardFreeInfo(true);
threadExit();
}
@ -656,87 +650,45 @@ NX_INLINE bool gamecardIsInserted(void)
static void gamecardLoadInfo(void)
{
if (g_gameCardInfoLoaded) return;
if (g_gameCardStatus == GameCardStatus_InsertedAndInfoLoaded) return;
HashFileSystemContext *root_fs_ctx = NULL;
u32 root_fs_entry_count = 0, root_fs_name_table_size = 0;
char *root_fs_name_table = NULL;
bool dump_gamecard_header = false;
/* Set initial gamecard status. */
g_gameCardStatus = GameCardStatus_InsertedAndInfoNotLoaded;
/* Read gamecard header. */
/* This step *will* fail if the running CFW enabled the "nogc" patch. */
/* gamecardGetHandleAndStorage() takes care of updating the gamecard status accordingly if this happens. */
if (!gamecardReadHeader()) goto end;
/* Get decrypted CardInfo area from header. */
if (!gamecardGetDecryptedCardInfoArea()) goto end;
/* Check if we meet the Lotus ASIC firmware (LAFW) version requirement. */
/* Lotus treats the GameCardFwVersion field as the maximum unsupported LAFW version, instead of treating it as the minimum supported version. */
if (g_lafwVersion <= g_gameCardInfoArea.fw_version)
{
LOG_MSG("LAFW version doesn't meet gamecard requirement! (%lu <= %lu).", g_lafwVersion, g_gameCardInfoArea.fw_version);
g_gameCardStatus = GameCardStatus_LotusAsicFirmwareUpdateRequired;
goto end;
}
/* Retrieve gamecard storage area sizes. */
/* gamecardReadStorageArea() actually checks if the storage area sizes are greater than zero, so we must first perform this step. */
/* gamecardReadStorageArea() actually checks if the storage area sizes are greater than zero, so we must perform this step. */
if (!gamecardGetStorageAreasSizes())
{
LOG_MSG("Failed to retrieve gamecard storage area sizes!");
goto end;
}
/* Read gamecard header. */
if (!gamecardReadStorageArea(&g_gameCardHeader, sizeof(GameCardHeader), 0))
{
LOG_MSG("Failed to read gamecard header!");
goto end;
}
/* Check magic word from gamecard header. */
if (__builtin_bswap32(g_gameCardHeader.magic) != GAMECARD_HEAD_MAGIC)
{
LOG_MSG("Invalid gamecard header magic word! (0x%08X).", __builtin_bswap32(g_gameCardHeader.magic));
dump_gamecard_header = true;
goto end;
}
/* Get decrypted CardInfo area. */
if (!gamecardGetDecryptedCardInfoArea()) goto end;
LOG_DATA(&g_gameCardInfoArea, sizeof(GameCardInfo), "Gamecard CardInfo area dump:");
/* Check if we meet the Lotus ASIC firmware (LAFW) version requirement. */
/* Lotus treats the GameCardFwVersion field as the maximum unsupported LAFW version, instead of treating it as the minimum supported version. */
/* TODO: move this check somewhere else. We're supposed to do it only if things go south while reading gamecard data. */
if (g_lafwVersion <= g_gameCardInfoArea.fw_version)
{
LOG_MSG("LAFW version not supported by the inserted gamecard (%lu <= %lu).", g_lafwVersion, g_gameCardInfoArea.fw_version);
goto end;
}
/* Get gamecard capacity. */
g_gameCardCapacity = gamecardGetCapacityFromRomSizeValue(g_gameCardHeader.rom_size);
if (!g_gameCardCapacity)
{
LOG_MSG("Invalid gamecard capacity value! (0x%02X).", g_gameCardHeader.rom_size);
dump_gamecard_header = true;
goto end;
}
@ -744,7 +696,7 @@ static void gamecardLoadInfo(void)
{
/* The total size for the secure storage area is maxed out under SX OS. */
/* Let's try to calculate it manually. */
g_gameCardStorageSecureAreaSize = (g_gameCardCapacity - (g_gameCardStorageNormalAreaSize + GAMECARD_UNUSED_AREA_SIZE(g_gameCardCapacity)));
g_gameCardSecureAreaSize = (g_gameCardCapacity - (g_gameCardNormalAreaSize + GAMECARD_UNUSED_AREA_SIZE(g_gameCardCapacity)));
}
/* Initialize Hash FS context for the root partition. */
@ -787,30 +739,29 @@ static void gamecardLoadInfo(void)
if (!g_gameCardHfsCtx[i + 1]) goto end;
}
g_gameCardInfoLoaded = true;
/* Update gamecard status. */
g_gameCardStatus = GameCardStatus_InsertedAndInfoLoaded;
end:
if (!g_gameCardInfoLoaded)
if (g_gameCardStatus != GameCardStatus_InsertedAndInfoLoaded)
{
if (dump_gamecard_header) LOG_DATA(&g_gameCardHeader, sizeof(GameCardHeader), "Gamecard header dump:");
if (!g_gameCardHfsCtx && root_fs_ctx)
{
hfsFreeContext(root_fs_ctx);
free(root_fs_ctx);
}
gamecardFreeInfo();
gamecardFreeInfo(false);
}
}
static void gamecardFreeInfo(void)
static void gamecardFreeInfo(bool clear_status)
{
memset(&g_gameCardHeader, 0, sizeof(GameCardHeader));
memset(&g_gameCardInfoArea, 0, sizeof(GameCardInfo));
g_gameCardStorageNormalAreaSize = g_gameCardStorageSecureAreaSize = g_gameCardStorageTotalSize = 0;
g_gameCardNormalAreaSize = g_gameCardSecureAreaSize = g_gameCardTotalSize = 0;
g_gameCardCapacity = 0;
@ -834,12 +785,70 @@ static void gamecardFreeInfo(void)
gamecardCloseStorageArea();
g_gameCardInfoLoaded = false;
if (clear_status) g_gameCardStatus = GameCardStatus_NotInserted;
}
static bool gamecardReadHeader(void)
{
/* Open normal storage area. */
if (!gamecardOpenStorageArea(GameCardStorageArea_Normal))
{
LOG_MSG("Failed to open normal storage area!");
return false;
}
/* Read gamecard header. */
/* This step doesn't rely on gamecardReadStorageArea() because of its dependence on storage area sizes (which we haven't retrieved). */
Result rc = fsStorageRead(&g_gameCardStorage, 0, &g_gameCardHeader, sizeof(GameCardHeader));
if (R_FAILED(rc))
{
LOG_MSG("fsStorageRead failed to read gamecard header! (0x%08X).", rc);
return false;
}
//LOG_DATA(&g_gameCardHeader, sizeof(GameCardHeader), "Gamecard header dump:");
/* Check magic word from gamecard header. */
if (__builtin_bswap32(g_gameCardHeader.magic) != GAMECARD_HEAD_MAGIC)
{
LOG_MSG("Invalid gamecard header magic word! (0x%08X).", __builtin_bswap32(g_gameCardHeader.magic));
return false;
}
return true;
}
static bool gamecardGetDecryptedCardInfoArea(void)
{
const u8 *card_info_key = NULL;
u8 card_info_iv[AES_128_KEY_SIZE] = {0};
Aes128CbcContext aes_ctx = {0};
/* Retrieve CardInfo area key. */
card_info_key = keysGetGameCardInfoKey();
if (!card_info_key)
{
LOG_MSG("Failed to retrieve CardInfo area key!");
return false;
}
/* Reverse CardInfo IV. */
for(u8 i = 0; i < AES_128_KEY_SIZE; i++) card_info_iv[i] = g_gameCardHeader.card_info_iv[AES_128_KEY_SIZE - i - 1];
/* Initialize AES-128-CBC context. */
aes128CbcContextCreate(&aes_ctx, card_info_key, card_info_iv, false);
/* Decrypt CardInfo area. */
aes128CbcDecrypt(&aes_ctx, &g_gameCardInfoArea, &(g_gameCardHeader.card_info), sizeof(GameCardInfo));
//LOG_DATA(&g_gameCardInfoArea, sizeof(GameCardInfo), "Gamecard CardInfo area dump:");
return true;
}
static bool gamecardReadInitialData(GameCardKeyArea *out)
{
if (!g_gameCardInserted || !g_gameCardInfoLoaded || !out)
if (!g_gameCardInterfaceInit || g_gameCardStatus != GameCardStatus_InsertedAndInfoLoaded || !out)
{
LOG_MSG("Invalid parameters!");
return false;
@ -891,7 +900,7 @@ static bool gamecardReadInitialData(GameCardKeyArea *out)
static bool gamecardGetHandleAndStorage(u32 partition)
{
if (!g_gameCardInserted || partition > 1)
if (g_gameCardStatus < GameCardStatus_InsertedAndInfoNotLoaded || partition > 1)
{
LOG_MSG("Invalid parameters!");
return false;
@ -906,7 +915,7 @@ static bool gamecardGetHandleAndStorage(u32 partition)
if (R_FAILED(rc)) svcSleepThread(100000000);
/* First, let's try to retrieve a gamecard handle. */
/* This can return 0x140A02 if the "nogc" patch is enabled by the running CFW. */
/* This can return an error if the "nogc" patch is enabled by the running CFW (most commonly 0x140A02). */
rc = fsDeviceOperatorGetGameCardHandle(&g_deviceOperator, &g_gameCardHandle);
if (R_FAILED(rc))
{
@ -927,7 +936,11 @@ static bool gamecardGetHandleAndStorage(u32 partition)
break;
}
if (R_FAILED(rc)) LOG_MSG("fsDeviceOperatorGetGameCardHandle / fsOpenGameCardStorage failed! (0x%08X).", rc);
if (R_FAILED(rc))
{
LOG_MSG("fsDeviceOperatorGetGameCardHandle / fsOpenGameCardStorage failed! (0x%08X).", rc);
if (g_gameCardStatus == GameCardStatus_InsertedAndInfoNotLoaded && partition == 0) g_gameCardStatus = GameCardStatus_NoGameCardPatchEnabled;
}
return R_SUCCEEDED(rc);
}
@ -942,14 +955,14 @@ NX_INLINE void gamecardCloseHandle(void)
static bool gamecardOpenStorageArea(u8 area)
{
if (!g_gameCardInserted || (area != GameCardStorageArea_Normal && area != GameCardStorageArea_Secure))
if (g_gameCardStatus < GameCardStatus_InsertedAndInfoNotLoaded || (area != GameCardStorageArea_Normal && area != GameCardStorageArea_Secure))
{
LOG_MSG("Invalid parameters!");
return false;
}
/* Return right away if a valid handle has already been retrieved and the desired gamecard storage area is currently open. */
if (g_gameCardHandle.value && serviceIsActive(&(g_gameCardStorage.s)) && g_gameCardStorageCurrentArea == area) return true;
if (g_gameCardHandle.value && serviceIsActive(&(g_gameCardStorage.s)) && g_gameCardCurrentStorageArea == area) return true;
/* Close both gamecard handle and open storage area. */
gamecardCloseStorageArea();
@ -962,14 +975,14 @@ static bool gamecardOpenStorageArea(u8 area)
}
/* Update current gamecard storage area. */
g_gameCardStorageCurrentArea = area;
g_gameCardCurrentStorageArea = area;
return true;
}
static bool gamecardReadStorageArea(void *out, u64 read_size, u64 offset)
{
if (!g_gameCardInserted || !g_gameCardStorageNormalAreaSize || !g_gameCardStorageSecureAreaSize || !out || !read_size || (offset + read_size) > g_gameCardStorageTotalSize)
if (g_gameCardStatus < GameCardStatus_InsertedAndInfoNotLoaded || !g_gameCardNormalAreaSize || !g_gameCardSecureAreaSize || !out || !read_size || (offset + read_size) > g_gameCardTotalSize)
{
LOG_MSG("Invalid parameters!");
return false;
@ -977,21 +990,21 @@ static bool gamecardReadStorageArea(void *out, u64 read_size, u64 offset)
Result rc = 0;
u8 *out_u8 = (u8*)out;
u8 area = (offset < g_gameCardStorageNormalAreaSize ? GameCardStorageArea_Normal : GameCardStorageArea_Secure);
u8 area = (offset < g_gameCardNormalAreaSize ? GameCardStorageArea_Normal : GameCardStorageArea_Secure);
bool success = false;
/* Handle reads that span both the normal and secure gamecard storage areas. */
if (area == GameCardStorageArea_Normal && (offset + read_size) > g_gameCardStorageNormalAreaSize)
if (area == GameCardStorageArea_Normal && (offset + read_size) > g_gameCardNormalAreaSize)
{
/* Calculate normal storage area size difference. */
u64 diff_size = (g_gameCardStorageNormalAreaSize - offset);
u64 diff_size = (g_gameCardNormalAreaSize - offset);
/* Read normal storage area data. */
if (!gamecardReadStorageArea(out_u8, diff_size, offset)) goto end;
/* Adjust variables to read right from the start of the secure storage area. */
read_size -= diff_size;
offset = g_gameCardStorageNormalAreaSize;
offset = g_gameCardNormalAreaSize;
out_u8 += diff_size;
area = GameCardStorageArea_Secure;
}
@ -1004,8 +1017,8 @@ static bool gamecardReadStorageArea(void *out, u64 read_size, u64 offset)
goto end;
}
/* Calculate appropiate storage area offset and retrieve the right storage area pointer. */
u64 base_offset = (area == GameCardStorageArea_Normal ? offset : (offset - g_gameCardStorageNormalAreaSize));
/* Calculate proper storage area offset. */
u64 base_offset = (area == GameCardStorageArea_Normal ? offset : (offset - g_gameCardNormalAreaSize));
if (!(base_offset % GAMECARD_PAGE_SIZE) && !(read_size % GAMECARD_PAGE_SIZE))
{
@ -1054,17 +1067,11 @@ static void gamecardCloseStorageArea(void)
gamecardCloseHandle();
g_gameCardStorageCurrentArea = GameCardStorageArea_None;
g_gameCardCurrentStorageArea = GameCardStorageArea_None;
}
static bool gamecardGetStorageAreasSizes(void)
{
if (!g_gameCardInserted)
{
LOG_MSG("Gamecard not inserted!");
return false;
}
for(u8 i = 0; i < 2; i++)
{
Result rc = 0;
@ -1084,19 +1091,18 @@ static bool gamecardGetStorageAreasSizes(void)
if (R_FAILED(rc) || !area_size)
{
LOG_MSG("fsStorageGetSize failed to retrieve %s storage area size! (0x%08X).", GAMECARD_STORAGE_AREA_NAME(area), rc);
g_gameCardStorageNormalAreaSize = g_gameCardStorageSecureAreaSize = g_gameCardStorageTotalSize = 0;
return false;
}
if (area == GameCardStorageArea_Normal)
{
g_gameCardStorageNormalAreaSize = area_size;
g_gameCardNormalAreaSize = area_size;
} else {
g_gameCardStorageSecureAreaSize = area_size;
g_gameCardSecureAreaSize = area_size;
}
}
g_gameCardStorageTotalSize = (g_gameCardStorageNormalAreaSize + g_gameCardStorageSecureAreaSize);
g_gameCardTotalSize = (g_gameCardNormalAreaSize + g_gameCardSecureAreaSize);
return true;
}
@ -1132,39 +1138,6 @@ NX_INLINE u64 gamecardGetCapacityFromRomSizeValue(u8 rom_size)
return capacity;
}
static bool gamecardGetDecryptedCardInfoArea(void)
{
const u8 *card_info_key = NULL;
u8 card_info_iv[AES_128_KEY_SIZE] = {0};
Aes128CbcContext aes_ctx = {0};
/* Retrieve CardInfo area key. */
card_info_key = keysGetGameCardInfoKey();
if (!card_info_key)
{
LOG_MSG("Failed to retrieve CardInfo area key!");
return false;
}
/* Reverse CardInfo IV. */
for(u8 i = 0; i < AES_128_KEY_SIZE; i++) card_info_iv[i] = g_gameCardHeader.card_info_iv[AES_128_KEY_SIZE - i - 1];
/* Initialize AES-128-CBC context. */
aes128CbcContextCreate(&aes_ctx, card_info_key, card_info_iv, false);
/* Decrypt CardInfo area. */
aes128CbcDecrypt(&aes_ctx, &g_gameCardInfoArea, &(g_gameCardHeader.card_info), sizeof(GameCardInfo));
/* Verify update ID. */
if (utilsGetCustomFirmwareType() != UtilsCustomFirmwareType_SXOS && g_gameCardInfoArea.upp_id != GAMECARD_UPDATE_TID)
{
LOG_MSG("Failed to decrypt CardInfo area!");
return false;
}
return true;
}
static HashFileSystemContext *gamecardInitializeHashFileSystemContext(const char *name, u64 offset, u64 size, u8 *hash, u64 hash_target_offset, u32 hash_target_size)
{
u32 i = 0, magic = 0;
@ -1175,7 +1148,7 @@ static HashFileSystemContext *gamecardInitializeHashFileSystemContext(const char
bool success = false, dump_fs_header = false;
if ((name && !*name) || offset < (GAMECARD_CERTIFICATE_OFFSET + sizeof(FsGameCardCertificate)) || !IS_ALIGNED(offset, GAMECARD_PAGE_SIZE) || \
(size && (!IS_ALIGNED(size, GAMECARD_PAGE_SIZE) || (offset + size) > g_gameCardStorageTotalSize)))
(size && (!IS_ALIGNED(size, GAMECARD_PAGE_SIZE) || (offset + size) > g_gameCardTotalSize)))
{
LOG_MSG("Invalid parameters!");
goto end;
@ -1302,7 +1275,7 @@ static HashFileSystemContext *_gamecardGetHashFileSystemContext(u8 hfs_partition
HashFileSystemContext *fs_ctx = NULL;
const char *partition_name = NULL;
if (!g_gameCardInserted || !g_gameCardInfoLoaded || !g_gameCardHfsCount || !g_gameCardHfsCtx || hfs_partition_type >= GameCardHashFileSystemPartitionType_Count)
if (!g_gameCardInterfaceInit || g_gameCardStatus != GameCardStatus_InsertedAndInfoLoaded || !g_gameCardHfsCount || !g_gameCardHfsCtx || hfs_partition_type >= GameCardHashFileSystemPartitionType_Count)
{
LOG_MSG("Invalid parameters!");
goto end;

View file

@ -338,7 +338,7 @@ end:
return success;
}
bool nacpGenerateNcaPatch(NacpContext *nacp_ctx, bool patch_sua, bool patch_screenshot, bool patch_video_capture)
bool nacpGenerateNcaPatch(NacpContext *nacp_ctx, bool patch_sua, bool patch_screenshot, bool patch_video_capture, bool patch_hdcp)
{
if (!nacpIsValidContext(nacp_ctx))
{
@ -350,7 +350,7 @@ bool nacpGenerateNcaPatch(NacpContext *nacp_ctx, bool patch_sua, bool patch_scre
u8 nacp_hash[SHA256_HASH_SIZE] = {0};
/* Check if we're not patching anything. */
if (!patch_sua && !patch_screenshot && !patch_video_capture) return true;
if (!patch_sua && !patch_screenshot && !patch_video_capture && !patch_hdcp) return true;
/* Patch StartupUserAccount, StartupUserAccountOption and UserAccountSwitchLock. */
if (patch_sua)
@ -366,6 +366,9 @@ bool nacpGenerateNcaPatch(NacpContext *nacp_ctx, bool patch_sua, bool patch_scre
/* 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)))

View file

@ -31,6 +31,9 @@
#include "bfttf.h"
#include "fatfs/ff.h"
/* Reference: https://docs.microsoft.com/en-us/windows/win32/fileio/filesystem-functionality-comparison#limits. */
#define NT_MAX_FILENAME_LENGTH 255
/* Global variables. */
static bool g_resourcesInit = false;
@ -78,6 +81,8 @@ static void utilsOverclockSystemAppletHook(AppletHookType hook, void *param);
static void utilsPrintConsoleError(void);
static size_t utilsGetUtf8CodepointCount(const char *str, size_t str_size, size_t cp_limit, size_t *last_cp_pos);
bool utilsInitializeResources(const int program_argc, const char **program_argv)
{
Result rc = 0;
@ -445,14 +450,32 @@ end:
void utilsReplaceIllegalCharacters(char *str, bool ascii_only)
{
size_t strsize = 0;
size_t str_size = 0, cur_pos = 0;
if (!str || !(strsize = strlen(str))) return;
if (!str || !(str_size = strlen(str))) return;
for(size_t i = 0; i < strsize; i++)
u8 *ptr1 = (u8*)str, *ptr2 = ptr1;
ssize_t units = 0;
u32 code = 0;
while(cur_pos < str_size)
{
if (memchr(g_illegalFileSystemChars, str[i], g_illegalFileSystemCharsLength) || str[i] < 0x20 || (!ascii_only && str[i] == 0x7F) || (ascii_only && str[i] >= 0x7F)) str[i] = '_';
units = decode_utf8(&code, ptr1);
if (units < 0) break;
if (memchr(g_illegalFileSystemChars, (int)code, g_illegalFileSystemCharsLength) || code < 0x20 || (!ascii_only && code == 0x7F) || (ascii_only && code >= 0x7F))
{
*ptr2++ = '_';
} else {
if (ptr2 != ptr1) memmove(ptr2, ptr1, (size_t)units);
ptr2 += units;
}
ptr1 += units;
cur_pos += (size_t)units;
}
*ptr2 = '\0';
}
void utilsTrimString(char *str)
@ -568,10 +591,10 @@ bool utilsCreateConcatenationFile(const char *path)
return false;
}
/* Safety check: remove any existant file/directory at the destination path. */
/* Safety measure: remove any existant file/directory at the destination path. */
utilsRemoveConcatenationFile(path);
/* Create ConcatenationFile */
/* Create ConcatenationFile. */
/* If the call succeeds, the caller function will be able to operate on this file using stdio calls. */
Result rc = fsdevCreateFile(path, 0, FsCreateOption_BigFile);
if (R_FAILED(rc)) LOG_MSG("fsdevCreateFile failed for \"%s\"! (0x%08X).", path, rc);
@ -604,25 +627,113 @@ void utilsCreateDirectoryTree(const char *path, bool create_last_element)
char *utilsGeneratePath(const char *prefix, const char *filename, const char *extension)
{
if (!filename || !*filename || !extension || !*extension)
if (!filename || !*filename)
{
LOG_MSG("Invalid parameters!");
return NULL;
}
char *path = NULL;
size_t path_len = (strlen(filename) + strlen(extension) + 1);
if (prefix && *prefix) path_len += strlen(prefix);
bool use_prefix = (prefix && *prefix);
size_t prefix_len = (use_prefix ? strlen(prefix) : 0);
bool append_path_sep = (use_prefix && prefix[prefix_len - 1] != '/');
if (!(path = calloc(path_len, sizeof(char))))
bool use_extension = (extension && *extension);
size_t extension_len = (use_extension ? strlen(extension) : 0);
bool append_dot = (use_extension && *extension != '.');
size_t path_len = (prefix_len + strlen(filename) + extension_len);
if (append_path_sep) path_len++;
if (append_dot) path_len++;
char *path = NULL, *ptr1 = NULL, *ptr2 = NULL;
bool filename_only = false, success = false;
/* Allocate memory for the output path. */
if (!(path = calloc(path_len + 1, sizeof(char))))
{
LOG_MSG("Failed to allocate 0x%lX bytes for output path!", path_len);
return NULL;
goto end;
}
if (prefix && *prefix) strcat(path, prefix);
/* Generate output path. */
if (use_prefix) strcat(path, prefix);
if (append_path_sep) strcat(path, "/");
strcat(path, filename);
strcat(path, extension);
if (append_dot) strcat(path, ".");
if (use_extension) strcat(path, extension);
/* Retrieve pointer to the first path separator. */
ptr1 = strchr(path, '/');
if (!ptr1)
{
filename_only = true;
ptr1 = path;
}
/* Make sure each path element doesn't exceed NT_MAX_FILENAME_LENGTH. */
while(ptr1)
{
/* End loop if we find a NULL terminator. */
if (!filename_only && !*ptr1++) break;
/* Get pointer to next path separator. */
ptr2 = strchr(ptr1, '/');
/* Get current path element size. */
size_t element_size = (ptr2 ? (size_t)(ptr2 - ptr1) : (path_len - (size_t)(ptr1 - path)));
/* Get UTF-8 codepoint count. */
/* Use NT_MAX_FILENAME_LENGTH as the codepoint count limit. */
size_t last_cp_pos = 0;
size_t cp_count = utilsGetUtf8CodepointCount(ptr1, element_size, NT_MAX_FILENAME_LENGTH, &last_cp_pos);
if (cp_count > NT_MAX_FILENAME_LENGTH)
{
if (ptr2)
{
/* Truncate current element by moving the rest of the path to the current position. */
memmove(ptr1 + last_cp_pos, ptr2, path_len - (size_t)(ptr2 - path));
/* Update pointer. */
ptr2 -= (element_size - last_cp_pos);
} else
if (use_extension)
{
/* Truncate last element. Make sure to preserve the provided file extension. */
size_t diff = extension_len;
if (append_dot) diff++;
if (diff >= last_cp_pos)
{
LOG_MSG("File extension length is >= truncated filename length! (0x%lX >= 0x%lX) (#1).", diff, last_cp_pos);
goto end;
}
memmove(ptr1 + last_cp_pos - diff, ptr1 + element_size - diff, diff);
}
path_len -= (element_size - last_cp_pos);
path[path_len] = '\0';
}
ptr1 = ptr2;
}
/* Check if the full length for the generated path is >= FS_MAX_PATH. */
if (path_len >= FS_MAX_PATH)
{
LOG_MSG("Generated path length is >= FS_MAX_PATH! (0x%lX).", path_len);
goto end;
}
/* Update flag. */
success = true;
end:
if (!success && path)
{
free(path);
path = NULL;
}
return path;
}
@ -759,3 +870,26 @@ static void utilsPrintConsoleError(void)
consoleExit(NULL);
}
static size_t utilsGetUtf8CodepointCount(const char *str, size_t str_size, size_t cp_limit, size_t *last_cp_pos)
{
if (!str || !*str || !str_size || (!cp_limit && last_cp_pos) || (cp_limit && !last_cp_pos)) return 0;
u32 code = 0;
ssize_t units = 0;
size_t cur_pos = 0, cp_count = 0;
const u8 *str_u8 = (const u8*)str;
while(cur_pos < str_size)
{
units = decode_utf8(&code, str_u8 + cur_pos);
size_t new_pos = (cur_pos + (size_t)units);
if (units < 0 || !code || new_pos > str_size) break;
cp_count++;
cur_pos = new_pos;
if (cp_limit && last_cp_pos && cp_count < cp_limit) *last_cp_pos = cur_pos;
}
return cp_count;
}

View file

@ -459,9 +459,13 @@ bool romfsGeneratePathFromDirectoryEntry(RomFileSystemContext *ctx, RomFileSyste
strncat(out_path, (*cur_dir_entry)->name, (*cur_dir_entry)->name_length);
path_len++;
if (illegal_char_replace_type) utilsReplaceIllegalCharacters(out_path + path_len, illegal_char_replace_type == RomFileSystemPathIllegalCharReplaceType_KeepAsciiCharsOnly);
path_len += (*cur_dir_entry)->name_length;
if (illegal_char_replace_type)
{
utilsReplaceIllegalCharacters(out_path + path_len, illegal_char_replace_type == RomFileSystemPathIllegalCharReplaceType_KeepAsciiCharsOnly);
path_len += strlen(out_path + path_len);
} else {
path_len += (*cur_dir_entry)->name_length;
}
}
success = true;

File diff suppressed because it is too large Load diff

View file

@ -8,14 +8,12 @@ todo:
usb: improve usbIsReady
title: orphan system titles
title: come up with a better way to handle gamecard titles
title: more functions for title lookup? (filters, patches / aoc, etc.)
title: more functions for content lookup? (based on id)
title: parse the update partition from gamecards (if available) to generate ncmcontentinfo data for all update titles
title: manually retrieve version string from control data while generating filenames?
gamecard: functions to display filelist
gamecard: move around code to perform the lafw version check at the places we need
pfs0: functions to display filelist