2020-04-16 01:06:41 +01:00
/*
2020-07-03 10:31:22 +01:00
* gamecard . c
2020-04-16 01:06:41 +01:00
*
2020-12-23 17:48:57 +00:00
* Copyright ( c ) 2020 - 2021 , DarkMatterCore < pabloacurielz @ gmail . com > .
2020-07-03 10:31:22 +01:00
*
* This file is part of nxdumptool ( https : //github.com/DarkMatterCore/nxdumptool).
*
* nxdumptool is free software ; you can redistribute it and / or modify it
2020-04-16 01:06:41 +01:00
* under the terms and conditions of the GNU General Public License ,
* version 2 , as published by the Free Software Foundation .
*
2020-07-03 10:31:22 +01:00
* nxdumptool is distributed in the hope it will be useful , but WITHOUT
2020-04-16 01:06:41 +01:00
* ANY WARRANTY ; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE . See the GNU General Public License for
* more details .
*
* You should have received a copy of the GNU General Public License
* along with this program . If not , see < http : //www.gnu.org/licenses/>.
*/
2020-04-15 06:59:12 +01:00
# include "utils.h"
2020-07-13 07:36:17 +01:00
# include "mem.h"
2020-07-03 10:31:22 +01:00
# include "gamecard.h"
2020-04-15 06:59:12 +01:00
2020-07-06 01:10:07 +01:00
# define GAMECARD_HFS0_MAGIC 0x48465330 /* "HFS0". */
2020-04-24 10:38:13 +01:00
2020-07-06 01:10:07 +01:00
# define GAMECARD_READ_BUFFER_SIZE 0x800000 /* 8 MiB. */
2020-04-16 05:37:16 +01:00
2020-07-06 01:10:07 +01:00
# define GAMECARD_ACCESS_WAIT_TIME 3 /* Seconds. */
2020-04-15 06:59:12 +01:00
2020-07-17 19:42:48 +01:00
# define GAMECARD_UNUSED_AREA_BLOCK_SIZE 0x24
# define GAMECARD_UNUSED_AREA_SIZE(x) (((x) / GAMECARD_MEDIA_UNIT_SIZE) * GAMECARD_UNUSED_AREA_BLOCK_SIZE)
2020-04-15 06:59:12 +01:00
2020-04-15 21:50:07 +01:00
# define GAMECARD_STORAGE_AREA_NAME(x) ((x) == GameCardStorageArea_Normal ? "normal" : ((x) == GameCardStorageArea_Secure ? "secure" : "none"))
/* Type definitions. */
2021-03-07 23:22:49 +00:00
typedef enum {
GameCardStorageArea_None = 0 ,
GameCardStorageArea_Normal = 1 ,
GameCardStorageArea_Secure = 2
} GameCardStorageArea ;
typedef enum {
GameCardCapacity_1GiB = BIT_LONG ( 30 ) ,
GameCardCapacity_2GiB = BIT_LONG ( 31 ) ,
GameCardCapacity_4GiB = BIT_LONG ( 32 ) ,
GameCardCapacity_8GiB = BIT_LONG ( 33 ) ,
GameCardCapacity_16GiB = BIT_LONG ( 34 ) ,
GameCardCapacity_32GiB = BIT_LONG ( 35 )
} GameCardCapacity ;
2020-07-22 09:03:28 +01:00
/// 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.
2020-07-13 07:36:17 +01:00
typedef struct {
u32 memory_interface_mode ;
u32 asic_status ;
u8 card_id_area [ 0x48 ] ;
u8 reserved [ 0x1B0 ] ;
FsGameCardCertificate certificate ;
2020-07-15 23:50:34 +01:00
GameCardInitialData initial_data ;
2020-07-13 07:36:17 +01:00
} GameCardSecurityInformation ;
2020-04-15 21:50:07 +01:00
/* Global variables. */
2021-03-07 23:22:49 +00:00
static Mutex g_gameCardMutex = 0 ;
static bool g_gameCardInterfaceInit = false ;
2020-05-03 00:40:50 +01:00
2020-04-15 06:59:12 +01:00
static FsDeviceOperator g_deviceOperator = { 0 } ;
static FsEventNotifier g_gameCardEventNotifier = { 0 } ;
static Event g_gameCardKernelEvent = { 0 } ;
static bool g_openDeviceOperator = false , g_openEventNotifier = false , g_loadKernelEvent = false ;
2020-08-18 06:04:13 +01:00
static Thread g_gameCardDetectionThread = { 0 } ;
2020-05-03 09:40:08 +01:00
static UEvent g_gameCardDetectionThreadExitEvent = { 0 } , g_gameCardStatusChangeEvent = { 0 } ;
2020-04-15 06:59:12 +01:00
static bool g_gameCardDetectionThreadCreated = false , g_gameCardInserted = false , g_gameCardInfoLoaded = false ;
static FsGameCardHandle g_gameCardHandle = { 0 } ;
2020-04-15 21:50:07 +01:00
static FsStorage g_gameCardStorage = { 0 } ;
static u8 g_gameCardStorageCurrentArea = GameCardStorageArea_None ;
2020-04-15 06:59:12 +01:00
static u8 * g_gameCardReadBuf = NULL ;
static GameCardHeader g_gameCardHeader = { 0 } ;
2020-10-21 05:27:48 +01:00
static u64 g_gameCardStorageNormalAreaSize = 0 , g_gameCardStorageSecureAreaSize = 0 , g_gameCardStorageTotalSize = 0 ;
2020-04-16 05:37:16 +01:00
static u64 g_gameCardCapacity = 0 ;
2020-04-15 06:59:12 +01:00
2021-03-07 23:22:49 +00:00
static u32 g_gameCardHfsCount = 0 ;
static HashFileSystemContext * * g_gameCardHfsCtx = NULL ;
2020-04-15 06:59:12 +01:00
2020-07-13 07:36:17 +01:00
static MemoryLocation g_fsProgramMemory = {
. program_id = FS_SYSMODULE_TID ,
. mask = 0 ,
. data = NULL ,
. data_size = 0
} ;
2020-07-29 22:02:21 +01:00
static const char * g_gameCardHfsPartitionNames [ ] = {
[ GameCardHashFileSystemPartitionType_Root ] = " root " ,
[ GameCardHashFileSystemPartitionType_Update ] = " update " ,
[ GameCardHashFileSystemPartitionType_Logo ] = " logo " ,
[ GameCardHashFileSystemPartitionType_Normal ] = " normal " ,
[ GameCardHashFileSystemPartitionType_Secure ] = " secure " ,
2020-10-03 18:09:29 +01:00
[ GameCardHashFileSystemPartitionType_Boot ] = " boot "
2020-07-29 22:02:21 +01:00
} ;
2020-04-15 21:50:07 +01:00
/* Function prototypes. */
2020-04-15 06:59:12 +01:00
static bool gamecardCreateDetectionThread ( void ) ;
static void gamecardDestroyDetectionThread ( void ) ;
2020-08-18 06:04:13 +01:00
static void gamecardDetectionThreadFunc ( void * arg ) ;
2020-04-15 06:59:12 +01:00
2020-04-26 09:35:01 +01:00
NX_INLINE bool gamecardIsInserted ( void ) ;
2020-04-15 06:59:12 +01:00
static void gamecardLoadInfo ( void ) ;
static void gamecardFreeInfo ( void ) ;
2020-08-01 06:17:08 +01:00
static bool gamecardReadInitialData ( GameCardKeyArea * out ) ;
2020-07-13 07:36:17 +01:00
2020-07-17 06:01:31 +01:00
static bool gamecardGetHandleAndStorage ( u32 partition ) ;
2020-04-26 09:35:01 +01:00
NX_INLINE void gamecardCloseHandle ( void ) ;
2020-04-15 06:59:12 +01:00
2020-04-15 21:50:07 +01:00
static bool gamecardOpenStorageArea ( u8 area ) ;
2020-04-17 22:59:05 +01:00
static bool gamecardReadStorageArea ( void * out , u64 read_size , u64 offset , bool lock ) ;
2020-04-15 21:50:07 +01:00
static void gamecardCloseStorageArea ( void ) ;
2020-04-15 06:59:12 +01:00
2020-04-16 05:37:16 +01:00
static bool gamecardGetStorageAreasSizes ( void ) ;
2020-04-26 09:35:01 +01:00
NX_INLINE u64 gamecardGetCapacityFromRomSizeValue ( u8 rom_size ) ;
2020-04-15 06:59:12 +01:00
2021-03-07 23:22:49 +00:00
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 ) ;
2020-04-16 11:13:11 +01:00
2020-05-03 00:40:50 +01:00
bool gamecardInitialize ( void )
{
2021-03-07 23:22:49 +00:00
mutexLock ( & g_gameCardMutex ) ;
2020-05-03 00:40:50 +01:00
Result rc = 0 ;
2021-03-07 23:22:49 +00:00
bool ret = g_gameCardInterfaceInit ;
2020-07-13 07:36:17 +01:00
if ( ret ) goto end ;
2020-05-03 00:40:50 +01:00
2020-07-06 01:10:07 +01:00
/* Allocate memory for the gamecard read buffer. */
2020-05-03 00:40:50 +01:00
g_gameCardReadBuf = malloc ( GAMECARD_READ_BUFFER_SIZE ) ;
if ( ! g_gameCardReadBuf )
{
2021-03-07 23:22:49 +00:00
LOG_MSG ( " Unable to allocate memory for the gamecard read buffer! " ) ;
2020-07-13 07:36:17 +01:00
goto end ;
2020-05-03 00:40:50 +01:00
}
2020-07-06 01:10:07 +01:00
/* Open device operator. */
2020-05-03 00:40:50 +01:00
rc = fsOpenDeviceOperator ( & g_deviceOperator ) ;
if ( R_FAILED ( rc ) )
{
2021-03-07 23:22:49 +00:00
LOG_MSG ( " fsOpenDeviceOperator failed! (0x%08X). " , rc ) ;
2020-07-13 07:36:17 +01:00
goto end ;
2020-05-03 00:40:50 +01:00
}
g_openDeviceOperator = true ;
2020-07-06 01:10:07 +01:00
/* Open gamecard detection event notifier. */
2020-05-03 00:40:50 +01:00
rc = fsOpenGameCardDetectionEventNotifier ( & g_gameCardEventNotifier ) ;
if ( R_FAILED ( rc ) )
{
2021-03-07 23:22:49 +00:00
LOG_MSG ( " fsOpenGameCardDetectionEventNotifier failed! (0x%08X) " , rc ) ;
2020-07-13 07:36:17 +01:00
goto end ;
2020-05-03 00:40:50 +01:00
}
g_openEventNotifier = true ;
2020-07-06 01:10:07 +01:00
/* Retrieve gamecard detection kernel event. */
2020-05-03 00:40:50 +01:00
rc = fsEventNotifierGetEventHandle ( & g_gameCardEventNotifier , & g_gameCardKernelEvent , true ) ;
if ( R_FAILED ( rc ) )
{
2021-03-07 23:22:49 +00:00
LOG_MSG ( " fsEventNotifierGetEventHandle failed! (0x%08X) " , rc ) ;
2020-07-13 07:36:17 +01:00
goto end ;
2020-05-03 00:40:50 +01:00
}
g_loadKernelEvent = true ;
2020-11-28 06:38:53 +00:00
/* Create user-mode exit event. */
2020-05-03 09:40:08 +01:00
ueventCreate ( & g_gameCardDetectionThreadExitEvent , true ) ;
2020-11-28 06:38:53 +00:00
/* Create user-mode gamecard status change event. */
2020-05-03 09:40:08 +01:00
ueventCreate ( & g_gameCardStatusChangeEvent , true ) ;
2020-05-03 00:40:50 +01:00
2020-07-06 01:10:07 +01:00
/* Create gamecard detection thread. */
2020-07-13 07:36:17 +01:00
if ( ! ( g_gameCardDetectionThreadCreated = gamecardCreateDetectionThread ( ) ) ) goto end ;
2020-05-03 00:40:50 +01:00
2021-03-07 23:22:49 +00:00
ret = g_gameCardInterfaceInit = true ;
2020-05-03 00:40:50 +01:00
2020-07-13 07:36:17 +01:00
end :
2021-03-07 23:22:49 +00:00
mutexUnlock ( & g_gameCardMutex ) ;
2020-05-03 00:40:50 +01:00
return ret ;
}
void gamecardExit ( void )
{
2021-03-07 23:22:49 +00:00
mutexLock ( & g_gameCardMutex ) ;
2020-05-03 00:40:50 +01:00
2020-07-06 01:10:07 +01:00
/* Destroy gamecard detection thread. */
2020-05-03 00:40:50 +01:00
if ( g_gameCardDetectionThreadCreated )
{
gamecardDestroyDetectionThread ( ) ;
g_gameCardDetectionThreadCreated = false ;
}
2020-07-06 01:10:07 +01:00
/* Close gamecard detection kernel event. */
2020-05-03 00:40:50 +01:00
if ( g_loadKernelEvent )
{
eventClose ( & g_gameCardKernelEvent ) ;
g_loadKernelEvent = false ;
}
2020-07-06 01:10:07 +01:00
/* Close gamecard detection event notifier. */
2020-05-03 00:40:50 +01:00
if ( g_openEventNotifier )
{
fsEventNotifierClose ( & g_gameCardEventNotifier ) ;
g_openEventNotifier = false ;
}
2020-07-06 01:10:07 +01:00
/* Close device operator. */
2020-05-03 00:40:50 +01:00
if ( g_openDeviceOperator )
{
fsDeviceOperatorClose ( & g_deviceOperator ) ;
g_openDeviceOperator = false ;
}
2020-07-06 01:10:07 +01:00
/* Free gamecard read buffer. */
2020-05-03 00:40:50 +01:00
if ( g_gameCardReadBuf )
{
free ( g_gameCardReadBuf ) ;
g_gameCardReadBuf = NULL ;
}
2021-03-07 23:22:49 +00:00
g_gameCardInterfaceInit = false ;
2020-05-03 00:40:50 +01:00
2021-03-07 23:22:49 +00:00
mutexUnlock ( & g_gameCardMutex ) ;
2020-05-03 00:40:50 +01:00
}
2020-04-15 06:59:12 +01:00
2020-05-03 09:40:08 +01:00
UEvent * gamecardGetStatusChangeUserEvent ( void )
{
2021-03-07 23:22:49 +00:00
mutexLock ( & g_gameCardMutex ) ;
UEvent * event = ( g_gameCardInterfaceInit ? & g_gameCardStatusChangeEvent : NULL ) ;
mutexUnlock ( & g_gameCardMutex ) ;
2020-05-03 09:40:08 +01:00
return event ;
}
2020-07-17 06:01:31 +01:00
u8 gamecardGetStatus ( void )
2020-04-15 06:59:12 +01:00
{
2021-03-07 23:22:49 +00:00
mutexLock ( & g_gameCardMutex ) ;
2020-07-17 06:01:31 +01:00
u8 status = ( g_gameCardInserted ? ( g_gameCardInfoLoaded ? GameCardStatus_InsertedAndInfoLoaded : GameCardStatus_InsertedAndInfoNotLoaded ) : GameCardStatus_NotInserted ) ;
2021-03-07 23:22:49 +00:00
mutexUnlock ( & g_gameCardMutex ) ;
2020-07-17 06:01:31 +01:00
return status ;
2020-04-15 06:59:12 +01:00
}
2020-04-24 10:38:13 +01:00
bool gamecardReadStorage ( void * out , u64 read_size , u64 offset )
2020-04-15 06:59:12 +01:00
{
2020-04-17 22:59:05 +01:00
return gamecardReadStorageArea ( out , read_size , offset , true ) ;
2020-04-15 06:59:12 +01:00
}
2020-07-13 07:36:17 +01:00
bool gamecardGetKeyArea ( GameCardKeyArea * out )
{
2020-08-01 06:17:08 +01:00
/* Read full FS program memory to retrieve the GameCardInitialData block, which is part of the GameCardKeyArea block. */
/* In FS program memory, this is stored as part of the GameCardSecurityInformation struct, which is returned by Lotus command "ChangeToSecureMode" (0xF). */
/* This means it is only available *after* the gamecard secure area has been mounted, which is taken care of in gamecardReadInitialData(). */
/* The GameCardSecurityInformation struct is only kept for documentation purposes. It isn't used at all to retrieve the GameCardInitialData block. */
2021-03-07 23:22:49 +00:00
mutexLock ( & g_gameCardMutex ) ;
2020-08-01 06:17:08 +01:00
bool ret = gamecardReadInitialData ( out ) ;
2021-03-07 23:22:49 +00:00
mutexUnlock ( & g_gameCardMutex ) ;
2020-07-13 07:36:17 +01:00
return ret ;
}
2020-04-15 06:59:12 +01:00
bool gamecardGetHeader ( GameCardHeader * out )
{
2021-03-07 23:22:49 +00:00
mutexLock ( & g_gameCardMutex ) ;
2020-07-06 01:10:07 +01:00
bool ret = ( g_gameCardInserted & & g_gameCardInfoLoaded & & out ) ;
if ( ret ) memcpy ( out , & g_gameCardHeader , sizeof ( GameCardHeader ) ) ;
2021-03-07 23:22:49 +00:00
mutexUnlock ( & g_gameCardMutex ) ;
2020-04-15 06:59:12 +01:00
return ret ;
}
2020-07-13 07:36:17 +01:00
bool gamecardGetCertificate ( FsGameCardCertificate * out )
{
Result rc = 0 ;
bool ret = false ;
2021-03-07 23:22:49 +00:00
mutexLock ( & g_gameCardMutex ) ;
2021-02-12 20:35:23 +00:00
2020-07-13 07:36:17 +01:00
if ( g_gameCardInserted & & g_gameCardHandle . value & & out )
{
2021-02-12 20:35:23 +00:00
/* Read the gamecard certificate using the official IPC call. */
2020-07-13 07:36:17 +01:00
rc = fsDeviceOperatorGetGameCardDeviceCertificate ( & g_deviceOperator , & g_gameCardHandle , out ) ;
2021-02-12 20:35:23 +00:00
if ( R_FAILED ( rc ) )
{
2021-03-07 23:22:49 +00:00
LOG_MSG ( " fsDeviceOperatorGetGameCardDeviceCertificate failed! (0x%08X) " , rc ) ;
2021-02-12 20:35:23 +00:00
/* Attempt to manually read the gamecard certificate. */
if ( gamecardReadStorageArea ( out , sizeof ( FsGameCardCertificate ) , GAMECARD_CERTIFICATE_OFFSET , false ) ) rc = 0 ;
}
2020-07-13 07:36:17 +01:00
ret = R_SUCCEEDED ( rc ) ;
}
2021-02-12 20:35:23 +00:00
2021-03-07 23:22:49 +00:00
mutexUnlock ( & g_gameCardMutex ) ;
2020-07-13 07:36:17 +01:00
return ret ;
}
2020-04-16 05:37:16 +01:00
bool gamecardGetTotalSize ( u64 * out )
2020-04-15 06:59:12 +01:00
{
2021-03-07 23:22:49 +00:00
mutexLock ( & g_gameCardMutex ) ;
2020-07-06 01:10:07 +01:00
bool ret = ( g_gameCardInserted & & g_gameCardInfoLoaded & & out ) ;
2020-10-21 05:27:48 +01:00
if ( ret ) * out = g_gameCardStorageTotalSize ;
2021-03-07 23:22:49 +00:00
mutexUnlock ( & g_gameCardMutex ) ;
2020-04-15 06:59:12 +01:00
return ret ;
}
2020-04-16 05:37:16 +01:00
bool gamecardGetTrimmedSize ( u64 * out )
2020-04-15 06:59:12 +01:00
{
2021-03-07 23:22:49 +00:00
mutexLock ( & g_gameCardMutex ) ;
2020-07-06 01:10:07 +01:00
bool ret = ( g_gameCardInserted & & g_gameCardInfoLoaded & & out ) ;
2020-07-17 19:42:48 +01:00
if ( ret ) * out = ( sizeof ( GameCardHeader ) + GAMECARD_MEDIA_UNIT_OFFSET ( g_gameCardHeader . valid_data_end_address ) ) ;
2021-03-07 23:22:49 +00:00
mutexUnlock ( & g_gameCardMutex ) ;
2020-04-15 06:59:12 +01:00
return ret ;
}
2020-04-16 05:37:16 +01:00
bool gamecardGetRomCapacity ( u64 * out )
{
2021-03-07 23:22:49 +00:00
mutexLock ( & g_gameCardMutex ) ;
2020-07-06 01:10:07 +01:00
bool ret = ( g_gameCardInserted & & g_gameCardInfoLoaded & & out ) ;
if ( ret ) * out = g_gameCardCapacity ;
2021-03-07 23:22:49 +00:00
mutexUnlock ( & g_gameCardMutex ) ;
2020-04-16 05:37:16 +01:00
return ret ;
}
2021-02-12 20:35:23 +00:00
bool gamecardGetBundledFirmwareUpdateVersion ( VersionType1 * out )
2020-04-15 06:59:12 +01:00
{
Result rc = 0 ;
u64 update_id = 0 ;
u32 update_version = 0 ;
bool ret = false ;
2021-03-07 23:22:49 +00:00
mutexLock ( & g_gameCardMutex ) ;
2021-02-12 20:35:23 +00:00
2020-04-15 06:59:12 +01:00
if ( g_gameCardInserted & & g_gameCardHandle . value & & out )
{
rc = fsDeviceOperatorUpdatePartitionInfo ( & g_deviceOperator , & g_gameCardHandle , & update_version , & update_id ) ;
2021-03-07 23:22:49 +00:00
if ( R_FAILED ( rc ) ) LOG_MSG ( " fsDeviceOperatorUpdatePartitionInfo failed! (0x%08X) " , rc ) ;
2020-04-24 10:38:13 +01:00
2020-04-15 06:59:12 +01:00
ret = ( R_SUCCEEDED ( rc ) & & update_id = = GAMECARD_UPDATE_TID ) ;
2021-02-12 20:35:23 +00:00
if ( ret ) out - > value = update_version ;
2020-04-15 06:59:12 +01:00
}
2021-02-12 20:35:23 +00:00
2021-03-07 23:22:49 +00:00
mutexUnlock ( & g_gameCardMutex ) ;
2020-04-15 06:59:12 +01:00
return ret ;
}
2021-03-10 01:12:01 +00:00
bool gamecardGetHashFileSystemContext ( u8 hfs_partition_type , HashFileSystemContext * out )
2020-04-24 10:38:13 +01:00
{
2021-03-10 01:12:01 +00:00
if ( hfs_partition_type > = GameCardHashFileSystemPartitionType_Count | | ! out )
{
LOG_MSG ( " Invalid parameters! " ) ;
return false ;
}
HashFileSystemContext * fs_ctx = NULL ;
2021-03-07 23:22:49 +00:00
bool success = false ;
mutexLock ( & g_gameCardMutex ) ;
2020-04-24 10:38:13 +01:00
2021-03-10 01:12:01 +00:00
/* Free Hash FS context. */
hfsFreeContext ( out ) ;
2021-03-07 23:22:49 +00:00
/* Get pointer to the Hash FS context for the requested partition. */
fs_ctx = _gamecardGetHashFileSystemContext ( hfs_partition_type ) ;
if ( ! fs_ctx ) goto end ;
2021-03-10 01:12:01 +00:00
/* Fill Hash FS context. */
out - > name = strdup ( fs_ctx - > name ) ;
if ( ! out - > name )
2021-03-07 23:22:49 +00:00
{
LOG_MSG ( " Failed to duplicate Hash FS partition name! (%s). " , fs_ctx - > name ) ;
goto end ;
}
2020-04-16 11:13:11 +01:00
2021-03-10 01:12:01 +00:00
out - > type = fs_ctx - > type ;
out - > offset = fs_ctx - > offset ;
out - > size = fs_ctx - > size ;
out - > header_size = fs_ctx - > header_size ;
2020-04-16 11:13:11 +01:00
2021-03-10 01:12:01 +00:00
out - > header = calloc ( fs_ctx - > header_size , sizeof ( u8 ) ) ;
if ( ! out - > header )
2020-04-16 11:13:11 +01:00
{
2021-03-07 23:22:49 +00:00
LOG_MSG ( " Failed to duplicate Hash FS partition header! (%s). " , fs_ctx - > name ) ;
goto end ;
2020-04-16 11:13:11 +01:00
}
2021-03-10 01:12:01 +00:00
memcpy ( out - > header , fs_ctx - > header , fs_ctx - > header_size ) ;
2021-03-07 23:22:49 +00:00
/* Update flag. */
success = true ;
2020-07-13 07:36:17 +01:00
end :
2021-03-10 01:12:01 +00:00
if ( ! success ) hfsFreeContext ( out ) ;
2020-04-16 11:13:11 +01:00
2021-03-07 23:22:49 +00:00
mutexUnlock ( & g_gameCardMutex ) ;
2021-03-10 01:12:01 +00:00
return success ;
2020-04-24 10:38:13 +01:00
}
2021-03-07 23:22:49 +00:00
bool gamecardGetHashFileSystemEntryInfoByName ( u8 hfs_partition_type , const char * entry_name , u64 * out_offset , u64 * out_size )
2020-04-24 10:38:13 +01:00
{
2021-03-07 23:22:49 +00:00
if ( ! entry_name | | ! * entry_name | | ( ! out_offset & & ! out_size ) )
2020-04-16 11:13:11 +01:00
{
2021-03-07 23:22:49 +00:00
LOG_MSG ( " Invalid parameters! " ) ;
return false ;
2020-04-16 11:13:11 +01:00
}
2021-03-07 23:22:49 +00:00
HashFileSystemContext * fs_ctx = NULL ;
HashFileSystemEntry * fs_entry = NULL ;
bool success = false ;
mutexLock ( & g_gameCardMutex ) ;
/* Get pointer to the Hash FS context for the requested partition. */
fs_ctx = _gamecardGetHashFileSystemContext ( hfs_partition_type ) ;
if ( ! fs_ctx ) goto end ;
/* Get Hash FS entry by name. */
fs_entry = hfsGetEntryByName ( fs_ctx , entry_name ) ;
if ( ! fs_entry ) goto end ;
/* Update output variables. */
if ( out_offset ) * out_offset = ( fs_ctx - > offset + fs_ctx - > header_size + fs_entry - > offset ) ;
if ( out_size ) * out_size = fs_entry - > size ;
/* Update flag. */
success = true ;
2020-07-13 07:36:17 +01:00
end :
2021-03-07 23:22:49 +00:00
mutexUnlock ( & g_gameCardMutex ) ;
2020-04-16 11:13:11 +01:00
2021-03-07 23:22:49 +00:00
return success ;
2020-04-16 11:13:11 +01:00
}
2020-04-15 06:59:12 +01:00
static bool gamecardCreateDetectionThread ( void )
{
2020-08-18 06:04:13 +01:00
if ( ! utilsCreateThread ( & g_gameCardDetectionThread , gamecardDetectionThreadFunc , NULL , 1 ) )
2020-04-15 06:59:12 +01:00
{
2021-03-07 23:22:49 +00:00
LOG_MSG ( " Failed to create gamecard detection thread! " ) ;
2020-04-15 06:59:12 +01:00
return false ;
}
return true ;
}
static void gamecardDestroyDetectionThread ( void )
{
2020-07-06 01:10:07 +01:00
/* Signal the exit event to terminate the gamecard detection thread. */
2020-04-15 06:59:12 +01:00
ueventSignal ( & g_gameCardDetectionThreadExitEvent ) ;
2020-07-06 01:10:07 +01:00
/* Wait for the gamecard detection thread to exit. */
2020-08-18 06:04:13 +01:00
utilsJoinThread ( & g_gameCardDetectionThread ) ;
2020-04-15 06:59:12 +01:00
}
2020-08-18 06:04:13 +01:00
static void gamecardDetectionThreadFunc ( void * arg )
2020-04-15 06:59:12 +01:00
{
( void ) arg ;
Result rc = 0 ;
int idx = 0 ;
Waiter gamecard_event_waiter = waiterForEvent ( & g_gameCardKernelEvent ) ;
Waiter exit_event_waiter = waiterForUEvent ( & g_gameCardDetectionThreadExitEvent ) ;
2020-07-06 01:10:07 +01:00
/* Retrieve initial gamecard insertion status. */
2020-07-17 06:01:31 +01:00
/* Load gamecard info right away if a gamecard is inserted, then signal the user mode gamecard status change event. */
2021-03-07 23:22:49 +00:00
mutexLock ( & g_gameCardMutex ) ;
2020-07-03 10:31:22 +01:00
g_gameCardInserted = gamecardIsInserted ( ) ;
2020-04-15 21:50:07 +01:00
if ( g_gameCardInserted ) gamecardLoadInfo ( ) ;
2021-03-07 23:22:49 +00:00
mutexUnlock ( & g_gameCardMutex ) ;
2020-07-25 19:50:42 +01:00
2020-05-03 09:40:08 +01:00
ueventSignal ( & g_gameCardStatusChangeEvent ) ;
2020-04-15 06:59:12 +01:00
while ( true )
{
2020-07-06 01:10:07 +01:00
/* Wait until an event is triggered. */
2020-04-15 06:59:12 +01:00
rc = waitMulti ( & idx , - 1 , gamecard_event_waiter , exit_event_waiter ) ;
if ( R_FAILED ( rc ) ) continue ;
2020-07-06 01:10:07 +01:00
/* Exit event triggered. */
2020-04-15 06:59:12 +01:00
if ( idx = = 1 ) break ;
2021-03-07 23:22:49 +00:00
mutexLock ( & g_gameCardMutex ) ;
2020-04-15 06:59:12 +01:00
2020-07-06 01:10:07 +01:00
/* Retrieve current gamecard insertion status. */
/* Only proceed if we're dealing with a status change. */
2020-04-15 21:50:07 +01:00
g_gameCardInserted = gamecardIsInserted ( ) ;
2020-07-03 10:31:22 +01:00
gamecardFreeInfo ( ) ;
2020-04-15 06:59:12 +01:00
2020-07-03 10:31:22 +01:00
if ( g_gameCardInserted )
2020-04-15 06:59:12 +01:00
{
2020-07-06 01:10:07 +01:00
/* Don't access the gamecard immediately to avoid conflicts with HOS / sysmodules. */
2020-04-17 22:59:05 +01:00
utilsSleep ( GAMECARD_ACCESS_WAIT_TIME ) ;
2020-04-15 06:59:12 +01:00
2020-07-06 01:10:07 +01:00
/* Load gamecard info. */
2020-04-15 21:50:07 +01:00
gamecardLoadInfo ( ) ;
2020-04-15 06:59:12 +01:00
}
2021-03-07 23:22:49 +00:00
mutexUnlock ( & g_gameCardMutex ) ;
2020-05-03 09:40:08 +01:00
2020-07-17 06:01:31 +01:00
/* Signal user mode gamecard status change event. */
2020-05-03 09:40:08 +01:00
ueventSignal ( & g_gameCardStatusChangeEvent ) ;
2020-04-15 06:59:12 +01:00
}
2020-07-06 01:10:07 +01:00
/* Free gamecard info and close gamecard handle. */
2020-04-15 06:59:12 +01:00
gamecardFreeInfo ( ) ;
g_gameCardInserted = false ;
2020-08-18 06:04:13 +01:00
threadExit ( ) ;
2020-04-15 06:59:12 +01:00
}
2020-04-26 09:35:01 +01:00
NX_INLINE bool gamecardIsInserted ( void )
2020-04-15 06:59:12 +01:00
{
bool inserted = false ;
Result rc = fsDeviceOperatorIsGameCardInserted ( & g_deviceOperator , & inserted ) ;
2021-03-07 23:22:49 +00:00
if ( R_FAILED ( rc ) ) LOG_MSG ( " fsDeviceOperatorIsGameCardInserted failed! (0x%08X) " , rc ) ;
2020-04-15 06:59:12 +01:00
return ( R_SUCCEEDED ( rc ) & & inserted ) ;
}
static void gamecardLoadInfo ( void )
{
if ( g_gameCardInfoLoaded ) return ;
2021-03-07 23:22:49 +00:00
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 ;
2020-04-15 06:59:12 +01:00
2020-07-06 01:10:07 +01:00
/* Retrieve gamecard storage area sizes. */
/* gamecardReadStorageArea() actually checks if the storage area sizes are greater than zero, so we must first perform this step. */
2020-04-16 05:37:16 +01:00
if ( ! gamecardGetStorageAreasSizes ( ) )
2020-04-15 06:59:12 +01:00
{
2021-03-07 23:22:49 +00:00
LOG_MSG ( " Failed to retrieve gamecard storage area sizes! " ) ;
2020-07-13 07:36:17 +01:00
goto end ;
2020-04-15 06:59:12 +01:00
}
2020-07-06 01:10:07 +01:00
/* Read gamecard header. */
2020-04-15 21:50:07 +01:00
if ( ! gamecardReadStorageArea ( & g_gameCardHeader , sizeof ( GameCardHeader ) , 0 , false ) )
2020-04-15 06:59:12 +01:00
{
2021-03-07 23:22:49 +00:00
LOG_MSG ( " Failed to read gamecard header! " ) ;
2020-07-13 07:36:17 +01:00
goto end ;
2020-04-15 06:59:12 +01:00
}
2020-07-06 01:10:07 +01:00
/* Check magic word from gamecard header. */
2020-04-15 06:59:12 +01:00
if ( __builtin_bswap32 ( g_gameCardHeader . magic ) ! = GAMECARD_HEAD_MAGIC )
{
2021-03-07 23:22:49 +00:00
LOG_MSG ( " Invalid gamecard header magic word! (0x%08X). " , __builtin_bswap32 ( g_gameCardHeader . magic ) ) ;
dump_gamecard_header = true ;
2020-07-13 07:36:17 +01:00
goto end ;
2020-04-15 06:59:12 +01:00
}
2020-07-06 01:10:07 +01:00
/* Get gamecard capacity. */
2020-04-16 05:37:16 +01:00
g_gameCardCapacity = gamecardGetCapacityFromRomSizeValue ( g_gameCardHeader . rom_size ) ;
if ( ! g_gameCardCapacity )
{
2021-03-07 23:22:49 +00:00
LOG_MSG ( " Invalid gamecard capacity value! (0x%02X). " , g_gameCardHeader . rom_size ) ;
dump_gamecard_header = true ;
2020-07-13 07:36:17 +01:00
goto end ;
2020-04-16 05:37:16 +01:00
}
2020-04-15 06:59:12 +01:00
if ( utilsGetCustomFirmwareType ( ) = = UtilsCustomFirmwareType_SXOS )
{
2020-07-06 01:10:07 +01:00
/* The total size for the secure storage area is maxed out under SX OS. */
/* Let's try to calculate it manually. */
2020-07-17 19:42:48 +01:00
g_gameCardStorageSecureAreaSize = ( g_gameCardCapacity - ( g_gameCardStorageNormalAreaSize + GAMECARD_UNUSED_AREA_SIZE ( g_gameCardCapacity ) ) ) ;
2020-04-15 06:59:12 +01:00
}
2021-03-07 23:22:49 +00:00
/* Initialize Hash FS context for the root partition. */
root_fs_ctx = gamecardInitializeHashFileSystemContext ( NULL , g_gameCardHeader . partition_fs_header_address , 0 , g_gameCardHeader . partition_fs_header_hash , 0 , g_gameCardHeader . partition_fs_header_size ) ;
if ( ! root_fs_ctx ) goto end ;
2020-04-15 06:59:12 +01:00
2021-03-07 23:22:49 +00:00
/* Calculate total Hash FS partition count. */
root_fs_entry_count = hfsGetEntryCount ( root_fs_ctx ) ;
g_gameCardHfsCount = ( root_fs_entry_count + 1 ) ;
2020-04-15 06:59:12 +01:00
2021-03-07 23:22:49 +00:00
/* Allocate Hash FS context pointer array. */
g_gameCardHfsCtx = calloc ( g_gameCardHfsCount , sizeof ( HashFileSystemContext * ) ) ;
if ( ! g_gameCardHfsCtx )
2020-04-15 06:59:12 +01:00
{
2021-03-07 23:22:49 +00:00
LOG_MSG ( " Unable to allocate Hash FS context pointer array! (%u). " , g_gameCardHfsCount ) ;
2020-07-13 07:36:17 +01:00
goto end ;
2020-04-15 06:59:12 +01:00
}
2021-03-07 23:22:49 +00:00
/* Set root partition context as the first pointer. */
g_gameCardHfsCtx [ 0 ] = root_fs_ctx ;
2020-04-15 06:59:12 +01:00
2021-03-07 23:22:49 +00:00
/* Get root partition name table. */
root_fs_name_table_size = ( ( HashFileSystemHeader * ) root_fs_ctx - > header ) - > name_table_size ;
root_fs_name_table = hfsGetNameTable ( root_fs_ctx ) ;
2020-04-15 06:59:12 +01:00
2021-03-07 23:22:49 +00:00
/* Initialize Hash FS contexts for the child partitions. */
for ( u32 i = 0 ; i < root_fs_entry_count ; i + + )
2020-04-15 06:59:12 +01:00
{
2021-03-07 23:22:49 +00:00
HashFileSystemEntry * fs_entry = hfsGetEntryByIndex ( root_fs_ctx , i ) ;
char * fs_entry_name = ( root_fs_name_table + fs_entry - > name_offset ) ;
u64 fs_entry_offset = ( root_fs_ctx - > offset + root_fs_ctx - > header_size + fs_entry - > offset ) ;
2020-04-15 06:59:12 +01:00
2021-03-07 23:22:49 +00:00
if ( fs_entry - > name_offset > = root_fs_name_table_size | | ! * fs_entry_name )
2020-04-15 06:59:12 +01:00
{
2021-03-07 23:22:49 +00:00
LOG_MSG ( " Invalid name for root Hash FS partition entry #%u! " , i ) ;
2020-07-13 07:36:17 +01:00
goto end ;
2020-04-15 06:59:12 +01:00
}
2021-03-07 23:22:49 +00:00
g_gameCardHfsCtx [ i + 1 ] = gamecardInitializeHashFileSystemContext ( fs_entry_name , fs_entry_offset , fs_entry - > size , fs_entry - > hash , fs_entry - > hash_target_offset , fs_entry - > hash_target_size ) ;
if ( ! g_gameCardHfsCtx [ i + 1 ] ) goto end ;
2020-04-15 06:59:12 +01:00
}
g_gameCardInfoLoaded = true ;
2020-07-13 07:36:17 +01:00
end :
2021-03-07 23:22:49 +00:00
if ( ! g_gameCardInfoLoaded )
{
if ( dump_gamecard_header ) LOG_DATA ( & g_gameCardHeader , sizeof ( GameCardHeader ) , " Gamecard header dump: " ) ;
2021-03-10 01:12:01 +00:00
if ( ! g_gameCardHfsCtx & & root_fs_ctx )
{
hfsFreeContext ( root_fs_ctx ) ;
free ( root_fs_ctx ) ;
}
2021-03-07 23:22:49 +00:00
gamecardFreeInfo ( ) ;
}
2020-04-15 06:59:12 +01:00
}
static void gamecardFreeInfo ( void )
{
2020-07-22 09:03:28 +01:00
memset ( & g_gameCardHeader , 0 , sizeof ( GameCardHeader ) ) ;
2020-07-13 07:36:17 +01:00
2020-10-21 05:27:48 +01:00
g_gameCardStorageNormalAreaSize = g_gameCardStorageSecureAreaSize = g_gameCardStorageTotalSize = 0 ;
2020-04-15 06:59:12 +01:00
2020-04-16 05:37:16 +01:00
g_gameCardCapacity = 0 ;
2021-03-07 23:22:49 +00:00
if ( g_gameCardHfsCtx )
2020-04-15 06:59:12 +01:00
{
2021-03-10 01:12:01 +00:00
for ( u32 i = 0 ; i < g_gameCardHfsCount ; i + + )
{
HashFileSystemContext * cur_fs_ctx = g_gameCardHfsCtx [ i ] ;
if ( cur_fs_ctx )
{
hfsFreeContext ( cur_fs_ctx ) ;
free ( cur_fs_ctx ) ;
}
}
2020-04-15 06:59:12 +01:00
2021-03-07 23:22:49 +00:00
free ( g_gameCardHfsCtx ) ;
g_gameCardHfsCtx = NULL ;
2020-04-15 06:59:12 +01:00
}
2021-03-07 23:22:49 +00:00
g_gameCardHfsCount = 0 ;
2020-04-15 06:59:12 +01:00
2020-04-15 21:50:07 +01:00
gamecardCloseStorageArea ( ) ;
2020-04-15 06:59:12 +01:00
g_gameCardInfoLoaded = false ;
}
2020-08-01 06:17:08 +01:00
static bool gamecardReadInitialData ( GameCardKeyArea * out )
2020-07-13 07:36:17 +01:00
{
2020-08-01 06:17:08 +01:00
if ( ! g_gameCardInserted | | ! g_gameCardInfoLoaded | | ! out )
{
2021-03-07 23:22:49 +00:00
LOG_MSG ( " Invalid parameters! " ) ;
2020-08-01 06:17:08 +01:00
return false ;
}
/* Clear output. */
memset ( out , 0 , sizeof ( GameCardKeyArea ) ) ;
/* Open secure storage area. */
if ( ! gamecardOpenStorageArea ( GameCardStorageArea_Secure ) )
{
2021-03-07 23:22:49 +00:00
LOG_MSG ( " Failed to open secure storage area! " ) ;
2020-08-01 06:17:08 +01:00
return false ;
}
2020-07-13 07:36:17 +01:00
bool found = false ;
2020-07-22 09:03:28 +01:00
u8 tmp_hash [ SHA256_HASH_SIZE ] = { 0 } ;
2020-07-13 07:36:17 +01:00
/* Retrieve full FS program memory dump. */
if ( ! memRetrieveFullProgramMemory ( & g_fsProgramMemory ) )
{
2021-03-07 23:22:49 +00:00
LOG_MSG ( " Failed to retrieve full FS program memory dump! " ) ;
2020-07-13 07:36:17 +01:00
return false ;
}
2020-07-22 09:03:28 +01:00
/* Look for the initial data block in the FS memory dump using the package ID and the initial data hash from the gamecard header. */
2020-07-13 07:36:17 +01:00
for ( u64 offset = 0 ; offset < g_fsProgramMemory . data_size ; offset + + )
{
2020-10-21 05:27:48 +01:00
if ( ( g_fsProgramMemory . data_size - offset ) < sizeof ( GameCardInitialData ) ) break ;
2020-07-22 09:03:28 +01:00
if ( memcmp ( g_fsProgramMemory . data + offset , & ( g_gameCardHeader . package_id ) , sizeof ( g_gameCardHeader . package_id ) ) ! = 0 ) continue ;
2020-07-13 07:36:17 +01:00
2020-07-22 09:03:28 +01:00
sha256CalculateHash ( tmp_hash , g_fsProgramMemory . data + offset , sizeof ( GameCardInitialData ) ) ;
2020-07-13 07:36:17 +01:00
2020-07-22 09:03:28 +01:00
if ( ! memcmp ( tmp_hash , g_gameCardHeader . initial_data_hash , SHA256_HASH_SIZE ) )
2020-07-13 07:36:17 +01:00
{
/* Jackpot. */
2020-08-01 06:17:08 +01:00
memcpy ( & ( out - > initial_data ) , g_fsProgramMemory . data + offset , sizeof ( GameCardInitialData ) ) ;
2020-07-13 07:36:17 +01:00
found = true ;
2020-07-22 09:03:28 +01:00
break ;
2020-07-13 07:36:17 +01:00
}
2020-07-15 23:50:34 +01:00
}
2020-07-13 07:36:17 +01:00
/* Free FS memory dump. */
memFreeMemoryLocation ( & g_fsProgramMemory ) ;
return found ;
}
2020-07-17 06:01:31 +01:00
static bool gamecardGetHandleAndStorage ( u32 partition )
2020-04-15 06:59:12 +01:00
{
2020-07-17 06:01:31 +01:00
if ( ! g_gameCardInserted | | partition > 1 )
2020-04-15 06:59:12 +01:00
{
2021-03-07 23:22:49 +00:00
LOG_MSG ( " Invalid parameters! " ) ;
2020-04-15 06:59:12 +01:00
return false ;
}
2020-07-22 09:03:28 +01:00
Result rc = 0 ;
2020-04-15 06:59:12 +01:00
2020-07-06 01:10:07 +01:00
/* 10 tries. */
2020-04-15 06:59:12 +01:00
for ( u8 i = 0 ; i < 10 ; i + + )
{
2020-07-17 06:01:31 +01:00
/* 100 ms wait in case there was an error in the previous loop. */
2020-07-22 09:03:28 +01:00
if ( R_FAILED ( rc ) ) svcSleepThread ( 100000000 ) ;
2020-07-17 06:01:31 +01:00
/* First, let's try to retrieve a gamecard handle. */
/* This can return 0x140A02 if the "nogc" patch is enabled by the running CFW. */
2020-07-22 09:03:28 +01:00
rc = fsDeviceOperatorGetGameCardHandle ( & g_deviceOperator , & g_gameCardHandle ) ;
if ( R_FAILED ( rc ) )
2020-04-15 06:59:12 +01:00
{
2021-03-07 23:22:49 +00:00
//LOG_MSG("fsDeviceOperatorGetGameCardHandle failed on try #%u! (0x%08X).", i + 1, rc);
2020-07-17 06:01:31 +01:00
continue ;
2020-04-15 06:59:12 +01:00
}
2020-07-17 06:01:31 +01:00
/* If the previous call succeeded, let's try to open the desired gamecard storage area. */
2020-07-22 09:03:28 +01:00
rc = fsOpenGameCardStorage ( & g_gameCardStorage , & g_gameCardHandle , partition ) ;
if ( R_FAILED ( rc ) )
2020-07-17 06:01:31 +01:00
{
gamecardCloseHandle ( ) ; /* Close invalid gamecard handle. */
2021-03-07 23:22:49 +00:00
//LOG_MSG("fsOpenGameCardStorage failed to open %s storage area on try #%u! (0x%08X).", GAMECARD_STORAGE_AREA_NAME(partition + 1), i + 1, rc);
2020-07-17 06:01:31 +01:00
continue ;
}
2020-04-15 06:59:12 +01:00
2020-07-17 06:01:31 +01:00
/* If we got up to this point, both a valid gamecard handle and a valid storage area handle are guaranteed. */
break ;
2020-04-15 06:59:12 +01:00
}
2021-03-07 23:22:49 +00:00
if ( R_FAILED ( rc ) ) LOG_MSG ( " fsDeviceOperatorGetGameCardHandle / fsOpenGameCardStorage failed! (0x%08X). " , rc ) ;
2020-07-23 22:57:43 +01:00
2020-07-22 09:03:28 +01:00
return R_SUCCEEDED ( rc ) ;
2020-04-15 06:59:12 +01:00
}
2020-04-26 09:35:01 +01:00
NX_INLINE void gamecardCloseHandle ( void )
2020-04-15 06:59:12 +01:00
{
2021-02-12 20:35:23 +00:00
/* TO DO: find a way to properly close a gamecard handle. */
2020-07-17 06:01:31 +01:00
if ( ! g_gameCardHandle . value ) return ;
2020-04-15 06:59:12 +01:00
svcCloseHandle ( g_gameCardHandle . value ) ;
g_gameCardHandle . value = 0 ;
}
2020-04-15 21:50:07 +01:00
static bool gamecardOpenStorageArea ( u8 area )
2020-04-15 06:59:12 +01:00
{
2020-04-15 21:50:07 +01:00
if ( ! g_gameCardInserted | | ( area ! = GameCardStorageArea_Normal & & area ! = GameCardStorageArea_Secure ) )
2020-04-15 06:59:12 +01:00
{
2021-03-07 23:22:49 +00:00
LOG_MSG ( " Invalid parameters! " ) ;
2020-04-15 06:59:12 +01:00
return false ;
}
2020-07-17 06:01:31 +01:00
/* Return right away if a valid handle has already been retrieved and the desired gamecard storage area is currently open. */
2020-04-15 21:50:07 +01:00
if ( g_gameCardHandle . value & & serviceIsActive ( & ( g_gameCardStorage . s ) ) & & g_gameCardStorageCurrentArea = = area ) return true ;
2020-04-15 06:59:12 +01:00
2020-07-17 06:01:31 +01:00
/* Close both gamecard handle and open storage area. */
2020-04-15 21:50:07 +01:00
gamecardCloseStorageArea ( ) ;
2020-04-15 06:59:12 +01:00
2020-07-17 06:01:31 +01:00
/* Retrieve both a new gamecard handle and a storage area handle. */
if ( ! gamecardGetHandleAndStorage ( area - 1 ) ) /* Zero-based index. */
2020-04-15 06:59:12 +01:00
{
2021-03-07 23:22:49 +00:00
LOG_MSG ( " Failed to retrieve gamecard handle and storage area handle! (%s). " , GAMECARD_STORAGE_AREA_NAME ( area ) ) ;
2020-04-15 21:50:07 +01:00
return false ;
2020-04-15 06:59:12 +01:00
}
2020-07-17 06:01:31 +01:00
/* Update current gamecard storage area. */
2020-04-15 21:50:07 +01:00
g_gameCardStorageCurrentArea = area ;
2020-04-15 06:59:12 +01:00
2020-04-15 21:50:07 +01:00
return true ;
2020-04-15 06:59:12 +01:00
}
2020-04-17 22:59:05 +01:00
static bool gamecardReadStorageArea ( void * out , u64 read_size , u64 offset , bool lock )
2020-04-15 06:59:12 +01:00
{
2021-03-07 23:22:49 +00:00
if ( lock ) mutexLock ( & g_gameCardMutex ) ;
2020-04-15 06:59:12 +01:00
bool success = false ;
2020-10-21 05:27:48 +01:00
if ( ! g_gameCardInserted | | ! g_gameCardStorageNormalAreaSize | | ! g_gameCardStorageSecureAreaSize | | ! out | | ! read_size | | ( offset + read_size ) > g_gameCardStorageTotalSize )
2020-04-15 06:59:12 +01:00
{
2021-03-07 23:22:49 +00:00
LOG_MSG ( " Invalid parameters! " ) ;
2020-07-13 07:36:17 +01:00
goto end ;
2020-04-15 06:59:12 +01:00
}
Result rc = 0 ;
u8 * out_u8 = ( u8 * ) out ;
2020-04-15 21:50:07 +01:00
u8 area = ( offset < g_gameCardStorageNormalAreaSize ? GameCardStorageArea_Normal : GameCardStorageArea_Secure ) ;
2020-04-15 06:59:12 +01:00
2020-07-06 01:10:07 +01:00
/* Handle reads that span both the normal and secure gamecard storage areas. */
2020-04-17 22:59:05 +01:00
if ( area = = GameCardStorageArea_Normal & & ( offset + read_size ) > g_gameCardStorageNormalAreaSize )
2020-04-15 06:59:12 +01:00
{
2020-07-06 01:10:07 +01:00
/* Calculate normal storage area size difference. */
2020-04-15 06:59:12 +01:00
u64 diff_size = ( g_gameCardStorageNormalAreaSize - offset ) ;
2020-07-13 07:36:17 +01:00
if ( ! gamecardReadStorageArea ( out_u8 , diff_size , offset , false ) ) goto end ;
2020-04-15 06:59:12 +01:00
2020-07-06 01:10:07 +01:00
/* Adjust variables to read right from the start of the secure storage area. */
2020-04-17 22:59:05 +01:00
read_size - = diff_size ;
2020-04-15 21:50:07 +01:00
offset = g_gameCardStorageNormalAreaSize ;
out_u8 + = diff_size ;
area = GameCardStorageArea_Secure ;
}
2020-07-06 01:10:07 +01:00
/* Open a storage area if needed. */
/* If the right storage area has already been opened, this will return true. */
2020-04-15 21:50:07 +01:00
if ( ! gamecardOpenStorageArea ( area ) )
{
2021-03-07 23:22:49 +00:00
LOG_MSG ( " Failed to open %s storage area! " , GAMECARD_STORAGE_AREA_NAME ( area ) ) ;
2020-07-13 07:36:17 +01:00
goto end ;
2020-04-15 06:59:12 +01:00
}
2020-07-06 01:10:07 +01:00
/* Calculate appropiate storage area offset and retrieve the right storage area pointer. */
2020-04-15 21:50:07 +01:00
u64 base_offset = ( area = = GameCardStorageArea_Normal ? offset : ( offset - g_gameCardStorageNormalAreaSize ) ) ;
2020-04-15 06:59:12 +01:00
2020-04-17 22:59:05 +01:00
if ( ! ( base_offset % GAMECARD_MEDIA_UNIT_SIZE ) & & ! ( read_size % GAMECARD_MEDIA_UNIT_SIZE ) )
2020-04-15 06:59:12 +01:00
{
2020-07-06 01:10:07 +01:00
/* Optimization for reads that are already aligned to a GAMECARD_MEDIA_UNIT_SIZE boundary. */
2020-04-17 22:59:05 +01:00
rc = fsStorageRead ( & g_gameCardStorage , base_offset , out_u8 , read_size ) ;
2020-04-15 06:59:12 +01:00
if ( R_FAILED ( rc ) )
{
2021-03-07 23:22:49 +00:00
LOG_MSG ( " fsStorageRead failed to read 0x%lX bytes at offset 0x%lX from %s storage area! (0x%08X) (aligned). " , read_size , base_offset , GAMECARD_STORAGE_AREA_NAME ( area ) , rc ) ;
2020-07-13 07:36:17 +01:00
goto end ;
2020-04-15 06:59:12 +01:00
}
success = true ;
} else {
2020-07-06 01:10:07 +01:00
/* Fix offset and/or size to avoid unaligned reads. */
2020-04-26 09:35:01 +01:00
u64 block_start_offset = ALIGN_DOWN ( base_offset , GAMECARD_MEDIA_UNIT_SIZE ) ;
u64 block_end_offset = ALIGN_UP ( base_offset + read_size , GAMECARD_MEDIA_UNIT_SIZE ) ;
2020-04-15 06:59:12 +01:00
u64 block_size = ( block_end_offset - block_start_offset ) ;
2020-04-21 11:23:33 +01:00
u64 data_start_offset = ( base_offset - block_start_offset ) ;
2020-04-15 06:59:12 +01:00
u64 chunk_size = ( block_size > GAMECARD_READ_BUFFER_SIZE ? GAMECARD_READ_BUFFER_SIZE : block_size ) ;
2020-04-21 11:23:33 +01:00
u64 out_chunk_size = ( block_size > GAMECARD_READ_BUFFER_SIZE ? ( GAMECARD_READ_BUFFER_SIZE - data_start_offset ) : read_size ) ;
2020-04-15 06:59:12 +01:00
2020-04-15 21:50:07 +01:00
rc = fsStorageRead ( & g_gameCardStorage , block_start_offset , g_gameCardReadBuf , chunk_size ) ;
if ( R_FAILED ( rc ) )
2020-04-15 06:59:12 +01:00
{
2021-03-07 23:22:49 +00:00
LOG_MSG ( " fsStorageRead failed to read 0x%lX bytes at offset 0x%lX from %s storage area! (0x%08X) (unaligned). " , chunk_size , block_start_offset , GAMECARD_STORAGE_AREA_NAME ( area ) , rc ) ;
2020-07-13 07:36:17 +01:00
goto end ;
2020-04-15 06:59:12 +01:00
}
2020-04-21 11:23:33 +01:00
memcpy ( out_u8 , g_gameCardReadBuf + data_start_offset , out_chunk_size ) ;
2020-04-15 06:59:12 +01:00
2020-09-19 11:21:23 +01:00
success = ( block_size > GAMECARD_READ_BUFFER_SIZE ? gamecardReadStorageArea ( out_u8 + out_chunk_size , read_size - out_chunk_size , offset + out_chunk_size , false ) : true ) ;
2020-04-15 06:59:12 +01:00
}
2020-07-13 07:36:17 +01:00
end :
2021-03-07 23:22:49 +00:00
if ( lock ) mutexUnlock ( & g_gameCardMutex ) ;
2020-04-15 06:59:12 +01:00
return success ;
}
2020-04-15 21:50:07 +01:00
static void gamecardCloseStorageArea ( void )
2020-04-15 06:59:12 +01:00
{
2020-04-15 21:50:07 +01:00
if ( serviceIsActive ( & ( g_gameCardStorage . s ) ) )
2020-04-15 06:59:12 +01:00
{
2020-04-15 21:50:07 +01:00
fsStorageClose ( & g_gameCardStorage ) ;
memset ( & g_gameCardStorage , 0 , sizeof ( FsStorage ) ) ;
2020-04-15 06:59:12 +01:00
}
2020-04-15 21:50:07 +01:00
gamecardCloseHandle ( ) ;
2020-04-15 06:59:12 +01:00
2020-04-15 21:50:07 +01:00
g_gameCardStorageCurrentArea = GameCardStorageArea_None ;
2020-04-15 06:59:12 +01:00
}
2020-04-16 05:37:16 +01:00
static bool gamecardGetStorageAreasSizes ( void )
2020-04-15 06:59:12 +01:00
{
2020-04-15 21:50:07 +01:00
if ( ! g_gameCardInserted )
2020-04-15 06:59:12 +01:00
{
2021-03-07 23:22:49 +00:00
LOG_MSG ( " Gamecard not inserted! " ) ;
2020-04-15 06:59:12 +01:00
return false ;
}
2020-04-15 21:50:07 +01:00
for ( u8 i = 0 ; i < 2 ; i + + )
2020-04-15 06:59:12 +01:00
{
2020-04-15 21:50:07 +01:00
Result rc = 0 ;
u64 area_size = 0 ;
u8 area = ( i = = 0 ? GameCardStorageArea_Normal : GameCardStorageArea_Secure ) ;
if ( ! gamecardOpenStorageArea ( area ) )
{
2021-03-07 23:22:49 +00:00
LOG_MSG ( " Failed to open %s storage area! " , GAMECARD_STORAGE_AREA_NAME ( area ) ) ;
2020-04-15 21:50:07 +01:00
return false ;
}
rc = fsStorageGetSize ( & g_gameCardStorage , ( s64 * ) & area_size ) ;
gamecardCloseStorageArea ( ) ;
2020-04-16 01:06:41 +01:00
if ( R_FAILED ( rc ) | | ! area_size )
2020-04-15 21:50:07 +01:00
{
2021-03-07 23:22:49 +00:00
LOG_MSG ( " fsStorageGetSize failed to retrieve %s storage area size! (0x%08X). " , GAMECARD_STORAGE_AREA_NAME ( area ) , rc ) ;
2020-10-21 05:27:48 +01:00
g_gameCardStorageNormalAreaSize = g_gameCardStorageSecureAreaSize = g_gameCardStorageTotalSize = 0 ;
2020-04-15 21:50:07 +01:00
return false ;
}
if ( area = = GameCardStorageArea_Normal )
{
g_gameCardStorageNormalAreaSize = area_size ;
} else {
g_gameCardStorageSecureAreaSize = area_size ;
}
2020-04-15 06:59:12 +01:00
}
2020-10-21 05:27:48 +01:00
g_gameCardStorageTotalSize = ( g_gameCardStorageNormalAreaSize + g_gameCardStorageSecureAreaSize ) ;
2020-04-15 06:59:12 +01:00
return true ;
}
2020-04-16 05:37:16 +01:00
2020-04-26 09:35:01 +01:00
NX_INLINE u64 gamecardGetCapacityFromRomSizeValue ( u8 rom_size )
2020-04-16 05:37:16 +01:00
{
u64 capacity = 0 ;
switch ( rom_size )
{
case GameCardRomSize_1GiB :
2021-03-07 23:22:49 +00:00
capacity = GameCardCapacity_1GiB ;
2020-04-16 05:37:16 +01:00
break ;
case GameCardRomSize_2GiB :
2021-03-07 23:22:49 +00:00
capacity = GameCardCapacity_2GiB ;
2020-04-16 05:37:16 +01:00
break ;
case GameCardRomSize_4GiB :
2021-03-07 23:22:49 +00:00
capacity = GameCardCapacity_4GiB ;
2020-04-16 05:37:16 +01:00
break ;
case GameCardRomSize_8GiB :
2021-03-07 23:22:49 +00:00
capacity = GameCardCapacity_8GiB ;
2020-04-16 05:37:16 +01:00
break ;
case GameCardRomSize_16GiB :
2021-03-07 23:22:49 +00:00
capacity = GameCardCapacity_16GiB ;
2020-04-16 05:37:16 +01:00
break ;
case GameCardRomSize_32GiB :
2021-03-07 23:22:49 +00:00
capacity = GameCardCapacity_32GiB ;
2020-04-16 05:37:16 +01:00
break ;
default :
break ;
}
return capacity ;
}
2020-04-16 11:13:11 +01:00
2021-03-07 23:22:49 +00:00
static HashFileSystemContext * gamecardInitializeHashFileSystemContext ( const char * name , u64 offset , u64 size , u8 * hash , u64 hash_target_offset , u32 hash_target_size )
2020-04-16 11:13:11 +01:00
{
2021-03-10 01:12:01 +00:00
u32 i = 0 , magic = 0 ;
2021-03-07 23:22:49 +00:00
HashFileSystemContext * fs_ctx = NULL ;
HashFileSystemHeader fs_header = { 0 } ;
u8 fs_header_hash [ SHA256_HASH_SIZE ] = { 0 } ;
bool success = false , dump_fs_header = false ;
if ( ( name & & ! * name ) | | offset < ( GAMECARD_CERTIFICATE_OFFSET + sizeof ( FsGameCardCertificate ) ) | | ! IS_ALIGNED ( offset , GAMECARD_MEDIA_UNIT_SIZE ) | | \
( size & & ( ! IS_ALIGNED ( size , GAMECARD_MEDIA_UNIT_SIZE ) | | ( offset + size ) > g_gameCardStorageTotalSize ) ) )
{
LOG_MSG ( " Invalid parameters! " ) ;
goto end ;
}
/* Allocate memory for the output context. */
fs_ctx = calloc ( 1 , sizeof ( HashFileSystemContext ) ) ;
if ( ! fs_ctx )
{
LOG_MSG ( " Unable to allocate memory for Hash FS context! (offset 0x%lX). " , offset ) ;
goto end ;
}
/* Duplicate partition name. */
fs_ctx - > name = ( name ? strdup ( name ) : strdup ( g_gameCardHfsPartitionNames [ GameCardHashFileSystemPartitionType_Root ] ) ) ;
if ( ! fs_ctx - > name )
{
LOG_MSG ( " Failed to duplicate Hash FS partition name! (offset 0x%lX). " , offset ) ;
goto end ;
}
2021-03-10 01:12:01 +00:00
/* Determine Hash FS partition type. */
for ( i = GameCardHashFileSystemPartitionType_Root ; i < GameCardHashFileSystemPartitionType_Count ; i + + )
{
if ( ! strcmp ( g_gameCardHfsPartitionNames [ i ] , fs_ctx - > name ) ) break ;
}
if ( i > = GameCardHashFileSystemPartitionType_Count )
{
LOG_MSG ( " Failed to find a matching Hash FS partition type for \" %s \" ! (offset 0x%lX). " , fs_ctx - > name , offset ) ;
goto end ;
}
fs_ctx - > type = i ;
2021-03-07 23:22:49 +00:00
/* Read partial Hash FS header. */
if ( ! gamecardReadStorageArea ( & fs_header , sizeof ( HashFileSystemHeader ) , offset , false ) )
{
LOG_MSG ( " Failed to read partial Hash FS header! ( \" %s \" , offset 0x%lX). " , fs_ctx - > name , offset ) ;
goto end ;
}
magic = __builtin_bswap32 ( fs_header . magic ) ;
if ( magic ! = HFS0_MAGIC )
{
LOG_MSG ( " Invalid Hash FS magic word! (0x%08X) ( \" %s \" , offset 0x%lX). " , magic , fs_ctx - > name , offset ) ;
dump_fs_header = true ;
goto end ;
}
/* Check Hash FS entry count and name table size. */
/* Only allow a zero entry count if we're not dealing with the root partition. Never allow a zero-sized name table. */
if ( ( ! name & & ! fs_header . entry_count ) | | ! fs_header . name_table_size )
{
LOG_MSG ( " Invalid Hash FS entry count / name table size! ( \" %s \" , offset 0x%lX). " , fs_ctx - > name , offset ) ;
dump_fs_header = true ;
goto end ;
}
/* Calculate full Hash FS header size. */
fs_ctx - > header_size = ( sizeof ( HashFileSystemHeader ) + ( fs_header . entry_count * sizeof ( HashFileSystemEntry ) ) + fs_header . name_table_size ) ;
fs_ctx - > header_size = ALIGN_UP ( fs_ctx - > header_size , GAMECARD_MEDIA_UNIT_SIZE ) ;
2020-04-17 22:59:05 +01:00
2021-03-07 23:22:49 +00:00
/* Allocate memory for the full Hash FS header. */
fs_ctx - > header = calloc ( fs_ctx - > header_size , sizeof ( u8 ) ) ;
if ( ! fs_ctx - > header )
{
LOG_MSG ( " Unable to allocate 0x%lX bytes buffer for the full Hash FS header! ( \" %s \" , offset 0x%lX). " , fs_ctx - > header_size , fs_ctx - > name , offset ) ;
goto end ;
}
/* Read full Hash FS header. */
if ( ! gamecardReadStorageArea ( fs_ctx - > header , fs_ctx - > header_size , offset , false ) )
{
LOG_MSG ( " Failed to read full Hash FS header! ( \" %s \" , offset 0x%lX). " , fs_ctx - > name , offset ) ;
goto end ;
}
2020-04-17 22:59:05 +01:00
2021-03-07 23:22:49 +00:00
/* Verify Hash FS header (if possible). */
if ( hash & & hash_target_size & & ( hash_target_offset + hash_target_size ) < = fs_ctx - > header_size )
2020-04-17 22:59:05 +01:00
{
2021-03-07 23:22:49 +00:00
sha256CalculateHash ( fs_header_hash , fs_ctx - > header + hash_target_offset , hash_target_size ) ;
if ( memcmp ( fs_header_hash , hash , SHA256_HASH_SIZE ) ! = 0 )
2020-04-17 22:59:05 +01:00
{
2021-03-07 23:22:49 +00:00
LOG_MSG ( " Hash FS header doesn't match expected SHA-256 hash! ( \" %s \" , offset 0x%lX). " , fs_ctx - > name , offset ) ;
goto end ;
2020-04-17 22:59:05 +01:00
}
}
2021-03-07 23:22:49 +00:00
/* Fill context. */
fs_ctx - > offset = offset ;
if ( name )
{
/* Use provided partition size. */
fs_ctx - > size = size ;
} else {
/* Calculate root partition size. */
HashFileSystemEntry * fs_entry = hfsGetEntryByIndex ( fs_ctx , fs_header . entry_count - 1 ) ;
fs_ctx - > size = ( fs_ctx - > header_size + fs_entry - > offset + fs_entry - > size ) ;
}
/* Update flag. */
success = true ;
end :
if ( ! success & & fs_ctx )
{
if ( dump_fs_header ) LOG_DATA ( & fs_header , sizeof ( HashFileSystemHeader ) , " Partial Hash FS header dump ( \" %s \" , offset 0x%lX): " , fs_ctx - > name , offset ) ;
if ( fs_ctx - > header ) free ( fs_ctx - > header ) ;
if ( fs_ctx - > name ) free ( fs_ctx - > name ) ;
free ( fs_ctx ) ;
fs_ctx = NULL ;
}
return fs_ctx ;
2020-04-24 10:38:13 +01:00
}
2021-03-07 23:22:49 +00:00
static HashFileSystemContext * _gamecardGetHashFileSystemContext ( u8 hfs_partition_type )
2020-04-24 10:38:13 +01:00
{
2021-03-07 23:22:49 +00:00
HashFileSystemContext * fs_ctx = NULL ;
const char * partition_name = NULL ;
2020-07-03 10:31:22 +01:00
2021-03-07 23:22:49 +00:00
if ( ! g_gameCardInserted | | ! g_gameCardInfoLoaded | | ! g_gameCardHfsCount | | ! g_gameCardHfsCtx | | hfs_partition_type > = GameCardHashFileSystemPartitionType_Count )
{
LOG_MSG ( " Invalid parameters! " ) ;
goto end ;
}
2020-04-16 11:13:11 +01:00
2021-03-07 23:22:49 +00:00
/* Return right away if the root partition was requested. */
if ( hfs_partition_type = = GameCardHashFileSystemPartitionType_Root )
2020-04-24 10:38:13 +01:00
{
2021-03-07 23:22:49 +00:00
fs_ctx = g_gameCardHfsCtx [ 0 ] ;
goto end ;
2020-04-24 10:38:13 +01:00
}
2020-04-16 11:13:11 +01:00
2021-03-07 23:22:49 +00:00
/* Get requested partition name. */
partition_name = g_gameCardHfsPartitionNames [ hfs_partition_type ] ;
/* Try to find the requested partition by looping through our Hash FS contexts. */
for ( u32 i = 1 ; i < g_gameCardHfsCount ; i + + )
{
fs_ctx = g_gameCardHfsCtx [ i ] ;
if ( ! strcmp ( fs_ctx - > name , partition_name ) ) break ;
fs_ctx = NULL ;
}
if ( ! fs_ctx ) LOG_MSG ( " Failed to locate Hash FS partition \" %s \" ! " , partition_name ) ;
end :
return fs_ctx ;
2020-04-16 11:13:11 +01:00
}