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
*
2023-04-08 12:42:22 +01:00
* Copyright ( c ) 2020 - 2023 , DarkMatterCore < pabloacurielz @ gmail . com > .
2020-07-03 10:31:22 +01:00
*
* This file is part of nxdumptool ( https : //github.com/DarkMatterCore/nxdumptool).
*
2021-03-25 19:26:58 +00:00
* nxdumptool is free software : you can redistribute it and / or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation , either version 3 of the License , or
* ( at your option ) any later version .
2020-04-16 01:06:41 +01:00
*
2021-03-25 19:26:58 +00:00
* nxdumptool is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
2020-04-16 01:06:41 +01:00
*
* You should have received a copy of the GNU General Public License
2021-03-25 19:26:58 +00:00
* along with this program . If not , see < https : //www.gnu.org/licenses/>.
2020-04-16 01:06:41 +01:00
*/
2021-03-26 04:35:14 +00:00
# include "nxdt_utils.h"
2020-07-13 07:36:17 +01:00
# include "mem.h"
2020-07-03 10:31:22 +01:00
# include "gamecard.h"
2021-05-22 09:45:40 +01:00
# include "keys.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
2023-11-26 21:13:49 +00:00
# define GAMECARD_ACCESS_DELAY 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
2021-04-06 01:40:57 +01:00
# define GAMECARD_UNUSED_AREA_SIZE(x) (((x) / GAMECARD_PAGE_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"))
2021-05-22 09:45:40 +01:00
# define LAFW_MAGIC 0x4C414657 /* "LAFW". */
2020-04-15 21:50:07 +01:00
/* Type definitions. */
2021-03-07 23:22:49 +00:00
typedef enum {
GameCardStorageArea_None = 0 ,
GameCardStorageArea_Normal = 1 ,
GameCardStorageArea_Secure = 2
} GameCardStorageArea ;
typedef enum {
2023-07-10 17:06:34 +01:00
GameCardCapacity_1GiB = BITL ( 30 ) ,
GameCardCapacity_2GiB = BITL ( 31 ) ,
GameCardCapacity_4GiB = BITL ( 32 ) ,
GameCardCapacity_8GiB = BITL ( 33 ) ,
GameCardCapacity_16GiB = BITL ( 34 ) ,
GameCardCapacity_32GiB = BITL ( 35 )
2021-03-07 23:22:49 +00:00
} GameCardCapacity ;
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 ;
2022-02-03 03:39:54 +00:00
static LotusAsicFirmwareBlob * g_lafwBlob = NULL ;
2021-05-22 09:45:40 +01:00
static u64 g_lafwVersion = 0 ;
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 } ;
2021-06-01 02:12:15 +01:00
static bool g_gameCardDetectionThreadCreated = false ;
static GameCardStatus g_gameCardStatus = GameCardStatus_NotInserted ;
2020-04-15 06:59:12 +01:00
static FsGameCardHandle g_gameCardHandle = { 0 } ;
2020-04-15 21:50:07 +01:00
static FsStorage g_gameCardStorage = { 0 } ;
2021-06-01 02:12:15 +01:00
static u8 g_gameCardCurrentStorageArea = GameCardStorageArea_None ;
2020-04-15 06:59:12 +01:00
static u8 * g_gameCardReadBuf = NULL ;
static GameCardHeader g_gameCardHeader = { 0 } ;
2021-05-22 09:45:40 +01:00
static GameCardInfo g_gameCardInfoArea = { 0 } ;
2021-06-01 02:12:15 +01:00
static u64 g_gameCardNormalAreaSize = 0 , g_gameCardSecureAreaSize = 0 , g_gameCardTotalSize = 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
} ;
2021-08-11 08:17:57 +01:00
static const char * g_gameCardHosVersionStrings [ GameCardFwVersion_Count ] = {
[ GameCardFwVersion_ForDev ] = " 1.0.0 " ,
[ GameCardFwVersion_Since100NUP ] = " 1.0.0 " ,
[ GameCardFwVersion_Since400NUP ] = " 4.0.0 " ,
[ GameCardFwVersion_Since900NUP ] = " 9.0.0 " ,
[ GameCardFwVersion_Since1100NUP ] = " 11.0.0 " ,
[ GameCardFwVersion_Since1200NUP ] = " 12.0.0 "
} ;
static const char * g_gameCardCompatibilityTypeStrings [ GameCardCompatibilityType_Count ] = {
[ GameCardCompatibilityType_Normal ] = " Normal " ,
[ GameCardCompatibilityType_Terra ] = " Terra "
} ;
2022-02-04 02:04:43 +00:00
static const char * g_lafwDeviceTypeStrings [ LotusAsicDeviceType_Count ] = {
[ LotusAsicDeviceType_Test ] = " Test " ,
[ LotusAsicDeviceType_Dev ] = " Dev " ,
[ LotusAsicDeviceType_Prod ] = " Prod " ,
[ LotusAsicDeviceType_Prod2Dev ] = " Prod2Dev "
} ;
2020-04-15 21:50:07 +01:00
/* Function prototypes. */
2022-02-03 03:39:54 +00:00
static bool gamecardReadLotusAsicFirmwareBlob ( void ) ;
2021-05-22 09:45:40 +01:00
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 ) ;
2021-06-01 02:12:15 +01:00
static void gamecardFreeInfo ( bool clear_status ) ;
static bool gamecardReadHeader ( void ) ;
2021-06-13 20:32:37 +01:00
static bool _gamecardGetDecryptedCardInfoArea ( void ) ;
2020-04-15 06:59:12 +01:00
2022-02-03 01:22:57 +00:00
static bool gamecardReadSecurityInformation ( GameCardSecurityInformation * out ) ;
2020-07-13 07:36:17 +01:00
2020-07-17 06:01:31 +01:00
static bool gamecardGetHandleAndStorage ( u32 partition ) ;
2020-04-15 06:59:12 +01:00
2020-04-15 21:50:07 +01:00
static bool gamecardOpenStorageArea ( u8 area ) ;
2021-05-18 13:32:43 +01:00
static bool gamecardReadStorageArea ( void * out , u64 read_size , u64 offset ) ;
2022-07-07 12:21:27 +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 )
{
Result rc = 0 ;
2021-05-18 13:32:43 +01:00
bool ret = false ;
2022-07-05 02:04:28 +01:00
2021-05-18 13:32:43 +01:00
SCOPED_LOCK ( & g_gameCardMutex )
2020-05-03 00:40:50 +01:00
{
2021-05-18 13:32:43 +01:00
ret = g_gameCardInterfaceInit ;
if ( ret ) break ;
2022-07-05 02:04:28 +01:00
2021-05-18 13:32:43 +01:00
/* Allocate memory for the gamecard read buffer. */
g_gameCardReadBuf = malloc ( GAMECARD_READ_BUFFER_SIZE ) ;
if ( ! g_gameCardReadBuf )
{
2022-07-12 17:34:49 +01:00
LOG_MSG_ERROR ( " Unable to allocate memory for the gamecard read buffer! " ) ;
2021-05-18 13:32:43 +01:00
break ;
}
2022-07-05 02:04:28 +01:00
2021-05-18 13:32:43 +01:00
/* Open device operator. */
rc = fsOpenDeviceOperator ( & g_deviceOperator ) ;
if ( R_FAILED ( rc ) )
{
2022-07-12 17:34:49 +01:00
LOG_MSG_ERROR ( " fsOpenDeviceOperator failed! (0x%X). " , rc ) ;
2021-05-18 13:32:43 +01:00
break ;
}
2022-07-05 02:04:28 +01:00
2021-05-18 13:32:43 +01:00
g_openDeviceOperator = true ;
2022-07-05 02:04:28 +01:00
2021-05-18 13:32:43 +01:00
/* Open gamecard detection event notifier. */
rc = fsOpenGameCardDetectionEventNotifier ( & g_gameCardEventNotifier ) ;
if ( R_FAILED ( rc ) )
{
2022-07-12 17:34:49 +01:00
LOG_MSG_ERROR ( " fsOpenGameCardDetectionEventNotifier failed! (0x%X) " , rc ) ;
2021-05-18 13:32:43 +01:00
break ;
}
2022-07-05 02:04:28 +01:00
2021-05-18 13:32:43 +01:00
g_openEventNotifier = true ;
2022-07-05 02:04:28 +01:00
2021-05-18 13:32:43 +01:00
/* Retrieve gamecard detection kernel event. */
rc = fsEventNotifierGetEventHandle ( & g_gameCardEventNotifier , & g_gameCardKernelEvent , true ) ;
if ( R_FAILED ( rc ) )
{
2022-07-12 17:34:49 +01:00
LOG_MSG_ERROR ( " fsEventNotifierGetEventHandle failed! (0x%X) " , rc ) ;
2021-05-18 13:32:43 +01:00
break ;
}
2022-07-05 02:04:28 +01:00
2021-05-18 13:32:43 +01:00
g_loadKernelEvent = true ;
2022-07-05 02:04:28 +01:00
2021-05-18 13:32:43 +01:00
/* Create user-mode exit event. */
ueventCreate ( & g_gameCardDetectionThreadExitEvent , true ) ;
2022-07-05 02:04:28 +01:00
2021-05-18 13:32:43 +01:00
/* Create user-mode gamecard status change event. */
ueventCreate ( & g_gameCardStatusChangeEvent , true ) ;
2022-07-05 02:04:28 +01:00
2022-02-03 03:39:54 +00:00
/* Retrieve LAFW blob. */
if ( ! gamecardReadLotusAsicFirmwareBlob ( ) ) break ;
2022-07-05 02:04:28 +01:00
2021-05-18 13:32:43 +01:00
/* Create gamecard detection thread. */
if ( ! ( g_gameCardDetectionThreadCreated = gamecardCreateDetectionThread ( ) ) ) break ;
2022-07-05 02:04:28 +01:00
2021-05-18 13:32:43 +01:00
/* Update flags. */
ret = g_gameCardInterfaceInit = true ;
2020-05-03 00:40:50 +01:00
}
2022-07-05 02:04:28 +01:00
2020-05-03 00:40:50 +01:00
return ret ;
}
void gamecardExit ( void )
{
2021-05-18 13:32:43 +01:00
SCOPED_LOCK ( & g_gameCardMutex )
2020-05-03 00:40:50 +01:00
{
2021-05-18 13:32:43 +01:00
/* Destroy gamecard detection thread. */
if ( g_gameCardDetectionThreadCreated )
{
gamecardDestroyDetectionThread ( ) ;
g_gameCardDetectionThreadCreated = false ;
}
2022-07-05 02:04:28 +01:00
2022-02-03 03:39:54 +00:00
/* Free LAFW blob buffer. */
if ( g_lafwBlob )
{
free ( g_lafwBlob ) ;
g_lafwBlob = NULL ;
}
2022-07-05 02:04:28 +01:00
2021-05-18 13:32:43 +01:00
/* Close gamecard detection kernel event. */
if ( g_loadKernelEvent )
{
eventClose ( & g_gameCardKernelEvent ) ;
g_loadKernelEvent = false ;
}
2022-07-05 02:04:28 +01:00
2021-05-18 13:32:43 +01:00
/* Close gamecard detection event notifier. */
if ( g_openEventNotifier )
{
fsEventNotifierClose ( & g_gameCardEventNotifier ) ;
g_openEventNotifier = false ;
}
2022-07-05 02:04:28 +01:00
2021-05-18 13:32:43 +01:00
/* Close device operator. */
if ( g_openDeviceOperator )
{
fsDeviceOperatorClose ( & g_deviceOperator ) ;
g_openDeviceOperator = false ;
}
2022-07-05 02:04:28 +01:00
2021-05-18 13:32:43 +01:00
/* Free gamecard read buffer. */
if ( g_gameCardReadBuf )
{
free ( g_gameCardReadBuf ) ;
g_gameCardReadBuf = NULL ;
}
2022-07-05 02:04:28 +01:00
2023-11-26 21:13:49 +00:00
/* Make sure NS can access the gamecard. */
/* Fixes gamecard launch errors after exiting the application. */
/* TODO: find out why this doesn't work. */
//Result rc = nsEnsureGameCardAccess();
//if (R_FAILED(rc)) LOG_MSG_ERROR("nsEnsureGameCardAccess failed! (0x%X).", rc);
2021-05-18 13:32:43 +01:00
g_gameCardInterfaceInit = false ;
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-05-18 13:32:43 +01:00
UEvent * event = NULL ;
2022-07-05 02:04:28 +01:00
2021-05-18 13:32:43 +01:00
SCOPED_LOCK ( & g_gameCardMutex )
{
if ( g_gameCardInterfaceInit ) event = & g_gameCardStatusChangeEvent ;
}
2022-07-05 02:04:28 +01:00
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-06-13 07:18:14 +01:00
u8 status = GameCardStatus_Processing ;
2022-07-05 02:04:28 +01:00
2021-06-09 05:48:17 +01:00
SCOPED_TRY_LOCK ( & g_gameCardMutex )
2021-05-18 13:32:43 +01:00
{
2021-06-01 02:12:15 +01:00
if ( g_gameCardInterfaceInit ) status = g_gameCardStatus ;
2021-05-18 13:32:43 +01:00
}
2022-07-05 02:04:28 +01:00
2020-07-17 06:01:31 +01:00
return status ;
2020-04-15 06:59:12 +01:00
}
2022-02-03 01:22:57 +00:00
/* Read full FS program memory to retrieve the GameCardSecurityInformation block. */
/* In FS program memory, this 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 gamecardReadSecurityInformation(). */
bool gamecardGetSecurityInformation ( GameCardSecurityInformation * out )
2020-07-13 07:36:17 +01:00
{
2021-05-18 13:32:43 +01:00
bool ret = false ;
2022-02-03 01:22:57 +00:00
SCOPED_LOCK ( & g_gameCardMutex ) ret = gamecardReadSecurityInformation ( out ) ;
2020-07-13 07:36:17 +01:00
return ret ;
}
2023-11-03 01:22:47 +00:00
bool gamecardGetCardIdSet ( FsGameCardIdSet * out )
2022-02-03 01:13:20 +00:00
{
bool ret = false ;
2022-07-05 02:04:28 +01:00
2022-02-03 01:13:20 +00:00
SCOPED_LOCK ( & g_gameCardMutex )
{
if ( ! g_gameCardInterfaceInit | | g_gameCardStatus ! = GameCardStatus_InsertedAndInfoLoaded | | ! out ) break ;
2022-07-05 02:04:28 +01:00
2023-11-03 01:22:47 +00:00
Result rc = fsDeviceOperatorGetGameCardIdSet ( & g_deviceOperator , out , sizeof ( FsGameCardIdSet ) , ( s64 ) sizeof ( FsGameCardIdSet ) ) ;
2022-07-12 17:34:49 +01:00
if ( R_FAILED ( rc ) ) LOG_MSG_ERROR ( " fsDeviceOperatorGetGameCardIdSet failed! (0x%X) " , rc ) ;
2022-07-05 02:04:28 +01:00
2022-02-03 01:13:20 +00:00
ret = R_SUCCEEDED ( rc ) ;
}
2022-07-05 02:04:28 +01:00
2022-02-03 01:13:20 +00:00
return ret ;
}
2022-02-03 03:39:54 +00:00
bool gamecardGetLotusAsicFirmwareBlob ( LotusAsicFirmwareBlob * out_lafw_blob , u64 * out_lafw_version )
{
bool ret = false ;
2022-07-05 02:04:28 +01:00
2022-02-03 03:39:54 +00:00
SCOPED_LOCK ( & g_gameCardMutex )
{
if ( ! g_gameCardInterfaceInit | | ! g_lafwBlob | | ( ! out_lafw_blob & & ! out_lafw_version ) ) break ;
2022-07-05 02:04:28 +01:00
2022-02-03 03:39:54 +00:00
/* Copy LAFW blob data. */
if ( out_lafw_blob ) memcpy ( out_lafw_blob , g_lafwBlob , sizeof ( LotusAsicFirmwareBlob ) ) ;
2022-07-05 02:04:28 +01:00
2022-02-03 03:39:54 +00:00
/* Copy LAFW version. */
if ( out_lafw_version ) * out_lafw_version = g_lafwVersion ;
2022-07-05 02:04:28 +01:00
2022-02-03 03:39:54 +00:00
ret = true ;
}
2022-07-05 02:04:28 +01:00
2022-02-03 03:39:54 +00:00
return ret ;
}
bool gamecardReadStorage ( void * out , u64 read_size , u64 offset )
{
bool ret = false ;
SCOPED_LOCK ( & g_gameCardMutex ) ret = gamecardReadStorageArea ( out , read_size , offset ) ;
return ret ;
}
2020-04-15 06:59:12 +01:00
bool gamecardGetHeader ( GameCardHeader * out )
{
2021-05-18 13:32:43 +01:00
bool ret = false ;
2022-07-05 02:04:28 +01:00
2021-05-18 13:32:43 +01:00
SCOPED_LOCK ( & g_gameCardMutex )
{
2021-06-01 02:12:15 +01:00
ret = ( g_gameCardInterfaceInit & & g_gameCardStatus = = GameCardStatus_InsertedAndInfoLoaded & & out ) ;
2021-05-18 13:32:43 +01:00
if ( ret ) memcpy ( out , & g_gameCardHeader , sizeof ( GameCardHeader ) ) ;
}
2022-07-05 02:04:28 +01:00
2020-04-15 06:59:12 +01:00
return ret ;
}
2021-06-13 20:32:37 +01:00
bool gamecardGetDecryptedCardInfoArea ( GameCardInfo * out )
{
bool ret = false ;
2022-07-05 02:04:28 +01:00
2021-06-13 20:32:37 +01:00
SCOPED_LOCK ( & g_gameCardMutex )
{
ret = ( g_gameCardInterfaceInit & & g_gameCardStatus = = GameCardStatus_InsertedAndInfoLoaded & & out ) ;
if ( ret ) memcpy ( out , & g_gameCardInfoArea , sizeof ( GameCardInfo ) ) ;
}
2022-07-05 02:04:28 +01:00
2021-06-13 20:32:37 +01:00
return ret ;
}
2020-07-13 07:36:17 +01:00
bool gamecardGetCertificate ( FsGameCardCertificate * out )
{
bool ret = false ;
2022-07-05 02:04:28 +01:00
2021-05-18 13:32:43 +01:00
SCOPED_LOCK ( & g_gameCardMutex )
2020-07-13 07:36:17 +01:00
{
2021-06-01 02:12:15 +01:00
if ( ! g_gameCardInterfaceInit | | g_gameCardStatus ! = GameCardStatus_InsertedAndInfoLoaded | | ! g_gameCardHandle . value | | ! out ) break ;
2022-07-05 02:04:28 +01:00
2021-02-12 20:35:23 +00:00
/* Read the gamecard certificate using the official IPC call. */
2021-05-18 13:32:43 +01:00
Result rc = fsDeviceOperatorGetGameCardDeviceCertificate ( & g_deviceOperator , & g_gameCardHandle , out ) ;
2022-07-12 17:34:49 +01:00
if ( R_FAILED ( rc ) ) LOG_MSG_ERROR ( " fsDeviceOperatorGetGameCardDeviceCertificate failed! (0x%X) " , rc ) ;
2022-07-05 02:04:28 +01:00
2020-07-13 07:36:17 +01:00
ret = R_SUCCEEDED ( rc ) ;
}
2022-07-05 02:04:28 +01:00
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-05-18 13:32:43 +01:00
bool ret = false ;
2022-07-05 02:04:28 +01:00
2021-05-18 13:32:43 +01:00
SCOPED_LOCK ( & g_gameCardMutex )
{
2021-06-01 02:12:15 +01:00
ret = ( g_gameCardInterfaceInit & & g_gameCardStatus = = GameCardStatus_InsertedAndInfoLoaded & & out ) ;
if ( ret ) * out = g_gameCardTotalSize ;
2021-05-18 13:32:43 +01:00
}
2022-07-05 02:04:28 +01:00
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-05-18 13:32:43 +01:00
bool ret = false ;
2022-07-05 02:04:28 +01:00
2021-05-18 13:32:43 +01:00
SCOPED_LOCK ( & g_gameCardMutex )
{
2021-06-01 02:12:15 +01:00
ret = ( g_gameCardInterfaceInit & & g_gameCardStatus = = GameCardStatus_InsertedAndInfoLoaded & & out ) ;
2023-11-03 01:22:47 +00:00
if ( ret ) * out = ( sizeof ( GameCardHeader ) + GAMECARD_PAGE_OFFSET ( g_gameCardHeader . valid_data_end_page ) ) ;
2021-05-18 13:32:43 +01:00
}
2022-07-05 02:04:28 +01:00
2020-04-15 06:59:12 +01:00
return ret ;
}
2020-04-16 05:37:16 +01:00
bool gamecardGetRomCapacity ( u64 * out )
{
2021-05-18 13:32:43 +01:00
bool ret = false ;
2022-07-05 02:04:28 +01:00
2021-05-18 13:32:43 +01:00
SCOPED_LOCK ( & g_gameCardMutex )
{
2021-06-01 02:12:15 +01:00
ret = ( g_gameCardInterfaceInit & & g_gameCardStatus = = GameCardStatus_InsertedAndInfoLoaded & & out ) ;
2021-05-18 13:32:43 +01:00
if ( ret ) * out = g_gameCardCapacity ;
}
2022-07-05 02:04:28 +01:00
2020-04-16 05:37:16 +01:00
return ret ;
}
2022-03-17 12:37:24 +00:00
bool gamecardGetBundledFirmwareUpdateVersion ( Version * out )
2020-04-15 06:59:12 +01:00
{
bool ret = false ;
2022-07-05 02:04:28 +01:00
2021-05-18 13:32:43 +01:00
SCOPED_LOCK ( & g_gameCardMutex )
2020-04-15 06:59:12 +01:00
{
2021-06-01 02:12:15 +01:00
if ( ! g_gameCardInterfaceInit | | g_gameCardStatus ! = GameCardStatus_InsertedAndInfoLoaded | | ! g_gameCardHandle . value | | ! out ) break ;
2022-07-05 02:04:28 +01:00
2021-05-18 13:32:43 +01:00
u64 update_id = 0 ;
u32 update_version = 0 ;
2022-07-05 02:04:28 +01:00
2021-05-18 13:32:43 +01:00
Result rc = fsDeviceOperatorUpdatePartitionInfo ( & g_deviceOperator , & g_gameCardHandle , & update_version , & update_id ) ;
2022-07-12 17:34:49 +01:00
if ( R_FAILED ( rc ) ) LOG_MSG_ERROR ( " fsDeviceOperatorUpdatePartitionInfo failed! (0x%X) " , rc ) ;
2022-07-05 02:04:28 +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
}
2022-07-05 02:04:28 +01:00
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
{
2023-05-24 20:05:34 +01:00
if ( hfs_partition_type < HashFileSystemPartitionType_Root | | hfs_partition_type > = HashFileSystemPartitionType_Count | | ! out )
2021-03-10 01:12:01 +00:00
{
2022-07-12 17:34:49 +01:00
LOG_MSG_ERROR ( " Invalid parameters! " ) ;
2021-03-10 01:12:01 +00:00
return false ;
}
2022-07-05 02:04:28 +01:00
2021-05-18 13:32:43 +01:00
bool ret = false ;
2022-07-05 02:04:28 +01:00
2021-03-10 01:12:01 +00:00
/* Free Hash FS context. */
hfsFreeContext ( out ) ;
2022-07-05 02:04:28 +01:00
2021-05-18 13:32:43 +01:00
SCOPED_LOCK ( & g_gameCardMutex )
2021-03-07 23:22:49 +00:00
{
2021-05-18 13:32:43 +01:00
/* Get pointer to the Hash FS context for the requested partition. */
2023-05-24 20:05:34 +01:00
HashFileSystemContext * hfs_ctx = _gamecardGetHashFileSystemContext ( hfs_partition_type ) ;
if ( ! hfs_ctx ) break ;
2022-07-05 02:04:28 +01:00
2021-05-18 13:32:43 +01:00
/* Fill Hash FS context. */
2023-05-24 20:05:34 +01:00
out - > name = strdup ( hfs_ctx - > name ) ;
2021-05-18 13:32:43 +01:00
if ( ! out - > name )
{
2023-05-24 20:05:34 +01:00
LOG_MSG_ERROR ( " Failed to duplicate Hash FS partition name! (%s). " , hfs_ctx - > name ) ;
2021-05-18 13:32:43 +01:00
break ;
}
2022-07-05 02:04:28 +01:00
2023-05-24 20:05:34 +01:00
out - > type = hfs_ctx - > type ;
out - > offset = hfs_ctx - > offset ;
out - > size = hfs_ctx - > size ;
out - > header_size = hfs_ctx - > header_size ;
2022-07-05 02:04:28 +01:00
2023-05-24 20:05:34 +01:00
out - > header = calloc ( hfs_ctx - > header_size , sizeof ( u8 ) ) ;
2021-05-18 13:32:43 +01:00
if ( ! out - > header )
{
2023-05-24 20:05:34 +01:00
LOG_MSG_ERROR ( " Failed to duplicate Hash FS partition header! (%s). " , hfs_ctx - > name ) ;
2021-05-18 13:32:43 +01:00
break ;
}
2022-07-05 02:04:28 +01:00
2023-05-24 20:05:34 +01:00
memcpy ( out - > header , hfs_ctx - > header , hfs_ctx - > header_size ) ;
2022-07-05 02:04:28 +01:00
2021-05-18 13:32:43 +01:00
/* Update flag. */
ret = true ;
2020-04-16 11:13:11 +01:00
}
2022-07-05 02:04:28 +01:00
2021-05-18 13:32:43 +01:00
if ( ! ret ) hfsFreeContext ( out ) ;
2022-07-05 02:04:28 +01:00
2021-05-18 13:32:43 +01:00
return ret ;
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
{
2023-05-24 20:05:34 +01:00
if ( hfs_partition_type < HashFileSystemPartitionType_Root | | hfs_partition_type > = HashFileSystemPartitionType_Count | | ! entry_name | | ! * entry_name | | ( ! out_offset & & ! out_size ) )
2020-04-16 11:13:11 +01:00
{
2022-07-12 17:34:49 +01:00
LOG_MSG_ERROR ( " Invalid parameters! " ) ;
2021-03-07 23:22:49 +00:00
return false ;
2020-04-16 11:13:11 +01:00
}
2022-07-05 02:04:28 +01:00
2021-05-18 13:32:43 +01:00
bool ret = false ;
2022-07-05 02:04:28 +01:00
2021-05-18 13:32:43 +01:00
SCOPED_LOCK ( & g_gameCardMutex )
{
/* Get pointer to the Hash FS context for the requested partition. */
2023-05-24 20:05:34 +01:00
HashFileSystemContext * hfs_ctx = _gamecardGetHashFileSystemContext ( hfs_partition_type ) ;
if ( ! hfs_ctx ) break ;
2022-07-05 02:04:28 +01:00
2021-05-18 13:32:43 +01:00
/* Get Hash FS entry by name. */
2023-05-24 20:05:34 +01:00
HashFileSystemEntry * hfs_entry = hfsGetEntryByName ( hfs_ctx , entry_name ) ;
if ( ! hfs_entry ) break ;
2022-07-05 02:04:28 +01:00
2021-05-18 13:32:43 +01:00
/* Update output variables. */
2023-05-24 20:05:34 +01:00
if ( out_offset ) * out_offset = ( hfs_ctx - > offset + hfs_ctx - > header_size + hfs_entry - > offset ) ;
if ( out_size ) * out_size = hfs_entry - > size ;
2022-07-05 02:04:28 +01:00
2021-05-18 13:32:43 +01:00
/* Update flag. */
ret = true ;
}
2022-07-05 02:04:28 +01:00
2021-05-18 13:32:43 +01:00
return ret ;
2020-04-16 11:13:11 +01:00
}
2020-04-15 06:59:12 +01:00
2021-08-11 08:17:57 +01:00
const char * gamecardGetRequiredHosVersionString ( u64 fw_version )
{
return ( fw_version < GameCardFwVersion_Count ? g_gameCardHosVersionStrings [ fw_version ] : NULL ) ;
}
const char * gamecardGetCompatibilityTypeString ( u8 compatibility_type )
{
return ( compatibility_type < GameCardCompatibilityType_Count ? g_gameCardCompatibilityTypeStrings [ compatibility_type ] : NULL ) ;
}
2022-02-04 02:04:43 +00:00
const char * gamecardGetLafwTypeString ( u32 fw_type )
2021-05-22 09:45:40 +01:00
{
2022-02-03 03:39:54 +00:00
const char * type = NULL ;
2022-07-05 02:04:28 +01:00
2022-02-04 02:04:43 +00:00
switch ( fw_type )
2021-05-22 09:45:40 +01:00
{
2022-02-03 03:39:54 +00:00
case LotusAsicFirmwareType_ReadFw :
type = " ReadFw " ;
break ;
case LotusAsicFirmwareType_ReadDevFw :
type = " ReadDevFw " ;
break ;
case LotusAsicFirmwareType_WriterFw :
type = " WriterFw " ;
break ;
case LotusAsicFirmwareType_RmaFw :
type = " RmaFw " ;
break ;
default :
break ;
2021-05-22 09:45:40 +01:00
}
2022-07-05 02:04:28 +01:00
2022-02-03 03:39:54 +00:00
return type ;
2022-02-03 01:22:57 +00:00
}
2022-02-04 02:04:43 +00:00
const char * gamecardGetLafwDeviceTypeString ( u64 device_type )
{
return ( device_type < LotusAsicDeviceType_Count ? g_lafwDeviceTypeStrings [ device_type ] : NULL ) ;
}
2022-02-03 03:39:54 +00:00
static bool gamecardReadLotusAsicFirmwareBlob ( void )
{
u64 fw_version = 0 ;
bool ret = false , found = false , dev_unit = utilsIsDevelopmentUnit ( ) ;
2022-07-05 02:04:28 +01:00
2022-02-03 03:39:54 +00:00
/* Allocate memory for the LAFW blob. */
g_lafwBlob = calloc ( 1 , sizeof ( LotusAsicFirmwareBlob ) ) ;
if ( ! g_lafwBlob )
2021-05-22 09:45:40 +01:00
{
2022-07-12 17:34:49 +01:00
LOG_MSG_ERROR ( " Failed to allocate memory for LAFW blob! " ) ;
2022-02-03 03:39:54 +00:00
goto end ;
2021-05-22 09:45:40 +01:00
}
2022-07-05 02:04:28 +01:00
2022-02-03 03:39:54 +00:00
/* Temporarily set the segment mask to .data. */
g_fsProgramMemory . mask = MemoryProgramSegmentType_Data ;
2022-07-05 02:04:28 +01:00
2023-07-17 00:03:05 +01:00
/* Retrieve FS .data segment memory dump. */
if ( ! memRetrieveProgramMemorySegment ( & g_fsProgramMemory ) )
2021-05-22 09:45:40 +01:00
{
2022-07-12 17:34:49 +01:00
LOG_MSG_ERROR ( " Failed to retrieve FS .data segment dump! " ) ;
2022-02-03 03:39:54 +00:00
goto end ;
}
2022-07-05 02:04:28 +01:00
2023-07-17 00:03:05 +01:00
/* Look for the LAFW ReadFw blob in the FS .data segment memory dump. */
2022-02-03 03:39:54 +00:00
for ( u64 offset = 0 ; offset < g_fsProgramMemory . data_size ; offset + + )
{
if ( ( g_fsProgramMemory . data_size - offset ) < sizeof ( LotusAsicFirmwareBlob ) ) break ;
2022-07-05 02:04:28 +01:00
2022-02-03 03:39:54 +00:00
LotusAsicFirmwareBlob * lafw_blob = ( LotusAsicFirmwareBlob * ) ( g_fsProgramMemory . data + offset ) ;
u32 magic = __builtin_bswap32 ( lafw_blob - > magic ) , fw_type = lafw_blob - > fw_type ;
2022-07-05 02:04:28 +01:00
2022-02-03 03:39:54 +00:00
if ( magic = = LAFW_MAGIC & & ( ( ! dev_unit & & fw_type = = LotusAsicFirmwareType_ReadFw ) | | ( dev_unit & & fw_type = = LotusAsicFirmwareType_ReadDevFw ) ) )
{
/* Jackpot. */
memcpy ( g_lafwBlob , lafw_blob , sizeof ( LotusAsicFirmwareBlob ) ) ;
fw_version = lafw_blob - > fw_version ;
found = true ;
break ;
}
}
2022-07-05 02:04:28 +01:00
2022-02-03 03:39:54 +00:00
if ( ! found )
{
2022-07-12 17:34:49 +01:00
LOG_MSG_ERROR ( " Unable to locate Lotus %s blob in FS .data segment! " , dev_unit ? " ReadDevFw " : " ReadFw " ) ;
2022-02-03 01:22:57 +00:00
goto end ;
2021-05-22 09:45:40 +01:00
}
2022-07-05 02:04:28 +01:00
2022-02-03 01:22:57 +00:00
/* Convert LAFW version bitmask to an integer. */
2022-02-03 03:39:54 +00:00
g_lafwVersion = 0 ;
2022-07-05 02:04:28 +01:00
2022-02-03 03:39:54 +00:00
while ( fw_version )
{
g_lafwVersion + = ( fw_version & 1 ) ;
fw_version > > = 1 ;
}
2022-07-05 02:04:28 +01:00
2022-07-12 17:34:49 +01:00
LOG_MSG_INFO ( " LAFW version: %lu. " , g_lafwVersion ) ;
2022-07-05 02:04:28 +01:00
2021-05-22 09:45:40 +01:00
/* Update flag. */
ret = true ;
2022-07-05 02:04:28 +01:00
2021-05-22 09:45:40 +01:00
end :
2022-02-03 03:39:54 +00:00
memFreeMemoryLocation ( & g_fsProgramMemory ) ;
2022-07-05 02:04:28 +01:00
2023-07-17 00:03:05 +01:00
g_fsProgramMemory . mask = MemoryProgramSegmentType_None ;
2021-05-22 09:45:40 +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
{
2022-07-12 17:34:49 +01:00
LOG_MSG_ERROR ( " Failed to create gamecard detection thread! " ) ;
2020-04-15 06:59:12 +01:00
return false ;
}
2022-07-05 02:04:28 +01:00
2020-04-15 06:59:12 +01:00
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 ) ;
2022-07-05 02:04:28 +01:00
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
{
2023-12-20 19:32:48 +00:00
NX_IGNORE_ARG ( arg ) ;
2022-07-05 02:04:28 +01:00
2020-04-15 06:59:12 +01:00
Result rc = 0 ;
int idx = 0 ;
2022-07-05 02:04:28 +01:00
2020-04-15 06:59:12 +01:00
Waiter gamecard_event_waiter = waiterForEvent ( & g_gameCardKernelEvent ) ;
Waiter exit_event_waiter = waiterForUEvent ( & g_gameCardDetectionThreadExitEvent ) ;
2022-07-05 02:04:28 +01:00
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-05-18 13:32:43 +01:00
SCOPED_LOCK ( & g_gameCardMutex )
{
2021-06-01 02:12:15 +01:00
if ( gamecardIsInserted ( ) ) gamecardLoadInfo ( ) ;
ueventSignal ( & g_gameCardStatusChangeEvent ) ;
2021-05-18 13:32:43 +01:00
}
2022-07-05 02:04:28 +01:00
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 ;
2022-07-05 02:04:28 +01:00
2020-07-06 01:10:07 +01:00
/* Exit event triggered. */
2020-04-15 06:59:12 +01:00
if ( idx = = 1 ) break ;
2022-07-05 02:04:28 +01:00
2021-05-18 13:32:43 +01:00
SCOPED_LOCK ( & g_gameCardMutex )
2020-04-15 06:59:12 +01:00
{
2021-05-18 13:32:43 +01:00
/* Free gamecard info before proceeding. */
2021-06-01 02:12:15 +01:00
gamecardFreeInfo ( true ) ;
2022-07-05 02:04:28 +01:00
2021-05-18 13:32:43 +01:00
/* Retrieve current gamecard insertion status. */
/* Only proceed if we're dealing with a status change. */
2021-06-01 02:12:15 +01:00
if ( gamecardIsInserted ( ) )
2021-05-18 13:32:43 +01:00
{
/* Don't access the gamecard immediately to avoid conflicts with HOS / sysmodules. */
2023-10-22 23:44:40 +01:00
utilsSleep ( GAMECARD_ACCESS_DELAY ) ;
2022-07-05 02:04:28 +01:00
2021-05-18 13:32:43 +01:00
/* Load gamecard info. */
gamecardLoadInfo ( ) ;
}
2022-07-05 02:04:28 +01:00
2021-06-01 02:12:15 +01:00
/* Signal user mode gamecard status change event. */
ueventSignal ( & g_gameCardStatusChangeEvent ) ;
2020-04-15 06:59:12 +01:00
}
}
2022-07-05 02:04:28 +01:00
2020-07-06 01:10:07 +01:00
/* Free gamecard info and close gamecard handle. */
2021-06-01 02:12:15 +01:00
gamecardFreeInfo ( true ) ;
2022-07-05 02:04:28 +01:00
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 ) ;
2022-07-12 17:34:49 +01:00
if ( R_FAILED ( rc ) ) LOG_MSG_ERROR ( " fsDeviceOperatorIsGameCardInserted failed! (0x%X) " , rc ) ;
2020-04-15 06:59:12 +01:00
return ( R_SUCCEEDED ( rc ) & & inserted ) ;
}
static void gamecardLoadInfo ( void )
{
2021-06-01 02:12:15 +01:00
if ( g_gameCardStatus = = GameCardStatus_InsertedAndInfoLoaded ) return ;
2022-07-05 02:04:28 +01:00
2023-05-24 20:05:34 +01:00
HashFileSystemContext * root_hfs_ctx = NULL ;
u32 root_hfs_entry_count = 0 , root_hfs_name_table_size = 0 ;
char * root_hfs_name_table = NULL ;
2022-07-05 02:04:28 +01:00
2021-06-01 02:12:15 +01:00
/* Set initial gamecard status. */
g_gameCardStatus = GameCardStatus_InsertedAndInfoNotLoaded ;
2022-07-05 02:04:28 +01:00
2020-07-06 01:10:07 +01:00
/* Read gamecard header. */
2021-06-01 02:12:15 +01:00
/* This step *will* fail if the running CFW enabled the "nogc" patch. */
/* gamecardGetHandleAndStorage() takes care of updating the gamecard status accordingly if this happens. */
if ( ! gamecardReadHeader ( ) ) goto end ;
2022-07-05 02:04:28 +01:00
2021-06-01 02:12:15 +01:00
/* Get decrypted CardInfo area from header. */
2021-06-13 20:32:37 +01:00
if ( ! _gamecardGetDecryptedCardInfoArea ( ) ) goto end ;
2022-07-05 02:04:28 +01:00
2021-05-22 09:45:40 +01:00
/* Check if we meet the Lotus ASIC firmware (LAFW) version requirement. */
2021-06-14 05:55:22 +01:00
if ( g_lafwVersion < g_gameCardInfoArea . fw_version )
2021-05-22 09:45:40 +01:00
{
2022-07-12 17:34:49 +01:00
LOG_MSG_ERROR ( " LAFW version doesn't meet gamecard requirement! (%lu < %lu). " , g_lafwVersion , g_gameCardInfoArea . fw_version ) ;
2021-06-01 02:12:15 +01:00
g_gameCardStatus = GameCardStatus_LotusAsicFirmwareUpdateRequired ;
2021-05-22 09:45:40 +01:00
goto end ;
}
2022-07-05 02:04:28 +01:00
2021-06-01 02:12:15 +01:00
/* Retrieve gamecard storage area sizes. */
/* gamecardReadStorageArea() actually checks if the storage area sizes are greater than zero, so we must perform this step. */
if ( ! gamecardGetStorageAreasSizes ( ) )
{
2022-07-12 17:34:49 +01:00
LOG_MSG_ERROR ( " Failed to retrieve gamecard storage area sizes! " ) ;
2021-06-01 02:12:15 +01:00
goto end ;
}
2022-07-05 02:04:28 +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 )
{
2022-07-12 17:34:49 +01:00
LOG_MSG_ERROR ( " 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
}
2022-07-05 02:04:28 +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. */
2021-06-01 02:12:15 +01:00
g_gameCardSecureAreaSize = ( g_gameCardCapacity - ( g_gameCardNormalAreaSize + GAMECARD_UNUSED_AREA_SIZE ( g_gameCardCapacity ) ) ) ;
2020-04-15 06:59:12 +01:00
}
2022-07-05 02:04:28 +01:00
2021-03-07 23:22:49 +00:00
/* Initialize Hash FS context for the root partition. */
2023-05-24 20:05:34 +01:00
root_hfs_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_hfs_ctx ) goto end ;
2022-07-05 02:04:28 +01:00
2021-03-07 23:22:49 +00:00
/* Calculate total Hash FS partition count. */
2023-05-24 20:05:34 +01:00
root_hfs_entry_count = hfsGetEntryCount ( root_hfs_ctx ) ;
g_gameCardHfsCount = ( root_hfs_entry_count + 1 ) ;
2022-07-05 02:04:28 +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
{
2022-07-12 17:34:49 +01:00
LOG_MSG_ERROR ( " 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
}
2022-07-05 02:04:28 +01:00
2021-03-07 23:22:49 +00:00
/* Set root partition context as the first pointer. */
2023-05-24 20:05:34 +01:00
g_gameCardHfsCtx [ 0 ] = root_hfs_ctx ;
2022-07-05 02:04:28 +01:00
2021-03-07 23:22:49 +00:00
/* Get root partition name table. */
2023-05-24 20:05:34 +01:00
root_hfs_name_table_size = ( ( HashFileSystemHeader * ) root_hfs_ctx - > header ) - > name_table_size ;
root_hfs_name_table = hfsGetNameTable ( root_hfs_ctx ) ;
2022-07-05 02:04:28 +01:00
2021-03-07 23:22:49 +00:00
/* Initialize Hash FS contexts for the child partitions. */
2023-05-24 20:05:34 +01:00
for ( u32 i = 0 ; i < root_hfs_entry_count ; i + + )
2020-04-15 06:59:12 +01:00
{
2023-05-24 20:05:34 +01:00
HashFileSystemEntry * hfs_entry = hfsGetEntryByIndex ( root_hfs_ctx , i ) ;
char * hfs_entry_name = ( root_hfs_name_table + hfs_entry - > name_offset ) ;
u64 hfs_entry_offset = ( root_hfs_ctx - > offset + root_hfs_ctx - > header_size + hfs_entry - > offset ) ;
2022-07-05 02:04:28 +01:00
2023-05-24 20:05:34 +01:00
if ( hfs_entry - > name_offset > = root_hfs_name_table_size | | ! * hfs_entry_name )
2020-04-15 06:59:12 +01:00
{
2022-07-12 17:34:49 +01:00
LOG_MSG_ERROR ( " 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
}
2022-07-05 02:04:28 +01:00
2023-05-24 20:05:34 +01:00
g_gameCardHfsCtx [ i + 1 ] = gamecardInitializeHashFileSystemContext ( hfs_entry_name , hfs_entry_offset , hfs_entry - > size , hfs_entry - > hash , hfs_entry - > hash_target_offset , hfs_entry - > hash_target_size ) ;
2021-03-07 23:22:49 +00:00
if ( ! g_gameCardHfsCtx [ i + 1 ] ) goto end ;
2020-04-15 06:59:12 +01:00
}
2022-07-05 02:04:28 +01:00
2021-06-01 02:12:15 +01:00
/* Update gamecard status. */
g_gameCardStatus = GameCardStatus_InsertedAndInfoLoaded ;
2022-07-05 02:04:28 +01:00
2020-07-13 07:36:17 +01:00
end :
2021-06-01 02:12:15 +01:00
if ( g_gameCardStatus ! = GameCardStatus_InsertedAndInfoLoaded )
2021-03-07 23:22:49 +00:00
{
2023-05-24 20:05:34 +01:00
if ( ! g_gameCardHfsCtx & & root_hfs_ctx )
2021-03-10 01:12:01 +00:00
{
2023-05-24 20:05:34 +01:00
hfsFreeContext ( root_hfs_ctx ) ;
free ( root_hfs_ctx ) ;
2021-03-10 01:12:01 +00:00
}
2022-07-05 02:04:28 +01:00
2021-06-01 02:12:15 +01:00
gamecardFreeInfo ( false ) ;
2021-03-07 23:22:49 +00:00
}
2020-04-15 06:59:12 +01:00
}
2021-06-01 02:12:15 +01:00
static void gamecardFreeInfo ( bool clear_status )
2020-04-15 06:59:12 +01:00
{
2020-07-22 09:03:28 +01:00
memset ( & g_gameCardHeader , 0 , sizeof ( GameCardHeader ) ) ;
2022-07-05 02:04:28 +01:00
2021-05-22 09:45:40 +01:00
memset ( & g_gameCardInfoArea , 0 , sizeof ( GameCardInfo ) ) ;
2022-07-05 02:04:28 +01:00
2021-06-01 02:12:15 +01:00
g_gameCardNormalAreaSize = g_gameCardSecureAreaSize = g_gameCardTotalSize = 0 ;
2022-07-05 02:04:28 +01:00
2020-04-16 05:37:16 +01:00
g_gameCardCapacity = 0 ;
2022-07-05 02:04:28 +01:00
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 + + )
{
2023-05-24 20:05:34 +01:00
HashFileSystemContext * cur_hfs_ctx = g_gameCardHfsCtx [ i ] ;
if ( cur_hfs_ctx )
2021-03-10 01:12:01 +00:00
{
2023-05-24 20:05:34 +01:00
hfsFreeContext ( cur_hfs_ctx ) ;
free ( cur_hfs_ctx ) ;
2021-03-10 01:12:01 +00:00
}
}
2022-07-05 02:04:28 +01:00
2021-03-07 23:22:49 +00:00
free ( g_gameCardHfsCtx ) ;
g_gameCardHfsCtx = NULL ;
2020-04-15 06:59:12 +01:00
}
2022-07-05 02:04:28 +01:00
2021-03-07 23:22:49 +00:00
g_gameCardHfsCount = 0 ;
2022-07-05 02:04:28 +01:00
2022-07-07 12:21:27 +01:00
gamecardCloseStorageArea ( ) ;
2021-06-01 02:12:15 +01:00
if ( clear_status ) g_gameCardStatus = GameCardStatus_NotInserted ;
}
static bool gamecardReadHeader ( void )
{
/* Open normal storage area. */
if ( ! gamecardOpenStorageArea ( GameCardStorageArea_Normal ) )
{
2022-07-12 17:34:49 +01:00
LOG_MSG_ERROR ( " Failed to open normal storage area! " ) ;
2021-06-01 02:12:15 +01:00
return false ;
}
2022-07-05 02:04:28 +01:00
2021-06-01 02:12:15 +01:00
/* Read gamecard header. */
2023-11-26 21:13:49 +00:00
/* We don't use gamecardReadStorageArea() here because of its dependence on storage area sizes (which we haven't yet retrieved). */
2021-06-01 02:12:15 +01:00
Result rc = fsStorageRead ( & g_gameCardStorage , 0 , & g_gameCardHeader , sizeof ( GameCardHeader ) ) ;
if ( R_FAILED ( rc ) )
{
2022-07-12 17:34:49 +01:00
LOG_MSG_ERROR ( " fsStorageRead failed to read gamecard header! (0x%X). " , rc ) ;
2021-06-01 02:12:15 +01:00
return false ;
}
2022-07-05 02:04:28 +01:00
2022-07-12 17:34:49 +01:00
LOG_DATA_DEBUG ( & g_gameCardHeader , sizeof ( GameCardHeader ) , " Gamecard header dump: " ) ;
2022-07-05 02:04:28 +01:00
2021-06-01 02:12:15 +01:00
/* Check magic word from gamecard header. */
if ( __builtin_bswap32 ( g_gameCardHeader . magic ) ! = GAMECARD_HEAD_MAGIC )
{
2022-07-12 17:34:49 +01:00
LOG_MSG_ERROR ( " Invalid gamecard header magic word! (0x%08X). " , __builtin_bswap32 ( g_gameCardHeader . magic ) ) ;
2021-06-01 02:12:15 +01:00
return false ;
}
2022-07-05 02:04:28 +01:00
2021-06-01 02:12:15 +01:00
return true ;
}
2021-06-13 20:32:37 +01:00
static bool _gamecardGetDecryptedCardInfoArea ( void )
2021-06-01 02:12:15 +01:00
{
const u8 * card_info_key = NULL ;
u8 card_info_iv [ AES_128_KEY_SIZE ] = { 0 } ;
Aes128CbcContext aes_ctx = { 0 } ;
2022-07-05 02:04:28 +01:00
2021-06-01 02:12:15 +01:00
/* Retrieve CardInfo area key. */
card_info_key = keysGetGameCardInfoKey ( ) ;
if ( ! card_info_key )
{
2022-07-12 17:34:49 +01:00
LOG_MSG_ERROR ( " Failed to retrieve CardInfo area key! " ) ;
2021-06-01 02:12:15 +01:00
return false ;
}
2022-07-05 02:04:28 +01:00
2021-06-01 02:12:15 +01:00
/* Reverse CardInfo IV. */
for ( u8 i = 0 ; i < AES_128_KEY_SIZE ; i + + ) card_info_iv [ i ] = g_gameCardHeader . card_info_iv [ AES_128_KEY_SIZE - i - 1 ] ;
2022-07-05 02:04:28 +01:00
2021-06-01 02:12:15 +01:00
/* Initialize AES-128-CBC context. */
aes128CbcContextCreate ( & aes_ctx , card_info_key , card_info_iv , false ) ;
2022-07-05 02:04:28 +01:00
2021-06-01 02:12:15 +01:00
/* Decrypt CardInfo area. */
aes128CbcDecrypt ( & aes_ctx , & g_gameCardInfoArea , & ( g_gameCardHeader . card_info ) , sizeof ( GameCardInfo ) ) ;
2022-07-05 02:04:28 +01:00
2022-07-12 17:34:49 +01:00
LOG_DATA_DEBUG ( & g_gameCardInfoArea , sizeof ( GameCardInfo ) , " Gamecard CardInfo area dump: " ) ;
2022-07-05 02:04:28 +01:00
2021-06-01 02:12:15 +01:00
return true ;
2020-04-15 06:59:12 +01:00
}
2022-02-03 01:22:57 +00:00
static bool gamecardReadSecurityInformation ( GameCardSecurityInformation * out )
2020-07-13 07:36:17 +01:00
{
2021-06-01 02:12:15 +01:00
if ( ! g_gameCardInterfaceInit | | g_gameCardStatus ! = GameCardStatus_InsertedAndInfoLoaded | | ! out )
2020-08-01 06:17:08 +01:00
{
2022-07-12 17:34:49 +01:00
LOG_MSG_ERROR ( " Invalid parameters! " ) ;
2020-08-01 06:17:08 +01:00
return false ;
}
2022-07-05 02:04:28 +01:00
2020-08-01 06:17:08 +01:00
/* Clear output. */
2022-02-03 01:22:57 +00:00
memset ( out , 0 , sizeof ( GameCardSecurityInformation ) ) ;
2022-07-05 02:04:28 +01:00
2020-08-01 06:17:08 +01:00
/* Open secure storage area. */
if ( ! gamecardOpenStorageArea ( GameCardStorageArea_Secure ) )
{
2022-07-12 17:34:49 +01:00
LOG_MSG_ERROR ( " Failed to open secure storage area! " ) ;
2020-08-01 06:17:08 +01:00
return false ;
}
2022-07-05 02:04:28 +01:00
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 } ;
2022-07-05 02:04:28 +01:00
2020-07-13 07:36:17 +01:00
/* Retrieve full FS program memory dump. */
if ( ! memRetrieveFullProgramMemory ( & g_fsProgramMemory ) )
{
2022-07-12 17:34:49 +01:00
LOG_MSG_ERROR ( " Failed to retrieve full FS program memory dump! " ) ;
2020-07-13 07:36:17 +01:00
return false ;
}
2022-07-05 02:04:28 +01:00
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 ;
2022-07-05 02:04:28 +01:00
2023-11-03 01:22:47 +00:00
if ( memcmp ( g_fsProgramMemory . data + offset , g_gameCardHeader . package_id , sizeof ( g_gameCardHeader . package_id ) ) ! = 0 ) continue ;
2022-07-05 02:04:28 +01:00
2020-07-22 09:03:28 +01:00
sha256CalculateHash ( tmp_hash , g_fsProgramMemory . data + offset , sizeof ( GameCardInitialData ) ) ;
2023-07-17 00:03:05 +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. */
2022-02-03 03:39:54 +00:00
memcpy ( out , g_fsProgramMemory . data + offset + sizeof ( GameCardInitialData ) - sizeof ( GameCardSecurityInformation ) , sizeof ( GameCardSecurityInformation ) ) ;
2022-07-05 02:04:28 +01:00
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
}
2022-07-05 02:04:28 +01:00
2020-07-13 07:36:17 +01:00
/* Free FS memory dump. */
memFreeMemoryLocation ( & g_fsProgramMemory ) ;
2022-07-05 02:04:28 +01:00
2020-07-13 07:36:17 +01:00
return found ;
}
2020-07-17 06:01:31 +01:00
static bool gamecardGetHandleAndStorage ( u32 partition )
2020-04-15 06:59:12 +01:00
{
2021-06-01 02:12:15 +01:00
if ( g_gameCardStatus < GameCardStatus_InsertedAndInfoNotLoaded | | partition > 1 )
2020-04-15 06:59:12 +01:00
{
2022-07-12 17:34:49 +01:00
LOG_MSG_ERROR ( " Invalid parameters! " ) ;
2020-04-15 06:59:12 +01:00
return false ;
}
2022-07-05 02:04:28 +01:00
2020-07-22 09:03:28 +01:00
Result rc = 0 ;
2022-07-05 02:04:28 +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 ) ;
2022-07-05 02:04:28 +01:00
2020-07-17 06:01:31 +01:00
/* First, let's try to retrieve a gamecard handle. */
2021-06-01 02:12:15 +01:00
/* This can return an error if the "nogc" patch is enabled by the running CFW (most commonly 0x140A02). */
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
{
2022-07-12 17:34:49 +01:00
LOG_MSG_DEBUG ( " fsDeviceOperatorGetGameCardHandle failed on try #%u! (0x%X). " , i + 1 , rc ) ;
2020-07-17 06:01:31 +01:00
continue ;
2020-04-15 06:59:12 +01:00
}
2022-07-05 02:04:28 +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
{
2022-07-12 17:34:49 +01:00
LOG_MSG_DEBUG ( " fsOpenGameCardStorage failed to open %s storage area on try #%u! (0x%X). " , GAMECARD_STORAGE_AREA_NAME ( partition + 1 ) , i + 1 , rc ) ;
2020-07-17 06:01:31 +01:00
continue ;
}
2022-07-05 02:04:28 +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
}
2022-07-05 02:04:28 +01:00
2021-06-01 02:12:15 +01:00
if ( R_FAILED ( rc ) )
{
2022-07-12 17:34:49 +01:00
LOG_MSG_ERROR ( " fsDeviceOperatorGetGameCardHandle / fsOpenGameCardStorage failed! (0x%X). " , rc ) ;
2021-06-01 02:12:15 +01:00
if ( g_gameCardStatus = = GameCardStatus_InsertedAndInfoNotLoaded & & partition = = 0 ) g_gameCardStatus = GameCardStatus_NoGameCardPatchEnabled ;
}
2022-07-05 02:04:28 +01:00
2020-07-22 09:03:28 +01:00
return R_SUCCEEDED ( rc ) ;
2020-04-15 06:59:12 +01:00
}
2020-04-15 21:50:07 +01:00
static bool gamecardOpenStorageArea ( u8 area )
2020-04-15 06:59:12 +01:00
{
2021-06-01 02:12:15 +01:00
if ( g_gameCardStatus < GameCardStatus_InsertedAndInfoNotLoaded | | ( area ! = GameCardStorageArea_Normal & & area ! = GameCardStorageArea_Secure ) )
2020-04-15 06:59:12 +01:00
{
2022-07-12 17:34:49 +01:00
LOG_MSG_ERROR ( " Invalid parameters! " ) ;
2020-04-15 06:59:12 +01:00
return false ;
}
2022-07-05 02:04:28 +01:00
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. */
2021-06-01 02:12:15 +01:00
if ( g_gameCardHandle . value & & serviceIsActive ( & ( g_gameCardStorage . s ) ) & & g_gameCardCurrentStorageArea = = area ) return true ;
2022-07-05 02:04:28 +01:00
2023-11-26 21:13:49 +00:00
/* Close both the gamecard handle and the open storage area. */
2022-07-07 12:21:27 +01:00
gamecardCloseStorageArea ( ) ;
2022-07-05 02:04:28 +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
{
2022-07-12 17:34:49 +01:00
LOG_MSG_ERROR ( " 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
}
2022-07-05 02:04:28 +01:00
2020-07-17 06:01:31 +01:00
/* Update current gamecard storage area. */
2021-06-01 02:12:15 +01:00
g_gameCardCurrentStorageArea = area ;
2022-07-05 02:04:28 +01:00
2020-04-15 21:50:07 +01:00
return true ;
2020-04-15 06:59:12 +01:00
}
2021-05-18 13:32:43 +01:00
static bool gamecardReadStorageArea ( void * out , u64 read_size , u64 offset )
2020-04-15 06:59:12 +01:00
{
2021-06-01 02:12:15 +01:00
if ( g_gameCardStatus < GameCardStatus_InsertedAndInfoNotLoaded | | ! g_gameCardNormalAreaSize | | ! g_gameCardSecureAreaSize | | ! out | | ! read_size | | ( offset + read_size ) > g_gameCardTotalSize )
2020-04-15 06:59:12 +01:00
{
2022-07-12 17:34:49 +01:00
LOG_MSG_ERROR ( " Invalid parameters! " ) ;
2021-05-18 13:32:43 +01:00
return false ;
2020-04-15 06:59:12 +01:00
}
2022-07-05 02:04:28 +01:00
2020-04-15 06:59:12 +01:00
Result rc = 0 ;
u8 * out_u8 = ( u8 * ) out ;
2021-06-01 02:12:15 +01:00
u8 area = ( offset < g_gameCardNormalAreaSize ? GameCardStorageArea_Normal : GameCardStorageArea_Secure ) ;
2021-05-18 13:32:43 +01:00
bool success = false ;
2022-07-05 02:04:28 +01:00
2020-07-06 01:10:07 +01:00
/* Handle reads that span both the normal and secure gamecard storage areas. */
2021-06-01 02:12:15 +01:00
if ( area = = GameCardStorageArea_Normal & & ( offset + read_size ) > g_gameCardNormalAreaSize )
2020-04-15 06:59:12 +01:00
{
2020-07-06 01:10:07 +01:00
/* Calculate normal storage area size difference. */
2021-06-01 02:12:15 +01:00
u64 diff_size = ( g_gameCardNormalAreaSize - offset ) ;
2022-07-05 02:04:28 +01:00
2021-05-18 13:32:43 +01:00
/* Read normal storage area data. */
if ( ! gamecardReadStorageArea ( out_u8 , diff_size , offset ) ) goto end ;
2022-07-05 02:04:28 +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 ;
2021-06-01 02:12:15 +01:00
offset = g_gameCardNormalAreaSize ;
2020-04-15 21:50:07 +01:00
out_u8 + = diff_size ;
area = GameCardStorageArea_Secure ;
}
2022-07-05 02:04:28 +01:00
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 ) )
{
2022-07-12 17:34:49 +01:00
LOG_MSG_ERROR ( " 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
}
2022-07-05 02:04:28 +01:00
2021-06-01 02:12:15 +01:00
/* Calculate proper storage area offset. */
u64 base_offset = ( area = = GameCardStorageArea_Normal ? offset : ( offset - g_gameCardNormalAreaSize ) ) ;
2022-07-05 02:04:28 +01:00
2021-04-06 01:40:57 +01:00
if ( ! ( base_offset % GAMECARD_PAGE_SIZE ) & & ! ( read_size % GAMECARD_PAGE_SIZE ) )
2020-04-15 06:59:12 +01:00
{
2021-04-06 01:40:57 +01:00
/* Optimization for reads that are already aligned to a GAMECARD_PAGE_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 ) )
{
2022-07-12 17:34:49 +01:00
LOG_MSG_ERROR ( " fsStorageRead failed to read 0x%lX bytes at offset 0x%lX from %s storage area! (0x%X) (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
}
2022-07-05 02:04:28 +01:00
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. */
2021-04-06 01:40:57 +01:00
u64 block_start_offset = ALIGN_DOWN ( base_offset , GAMECARD_PAGE_SIZE ) ;
u64 block_end_offset = ALIGN_UP ( base_offset + read_size , GAMECARD_PAGE_SIZE ) ;
2020-04-15 06:59:12 +01:00
u64 block_size = ( block_end_offset - block_start_offset ) ;
2022-07-05 02:04:28 +01:00
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 ) ;
2022-07-05 02:04:28 +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
{
2022-07-12 17:34:49 +01:00
LOG_MSG_ERROR ( " fsStorageRead failed to read 0x%lX bytes at offset 0x%lX from %s storage area! (0x%X) (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
}
2022-07-05 02:04:28 +01:00
2020-04-21 11:23:33 +01:00
memcpy ( out_u8 , g_gameCardReadBuf + data_start_offset , out_chunk_size ) ;
2022-07-05 02:04:28 +01:00
2021-05-18 13:32:43 +01:00
success = ( block_size > GAMECARD_READ_BUFFER_SIZE ? gamecardReadStorageArea ( out_u8 + out_chunk_size , read_size - out_chunk_size , offset + out_chunk_size ) : true ) ;
2020-04-15 06:59:12 +01:00
}
2022-07-05 02:04:28 +01:00
2020-07-13 07:36:17 +01:00
end :
2020-04-15 06:59:12 +01:00
return success ;
}
2022-07-07 12:21:27 +01:00
static void gamecardCloseStorageArea ( void )
2020-04-15 06:59:12 +01:00
{
2022-06-24 01:22:01 +01:00
if ( g_gameCardCurrentStorageArea = = GameCardStorageArea_None ) return ;
2022-07-05 02:04:28 +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
}
2022-07-05 02:04:28 +01:00
2023-11-26 21:13:49 +00:00
g_gameCardHandle . value = 0 ;
2022-07-05 02:04:28 +01:00
2021-06-01 02:12:15 +01:00
g_gameCardCurrentStorageArea = 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
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 ) ;
2022-07-05 02:04:28 +01:00
2020-04-15 21:50:07 +01:00
if ( ! gamecardOpenStorageArea ( area ) )
{
2022-07-12 17:34:49 +01:00
LOG_MSG_ERROR ( " Failed to open %s storage area! " , GAMECARD_STORAGE_AREA_NAME ( area ) ) ;
2020-04-15 21:50:07 +01:00
return false ;
}
2022-07-05 02:04:28 +01:00
2020-04-15 21:50:07 +01:00
rc = fsStorageGetSize ( & g_gameCardStorage , ( s64 * ) & area_size ) ;
2022-07-05 02:04:28 +01:00
2022-07-07 12:21:27 +01:00
gamecardCloseStorageArea ( ) ;
2022-07-05 02:04:28 +01:00
2020-04-16 01:06:41 +01:00
if ( R_FAILED ( rc ) | | ! area_size )
2020-04-15 21:50:07 +01:00
{
2022-07-12 17:34:49 +01:00
LOG_MSG_ERROR ( " fsStorageGetSize failed to retrieve %s storage area size! (0x%X). " , GAMECARD_STORAGE_AREA_NAME ( area ) , rc ) ;
2020-04-15 21:50:07 +01:00
return false ;
}
2022-07-05 02:04:28 +01:00
2020-04-15 21:50:07 +01:00
if ( area = = GameCardStorageArea_Normal )
{
2021-06-01 02:12:15 +01:00
g_gameCardNormalAreaSize = area_size ;
2020-04-15 21:50:07 +01:00
} else {
2021-06-01 02:12:15 +01:00
g_gameCardSecureAreaSize = area_size ;
2020-04-15 21:50:07 +01:00
}
2020-04-15 06:59:12 +01:00
}
2022-07-05 02:04:28 +01:00
2021-06-01 02:12:15 +01:00
g_gameCardTotalSize = ( g_gameCardNormalAreaSize + g_gameCardSecureAreaSize ) ;
2022-07-05 02:04:28 +01:00
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 ;
2022-07-05 02:04:28 +01:00
2020-04-16 05:37:16 +01:00
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 ;
}
2022-07-05 02:04:28 +01:00
2020-04-16 05:37:16 +01:00
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 ;
2023-05-24 20:05:34 +01:00
HashFileSystemContext * hfs_ctx = NULL ;
HashFileSystemHeader hfs_header = { 0 } ;
u8 hfs_header_hash [ SHA256_HASH_SIZE ] = { 0 } ;
2022-07-05 02:04:28 +01:00
2021-03-07 23:22:49 +00:00
bool success = false , dump_fs_header = false ;
2022-07-05 02:04:28 +01:00
2021-04-06 01:40:57 +01:00
if ( ( name & & ! * name ) | | offset < ( GAMECARD_CERTIFICATE_OFFSET + sizeof ( FsGameCardCertificate ) ) | | ! IS_ALIGNED ( offset , GAMECARD_PAGE_SIZE ) | | \
2021-06-01 02:12:15 +01:00
( size & & ( ! IS_ALIGNED ( size , GAMECARD_PAGE_SIZE ) | | ( offset + size ) > g_gameCardTotalSize ) ) )
2021-03-07 23:22:49 +00:00
{
2022-07-12 17:34:49 +01:00
LOG_MSG_ERROR ( " Invalid parameters! " ) ;
2021-03-07 23:22:49 +00:00
goto end ;
}
2022-07-05 02:04:28 +01:00
2021-03-07 23:22:49 +00:00
/* Allocate memory for the output context. */
2023-05-24 20:05:34 +01:00
hfs_ctx = calloc ( 1 , sizeof ( HashFileSystemContext ) ) ;
if ( ! hfs_ctx )
2021-03-07 23:22:49 +00:00
{
2022-07-12 17:34:49 +01:00
LOG_MSG_ERROR ( " Unable to allocate memory for Hash FS context! (offset 0x%lX). " , offset ) ;
2021-03-07 23:22:49 +00:00
goto end ;
}
2022-07-05 02:04:28 +01:00
2021-03-07 23:22:49 +00:00
/* Duplicate partition name. */
2023-05-24 20:05:34 +01:00
hfs_ctx - > name = ( name ? strdup ( name ) : strdup ( hfsGetPartitionNameString ( HashFileSystemPartitionType_Root ) ) ) ;
if ( ! hfs_ctx - > name )
2021-03-07 23:22:49 +00:00
{
2022-07-12 17:34:49 +01:00
LOG_MSG_ERROR ( " Failed to duplicate Hash FS partition name! (offset 0x%lX). " , offset ) ;
2021-03-07 23:22:49 +00:00
goto end ;
}
2022-07-05 02:04:28 +01:00
2021-03-10 01:12:01 +00:00
/* Determine Hash FS partition type. */
2023-05-24 20:05:34 +01:00
for ( i = HashFileSystemPartitionType_Root ; i < HashFileSystemPartitionType_Count ; i + + )
2021-03-10 01:12:01 +00:00
{
2023-05-24 20:05:34 +01:00
const char * hfs_partition_name = hfsGetPartitionNameString ( ( u8 ) i ) ;
if ( hfs_partition_name & & ! strcmp ( hfs_partition_name , hfs_ctx - > name ) ) break ;
2021-03-10 01:12:01 +00:00
}
2022-07-05 02:04:28 +01:00
2023-05-24 20:05:34 +01:00
if ( i > = HashFileSystemPartitionType_Count )
2021-03-10 01:12:01 +00:00
{
2023-05-24 20:05:34 +01:00
LOG_MSG_ERROR ( " Failed to find a matching Hash FS partition type for \" %s \" ! (offset 0x%lX). " , hfs_ctx - > name , offset ) ;
2021-03-10 01:12:01 +00:00
goto end ;
}
2022-07-05 02:04:28 +01:00
2023-05-24 20:05:34 +01:00
hfs_ctx - > type = i ;
2022-07-05 02:04:28 +01:00
2021-03-07 23:22:49 +00:00
/* Read partial Hash FS header. */
2023-05-24 20:05:34 +01:00
if ( ! gamecardReadStorageArea ( & hfs_header , sizeof ( HashFileSystemHeader ) , offset ) )
2021-03-07 23:22:49 +00:00
{
2023-05-24 20:05:34 +01:00
LOG_MSG_ERROR ( " Failed to read partial Hash FS header! ( \" %s \" , offset 0x%lX). " , hfs_ctx - > name , offset ) ;
2021-03-07 23:22:49 +00:00
goto end ;
}
2022-07-05 02:04:28 +01:00
2023-05-24 20:05:34 +01:00
magic = __builtin_bswap32 ( hfs_header . magic ) ;
2021-03-07 23:22:49 +00:00
if ( magic ! = HFS0_MAGIC )
{
2023-05-24 20:05:34 +01:00
LOG_MSG_ERROR ( " Invalid Hash FS magic word! (0x%08X) ( \" %s \" , offset 0x%lX). " , magic , hfs_ctx - > name , offset ) ;
2021-03-07 23:22:49 +00:00
dump_fs_header = true ;
goto end ;
}
2022-07-05 02:04:28 +01:00
2021-03-07 23:22:49 +00:00
/* 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. */
2023-05-24 20:05:34 +01:00
if ( ( ! name & & ! hfs_header . entry_count ) | | ! hfs_header . name_table_size )
2021-03-07 23:22:49 +00:00
{
2023-05-24 20:05:34 +01:00
LOG_MSG_ERROR ( " Invalid Hash FS entry count / name table size! ( \" %s \" , offset 0x%lX). " , hfs_ctx - > name , offset ) ;
2021-03-07 23:22:49 +00:00
dump_fs_header = true ;
goto end ;
}
2022-07-05 02:04:28 +01:00
2021-03-07 23:22:49 +00:00
/* Calculate full Hash FS header size. */
2023-05-24 20:05:34 +01:00
hfs_ctx - > header_size = ( sizeof ( HashFileSystemHeader ) + ( hfs_header . entry_count * sizeof ( HashFileSystemEntry ) ) + hfs_header . name_table_size ) ;
hfs_ctx - > header_size = ALIGN_UP ( hfs_ctx - > header_size , GAMECARD_PAGE_SIZE ) ;
2022-07-05 02:04:28 +01:00
2021-03-07 23:22:49 +00:00
/* Allocate memory for the full Hash FS header. */
2023-05-24 20:05:34 +01:00
hfs_ctx - > header = calloc ( hfs_ctx - > header_size , sizeof ( u8 ) ) ;
if ( ! hfs_ctx - > header )
2021-03-07 23:22:49 +00:00
{
2023-05-24 20:05:34 +01:00
LOG_MSG_ERROR ( " Unable to allocate 0x%lX bytes buffer for the full Hash FS header! ( \" %s \" , offset 0x%lX). " , hfs_ctx - > header_size , hfs_ctx - > name , offset ) ;
2021-03-07 23:22:49 +00:00
goto end ;
}
2022-07-05 02:04:28 +01:00
2021-03-07 23:22:49 +00:00
/* Read full Hash FS header. */
2023-05-24 20:05:34 +01:00
if ( ! gamecardReadStorageArea ( hfs_ctx - > header , hfs_ctx - > header_size , offset ) )
2021-03-07 23:22:49 +00:00
{
2023-05-24 20:05:34 +01:00
LOG_MSG_ERROR ( " Failed to read full Hash FS header! ( \" %s \" , offset 0x%lX). " , hfs_ctx - > name , offset ) ;
2021-03-07 23:22:49 +00:00
goto end ;
}
2022-07-05 02:04:28 +01:00
2021-03-07 23:22:49 +00:00
/* Verify Hash FS header (if possible). */
2023-05-24 20:05:34 +01:00
if ( hash & & hash_target_size & & ( hash_target_offset + hash_target_size ) < = hfs_ctx - > header_size )
2020-04-17 22:59:05 +01:00
{
2023-05-24 20:05:34 +01:00
sha256CalculateHash ( hfs_header_hash , hfs_ctx - > header + hash_target_offset , hash_target_size ) ;
if ( memcmp ( hfs_header_hash , hash , SHA256_HASH_SIZE ) ! = 0 )
2020-04-17 22:59:05 +01:00
{
2023-05-24 20:05:34 +01:00
LOG_MSG_ERROR ( " Hash FS header doesn't match expected SHA-256 hash! ( \" %s \" , offset 0x%lX). " , hfs_ctx - > name , offset ) ;
2021-03-07 23:22:49 +00:00
goto end ;
2020-04-17 22:59:05 +01:00
}
}
2022-07-05 02:04:28 +01:00
2021-03-07 23:22:49 +00:00
/* Fill context. */
2023-05-24 20:05:34 +01:00
hfs_ctx - > offset = offset ;
2022-07-05 02:04:28 +01:00
2021-03-07 23:22:49 +00:00
if ( name )
{
/* Use provided partition size. */
2023-05-24 20:05:34 +01:00
hfs_ctx - > size = size ;
2021-03-07 23:22:49 +00:00
} else {
/* Calculate root partition size. */
2023-12-20 23:39:56 +00:00
hfs_ctx - > size = 1 ; // Prevents hfsGetEntryByIndex() from returning NULL.
2023-05-24 20:05:34 +01:00
HashFileSystemEntry * hfs_entry = hfsGetEntryByIndex ( hfs_ctx , hfs_header . entry_count - 1 ) ;
hfs_ctx - > size = ( hfs_ctx - > header_size + hfs_entry - > offset + hfs_entry - > size ) ;
2021-03-07 23:22:49 +00:00
}
2022-07-05 02:04:28 +01:00
2021-03-07 23:22:49 +00:00
/* Update flag. */
success = true ;
2022-07-05 02:04:28 +01:00
2021-03-07 23:22:49 +00:00
end :
2023-05-24 20:05:34 +01:00
if ( ! success & & hfs_ctx )
2021-03-07 23:22:49 +00:00
{
2023-05-24 20:05:34 +01:00
if ( dump_fs_header ) LOG_DATA_DEBUG ( & hfs_header , sizeof ( HashFileSystemHeader ) , " Partial Hash FS header dump ( \" %s \" , offset 0x%lX): " , hfs_ctx - > name , offset ) ;
2022-07-05 02:04:28 +01:00
2023-05-24 20:05:34 +01:00
if ( hfs_ctx - > header ) free ( hfs_ctx - > header ) ;
2022-07-05 02:04:28 +01:00
2023-05-24 20:05:34 +01:00
if ( hfs_ctx - > name ) free ( hfs_ctx - > name ) ;
2022-07-05 02:04:28 +01:00
2023-05-24 20:05:34 +01:00
free ( hfs_ctx ) ;
hfs_ctx = NULL ;
2021-03-07 23:22:49 +00:00
}
2022-07-05 02:04:28 +01:00
2023-05-24 20:05:34 +01:00
return hfs_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
{
2023-05-24 20:05:34 +01:00
HashFileSystemContext * hfs_ctx = NULL ;
2022-07-05 02:04:28 +01:00
2023-05-24 20:05:34 +01:00
if ( ! g_gameCardInterfaceInit | | g_gameCardStatus ! = GameCardStatus_InsertedAndInfoLoaded | | ! g_gameCardHfsCount | | ! g_gameCardHfsCtx | | \
hfs_partition_type < HashFileSystemPartitionType_Root | | hfs_partition_type > = HashFileSystemPartitionType_Count )
2021-03-07 23:22:49 +00:00
{
2022-07-12 17:34:49 +01:00
LOG_MSG_ERROR ( " Invalid parameters! " ) ;
2021-03-07 23:22:49 +00:00
goto end ;
}
2022-07-05 02:04:28 +01:00
2021-03-07 23:22:49 +00:00
/* Return right away if the root partition was requested. */
2023-05-24 20:05:34 +01:00
if ( hfs_partition_type = = HashFileSystemPartitionType_Root )
2020-04-24 10:38:13 +01:00
{
2023-05-24 20:05:34 +01:00
hfs_ctx = g_gameCardHfsCtx [ 0 ] ;
2021-03-07 23:22:49 +00:00
goto end ;
2020-04-24 10:38:13 +01:00
}
2022-07-05 02:04:28 +01:00
2021-03-07 23:22:49 +00:00
/* Try to find the requested partition by looping through our Hash FS contexts. */
for ( u32 i = 1 ; i < g_gameCardHfsCount ; i + + )
{
2023-05-24 20:05:34 +01:00
hfs_ctx = g_gameCardHfsCtx [ i ] ;
if ( hfs_ctx - > type = = hfs_partition_type ) break ;
hfs_ctx = NULL ;
2021-03-07 23:22:49 +00:00
}
2022-07-05 02:04:28 +01:00
2023-05-24 20:05:34 +01:00
if ( ! hfs_ctx ) LOG_MSG_ERROR ( " Failed to locate Hash FS partition with type %u! " , hfs_partition_type ) ;
2022-07-05 02:04:28 +01:00
2021-03-07 23:22:49 +00:00
end :
2023-05-24 20:05:34 +01:00
return hfs_ctx ;
2020-04-16 11:13:11 +01:00
}