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-07-03 10:31:22 +01:00
* Copyright ( c ) 2020 , DarkMatterCore < pabloacurielz @ gmail . com > .
*
* This file is part of nxdumptool ( https : //github.com/DarkMatterCore/nxdumptool).
*
* nxdumptool is free software ; you can redistribute it and / or modify it
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"))
2020-04-16 05:37:16 +01:00
# define GAMECARD_CAPACITY_1GiB (u64)0x40000000
# define GAMECARD_CAPACITY_2GiB (u64)0x80000000
# define GAMECARD_CAPACITY_4GiB (u64)0x100000000
# define GAMECARD_CAPACITY_8GiB (u64)0x200000000
# define GAMECARD_CAPACITY_16GiB (u64)0x400000000
# define GAMECARD_CAPACITY_32GiB (u64)0x800000000
2020-04-15 21:50:07 +01:00
/* Type definitions. */
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-24 10:38:13 +01:00
typedef struct {
u32 magic ; ///< "HFS0".
u32 entry_count ;
u32 name_table_size ;
u8 reserved [ 0x4 ] ;
} GameCardHashFileSystemHeader ;
typedef struct {
u64 offset ;
u64 size ;
u32 name_offset ;
u32 hash_target_size ;
u64 hash_target_offset ;
u8 hash [ SHA256_HASH_SIZE ] ;
} GameCardHashFileSystemEntry ;
2020-04-15 21:50:07 +01:00
typedef enum {
GameCardStorageArea_None = 0 ,
GameCardStorageArea_Normal = 1 ,
GameCardStorageArea_Secure = 2
} GameCardStorageArea ;
2020-04-15 06:59:12 +01:00
typedef struct {
2020-04-16 11:13:11 +01:00
u64 offset ; ///< Relative to the start of the gamecard header.
u64 size ; ///< Whole partition size.
u64 header_size ; ///< Full header size.
2020-04-24 10:38:13 +01:00
u8 * header ; ///< GameCardHashFileSystemHeader + (GameCardHashFileSystemEntry * entry_count) + Name Table.
2020-04-15 06:59:12 +01:00
} GameCardHashFileSystemPartitionInfo ;
2020-04-15 21:50:07 +01:00
/* Global variables. */
2020-05-03 00:40:50 +01:00
static Mutex g_gamecardMutex = 0 ;
2020-05-03 09:40:08 +01:00
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 } ;
static u64 g_gameCardStorageNormalAreaSize = 0 , g_gameCardStorageSecureAreaSize = 0 ;
2020-04-16 05:37:16 +01:00
static u64 g_gameCardCapacity = 0 ;
2020-04-15 06:59:12 +01:00
2020-04-24 10:38:13 +01:00
static u8 * g_gameCardHfsRootHeader = NULL ; /// GameCardHashFileSystemHeader + (entry_count * GameCardHashFileSystemEntry) + Name Table.
2020-04-15 06:59:12 +01:00
static GameCardHashFileSystemPartitionInfo * g_gameCardHfsPartitions = NULL ;
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
2020-04-24 10:38:13 +01:00
static GameCardHashFileSystemHeader * gamecardGetHashFileSystemPartitionHeader ( u8 hfs_partition_type , u32 * out_hfs_partition_idx ) ;
2020-04-26 09:35:01 +01:00
NX_INLINE GameCardHashFileSystemEntry * gamecardGetHashFileSystemEntryByIndex ( void * header , u32 idx ) ;
NX_INLINE char * gamecardGetHashFileSystemNameTable ( void * header ) ;
NX_INLINE char * gamecardGetHashFileSystemEntryNameByIndex ( void * header , u32 idx ) ;
2020-07-16 00:43:58 +01:00
static bool gamecardGetHashFileSystemEntryIndexByName ( void * header , const char * name , u32 * out_idx ) ;
2020-04-16 11:13:11 +01:00
2020-05-03 00:40:50 +01:00
bool gamecardInitialize ( void )
{
mutexLock ( & g_gamecardMutex ) ;
Result rc = 0 ;
2020-05-03 09:40:08 +01: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 )
{
LOGFILE ( " 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 ) )
{
2020-07-06 01:10:07 +01:00
LOGFILE ( " 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 ) )
{
LOGFILE ( " 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 ) )
{
LOGFILE ( " 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-07-06 01:10:07 +01:00
/* Create usermode exit event. */
2020-05-03 09:40:08 +01:00
ueventCreate ( & g_gameCardDetectionThreadExitEvent , true ) ;
2020-07-06 01:10:07 +01:00
/* Create usermode 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
2020-05-03 09:40:08 +01:00
ret = g_gamecardInterfaceInit = true ;
2020-05-03 00:40:50 +01:00
2020-07-13 07:36:17 +01:00
end :
2020-05-03 00:40:50 +01:00
mutexUnlock ( & g_gamecardMutex ) ;
return ret ;
}
void gamecardExit ( void )
{
mutexLock ( & g_gamecardMutex ) ;
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 ;
}
2020-05-03 09:40:08 +01:00
g_gamecardInterfaceInit = false ;
2020-05-03 00:40:50 +01:00
mutexUnlock ( & g_gamecardMutex ) ;
}
2020-04-15 06:59:12 +01:00
2020-05-03 09:40:08 +01:00
UEvent * gamecardGetStatusChangeUserEvent ( void )
{
mutexLock ( & g_gamecardMutex ) ;
UEvent * event = ( g_gamecardInterfaceInit ? & g_gameCardStatusChangeEvent : NULL ) ;
mutexUnlock ( & g_gamecardMutex ) ;
return event ;
}
2020-07-17 06:01:31 +01:00
u8 gamecardGetStatus ( void )
2020-04-15 06:59:12 +01:00
{
2020-05-03 00:40:50 +01:00
mutexLock ( & g_gamecardMutex ) ;
2020-07-17 06:01:31 +01:00
u8 status = ( g_gameCardInserted ? ( g_gameCardInfoLoaded ? GameCardStatus_InsertedAndInfoLoaded : GameCardStatus_InsertedAndInfoNotLoaded ) : GameCardStatus_NotInserted ) ;
2020-05-03 00:40:50 +01: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. */
2020-07-13 07:36:17 +01:00
mutexLock ( & g_gamecardMutex ) ;
2020-08-01 06:17:08 +01:00
bool ret = gamecardReadInitialData ( out ) ;
2020-07-13 07:36:17 +01:00
mutexUnlock ( & g_gamecardMutex ) ;
return ret ;
}
2020-04-15 06:59:12 +01:00
bool gamecardGetHeader ( GameCardHeader * out )
{
2020-05-03 00:40:50 +01: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 ) ) ;
2020-05-03 00:40:50 +01: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 ;
mutexLock ( & g_gamecardMutex ) ;
if ( g_gameCardInserted & & g_gameCardHandle . value & & out )
{
rc = fsDeviceOperatorGetGameCardDeviceCertificate ( & g_deviceOperator , & g_gameCardHandle , out ) ;
if ( R_FAILED ( rc ) ) LOGFILE ( " fsDeviceOperatorGetGameCardDeviceCertificate failed! (0x%08X) " , rc ) ;
ret = R_SUCCEEDED ( rc ) ;
}
mutexUnlock ( & g_gamecardMutex ) ;
return ret ;
}
2020-04-16 05:37:16 +01:00
bool gamecardGetTotalSize ( u64 * out )
2020-04-15 06:59:12 +01:00
{
2020-05-03 00:40:50 +01:00
mutexLock ( & g_gamecardMutex ) ;
2020-07-06 01:10:07 +01:00
bool ret = ( g_gameCardInserted & & g_gameCardInfoLoaded & & out ) ;
if ( ret ) * out = ( g_gameCardStorageNormalAreaSize + g_gameCardStorageSecureAreaSize ) ;
2020-05-03 00:40:50 +01: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
{
2020-05-03 00:40:50 +01: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 ) ) ;
2020-05-03 00:40:50 +01: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 )
{
2020-05-03 00:40:50 +01:00
mutexLock ( & g_gamecardMutex ) ;
2020-07-06 01:10:07 +01:00
bool ret = ( g_gameCardInserted & & g_gameCardInfoLoaded & & out ) ;
if ( ret ) * out = g_gameCardCapacity ;
2020-05-03 00:40:50 +01:00
mutexUnlock ( & g_gamecardMutex ) ;
2020-04-16 05:37:16 +01:00
return ret ;
}
2020-04-15 06:59:12 +01:00
bool gamecardGetBundledFirmwareUpdateVersion ( u32 * out )
{
Result rc = 0 ;
u64 update_id = 0 ;
u32 update_version = 0 ;
bool ret = false ;
2020-05-03 00:40:50 +01:00
mutexLock ( & g_gamecardMutex ) ;
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 ) ;
if ( R_FAILED ( rc ) ) LOGFILE ( " 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 ) ;
if ( ret ) * out = update_version ;
}
2020-05-03 00:40:50 +01:00
mutexUnlock ( & g_gamecardMutex ) ;
2020-04-15 06:59:12 +01:00
return ret ;
}
2020-07-29 22:02:21 +01:00
const char * gamecardGetHashFileSystemPartitionName ( u8 hfs_partition_type )
{
2020-10-03 18:09:29 +01:00
return ( hfs_partition_type < GameCardHashFileSystemPartitionType_Count ? g_gameCardHfsPartitionNames [ hfs_partition_type ] : NULL ) ;
2020-07-29 22:02:21 +01:00
}
2020-04-24 10:38:13 +01:00
bool gamecardGetEntryCountFromHashFileSystemPartition ( u8 hfs_partition_type , u32 * out_count )
{
bool ret = false ;
GameCardHashFileSystemHeader * fs_header = NULL ;
2020-05-03 00:40:50 +01:00
mutexLock ( & g_gamecardMutex ) ;
2020-04-24 10:38:13 +01:00
if ( g_gameCardInserted & & g_gameCardInfoLoaded & & out_count )
{
fs_header = gamecardGetHashFileSystemPartitionHeader ( hfs_partition_type , NULL ) ;
if ( fs_header )
{
* out_count = fs_header - > entry_count ;
ret = true ;
} else {
LOGFILE ( " Failed to retrieve hash FS partition header! " ) ;
}
}
2020-05-03 00:40:50 +01:00
mutexUnlock ( & g_gamecardMutex ) ;
2020-04-24 10:38:13 +01:00
return ret ;
}
bool gamecardGetEntryInfoFromHashFileSystemPartitionByIndex ( u8 hfs_partition_type , u32 idx , u64 * out_offset , u64 * out_size , char * * out_name )
2020-04-16 11:13:11 +01:00
{
bool ret = false ;
char * entry_name = NULL ;
2020-04-17 22:59:05 +01:00
u32 hfs_partition_idx = 0 ;
2020-04-16 11:13:11 +01:00
GameCardHashFileSystemHeader * fs_header = NULL ;
GameCardHashFileSystemEntry * fs_entry = NULL ;
2020-05-03 00:40:50 +01:00
mutexLock ( & g_gamecardMutex ) ;
2020-04-16 11:13:11 +01:00
2020-04-24 10:38:13 +01:00
if ( g_gameCardInserted & & g_gameCardInfoLoaded & & ( out_offset | | out_size | | out_name ) )
2020-04-16 11:13:11 +01:00
{
2020-04-24 10:38:13 +01:00
fs_header = gamecardGetHashFileSystemPartitionHeader ( hfs_partition_type , & hfs_partition_idx ) ;
if ( ! fs_header )
{
LOGFILE ( " Failed to retrieve hash FS partition header! " ) ;
2020-07-13 07:36:17 +01:00
goto end ;
2020-04-24 10:38:13 +01:00
}
fs_entry = gamecardGetHashFileSystemEntryByIndex ( fs_header , idx ) ;
if ( ! fs_entry )
{
LOGFILE ( " Failed to retrieve hash FS partition entry by index! " ) ;
2020-07-13 07:36:17 +01:00
goto end ;
2020-04-24 10:38:13 +01:00
}
if ( out_offset )
{
if ( hfs_partition_type = = GameCardHashFileSystemPartitionType_Root )
{
2020-07-16 00:43:58 +01:00
* out_offset = g_gameCardHfsPartitions [ idx ] . offset ; /* No need to recalculate what we already have. */
2020-04-24 10:38:13 +01:00
} else {
* out_offset = ( g_gameCardHfsPartitions [ hfs_partition_idx ] . offset + g_gameCardHfsPartitions [ hfs_partition_idx ] . header_size + fs_entry - > offset ) ;
}
}
if ( out_size ) * out_size = fs_entry - > size ;
if ( out_name )
{
entry_name = gamecardGetHashFileSystemEntryNameByIndex ( fs_header , idx ) ;
if ( ! entry_name | | ! strlen ( entry_name ) )
{
LOGFILE ( " Invalid hash FS partition entry name! " ) ;
2020-07-13 07:36:17 +01:00
goto end ;
2020-04-24 10:38:13 +01:00
}
* out_name = strdup ( entry_name ) ;
if ( ! * out_name )
{
LOGFILE ( " Failed to duplicate hash FS partition entry name! " ) ;
2020-07-13 07:36:17 +01:00
goto end ;
2020-04-24 10:38:13 +01:00
}
}
ret = true ;
2020-04-16 11:13:11 +01:00
}
2020-07-13 07:36:17 +01:00
end :
2020-05-03 00:40:50 +01:00
mutexUnlock ( & g_gamecardMutex ) ;
2020-04-16 11:13:11 +01:00
2020-04-24 10:38:13 +01:00
return ret ;
}
bool gamecardGetEntryInfoFromHashFileSystemPartitionByName ( u8 hfs_partition_type , const char * name , u64 * out_offset , u64 * out_size )
{
bool ret = false ;
u32 hfs_partition_idx = 0 , fs_entry_idx = 0 ;
GameCardHashFileSystemHeader * fs_header = NULL ;
GameCardHashFileSystemEntry * fs_entry = NULL ;
2020-05-03 00:40:50 +01:00
mutexLock ( & g_gamecardMutex ) ;
2020-04-24 10:38:13 +01:00
if ( g_gameCardInserted & & g_gameCardInfoLoaded & & ( out_offset | | out_size ) )
2020-04-16 11:13:11 +01:00
{
2020-04-24 10:38:13 +01:00
fs_header = gamecardGetHashFileSystemPartitionHeader ( hfs_partition_type , & hfs_partition_idx ) ;
if ( ! fs_header )
{
LOGFILE ( " Failed to retrieve hash FS partition header! " ) ;
2020-07-13 07:36:17 +01:00
goto end ;
2020-04-24 10:38:13 +01:00
}
2020-04-16 11:13:11 +01:00
2020-04-24 10:38:13 +01:00
if ( ! gamecardGetHashFileSystemEntryIndexByName ( fs_header , name , & fs_entry_idx ) )
{
LOGFILE ( " Failed to retrieve hash FS partition entry index by name! " ) ;
2020-07-13 07:36:17 +01:00
goto end ;
2020-04-24 10:38:13 +01:00
}
2020-04-16 11:13:11 +01:00
2020-04-24 10:38:13 +01:00
fs_entry = gamecardGetHashFileSystemEntryByIndex ( fs_header , fs_entry_idx ) ;
if ( ! fs_entry )
2020-04-16 11:13:11 +01:00
{
2020-04-24 10:38:13 +01:00
LOGFILE ( " Failed to retrieve hash FS partition entry by index! " ) ;
2020-07-13 07:36:17 +01:00
goto end ;
2020-04-24 10:38:13 +01:00
}
if ( out_offset )
{
if ( hfs_partition_type = = GameCardHashFileSystemPartitionType_Root )
{
2020-07-06 01:10:07 +01:00
* out_offset = g_gameCardHfsPartitions [ fs_entry_idx ] . offset ; /* No need to recalculate what we already have. */
2020-04-24 10:38:13 +01:00
} else {
* out_offset = ( g_gameCardHfsPartitions [ hfs_partition_idx ] . offset + g_gameCardHfsPartitions [ hfs_partition_idx ] . header_size + fs_entry - > offset ) ;
}
2020-04-16 11:13:11 +01:00
}
2020-04-24 10:38:13 +01:00
if ( out_size ) * out_size = fs_entry - > size ;
ret = true ;
2020-04-16 11:13:11 +01:00
}
2020-07-13 07:36:17 +01:00
end :
2020-05-03 00:40:50 +01:00
mutexUnlock ( & g_gamecardMutex ) ;
2020-04-16 11:13:11 +01:00
return ret ;
}
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
{
LOGFILE ( " Failed to create gamecard detection thread! " ) ;
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. */
2020-07-25 19:50:42 +01: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 ( ) ;
2020-07-25 19:50:42 +01:00
mutexUnlock ( & g_gamecardMutex ) ;
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 ;
2020-05-03 00:40:50 +01: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
}
2020-05-03 00:40:50 +01: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 ) ;
if ( R_FAILED ( rc ) ) LOGFILE ( " fsDeviceOperatorIsGameCardInserted failed! (0x%08X) " , rc ) ;
return ( R_SUCCEEDED ( rc ) & & inserted ) ;
}
static void gamecardLoadInfo ( void )
{
if ( g_gameCardInfoLoaded ) return ;
GameCardHashFileSystemHeader * fs_header = NULL ;
GameCardHashFileSystemEntry * fs_entry = NULL ;
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
{
2020-04-15 21:50:07 +01:00
LOGFILE ( " 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
{
LOGFILE ( " 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 )
{
LOGFILE ( " Invalid gamecard header magic word! (0x%08X) " , __builtin_bswap32 ( g_gameCardHeader . magic ) ) ;
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 )
{
2020-07-06 01:10:07 +01:00
LOGFILE ( " Invalid gamecard capacity value! (0x%02X). " , g_gameCardHeader . rom_size ) ;
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
}
2020-07-06 01:10:07 +01:00
/* Allocate memory for the root hash FS header. */
2020-04-15 06:59:12 +01:00
g_gameCardHfsRootHeader = calloc ( g_gameCardHeader . partition_fs_header_size , sizeof ( u8 ) ) ;
if ( ! g_gameCardHfsRootHeader )
{
LOGFILE ( " Unable to allocate memory for the root hash FS 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
/* Read root hash FS header. */
2020-04-15 21:50:07 +01:00
if ( ! gamecardReadStorageArea ( g_gameCardHfsRootHeader , g_gameCardHeader . partition_fs_header_size , g_gameCardHeader . partition_fs_header_address , false ) )
2020-04-15 06:59:12 +01:00
{
LOGFILE ( " Failed to read root hash FS header from offset 0x%lX! " , g_gameCardHeader . partition_fs_header_address ) ;
2020-07-13 07:36:17 +01:00
goto end ;
2020-04-15 06:59:12 +01:00
}
fs_header = ( GameCardHashFileSystemHeader * ) g_gameCardHfsRootHeader ;
if ( __builtin_bswap32 ( fs_header - > magic ) ! = GAMECARD_HFS0_MAGIC )
{
2020-07-06 01:10:07 +01:00
LOGFILE ( " Invalid magic word in root hash FS header! (0x%08X). " , __builtin_bswap32 ( fs_header - > magic ) ) ;
2020-07-13 07:36:17 +01:00
goto end ;
2020-04-15 06:59:12 +01:00
}
if ( ! fs_header - > entry_count | | ! fs_header - > name_table_size | | \
( sizeof ( GameCardHashFileSystemHeader ) + ( fs_header - > entry_count * sizeof ( GameCardHashFileSystemEntry ) ) + fs_header - > name_table_size ) > g_gameCardHeader . partition_fs_header_size )
{
LOGFILE ( " Invalid file count and/or name table size in root hash FS 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
/* Allocate memory for the hash FS partitions info. */
2020-04-15 06:59:12 +01:00
g_gameCardHfsPartitions = calloc ( fs_header - > entry_count , sizeof ( GameCardHashFileSystemEntry ) ) ;
if ( ! g_gameCardHfsPartitions )
{
LOGFILE ( " Unable to allocate memory for the hash FS partitions info! " ) ;
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 hash FS partitions. */
2020-04-15 06:59:12 +01:00
for ( u32 i = 0 ; i < fs_header - > entry_count ; i + + )
{
2020-04-16 11:13:11 +01:00
fs_entry = gamecardGetHashFileSystemEntryByIndex ( g_gameCardHfsRootHeader , i ) ;
if ( ! fs_entry | | ! fs_entry - > size )
2020-04-15 06:59:12 +01:00
{
2020-04-16 11:13:11 +01:00
LOGFILE ( " Invalid hash FS partition entry! " ) ;
2020-07-13 07:36:17 +01:00
goto end ;
2020-04-15 06:59:12 +01:00
}
g_gameCardHfsPartitions [ i ] . offset = ( g_gameCardHeader . partition_fs_header_address + g_gameCardHeader . partition_fs_header_size + fs_entry - > offset ) ;
g_gameCardHfsPartitions [ i ] . size = fs_entry - > size ;
2020-07-06 01:10:07 +01:00
/* Partially read the current hash FS partition header. */
2020-04-15 06:59:12 +01:00
GameCardHashFileSystemHeader partition_header = { 0 } ;
2020-04-15 21:50:07 +01:00
if ( ! gamecardReadStorageArea ( & partition_header , sizeof ( GameCardHashFileSystemHeader ) , g_gameCardHfsPartitions [ i ] . offset , false ) )
2020-04-15 06:59:12 +01:00
{
LOGFILE ( " Failed to partially read hash FS partition #%u header from offset 0x%lX! " , i , g_gameCardHfsPartitions [ i ] . offset ) ;
2020-07-13 07:36:17 +01:00
goto end ;
2020-04-15 06:59:12 +01:00
}
if ( __builtin_bswap32 ( partition_header . magic ) ! = GAMECARD_HFS0_MAGIC )
{
2020-07-06 01:10:07 +01:00
LOGFILE ( " Invalid magic word in hash FS partition #%u header! (0x%08X). " , i , __builtin_bswap32 ( partition_header . magic ) ) ;
2020-07-13 07:36:17 +01:00
goto end ;
2020-04-15 06:59:12 +01:00
}
if ( ! partition_header . name_table_size )
{
LOGFILE ( " Invalid name table size in hash FS partition #%u header! " , i ) ;
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 the full header size for the current hash FS partition and round it to a GAMECARD_MEDIA_UNIT_SIZE bytes boundary. */
2020-04-16 11:13:11 +01:00
g_gameCardHfsPartitions [ i ] . header_size = ( sizeof ( GameCardHashFileSystemHeader ) + ( partition_header . entry_count * sizeof ( GameCardHashFileSystemEntry ) ) + partition_header . name_table_size ) ;
2020-04-26 09:35:01 +01:00
g_gameCardHfsPartitions [ i ] . header_size = ALIGN_UP ( g_gameCardHfsPartitions [ i ] . header_size , GAMECARD_MEDIA_UNIT_SIZE ) ;
2020-04-15 06:59:12 +01:00
2020-07-06 01:10:07 +01:00
/* Allocate memory for the hash FS partition header. */
2020-04-16 11:13:11 +01:00
g_gameCardHfsPartitions [ i ] . header = calloc ( g_gameCardHfsPartitions [ i ] . header_size , sizeof ( u8 ) ) ;
2020-04-15 06:59:12 +01:00
if ( ! g_gameCardHfsPartitions [ i ] . header )
{
LOGFILE ( " Unable to allocate memory for the hash FS partition #%u header! " , i ) ;
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
/* Finally, read the full hash FS partition header. */
2020-04-16 11:13:11 +01:00
if ( ! gamecardReadStorageArea ( g_gameCardHfsPartitions [ i ] . header , g_gameCardHfsPartitions [ i ] . header_size , g_gameCardHfsPartitions [ i ] . offset , false ) )
2020-04-15 06:59:12 +01:00
{
LOGFILE ( " Failed to read full hash FS partition #%u header from offset 0x%lX! " , i , g_gameCardHfsPartitions [ i ] . offset ) ;
2020-07-13 07:36:17 +01:00
goto end ;
2020-04-15 06:59:12 +01:00
}
}
g_gameCardInfoLoaded = true ;
2020-07-13 07:36:17 +01:00
end :
2020-04-15 06:59:12 +01:00
if ( ! g_gameCardInfoLoaded ) gamecardFreeInfo ( ) ;
}
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-04-15 21:50:07 +01:00
g_gameCardStorageNormalAreaSize = 0 ;
g_gameCardStorageSecureAreaSize = 0 ;
2020-04-15 06:59:12 +01:00
2020-04-16 05:37:16 +01:00
g_gameCardCapacity = 0 ;
2020-04-15 06:59:12 +01:00
if ( g_gameCardHfsRootHeader )
{
if ( g_gameCardHfsPartitions )
{
GameCardHashFileSystemHeader * fs_header = ( GameCardHashFileSystemHeader * ) g_gameCardHfsRootHeader ;
for ( u32 i = 0 ; i < fs_header - > entry_count ; i + + )
{
if ( g_gameCardHfsPartitions [ i ] . header ) free ( g_gameCardHfsPartitions [ i ] . header ) ;
}
}
free ( g_gameCardHfsRootHeader ) ;
g_gameCardHfsRootHeader = NULL ;
}
if ( g_gameCardHfsPartitions )
{
free ( g_gameCardHfsPartitions ) ;
g_gameCardHfsPartitions = NULL ;
}
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 )
{
LOGFILE ( " Invalid parameters! " ) ;
return false ;
}
/* Clear output. */
memset ( out , 0 , sizeof ( GameCardKeyArea ) ) ;
/* Open secure storage area. */
if ( ! gamecardOpenStorageArea ( GameCardStorageArea_Secure ) )
{
LOGFILE ( " Failed to open secure storage area! " ) ;
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 ) )
{
LOGFILE ( " Failed to retrieve full FS program memory dump! " ) ;
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-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
{
2020-07-17 06:01:31 +01:00
LOGFILE ( " 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
{
2020-07-23 22:57:43 +01:00
//LOGFILE("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. */
2020-07-23 22:57:43 +01:00
//LOGFILE("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
}
2020-07-23 22:57:43 +01:00
if ( R_FAILED ( rc ) ) LOGFILE ( " fsDeviceOperatorGetGameCardHandle / fsOpenGameCardStorage failed! (0x%08X). " , rc ) ;
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
{
2020-07-17 06:01:31 +01:00
/* I need to find a way to properly close a gamecard handle... */
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
{
LOGFILE ( " Invalid parameters! " ) ;
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
{
2020-07-17 06:01:31 +01:00
LOGFILE ( " Failed to retrieve gamecard handle and storage area handle! " ) ;
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
{
2020-05-03 00:40:50 +01:00
if ( lock ) mutexLock ( & g_gamecardMutex ) ;
2020-04-15 06:59:12 +01:00
bool success = false ;
2020-04-17 22:59:05 +01:00
if ( ! g_gameCardInserted | | ! g_gameCardStorageNormalAreaSize | | ! g_gameCardStorageSecureAreaSize | | ! out | | ! read_size | | \
offset > = ( g_gameCardStorageNormalAreaSize + g_gameCardStorageSecureAreaSize ) | | ( offset + read_size ) > ( g_gameCardStorageNormalAreaSize + g_gameCardStorageSecureAreaSize ) )
2020-04-15 06:59:12 +01:00
{
LOGFILE ( " 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 ) )
{
LOGFILE ( " 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 ) )
{
2020-07-06 01:10:07 +01:00
LOGFILE ( " 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
{
2020-07-06 01:10:07 +01:00
LOGFILE ( " 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 :
2020-05-03 00:40:50 +01: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
{
2020-04-15 21:50:07 +01:00
LOGFILE ( " 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 ) )
{
LOGFILE ( " Failed to open %s storage area! " , GAMECARD_STORAGE_AREA_NAME ( area ) ) ;
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
{
2020-07-06 01:10:07 +01:00
LOGFILE ( " fsStorageGetSize failed to retrieve %s storage area size! (0x%08X). " , GAMECARD_STORAGE_AREA_NAME ( area ) , rc ) ;
2020-04-15 21:50:07 +01:00
g_gameCardStorageNormalAreaSize = g_gameCardStorageSecureAreaSize = 0 ;
return false ;
}
if ( area = = GameCardStorageArea_Normal )
{
g_gameCardStorageNormalAreaSize = area_size ;
} else {
g_gameCardStorageSecureAreaSize = area_size ;
}
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 :
capacity = GAMECARD_CAPACITY_1GiB ;
break ;
case GameCardRomSize_2GiB :
capacity = GAMECARD_CAPACITY_2GiB ;
break ;
case GameCardRomSize_4GiB :
capacity = GAMECARD_CAPACITY_4GiB ;
break ;
case GameCardRomSize_8GiB :
capacity = GAMECARD_CAPACITY_8GiB ;
break ;
case GameCardRomSize_16GiB :
capacity = GAMECARD_CAPACITY_16GiB ;
break ;
case GameCardRomSize_32GiB :
capacity = GAMECARD_CAPACITY_32GiB ;
break ;
default :
break ;
}
return capacity ;
}
2020-04-16 11:13:11 +01:00
2020-04-24 10:38:13 +01:00
static GameCardHashFileSystemHeader * gamecardGetHashFileSystemPartitionHeader ( u8 hfs_partition_type , u32 * out_hfs_partition_idx )
2020-04-16 11:13:11 +01:00
{
2020-04-24 10:38:13 +01:00
if ( hfs_partition_type > GameCardHashFileSystemPartitionType_Secure ) return NULL ;
2020-04-17 22:59:05 +01:00
2020-04-24 10:38:13 +01:00
u32 hfs_partition_idx = 0 ;
2020-04-17 22:59:05 +01:00
GameCardHashFileSystemHeader * fs_header = ( GameCardHashFileSystemHeader * ) g_gameCardHfsRootHeader ;
2020-04-24 10:38:13 +01:00
if ( hfs_partition_type ! = GameCardHashFileSystemPartitionType_Root )
2020-04-17 22:59:05 +01:00
{
2020-07-29 22:02:21 +01:00
if ( gamecardGetHashFileSystemEntryIndexByName ( fs_header , gamecardGetHashFileSystemPartitionName ( hfs_partition_type ) , & hfs_partition_idx ) )
2020-04-17 22:59:05 +01:00
{
2020-04-24 10:38:13 +01:00
fs_header = ( GameCardHashFileSystemHeader * ) g_gameCardHfsPartitions [ hfs_partition_idx ] . header ;
if ( out_hfs_partition_idx ) * out_hfs_partition_idx = hfs_partition_idx ;
} else {
fs_header = NULL ;
2020-04-17 22:59:05 +01:00
}
}
2020-04-24 10:38:13 +01:00
return fs_header ;
2020-04-16 11:13:11 +01:00
}
2020-04-26 09:35:01 +01:00
NX_INLINE GameCardHashFileSystemEntry * gamecardGetHashFileSystemEntryByIndex ( void * header , u32 idx )
2020-04-16 11:13:11 +01:00
{
2020-04-24 10:38:13 +01:00
if ( ! header | | idx > = ( ( GameCardHashFileSystemHeader * ) header ) - > entry_count ) return NULL ;
return ( GameCardHashFileSystemEntry * ) ( ( u8 * ) header + sizeof ( GameCardHashFileSystemHeader ) + ( idx * sizeof ( GameCardHashFileSystemEntry ) ) ) ;
2020-04-16 11:13:11 +01:00
}
2020-04-26 09:35:01 +01:00
NX_INLINE char * gamecardGetHashFileSystemNameTable ( void * header )
2020-04-16 11:13:11 +01:00
{
2020-04-24 10:38:13 +01:00
GameCardHashFileSystemHeader * fs_header = ( GameCardHashFileSystemHeader * ) header ;
if ( ! fs_header | | ! fs_header - > entry_count ) return NULL ;
return ( ( char * ) header + sizeof ( GameCardHashFileSystemHeader ) + ( fs_header - > entry_count * sizeof ( GameCardHashFileSystemEntry ) ) ) ;
}
2020-04-26 09:35:01 +01:00
NX_INLINE char * gamecardGetHashFileSystemEntryNameByIndex ( void * header , u32 idx )
2020-04-24 10:38:13 +01:00
{
GameCardHashFileSystemEntry * fs_entry = gamecardGetHashFileSystemEntryByIndex ( header , idx ) ;
char * name_table = gamecardGetHashFileSystemNameTable ( header ) ;
if ( ! fs_entry | | ! name_table ) return NULL ;
return ( name_table + fs_entry - > name_offset ) ;
}
2020-07-16 00:43:58 +01:00
static bool gamecardGetHashFileSystemEntryIndexByName ( void * header , const char * name , u32 * out_idx )
2020-04-24 10:38:13 +01:00
{
size_t name_len = 0 ;
2020-04-26 09:35:01 +01:00
GameCardHashFileSystemEntry * fs_entry = NULL ;
2020-04-24 10:38:13 +01:00
GameCardHashFileSystemHeader * fs_header = ( GameCardHashFileSystemHeader * ) header ;
char * name_table = gamecardGetHashFileSystemNameTable ( header ) ;
2020-07-03 10:31:22 +01:00
2020-04-24 10:38:13 +01:00
if ( ! fs_header | | ! fs_header - > entry_count | | ! name_table | | ! name | | ! ( name_len = strlen ( name ) ) | | ! out_idx ) return false ;
2020-04-16 11:13:11 +01:00
2020-04-24 10:38:13 +01:00
for ( u32 i = 0 ; i < fs_header - > entry_count ; i + + )
{
2020-04-26 09:35:01 +01:00
if ( ! ( fs_entry = gamecardGetHashFileSystemEntryByIndex ( header , i ) ) ) return false ;
2020-04-24 10:38:13 +01:00
2020-07-03 10:31:22 +01:00
if ( strlen ( name_table + fs_entry - > name_offset ) = = name_len & & ! strcmp ( name_table + fs_entry - > name_offset , name ) )
2020-04-24 10:38:13 +01:00
{
* out_idx = i ;
return true ;
}
}
2020-04-16 11:13:11 +01:00
2020-04-24 10:38:13 +01:00
return false ;
2020-04-16 11:13:11 +01:00
}