mirror of
https://github.com/DarkMatterCore/nxdumptool.git
synced 2024-12-23 00:52:10 +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:
parent
21d1b001ee
commit
04abb342bb
18 changed files with 1300 additions and 874 deletions
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)))
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
1542
source/core/title.c
1542
source/core/title.c
File diff suppressed because it is too large
Load diff
4
todo.txt
4
todo.txt
|
@ -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
|
||||
|
||||
|
|
Loading…
Reference in a new issue