2026-06-26 23:56:28 +03:00
# include "fsinit.h"
# include "fsperm.h"
# include "ifat_common.h"
2019-07-13 23:00:10 -04:00
# include "bdri.h"
2026-06-26 23:56:28 +03:00
# include "image.h"
# include "language.h"
# include "nandcmac.h"
# include "ui.h"
2019-09-21 10:30:18 -04:00
# include "vff.h"
2019-07-13 23:00:10 -04:00
2026-06-26 23:56:28 +03:00
# define FAT_ENTRY_SIZE sizeof(IFatEntry)
2021-10-23 14:19:13 +03:00
# define REPLACE_SIZE_MISMATCH 2
2019-07-13 23:00:10 -04:00
2019-09-21 10:30:18 -04:00
# define getfatflag(uv) (((uv) & 0x80000000UL) != 0)
# define getfatindex(uv) ((uv) & 0x7FFFFFFFUL)
# define buildfatuv(index, flag) ((index) | ((flag) ? 0x80000000UL : 0))
2019-07-13 23:00:10 -04:00
2020-01-11 12:59:12 -05:00
typedef struct {
2026-06-26 23:56:28 +03:00
IFatPreHeader pre_header ;
IFatFsInfo fs_info ;
} PACKED_STRUCT BDRIFsHeader ;
2020-01-11 12:59:12 -05:00
typedef struct {
char magic [ 8 ] ; // varies based on media type and importdb vs titledb
2026-06-26 23:56:28 +03:00
u32 version ; // should always be 0
u8 reserved [ 0x74 ] ;
} PACKED_STRUCT TitleDBPreHeader ;
typedef struct {
TitleDBPreHeader pre_header ;
2020-01-11 12:59:12 -05:00
BDRIFsHeader fs_header ;
2026-06-26 23:56:28 +03:00
} PACKED_STRUCT TitleDBHeader ;
2020-01-11 12:59:12 -05:00
typedef struct {
char magic [ 4 ] ; // "TICK"
2026-06-26 23:56:28 +03:00
u32 version ; // always 1
u8 padding [ 8 ] ;
} PACKED_STRUCT TickDBPreHeader ;
typedef struct {
TickDBPreHeader pre_header ;
2020-01-11 12:59:12 -05:00
BDRIFsHeader fs_header ;
2026-06-26 23:56:28 +03:00
} PACKED_STRUCT TickDBHeader ;
2020-01-11 12:59:12 -05:00
typedef struct {
u32 parent_index ;
u8 title_id [ 8 ] ;
u32 next_sibling_index ;
u8 padding1 [ 4 ] ;
u32 start_block_index ;
u64 size ; // in bytes
u8 padding2 [ 8 ] ;
u32 hash_bucket_next_index ;
2026-06-26 23:56:28 +03:00
} __attribute__ ( ( packed ) ) BDRIFileEntry ;
2020-01-11 12:59:12 -05:00
typedef struct {
u32 total_entry_count ;
u32 max_entry_count ; // == max_file_count + 1
u8 padding [ 32 ] ;
u32 next_dummy_index ;
2026-06-26 23:56:28 +03:00
} __attribute__ ( ( packed ) ) BDRIDummyFileEntry ;
typedef struct {
u64 id ;
u32 next_sibling_index ;
u32 first_subdir_index ;
u32 first_subfile_index ;
u8 pad0 [ 4 ] ;
u32 hash_bucket_next_index ;
} __attribute__ ( ( packed ) ) BDRIDirectoryEntry ;
typedef struct {
u32 total_entry_count ;
u32 max_entry_count ;
u8 padding [ 16 ] ;
u32 next_dummy_index ;
} __attribute__ ( ( packed ) ) BDRIDummyDirectoryEntry ;
2020-01-11 12:59:12 -05:00
typedef struct {
2026-06-26 23:56:28 +03:00
u32 ticket_count ; // usually == 1
u32 ticket_size ; // usually == 0x350 == sizeof(Ticket)
2020-01-11 12:59:12 -05:00
Ticket ticket ;
} __attribute__ ( ( packed , aligned ( 4 ) ) ) TicketEntry ;
2019-09-21 10:30:18 -04:00
static FIL * bdrifp ;
static FRESULT BDRIRead ( UINT ofs , UINT btr , void * buf ) {
if ( bdrifp ) {
FRESULT res ;
UINT br ;
if ( ( fvx_tell ( bdrifp ) ! = ofs ) & &
( fvx_lseek ( bdrifp , ofs ) ! = FR_OK ) ) return FR_DENIED ;
res = fvx_read ( bdrifp , buf , btr , & br ) ;
if ( ( res = = FR_OK ) & & ( br ! = btr ) ) res = FR_DENIED ;
return res ;
} else return FR_DENIED ;
}
static FRESULT BDRIWrite ( UINT ofs , UINT btw , const void * buf ) {
if ( bdrifp ) {
FRESULT res ;
UINT bw ;
if ( ( fvx_tell ( bdrifp ) ! = ofs ) & &
( fvx_lseek ( bdrifp , ofs ) ! = FR_OK ) ) return FR_DENIED ;
res = fvx_write ( bdrifp , buf , btw , & bw ) ;
if ( ( res = = FR_OK ) & & ( bw ! = btw ) ) res = FR_DENIED ;
return res ;
} else return FR_DENIED ;
}
bool CheckDBMagic ( const u8 * pre_header , bool tickdb ) {
2026-06-26 23:56:28 +03:00
const TitleDBHeader * title = ( TitleDBHeader * ) ( void * ) pre_header ;
const TickDBHeader * tick = ( TickDBHeader * ) ( void * ) pre_header ;
return ( tickdb ? ( ( strncmp ( tick - > pre_header . magic , " TICK " , 4 ) = = 0 ) & & ( tick - > pre_header . version = = 1 ) ) :
( ( strcmp ( title - > pre_header . magic , " NANDIDB " ) = = 0 ) | | ( strcmp ( title - > pre_header . magic , " NANDTDB " ) = = 0 ) | |
( strcmp ( title - > pre_header . magic , " TEMPIDB " ) = = 0 ) | | ( strcmp ( title - > pre_header . magic , " TEMPTDB " ) = = 0 ) ) ) & &
( strcmp ( ( tickdb ? tick - > fs_header : title - > fs_header ) . pre_header . magic_str , " BDRI " ) = = 0 ) & &
( ( tickdb ? tick - > fs_header : title - > fs_header ) . pre_header . version = = 0x30000 ) ;
2019-07-13 23:00:10 -04:00
}
// This function was taken, with slight modification, from https://3dbrew.org/wiki/Inner_FAT
2019-09-21 10:30:18 -04:00
static u32 GetHashBucket ( const u8 * tid , u32 parent_dir_index , u32 bucket_count ) {
2019-07-13 23:00:10 -04:00
u32 hash = parent_dir_index ^ 0x091A2B3C ;
for ( u8 i = 0 ; i < 2 ; + + i ) {
hash = ( hash > > 1 ) | ( hash < < 31 ) ;
hash ^ = ( u32 ) tid [ i * 4 ] ;
hash ^ = ( u32 ) tid [ i * 4 + 1 ] < < 8 ;
hash ^ = ( u32 ) tid [ i * 4 + 2 ] < < 16 ;
hash ^ = ( u32 ) tid [ i * 4 + 3 ] < < 24 ;
}
return hash % bucket_count ;
}
2020-01-23 00:15:01 +00:00
static u32 GetBDRIEntrySize ( const BDRIFsHeader * fs_header , const u32 fs_header_offset , const u8 * title_id , u32 * size ) {
2026-06-26 23:56:28 +03:00
if ( ( fs_header - > pre_header . fs_info_offset ! = 0x20 ) | |
( fs_header - > fs_info . fat . outfat_count ! = fs_header - > fs_info . data_region . outfat_count ) ) // Could be more thorough
2020-01-23 00:15:01 +00:00
return 1 ;
2026-06-26 23:56:28 +03:00
const u32 data_offset = fs_header_offset + fs_header - > fs_info . data_region . outfat_offset ;
const u32 fet_offset = data_offset + fs_header - > fs_info . filetable_info . starting_block_index * fs_header - > fs_info . data_region_blocksize ;
const u32 fht_offset = fs_header_offset + fs_header - > fs_info . file_hashtbl . outfat_offset ;
2020-08-24 21:27:19 -07:00
2020-01-23 00:15:01 +00:00
u32 index = 0 ;
2026-06-26 23:56:28 +03:00
BDRIFileEntry file_entry ;
2020-01-23 00:15:01 +00:00
u64 tid_be = getbe64 ( title_id ) ;
u8 * title_id_be = ( u8 * ) & tid_be ;
2026-06-26 23:56:28 +03:00
const u32 hash_bucket = GetHashBucket ( title_id_be , 1 , fs_header - > fs_info . file_hashtbl . outfat_count ) ;
2020-08-24 21:27:19 -07:00
2020-05-23 19:57:47 -04:00
if ( BDRIRead ( fht_offset + hash_bucket * sizeof ( u32 ) , sizeof ( u32 ) , & ( file_entry . hash_bucket_next_index ) ) ! = FR_OK )
2020-01-23 00:15:01 +00:00
return 1 ;
2020-05-23 19:57:47 -04:00
2020-01-23 00:15:01 +00:00
// Find the file entry for the tid specified, fail if it doesn't exist
do {
2020-05-23 19:57:47 -04:00
if ( file_entry . hash_bucket_next_index = = 0 )
2020-01-23 00:15:01 +00:00
return 1 ;
2020-08-24 21:27:19 -07:00
2020-05-23 19:57:47 -04:00
index = file_entry . hash_bucket_next_index ;
2020-08-24 21:27:19 -07:00
2026-06-26 23:56:28 +03:00
if ( BDRIRead ( fet_offset + index * sizeof ( BDRIFileEntry ) , sizeof ( BDRIFileEntry ) , & file_entry ) ! = FR_OK )
2020-01-23 00:15:01 +00:00
return 1 ;
} while ( memcmp ( title_id_be , file_entry . title_id , 8 ) ! = 0 ) ;
2020-08-24 21:27:19 -07:00
2020-01-23 00:15:01 +00:00
* size = file_entry . size ;
return 0 ;
}
2019-09-21 10:30:18 -04:00
static u32 ReadBDRIEntry ( const BDRIFsHeader * fs_header , const u32 fs_header_offset , const u8 * title_id , u8 * entry , const u32 expected_size ) {
2026-06-26 23:56:28 +03:00
if ( ( fs_header - > pre_header . fs_info_offset ! = 0x20 ) | |
( fs_header - > fs_info . fat . outfat_count ! = fs_header - > fs_info . data_region . outfat_count ) ) // Could be more thorough
2019-07-13 23:00:10 -04:00
return 1 ;
2020-08-24 21:27:19 -07:00
2026-06-26 23:56:28 +03:00
const u32 data_offset = fs_header_offset + fs_header - > fs_info . data_region . outfat_offset ;
const u32 fet_offset = data_offset + fs_header - > fs_info . filetable_info . starting_block_index * fs_header - > fs_info . data_region_blocksize ;
const u32 fht_offset = fs_header_offset + fs_header - > fs_info . file_hashtbl . outfat_offset ;
const u32 fat_offset = fs_header_offset + fs_header - > fs_info . fat . outfat_offset ;
2020-08-24 21:27:19 -07:00
2019-07-13 23:00:10 -04:00
u32 index = 0 ;
2026-06-26 23:56:28 +03:00
BDRIFileEntry file_entry ;
2019-07-13 23:00:10 -04:00
u64 tid_be = getbe64 ( title_id ) ;
u8 * title_id_be = ( u8 * ) & tid_be ;
2026-06-26 23:56:28 +03:00
const u32 hash_bucket = GetHashBucket ( title_id_be , 1 , fs_header - > fs_info . file_hashtbl . outfat_count ) ;
2020-08-24 21:27:19 -07:00
2020-05-23 19:57:47 -04:00
if ( BDRIRead ( fht_offset + hash_bucket * sizeof ( u32 ) , sizeof ( u32 ) , & ( file_entry . hash_bucket_next_index ) ) ! = FR_OK )
2019-07-13 23:00:10 -04:00
return 1 ;
2020-05-23 19:57:47 -04:00
2019-07-13 23:00:10 -04:00
// Find the file entry for the tid specified, fail if it doesn't exist
do {
2020-05-23 19:57:47 -04:00
if ( file_entry . hash_bucket_next_index = = 0 )
2019-07-13 23:00:10 -04:00
return 1 ;
2020-08-24 21:27:19 -07:00
2020-05-23 19:57:47 -04:00
index = file_entry . hash_bucket_next_index ;
2020-08-24 21:27:19 -07:00
2026-06-26 23:56:28 +03:00
if ( BDRIRead ( fet_offset + index * sizeof ( BDRIFileEntry ) , sizeof ( BDRIFileEntry ) , & file_entry ) ! = FR_OK )
2019-07-13 23:00:10 -04:00
return 1 ;
} while ( memcmp ( title_id_be , file_entry . title_id , 8 ) ! = 0 ) ;
2020-08-24 21:27:19 -07:00
2019-07-13 23:00:10 -04:00
if ( expected_size & & ( file_entry . size ! = expected_size ) )
return 1 ;
2020-08-24 21:27:19 -07:00
2019-07-13 23:00:10 -04:00
index = file_entry . start_block_index + 1 ; // FAT entry index
2020-08-24 21:27:19 -07:00
2019-07-13 23:00:10 -04:00
u32 bytes_read = 0 ;
u32 fat_entry [ 2 ] ;
2020-08-24 21:27:19 -07:00
2019-09-21 10:30:18 -04:00
while ( bytes_read < file_entry . size ) { // Read the full entry, walking the FAT node chain
2019-07-13 23:00:10 -04:00
u32 read_start = index - 1 ; // Data region block index
u32 read_count = 0 ;
2020-08-24 21:27:19 -07:00
2019-09-21 10:30:18 -04:00
if ( BDRIRead ( fat_offset + index * FAT_ENTRY_SIZE , FAT_ENTRY_SIZE , fat_entry ) ! = FR_OK )
2019-07-13 23:00:10 -04:00
return 1 ;
2020-08-24 21:27:19 -07:00
2019-09-21 10:30:18 -04:00
if ( ( bytes_read = = 0 ) & & ! getfatflag ( fat_entry [ 0 ] ) )
2019-07-13 23:00:10 -04:00
return 1 ;
2020-08-24 21:27:19 -07:00
2019-09-21 10:30:18 -04:00
u32 next_index = getfatindex ( fat_entry [ 1 ] ) ;
2020-08-24 21:27:19 -07:00
2019-09-21 10:30:18 -04:00
if ( getfatflag ( fat_entry [ 1 ] ) ) { // Multi-entry node
if ( BDRIRead ( fat_offset + ( index + 1 ) * FAT_ENTRY_SIZE , FAT_ENTRY_SIZE , fat_entry ) ! = FR_OK )
2019-07-13 23:00:10 -04:00
return 1 ;
2020-08-24 21:27:19 -07:00
2019-09-21 10:30:18 -04:00
if ( ! getfatflag ( fat_entry [ 0 ] ) | | getfatflag ( fat_entry [ 1 ] ) | | ( getfatindex ( fat_entry [ 0 ] ) ! = index ) | | ( getfatindex ( fat_entry [ 0 ] ) > = getfatindex ( fat_entry [ 1 ] ) ) )
2019-07-13 23:00:10 -04:00
return 1 ;
2020-08-24 21:27:19 -07:00
2019-09-21 10:30:18 -04:00
read_count = getfatindex ( fat_entry [ 1 ] ) + 1 - index ;
2019-07-13 23:00:10 -04:00
} else { // Single-entry node
read_count = 1 ;
}
2020-08-24 21:27:19 -07:00
2019-07-13 23:00:10 -04:00
index = next_index ;
2020-08-24 21:27:19 -07:00
2026-06-26 23:56:28 +03:00
u32 btr = min ( file_entry . size - bytes_read , read_count * fs_header - > fs_info . data_region_blocksize ) ;
if ( entry & & ( BDRIRead ( data_offset + read_start * fs_header - > fs_info . data_region_blocksize , btr , entry + bytes_read ) ! = FR_OK ) )
2019-07-13 23:00:10 -04:00
return 1 ;
2020-08-24 21:27:19 -07:00
2019-07-13 23:00:10 -04:00
bytes_read + = btr ;
}
2020-08-24 21:27:19 -07:00
2019-07-13 23:00:10 -04:00
return 0 ;
}
2019-09-21 10:30:18 -04:00
static u32 RemoveBDRIEntry ( const BDRIFsHeader * fs_header , const u32 fs_header_offset , const u8 * title_id ) {
2026-06-26 23:56:28 +03:00
if ( ( fs_header - > pre_header . fs_info_offset ! = 0x20 ) | |
( fs_header - > fs_info . fat . outfat_count ! = fs_header - > fs_info . data_region . outfat_count ) ) // Could be more thorough
2019-07-13 23:00:10 -04:00
return 1 ;
2020-08-24 21:27:19 -07:00
2026-06-26 23:56:28 +03:00
const u32 data_offset = fs_header_offset + fs_header - > fs_info . data_region . outfat_offset ;
const u32 det_offset = data_offset + fs_header - > fs_info . dirtable_info . starting_block_index * fs_header - > fs_info . data_region_blocksize ;
const u32 fet_offset = data_offset + fs_header - > fs_info . filetable_info . starting_block_index * fs_header - > fs_info . data_region_blocksize ;
const u32 fht_offset = fs_header_offset + fs_header - > fs_info . file_hashtbl . outfat_offset ;
const u32 fat_offset = fs_header_offset + fs_header - > fs_info . fat . outfat_offset ;
2020-08-24 21:27:19 -07:00
2019-07-13 23:00:10 -04:00
u32 index = 0 , previous_index = 0 ;
2026-06-26 23:56:28 +03:00
BDRIFileEntry file_entry ;
2019-07-13 23:00:10 -04:00
u64 tid_be = getbe64 ( title_id ) ;
u8 * title_id_be = ( u8 * ) & tid_be ;
2020-08-24 21:27:19 -07:00
2019-07-13 23:00:10 -04:00
// Read the index of the first file entry from the directory entry table
2019-09-21 10:30:18 -04:00
if ( BDRIRead ( det_offset + 0x2C , sizeof ( u32 ) , & ( file_entry . next_sibling_index ) ) ! = FR_OK )
2019-07-13 23:00:10 -04:00
return 1 ;
2020-08-24 21:27:19 -07:00
2019-07-13 23:00:10 -04:00
// Find the file entry for the tid specified, fail if it doesn't exist
do {
previous_index = index ;
index = file_entry . next_sibling_index ;
2020-08-24 21:27:19 -07:00
2019-07-13 23:00:10 -04:00
if ( index = = 0 )
return 1 ;
2020-08-24 21:27:19 -07:00
2026-06-26 23:56:28 +03:00
if ( BDRIRead ( fet_offset + index * sizeof ( BDRIFileEntry ) , sizeof ( BDRIFileEntry ) , & file_entry ) ! = FR_OK )
2019-07-13 23:00:10 -04:00
return 1 ;
} while ( memcmp ( title_id_be , file_entry . title_id , 8 ) ! = 0 ) ;
2020-08-24 21:27:19 -07:00
2026-06-26 23:56:28 +03:00
BDRIDummyFileEntry dummy_entry ;
2020-08-24 21:27:19 -07:00
2019-07-13 23:00:10 -04:00
// Read the 0th entry in the FET, which is always a dummy entry
2026-06-26 23:56:28 +03:00
if ( BDRIRead ( fet_offset , sizeof ( BDRIDummyFileEntry ) , & dummy_entry ) ! = FR_OK )
2019-07-13 23:00:10 -04:00
return 1 ;
2020-08-24 21:27:19 -07:00
2026-06-26 23:56:28 +03:00
if ( dummy_entry . max_entry_count ! = fs_header - > fs_info . filetable_info . max_entry_count + 1 )
2019-07-13 23:00:10 -04:00
return 1 ;
2020-08-24 21:27:19 -07:00
2026-06-26 23:56:28 +03:00
if ( ( BDRIWrite ( fet_offset + index * sizeof ( BDRIFileEntry ) , sizeof ( BDRIFileEntry ) , & dummy_entry ) ! = FR_OK ) | |
2019-09-21 10:30:18 -04:00
( BDRIWrite ( fet_offset + 0x28 , sizeof ( u32 ) , & index ) ! = FR_OK ) | |
2026-06-26 23:56:28 +03:00
( BDRIWrite ( ( previous_index = = 0 ) ? det_offset + 0x2C : fet_offset + previous_index * sizeof ( BDRIFileEntry ) + 0xC , sizeof ( u32 ) , & ( file_entry . next_sibling_index ) ) ! = FR_OK ) )
2019-07-13 23:00:10 -04:00
return 1 ;
2020-08-24 21:27:19 -07:00
2026-06-26 23:56:28 +03:00
const u32 hash_bucket = GetHashBucket ( file_entry . title_id , file_entry . parent_index , fs_header - > fs_info . file_hashtbl . outfat_count ) ;
2019-07-13 23:00:10 -04:00
u32 index_hash = 0 ;
2020-08-24 21:27:19 -07:00
2019-09-21 10:30:18 -04:00
if ( BDRIRead ( fht_offset + hash_bucket * sizeof ( u32 ) , sizeof ( u32 ) , & index_hash ) ! = FR_OK )
2019-07-13 23:00:10 -04:00
return 1 ;
2020-08-24 21:27:19 -07:00
2019-07-13 23:00:10 -04:00
if ( index_hash = = index ) {
2019-09-21 10:30:18 -04:00
if ( BDRIWrite ( fht_offset + hash_bucket * sizeof ( u32 ) , sizeof ( u32 ) , & ( file_entry . hash_bucket_next_index ) ) ! = FR_OK )
2019-07-13 23:00:10 -04:00
return 1 ;
} else {
2026-05-10 12:30:00 +02:00
u32 prev_hash_index = 0 ;
2019-07-13 23:00:10 -04:00
do {
if ( index_hash = = 0 ) // This shouldn't happen if the entry was properly added
break ;
2020-08-24 21:27:19 -07:00
2026-05-10 12:30:00 +02:00
prev_hash_index = index_hash ;
2026-06-26 23:56:28 +03:00
if ( BDRIRead ( fet_offset + index_hash * sizeof ( BDRIFileEntry ) + 0x28 , sizeof ( u32 ) , & index_hash ) ! = FR_OK )
2019-07-13 23:00:10 -04:00
return 1 ;
} while ( index_hash ! = index ) ;
2020-08-24 21:27:19 -07:00
2026-06-26 23:56:28 +03:00
if ( ( prev_hash_index ! = 0 ) & & BDRIWrite ( fet_offset + prev_hash_index * sizeof ( BDRIFileEntry ) + 0x28 , sizeof ( u32 ) , & ( file_entry . hash_bucket_next_index ) ) ! = FR_OK )
2019-07-13 23:00:10 -04:00
return 1 ;
}
2020-08-24 21:27:19 -07:00
2019-07-13 23:00:10 -04:00
u32 fat_entry [ 2 ] ;
2020-08-24 21:27:19 -07:00
2019-09-21 10:30:18 -04:00
if ( BDRIRead ( fat_offset , FAT_ENTRY_SIZE , fat_entry ) ! = FR_OK )
2019-07-13 23:00:10 -04:00
return 1 ;
2020-08-24 21:27:19 -07:00
2019-09-21 10:30:18 -04:00
if ( getfatflag ( fat_entry [ 1 ] ) | | ( fat_entry [ 0 ] ! = 0 ) )
2019-07-13 23:00:10 -04:00
return 1 ;
2020-08-24 21:27:19 -07:00
2019-09-21 10:30:18 -04:00
u32 next_free_index = getfatindex ( fat_entry [ 1 ] ) , fat_index = file_entry . start_block_index + 1 ;
2020-08-24 21:27:19 -07:00
2019-09-21 10:30:18 -04:00
if ( BDRIWrite ( fat_offset + sizeof ( u32 ) , sizeof ( u32 ) , & fat_index ) ! = FR_OK )
2019-07-13 23:00:10 -04:00
return 1 ;
2020-08-24 21:27:19 -07:00
2019-07-13 23:00:10 -04:00
fat_entry [ 1 ] = fat_index ;
2020-08-24 21:27:19 -07:00
2019-07-13 23:00:10 -04:00
do {
2019-09-21 10:30:18 -04:00
fat_index = getfatindex ( fat_entry [ 1 ] ) ;
2020-08-24 21:27:19 -07:00
2019-09-21 10:30:18 -04:00
if ( BDRIRead ( fat_offset + FAT_ENTRY_SIZE * fat_index , FAT_ENTRY_SIZE , fat_entry ) ! = FR_OK )
2019-07-13 23:00:10 -04:00
return 1 ;
2019-09-21 10:30:18 -04:00
} while ( getfatindex ( fat_entry [ 1 ] ) ! = 0 ) ;
2020-08-24 21:27:19 -07:00
2026-05-10 12:30:00 +02:00
// Bug fix: use buildfatuv to explicitly clear Bit 31 (the multi-block flag).
// If the tail of the freed chain is a multi-block node start, Bit 31 is already
// set in fat_entry[1]. A plain |= would keep it set, corrupting the free list.
fat_entry [ 1 ] = buildfatuv ( next_free_index , false ) ;
2020-08-24 21:27:19 -07:00
2026-05-10 12:30:00 +02:00
if ( BDRIWrite ( fat_offset + fat_index * FAT_ENTRY_SIZE , FAT_ENTRY_SIZE , fat_entry ) ! = FR_OK )
2019-07-13 23:00:10 -04:00
return 1 ;
2020-08-24 21:27:19 -07:00
2026-05-10 12:30:00 +02:00
// Bug fix: guard against next_free_index == 0 (freed entry was the last free
// block). Without this guard, FAT[0] (the free-list sentinel) gets corrupted
// by a stray back-pointer write.
if ( next_free_index ! = 0 ) {
if ( BDRIRead ( fat_offset + next_free_index * FAT_ENTRY_SIZE , FAT_ENTRY_SIZE , fat_entry ) ! = FR_OK )
return 1 ;
2020-08-24 21:27:19 -07:00
2026-05-10 12:30:00 +02:00
fat_entry [ 0 ] = buildfatuv ( fat_index , false ) ;
if ( BDRIWrite ( fat_offset + next_free_index * FAT_ENTRY_SIZE , FAT_ENTRY_SIZE , fat_entry ) ! = FR_OK )
return 1 ;
}
2020-08-24 21:27:19 -07:00
2019-07-13 23:00:10 -04:00
return 0 ;
}
2026-06-26 23:56:28 +03:00
static u32 AllocateBDRIBlocks ( const BDRIFsHeader * fs_header , const u32 fs_header_offset , const u32 size_blocks , u32 * out_fat_index ) {
u32 fat_entry [ 2 ] ;
u32 fat_index = 0 ;
const u32 fat_offset = fs_header_offset + fs_header - > fs_info . fat . outfat_offset ;
2020-08-24 21:27:19 -07:00
2026-06-26 23:56:28 +03:00
if ( BDRIRead ( fat_offset , FAT_ENTRY_SIZE , fat_entry ) ! = FR_OK )
2019-07-13 23:00:10 -04:00
return 1 ;
2020-08-24 21:27:19 -07:00
2026-06-26 23:56:28 +03:00
if ( getfatflag ( fat_entry [ 1 ] ) | | ( fat_entry [ 0 ] ! = 0 ) )
return 1 ;
2020-08-24 21:27:19 -07:00
2026-06-26 23:56:28 +03:00
u32 next_fat_index = getfatindex ( fat_entry [ 1 ] ) , node_size = 0 ;
2020-08-24 21:27:19 -07:00
2026-06-26 23:56:28 +03:00
// Find contiguous free space in the FAT for the entry. Technically there could be a case of enough space existing, but not in a contiguous fasion, but this would never realistically happen
do {
if ( next_fat_index = = 0 )
return 1 ; // Reached the end of the free node chain without finding enough contiguous free space - this should never realistically happen
2020-08-24 21:27:19 -07:00
2026-06-26 23:56:28 +03:00
fat_index = next_fat_index ;
2020-08-24 21:27:19 -07:00
2026-06-26 23:56:28 +03:00
if ( BDRIRead ( fat_offset + fat_index * FAT_ENTRY_SIZE , FAT_ENTRY_SIZE , fat_entry ) ! = FR_OK )
2019-07-13 23:00:10 -04:00
return 1 ;
2020-08-24 21:27:19 -07:00
2026-06-26 23:56:28 +03:00
next_fat_index = getfatindex ( fat_entry [ 1 ] ) ;
if ( getfatflag ( fat_entry [ 1 ] ) ) { // Multi-entry node
if ( BDRIRead ( fat_offset + ( fat_index + 1 ) * FAT_ENTRY_SIZE , FAT_ENTRY_SIZE , fat_entry ) ! = FR_OK )
return 1 ;
if ( ! getfatflag ( fat_entry [ 0 ] ) | | getfatflag ( fat_entry [ 1 ] ) | | ( getfatindex ( fat_entry [ 0 ] ) ! = fat_index ) | | ( getfatindex ( fat_entry [ 0 ] ) > = getfatindex ( fat_entry [ 1 ] ) ) )
return 1 ;
node_size = getfatindex ( fat_entry [ 1 ] ) + 1 - fat_index ;
} else { // Single-entry node
node_size = 1 ;
2019-09-21 10:30:18 -04:00
}
2026-06-26 23:56:28 +03:00
} while ( node_size < size_blocks ) ;
2020-08-24 21:27:19 -07:00
2026-06-26 23:56:28 +03:00
const bool shrink_free_node = node_size > size_blocks ;
2020-08-24 21:27:19 -07:00
2026-06-26 23:56:28 +03:00
if ( shrink_free_node ) {
if ( BDRIRead ( fat_offset + fat_index * FAT_ENTRY_SIZE , FAT_ENTRY_SIZE , fat_entry ) ! = FR_OK )
2019-09-21 10:30:18 -04:00
return 1 ;
2026-06-26 23:56:28 +03:00
if ( node_size - size_blocks = = 1 )
fat_entry [ 1 ] = buildfatuv ( getfatindex ( fat_entry [ 1 ] ) , false ) ;
2019-09-21 10:30:18 -04:00
2026-06-26 23:56:28 +03:00
if ( BDRIWrite ( fat_offset + ( fat_index + size_blocks ) * FAT_ENTRY_SIZE , FAT_ENTRY_SIZE , fat_entry ) ! = FR_OK )
return 1 ;
2019-09-21 10:30:18 -04:00
2026-06-26 23:56:28 +03:00
if ( node_size - size_blocks > 1 ) {
if ( BDRIRead ( fat_offset + ( fat_index + node_size - 1 ) * FAT_ENTRY_SIZE , FAT_ENTRY_SIZE , fat_entry ) ! = FR_OK )
return 1 ;
2019-09-21 10:30:18 -04:00
2026-06-26 23:56:28 +03:00
fat_entry [ 0 ] = buildfatuv ( fat_index + size_blocks , true ) ;
2019-09-21 10:30:18 -04:00
2026-06-26 23:56:28 +03:00
if ( ( BDRIWrite ( fat_offset + ( fat_index + node_size - 1 ) * FAT_ENTRY_SIZE , FAT_ENTRY_SIZE , fat_entry ) ! = FR_OK ) | |
( BDRIWrite ( fat_offset + ( fat_index + size_blocks + 1 ) * FAT_ENTRY_SIZE , FAT_ENTRY_SIZE , fat_entry ) ! = FR_OK ) )
2019-09-21 10:30:18 -04:00
return 1 ;
2026-06-26 23:56:28 +03:00
}
}
2019-09-21 10:30:18 -04:00
2026-06-26 23:56:28 +03:00
if ( BDRIRead ( fat_offset + fat_index * FAT_ENTRY_SIZE , FAT_ENTRY_SIZE , fat_entry ) ! = FR_OK )
return 1 ;
2019-09-21 10:30:18 -04:00
2026-06-26 23:56:28 +03:00
const u32 previous_free_index = getfatindex ( fat_entry [ 0 ] ) , next_free_index = getfatindex ( fat_entry [ 1 ] ) ;
2019-09-21 10:30:18 -04:00
2026-06-26 23:56:28 +03:00
fat_entry [ 0 ] = buildfatuv ( 0 , true ) ;
fat_entry [ 1 ] = buildfatuv ( 0 , size_blocks > 1 ) ;
2019-09-21 10:30:18 -04:00
2026-06-26 23:56:28 +03:00
if ( BDRIWrite ( fat_offset + fat_index * FAT_ENTRY_SIZE , FAT_ENTRY_SIZE , fat_entry ) ! = FR_OK )
return 1 ;
2019-09-21 10:30:18 -04:00
2026-06-26 23:56:28 +03:00
if ( size_blocks > 1 ) {
fat_entry [ 0 ] = buildfatuv ( fat_index , true ) ;
fat_entry [ 1 ] = buildfatuv ( fat_index + size_blocks - 1 , false ) ;
2019-09-21 10:30:18 -04:00
2026-06-26 23:56:28 +03:00
if ( ( BDRIWrite ( fat_offset + ( fat_index + size_blocks - 1 ) * FAT_ENTRY_SIZE , FAT_ENTRY_SIZE , fat_entry ) ! = FR_OK ) | |
( BDRIWrite ( fat_offset + ( fat_index + 1 ) * FAT_ENTRY_SIZE , FAT_ENTRY_SIZE , fat_entry ) ! = FR_OK ) )
return 1 ;
}
2019-09-21 10:30:18 -04:00
2026-06-26 23:56:28 +03:00
if ( next_free_index ! = 0 ) {
if ( BDRIRead ( fat_offset + next_free_index * FAT_ENTRY_SIZE , FAT_ENTRY_SIZE , fat_entry ) ! = FR_OK )
return 1 ;
2019-09-21 10:30:18 -04:00
2026-06-26 23:56:28 +03:00
fat_entry [ 0 ] = buildfatuv ( shrink_free_node ? fat_index + size_blocks : previous_free_index , ( ! shrink_free_node & & ( previous_free_index = = 0 ) ) ) ;
2019-09-21 10:30:18 -04:00
2026-06-26 23:56:28 +03:00
if ( BDRIWrite ( fat_offset + next_free_index * FAT_ENTRY_SIZE , FAT_ENTRY_SIZE , fat_entry ) ! = FR_OK )
return 1 ;
}
2019-09-21 10:30:18 -04:00
2026-06-26 23:56:28 +03:00
if ( BDRIRead ( fat_offset + previous_free_index * FAT_ENTRY_SIZE , FAT_ENTRY_SIZE , fat_entry ) ! = FR_OK )
return 1 ;
2019-09-21 10:30:18 -04:00
2026-06-26 23:56:28 +03:00
fat_entry [ 1 ] = buildfatuv ( shrink_free_node ? fat_index + size_blocks : next_free_index , getfatflag ( fat_entry [ 1 ] ) ) ;
2019-09-21 10:30:18 -04:00
2026-06-26 23:56:28 +03:00
if ( BDRIWrite ( fat_offset + previous_free_index * FAT_ENTRY_SIZE , FAT_ENTRY_SIZE , fat_entry ) ! = FR_OK )
return 1 ;
2019-09-21 10:30:18 -04:00
2026-06-26 23:56:28 +03:00
* out_fat_index = fat_index ;
return 0 ;
}
2019-09-21 10:30:18 -04:00
2026-06-26 23:56:28 +03:00
static u32 AddBDRIEntry ( const BDRIFsHeader * fs_header , const u32 fs_header_offset , const u8 * title_id , const u8 * entry , const u32 size , bool replace ) {
if ( ( fs_header - > pre_header . fs_info_offset ! = 0x20 ) | |
( fs_header - > fs_info . fat . outfat_count ! = fs_header - > fs_info . data_region . outfat_count ) ) // Could be more thorough
return 1 ;
2019-09-21 10:30:18 -04:00
2026-06-26 23:56:28 +03:00
if ( ! entry | | ! size )
return 1 ;
2019-09-21 10:30:18 -04:00
2026-06-26 23:56:28 +03:00
const u32 data_offset = fs_header_offset + fs_header - > fs_info . data_region . outfat_offset ;
const u32 det_offset = data_offset + fs_header - > fs_info . dirtable_info . starting_block_index * fs_header - > fs_info . data_region_blocksize ;
const u32 fet_offset = data_offset + fs_header - > fs_info . filetable_info . starting_block_index * fs_header - > fs_info . data_region_blocksize ;
const u32 fht_offset = fs_header_offset + fs_header - > fs_info . file_hashtbl . outfat_offset ;
const u32 fat_offset = fs_header_offset + fs_header - > fs_info . fat . outfat_offset ;
const u32 size_blocks = ( size / fs_header - > fs_info . data_region_blocksize ) + ( ( ( size % fs_header - > fs_info . data_region_blocksize ) = = 0 ) ? 0 : 1 ) ;
2019-09-21 10:30:18 -04:00
2026-06-26 23:56:28 +03:00
u32 index = 0 , max_index = 0 ;
BDRIFileEntry file_entry ;
u64 tid_be = getbe64 ( title_id ) ;
u8 * title_id_be = ( u8 * ) & tid_be ;
bool do_replace = false ;
2019-09-21 10:30:18 -04:00
2026-06-26 23:56:28 +03:00
// Read the index of the first file entry from the directory entry table
if ( BDRIRead ( det_offset + 0x2C , sizeof ( u32 ) , & ( file_entry . next_sibling_index ) ) ! = FR_OK )
return 1 ;
2019-09-21 10:30:18 -04:00
2026-06-26 23:56:28 +03:00
// Try to find the file entry for the tid specified
while ( file_entry . next_sibling_index ! = 0 ) {
index = file_entry . next_sibling_index ;
max_index = max ( index , max_index ) ;
2019-09-21 10:30:18 -04:00
2026-06-26 23:56:28 +03:00
if ( BDRIRead ( fet_offset + index * sizeof ( BDRIFileEntry ) , sizeof ( BDRIFileEntry ) , & file_entry ) ! = FR_OK )
2019-09-21 10:30:18 -04:00
return 1 ;
2026-06-26 23:56:28 +03:00
// If an entry for the tid already existed that is already the specified size and replace was specified, just replace the existing entry
if ( memcmp ( title_id_be , file_entry . title_id , 8 ) = = 0 ) {
if ( ! replace ) return 1 ;
else if ( file_entry . size ! = size ) return REPLACE_SIZE_MISMATCH ;
else {
do_replace = true ;
break ;
}
}
}
u32 fat_entry [ 2 ] ;
u32 fat_index = 0 ;
2019-09-21 10:30:18 -04:00
2026-06-26 23:56:28 +03:00
if ( ! do_replace ) {
if ( AllocateBDRIBlocks ( fs_header , fs_header_offset , size_blocks , & fat_index ) ! = 0 )
2019-09-21 10:30:18 -04:00
return 1 ;
2026-06-26 23:56:28 +03:00
} else {
fat_index = file_entry . start_block_index + 1 ;
}
2020-08-24 21:27:19 -07:00
2019-09-21 10:30:18 -04:00
u32 bytes_written = 0 , fat_index_write = fat_index ;
2020-08-24 21:27:19 -07:00
2020-06-10 03:53:24 -04:00
while ( bytes_written < size ) { // Write the full entry, walking the FAT node chain
// Can't assume contiguity here, because we might be replacing an existing entry
2019-09-21 10:30:18 -04:00
u32 write_start = fat_index_write - 1 ; // Data region block index
u32 write_count = 0 ;
2020-08-24 21:27:19 -07:00
2019-09-21 10:30:18 -04:00
if ( BDRIRead ( fat_offset + fat_index_write * FAT_ENTRY_SIZE , FAT_ENTRY_SIZE , fat_entry ) ! = FR_OK )
return 1 ;
2020-08-24 21:27:19 -07:00
2019-09-21 10:30:18 -04:00
if ( ( bytes_written = = 0 ) & & ! getfatflag ( fat_entry [ 0 ] ) )
2019-07-13 23:00:10 -04:00
return 1 ;
2020-08-24 21:27:19 -07:00
2019-09-21 10:30:18 -04:00
u32 next_index = getfatindex ( fat_entry [ 1 ] ) ;
2020-08-24 21:27:19 -07:00
2019-09-21 10:30:18 -04:00
if ( getfatflag ( fat_entry [ 1 ] ) ) { // Multi-entry node
if ( BDRIRead ( fat_offset + ( fat_index_write + 1 ) * FAT_ENTRY_SIZE , FAT_ENTRY_SIZE , fat_entry ) ! = FR_OK )
2019-07-13 23:00:10 -04:00
return 1 ;
2020-08-24 21:27:19 -07:00
2019-09-21 10:30:18 -04:00
if ( ! getfatflag ( fat_entry [ 0 ] ) | | getfatflag ( fat_entry [ 1 ] ) | | ( getfatindex ( fat_entry [ 0 ] ) ! = fat_index_write ) | | ( getfatindex ( fat_entry [ 0 ] ) > = getfatindex ( fat_entry [ 1 ] ) ) )
2019-07-13 23:00:10 -04:00
return 1 ;
2020-08-24 21:27:19 -07:00
2019-09-21 10:30:18 -04:00
write_count = getfatindex ( fat_entry [ 1 ] ) + 1 - fat_index_write ;
2019-07-13 23:00:10 -04:00
} else { // Single-entry node
2019-09-21 10:30:18 -04:00
write_count = 1 ;
2019-07-13 23:00:10 -04:00
}
2020-08-24 21:27:19 -07:00
2019-09-21 10:30:18 -04:00
fat_index_write = next_index ;
2020-08-24 21:27:19 -07:00
2026-06-26 23:56:28 +03:00
u32 btw = min ( size - bytes_written , write_count * fs_header - > fs_info . data_region_blocksize ) ;
if ( BDRIWrite ( data_offset + write_start * fs_header - > fs_info . data_region_blocksize , btw , entry + bytes_written ) ! = FR_OK )
2019-07-13 23:00:10 -04:00
return 1 ;
2020-08-24 21:27:19 -07:00
2019-09-21 10:30:18 -04:00
bytes_written + = btw ;
2019-07-13 23:00:10 -04:00
}
2020-08-24 21:27:19 -07:00
2019-09-21 10:30:18 -04:00
if ( ! do_replace ) {
2026-06-26 23:56:28 +03:00
BDRIDummyFileEntry dummy_entry ;
2019-09-21 10:30:18 -04:00
// Read the 0th entry in the FET, which is always a dummy entry
2026-06-26 23:56:28 +03:00
if ( BDRIRead ( fet_offset , sizeof ( BDRIDummyFileEntry ) , & dummy_entry ) ! = FR_OK )
2019-07-13 23:00:10 -04:00
return 1 ;
2019-09-21 10:30:18 -04:00
2026-06-26 23:56:28 +03:00
if ( dummy_entry . max_entry_count ! = fs_header - > fs_info . filetable_info . max_entry_count + 1 )
2019-07-13 23:00:10 -04:00
return 1 ;
2019-09-21 10:30:18 -04:00
if ( dummy_entry . next_dummy_index = = 0 ) { // If the 0th entry is the only dummy entry, make a new entry
file_entry . next_sibling_index = max_index + 1 ;
dummy_entry . total_entry_count + + ;
if ( BDRIWrite ( fet_offset , sizeof ( u32 ) , & ( dummy_entry . total_entry_count ) ) ! = FR_OK )
return 1 ;
} else { // If there's at least one extraneous dummy entry, replace it
file_entry . next_sibling_index = dummy_entry . next_dummy_index ;
2026-06-26 23:56:28 +03:00
if ( ( BDRIRead ( fet_offset + dummy_entry . next_dummy_index * sizeof ( BDRIDummyFileEntry ) , sizeof ( BDRIDummyFileEntry ) , & dummy_entry ) ! = FR_OK ) | |
( BDRIWrite ( fet_offset , sizeof ( BDRIDummyFileEntry ) , & dummy_entry ) ! = FR_OK ) )
2019-09-21 10:30:18 -04:00
return 1 ;
}
2026-06-26 23:56:28 +03:00
if ( BDRIWrite ( ( index = = 0 ) ? det_offset + 0x2C : fet_offset + index * sizeof ( BDRIFileEntry ) + 0xC , sizeof ( u32 ) , & ( file_entry . next_sibling_index ) ) ! = FR_OK )
2019-09-21 10:30:18 -04:00
return 1 ;
index = file_entry . next_sibling_index ;
2026-06-26 23:56:28 +03:00
const u32 hash_bucket = GetHashBucket ( title_id_be , 1 , fs_header - > fs_info . file_hashtbl . outfat_count ) ;
2019-09-21 10:30:18 -04:00
u32 index_hash = 0 ;
if ( ( BDRIRead ( fht_offset + hash_bucket * sizeof ( u32 ) , sizeof ( u32 ) , & index_hash ) ! = FR_OK ) | |
( BDRIWrite ( fht_offset + hash_bucket * sizeof ( u32 ) , sizeof ( u32 ) , & index ) ! = FR_OK ) )
return 1 ;
2026-06-26 23:56:28 +03:00
memset ( & file_entry , 0 , sizeof ( BDRIFileEntry ) ) ;
2019-09-21 10:30:18 -04:00
file_entry . parent_index = 1 ;
memcpy ( file_entry . title_id , title_id_be , 8 ) ;
file_entry . start_block_index = fat_index - 1 ;
file_entry . size = ( u64 ) size ;
file_entry . hash_bucket_next_index = index_hash ;
2026-06-26 23:56:28 +03:00
if ( BDRIWrite ( fet_offset + index * sizeof ( BDRIFileEntry ) , sizeof ( BDRIFileEntry ) , & file_entry ) ! = FR_OK )
2019-07-13 23:00:10 -04:00
return 1 ;
}
2020-08-24 21:27:19 -07:00
2019-09-21 10:30:18 -04:00
return 0 ;
}
static u32 GetNumBDRIEntries ( const BDRIFsHeader * fs_header , const u32 fs_header_offset ) {
2026-06-26 23:56:28 +03:00
if ( ( fs_header - > pre_header . fs_info_offset ! = 0x20 ) | |
( fs_header - > fs_info . fat . outfat_count ! = fs_header - > fs_info . data_region . outfat_count ) ) // Could be more thorough
2020-08-24 21:27:19 -07:00
return 0 ;
2026-06-26 23:56:28 +03:00
const u32 data_offset = fs_header_offset + fs_header - > fs_info . data_region . outfat_offset ;
const u32 det_offset = data_offset + fs_header - > fs_info . dirtable_info . starting_block_index * fs_header - > fs_info . data_region_blocksize ;
const u32 fet_offset = data_offset + fs_header - > fs_info . filetable_info . starting_block_index * fs_header - > fs_info . data_region_blocksize ;
2020-08-24 21:27:19 -07:00
2019-09-21 10:30:18 -04:00
u32 num_entries = 0 ;
2026-06-26 23:56:28 +03:00
BDRIFileEntry file_entry ;
2020-08-24 21:27:19 -07:00
2019-09-21 10:30:18 -04:00
// Read the index of the first file entry from the directory entry table
if ( BDRIRead ( det_offset + 0x2C , sizeof ( u32 ) , & ( file_entry . next_sibling_index ) ) ! = FR_OK )
return 0 ;
2020-08-24 21:27:19 -07:00
2019-09-21 10:30:18 -04:00
while ( file_entry . next_sibling_index ! = 0 ) {
num_entries + + ;
2026-06-26 23:56:28 +03:00
if ( BDRIRead ( fet_offset + file_entry . next_sibling_index * sizeof ( BDRIFileEntry ) , sizeof ( BDRIFileEntry ) , & file_entry ) ! = FR_OK )
2019-09-21 10:30:18 -04:00
return 0 ;
}
2020-08-24 21:27:19 -07:00
2019-09-21 10:30:18 -04:00
return num_entries ;
}
static u32 ListBDRIEntryTitleIDs ( const BDRIFsHeader * fs_header , const u32 fs_header_offset , u8 * title_ids , u32 max_title_ids ) {
2026-06-26 23:56:28 +03:00
if ( ( fs_header - > pre_header . fs_info_offset ! = 0x20 ) | |
( fs_header - > fs_info . fat . outfat_count ! = fs_header - > fs_info . data_region . outfat_count ) )
2019-09-21 10:30:18 -04:00
return 0 ;
2020-08-24 21:27:19 -07:00
2026-06-26 23:56:28 +03:00
const u32 data_offset = fs_header_offset + fs_header - > fs_info . data_region . outfat_offset ;
const u32 det_offset = data_offset + fs_header - > fs_info . dirtable_info . starting_block_index * fs_header - > fs_info . data_region_blocksize ;
const u32 fet_offset = data_offset + fs_header - > fs_info . filetable_info . starting_block_index * fs_header - > fs_info . data_region_blocksize ;
2020-08-24 21:27:19 -07:00
2019-09-21 10:30:18 -04:00
u32 num_entries = 0 ;
2026-06-26 23:56:28 +03:00
BDRIFileEntry file_entry ;
2020-08-24 21:27:19 -07:00
2019-09-21 10:30:18 -04:00
for ( u32 i = 0 ; i < max_title_ids ; i + + )
memset ( title_ids , 0 , max_title_ids * 8 ) ;
2020-08-24 21:27:19 -07:00
2019-09-21 10:30:18 -04:00
// Read the index of the first file entry from the directory entry table
if ( BDRIRead ( det_offset + 0x2C , sizeof ( u32 ) , & ( file_entry . next_sibling_index ) ) ! = FR_OK )
return 1 ;
2020-08-24 21:27:19 -07:00
2019-09-21 10:30:18 -04:00
while ( ( file_entry . next_sibling_index ! = 0 ) & & ( num_entries < max_title_ids ) ) {
2026-06-26 23:56:28 +03:00
if ( BDRIRead ( fet_offset + file_entry . next_sibling_index * sizeof ( BDRIFileEntry ) , sizeof ( BDRIFileEntry ) , & file_entry ) ! = FR_OK )
2019-07-13 23:00:10 -04:00
return 1 ;
2020-08-24 21:27:19 -07:00
2020-05-23 19:57:47 -04:00
u64 tid_be = getbe64 ( file_entry . title_id ) ;
memcpy ( title_ids + num_entries * 8 , ( u8 * ) & tid_be , 8 ) ;
2020-08-24 21:27:19 -07:00
2019-09-21 10:30:18 -04:00
num_entries + + ;
2019-07-13 23:00:10 -04:00
}
2020-08-24 21:27:19 -07:00
2019-09-21 10:30:18 -04:00
return 0 ;
}
u32 GetNumTitleInfoEntries ( const char * path ) {
FIL file ;
2026-06-26 23:56:28 +03:00
TitleDBHeader pre_header ;
2020-08-24 21:27:19 -07:00
2019-09-21 10:30:18 -04:00
if ( fvx_open ( & file , path , FA_READ | FA_OPEN_EXISTING ) ! = FR_OK )
return 0 ;
2020-08-24 21:27:19 -07:00
2019-09-21 10:30:18 -04:00
bdrifp = & file ;
2020-08-24 21:27:19 -07:00
2026-06-26 23:56:28 +03:00
if ( ( BDRIRead ( 0 , sizeof ( TitleDBHeader ) , & pre_header ) ! = FR_OK ) | |
2019-09-21 10:30:18 -04:00
! CheckDBMagic ( ( u8 * ) & pre_header , false ) ) {
fvx_close ( bdrifp ) ;
bdrifp = NULL ;
return 0 ;
}
2020-08-24 21:27:19 -07:00
2026-06-26 23:56:28 +03:00
u32 num = GetNumBDRIEntries ( & ( pre_header . fs_header ) , sizeof ( TitleDBHeader ) - sizeof ( BDRIFsHeader ) ) ;
2020-08-24 21:27:19 -07:00
2019-09-21 10:30:18 -04:00
fvx_close ( bdrifp ) ;
bdrifp = NULL ;
return num ;
2019-07-13 23:00:10 -04:00
}
2019-09-21 10:30:18 -04:00
u32 GetNumTickets ( const char * path ) {
2019-07-13 23:00:10 -04:00
FIL file ;
2026-06-26 23:56:28 +03:00
TickDBHeader pre_header ;
2020-08-24 21:27:19 -07:00
2019-07-13 23:00:10 -04:00
if ( fvx_open ( & file , path , FA_READ | FA_OPEN_EXISTING ) ! = FR_OK )
2019-09-21 10:30:18 -04:00
return 0 ;
2020-08-24 21:27:19 -07:00
2019-09-21 10:30:18 -04:00
bdrifp = & file ;
2020-08-24 21:27:19 -07:00
2026-06-26 23:56:28 +03:00
if ( ( BDRIRead ( 0 , sizeof ( TickDBHeader ) , & pre_header ) ! = FR_OK ) | |
2019-09-21 10:30:18 -04:00
! CheckDBMagic ( ( u8 * ) & pre_header , true ) ) {
fvx_close ( bdrifp ) ;
bdrifp = NULL ;
return 0 ;
2019-07-13 23:00:10 -04:00
}
2019-09-21 10:30:18 -04:00
2026-06-26 23:56:28 +03:00
u32 num = GetNumBDRIEntries ( & ( pre_header . fs_header ) , sizeof ( TickDBHeader ) - sizeof ( BDRIFsHeader ) ) ;
2020-08-24 21:27:19 -07:00
2019-09-21 10:30:18 -04:00
fvx_close ( bdrifp ) ;
bdrifp = NULL ;
return num ;
}
u32 ListTitleInfoEntryTitleIDs ( const char * path , u8 * title_ids , u32 max_title_ids ) {
FIL file ;
2026-06-26 23:56:28 +03:00
TitleDBHeader pre_header ;
2020-08-24 21:27:19 -07:00
2019-09-21 10:30:18 -04:00
if ( fvx_open ( & file , path , FA_READ | FA_OPEN_EXISTING ) ! = FR_OK )
2019-07-13 23:00:10 -04:00
return 1 ;
2020-08-24 21:27:19 -07:00
2019-09-21 10:30:18 -04:00
bdrifp = & file ;
2020-08-24 21:27:19 -07:00
2026-06-26 23:56:28 +03:00
if ( ( BDRIRead ( 0 , sizeof ( TitleDBHeader ) , & pre_header ) ! = FR_OK ) | |
2019-09-21 10:30:18 -04:00
! CheckDBMagic ( ( u8 * ) & pre_header , false ) | |
2026-06-26 23:56:28 +03:00
( ListBDRIEntryTitleIDs ( & ( pre_header . fs_header ) , sizeof ( TitleDBHeader ) - sizeof ( BDRIFsHeader ) , title_ids , max_title_ids ) ! = 0 ) ) {
2019-09-21 10:30:18 -04:00
fvx_close ( bdrifp ) ;
bdrifp = NULL ;
2019-07-13 23:00:10 -04:00
return 1 ;
}
2020-08-24 21:27:19 -07:00
2019-09-21 10:30:18 -04:00
fvx_close ( bdrifp ) ;
bdrifp = NULL ;
2019-07-13 23:00:10 -04:00
return 0 ;
}
2019-09-21 10:30:18 -04:00
u32 ListTicketTitleIDs ( const char * path , u8 * title_ids , u32 max_title_ids ) {
2019-07-13 23:00:10 -04:00
FIL file ;
2026-06-26 23:56:28 +03:00
TickDBHeader pre_header ;
2020-08-24 21:27:19 -07:00
2019-07-13 23:00:10 -04:00
if ( fvx_open ( & file , path , FA_READ | FA_OPEN_EXISTING ) ! = FR_OK )
return 1 ;
2020-08-24 21:27:19 -07:00
2019-09-21 10:30:18 -04:00
bdrifp = & file ;
2020-08-24 21:27:19 -07:00
2026-06-26 23:56:28 +03:00
if ( ( BDRIRead ( 0 , sizeof ( TickDBHeader ) , & pre_header ) ! = FR_OK ) | |
2019-09-21 10:30:18 -04:00
! CheckDBMagic ( ( u8 * ) & pre_header , true ) | |
2026-06-26 23:56:28 +03:00
( ListBDRIEntryTitleIDs ( & ( pre_header . fs_header ) , sizeof ( TickDBHeader ) - sizeof ( BDRIFsHeader ) , title_ids , max_title_ids ) ! = 0 ) ) {
2019-09-21 10:30:18 -04:00
fvx_close ( bdrifp ) ;
bdrifp = NULL ;
2019-07-13 23:00:10 -04:00
return 1 ;
}
2020-08-24 21:27:19 -07:00
2019-09-21 10:30:18 -04:00
fvx_close ( bdrifp ) ;
bdrifp = NULL ;
return 0 ;
}
u32 ReadTitleInfoEntryFromDB ( const char * path , const u8 * title_id , TitleInfoEntry * tie ) {
FIL file ;
2026-06-26 23:56:28 +03:00
TitleDBHeader pre_header ;
2020-08-24 21:27:19 -07:00
2019-09-21 10:30:18 -04:00
if ( fvx_open ( & file , path , FA_READ | FA_OPEN_EXISTING ) ! = FR_OK )
2019-07-13 23:00:10 -04:00
return 1 ;
2020-08-24 21:27:19 -07:00
2019-09-21 10:30:18 -04:00
bdrifp = & file ;
2020-08-24 21:27:19 -07:00
2026-06-26 23:56:28 +03:00
if ( ( BDRIRead ( 0 , sizeof ( TitleDBHeader ) , & pre_header ) ! = FR_OK ) | |
2019-09-21 10:30:18 -04:00
! CheckDBMagic ( ( u8 * ) & pre_header , false ) | |
2026-06-26 23:56:28 +03:00
( ReadBDRIEntry ( & ( pre_header . fs_header ) , sizeof ( TitleDBHeader ) - sizeof ( BDRIFsHeader ) , title_id , ( u8 * ) tie ,
2019-09-21 10:30:18 -04:00
sizeof ( TitleInfoEntry ) ) ! = 0 ) ) {
fvx_close ( bdrifp ) ;
bdrifp = NULL ;
2019-07-13 23:00:10 -04:00
return 1 ;
}
2020-08-24 21:27:19 -07:00
2019-09-21 10:30:18 -04:00
fvx_close ( bdrifp ) ;
bdrifp = NULL ;
2019-07-13 23:00:10 -04:00
return 0 ;
}
2020-05-23 19:57:47 -04:00
u32 ReadTicketFromDB ( const char * path , const u8 * title_id , Ticket * * ticket ) {
2019-07-13 23:00:10 -04:00
FIL file ;
2026-06-26 23:56:28 +03:00
TickDBHeader pre_header ;
2020-01-23 00:29:11 +00:00
TicketEntry * te = NULL ;
u32 entry_size ;
2020-10-28 00:00:18 +01:00
* ticket = NULL ;
2019-09-21 10:30:18 -04:00
if ( fvx_open ( & file , path , FA_READ | FA_OPEN_EXISTING ) ! = FR_OK )
2019-07-13 23:00:10 -04:00
return 1 ;
2020-08-24 21:27:19 -07:00
2019-09-21 10:30:18 -04:00
bdrifp = & file ;
2020-08-24 21:27:19 -07:00
2026-06-26 23:56:28 +03:00
if ( ( BDRIRead ( 0 , sizeof ( TickDBHeader ) , & pre_header ) ! = FR_OK ) | |
2019-09-21 10:30:18 -04:00
! CheckDBMagic ( ( u8 * ) & pre_header , true ) | |
2026-06-26 23:56:28 +03:00
( GetBDRIEntrySize ( & ( pre_header . fs_header ) , sizeof ( TickDBHeader ) - sizeof ( BDRIFsHeader ) , title_id , & entry_size ) ! = 0 ) | |
2020-08-24 21:27:19 -07:00
entry_size < sizeof ( TicketEntry ) + 0x14 | |
2020-01-23 00:29:11 +00:00
( te = ( TicketEntry * ) malloc ( entry_size ) , te = = NULL ) | |
2026-06-26 23:56:28 +03:00
( ReadBDRIEntry ( & ( pre_header . fs_header ) , sizeof ( TickDBHeader ) - sizeof ( BDRIFsHeader ) , title_id , ( u8 * ) te ,
2020-05-23 19:57:47 -04:00
entry_size ) ! = 0 ) ) {
2020-01-23 00:29:11 +00:00
free ( te ) ; // if allocated
2019-09-21 10:30:18 -04:00
fvx_close ( bdrifp ) ;
bdrifp = NULL ;
2019-07-13 23:00:10 -04:00
return 1 ;
}
2020-08-24 21:27:19 -07:00
2019-09-21 10:30:18 -04:00
fvx_close ( bdrifp ) ;
bdrifp = NULL ;
2020-08-24 21:27:19 -07:00
2020-01-23 00:29:11 +00:00
if ( te - > ticket_size ! = GetTicketSize ( & te - > ticket ) ) {
free ( te ) ;
2019-07-13 23:00:10 -04:00
return 1 ;
2020-01-23 00:29:11 +00:00
}
2020-08-24 21:27:19 -07:00
2020-01-23 00:29:11 +00:00
if ( ticket ) {
u32 size = te - > ticket_size ;
memmove ( te , & te - > ticket , size ) ; // recycle this memory, instead of allocating another
Ticket * tik = realloc ( te , size ) ;
if ( ! tik ) tik = ( Ticket * ) te ;
* ticket = tik ;
return 0 ;
}
2020-08-24 21:27:19 -07:00
2020-01-23 00:29:11 +00:00
free ( te ) ;
2019-07-13 23:00:10 -04:00
return 0 ;
}
2019-09-21 10:30:18 -04:00
u32 RemoveTitleInfoEntryFromDB ( const char * path , const u8 * title_id ) {
2019-07-13 23:00:10 -04:00
FIL file ;
2026-06-26 23:56:28 +03:00
TitleDBHeader pre_header ;
2020-08-24 21:27:19 -07:00
2019-07-13 23:00:10 -04:00
if ( fvx_open ( & file , path , FA_READ | FA_WRITE | FA_OPEN_EXISTING ) ! = FR_OK )
return 1 ;
2020-08-24 21:27:19 -07:00
2019-09-21 10:30:18 -04:00
bdrifp = & file ;
2020-08-24 21:27:19 -07:00
2026-06-26 23:56:28 +03:00
if ( ( BDRIRead ( 0 , sizeof ( TitleDBHeader ) , & pre_header ) ! = FR_OK ) | |
2019-09-21 10:30:18 -04:00
! CheckDBMagic ( ( u8 * ) & pre_header , false ) | |
2026-06-26 23:56:28 +03:00
( RemoveBDRIEntry ( & ( pre_header . fs_header ) , sizeof ( TitleDBHeader ) - sizeof ( BDRIFsHeader ) , title_id ) ! = 0 ) ) {
2019-09-21 10:30:18 -04:00
fvx_close ( bdrifp ) ;
bdrifp = NULL ;
2019-07-13 23:00:10 -04:00
return 1 ;
}
2020-08-24 21:27:19 -07:00
2019-09-21 10:30:18 -04:00
fvx_close ( bdrifp ) ;
bdrifp = NULL ;
return 0 ;
}
u32 RemoveTicketFromDB ( const char * path , const u8 * title_id ) {
FIL file ;
2026-06-26 23:56:28 +03:00
TickDBHeader pre_header ;
2020-08-24 21:27:19 -07:00
2019-09-21 10:30:18 -04:00
if ( fvx_open ( & file , path , FA_READ | FA_WRITE | FA_OPEN_EXISTING ) ! = FR_OK )
2019-07-13 23:00:10 -04:00
return 1 ;
2020-08-24 21:27:19 -07:00
2019-09-21 10:30:18 -04:00
bdrifp = & file ;
2020-08-24 21:27:19 -07:00
2026-06-26 23:56:28 +03:00
if ( ( BDRIRead ( 0 , sizeof ( TickDBHeader ) , & pre_header ) ! = FR_OK ) | |
2019-09-21 10:30:18 -04:00
! CheckDBMagic ( ( u8 * ) & pre_header , true ) | |
2026-06-26 23:56:28 +03:00
( RemoveBDRIEntry ( & ( pre_header . fs_header ) , sizeof ( TickDBHeader ) - sizeof ( BDRIFsHeader ) , title_id ) ! = 0 ) ) {
2019-09-21 10:30:18 -04:00
fvx_close ( bdrifp ) ;
bdrifp = NULL ;
2019-07-13 23:00:10 -04:00
return 1 ;
}
2020-08-24 21:27:19 -07:00
2019-07-13 23:00:10 -04:00
fvx_close ( & file ) ;
2019-09-21 10:30:18 -04:00
bdrifp = NULL ;
2019-07-13 23:00:10 -04:00
return 0 ;
}
2019-09-21 10:30:18 -04:00
u32 AddTitleInfoEntryToDB ( const char * path , const u8 * title_id , const TitleInfoEntry * tie , bool replace ) {
2019-07-13 23:00:10 -04:00
FIL file ;
2026-06-26 23:56:28 +03:00
TitleDBHeader pre_header ;
2020-08-24 21:27:19 -07:00
2019-09-21 10:30:18 -04:00
if ( fvx_open ( & file , path , FA_READ | FA_WRITE | FA_OPEN_EXISTING ) ! = FR_OK )
2019-07-13 23:00:10 -04:00
return 1 ;
2020-08-24 21:27:19 -07:00
2019-09-21 10:30:18 -04:00
bdrifp = & file ;
2020-08-24 21:27:19 -07:00
2026-06-26 23:56:28 +03:00
if ( ( BDRIRead ( 0 , sizeof ( TitleDBHeader ) , & pre_header ) ! = FR_OK ) | |
2019-09-21 10:30:18 -04:00
! CheckDBMagic ( ( u8 * ) & pre_header , false ) | |
2026-06-26 23:56:28 +03:00
( AddBDRIEntry ( & ( pre_header . fs_header ) , sizeof ( TitleDBHeader ) - sizeof ( BDRIFsHeader ) , title_id ,
2019-09-21 10:30:18 -04:00
( const u8 * ) tie , sizeof ( TitleInfoEntry ) , replace ) ! = 0 ) ) {
fvx_close ( bdrifp ) ;
bdrifp = NULL ;
2019-07-13 23:00:10 -04:00
return 1 ;
}
2020-08-24 21:27:19 -07:00
2019-09-21 10:30:18 -04:00
fvx_close ( bdrifp ) ;
bdrifp = NULL ;
2019-07-13 23:00:10 -04:00
return 0 ;
}
2019-09-21 10:30:18 -04:00
u32 AddTicketToDB ( const char * path , const u8 * title_id , const Ticket * ticket , bool replace ) {
2019-07-13 23:00:10 -04:00
FIL file ;
2026-06-26 23:56:28 +03:00
TickDBHeader pre_header ;
2020-01-23 00:29:11 +00:00
u32 entry_size = sizeof ( TicketEntry ) + GetTicketContentIndexSize ( ticket ) ;
2020-08-24 21:27:19 -07:00
2020-01-23 00:29:11 +00:00
TicketEntry * te = ( TicketEntry * ) malloc ( entry_size ) ;
if ( ! te ) {
return 1 ;
}
2026-06-26 23:56:28 +03:00
te - > ticket_count = 1 ;
2020-01-23 00:29:11 +00:00
te - > ticket_size = GetTicketSize ( ticket ) ;
memcpy ( & te - > ticket , ticket , te - > ticket_size ) ;
if ( fvx_open ( & file , path , FA_READ | FA_WRITE | FA_OPEN_EXISTING ) ! = FR_OK ) {
free ( te ) ;
2019-07-13 23:00:10 -04:00
return 1 ;
2020-01-23 00:29:11 +00:00
}
2020-08-24 21:27:19 -07:00
2019-09-21 10:30:18 -04:00
bdrifp = & file ;
2020-08-24 21:27:19 -07:00
2021-10-23 14:19:13 +03:00
u32 add_bdri_res = 0 ;
2026-06-26 23:56:28 +03:00
if ( ( BDRIRead ( 0 , sizeof ( TickDBHeader ) , & pre_header ) ! = FR_OK ) | |
2019-09-21 10:30:18 -04:00
! CheckDBMagic ( ( u8 * ) & pre_header , true ) | |
2026-06-26 23:56:28 +03:00
( ( add_bdri_res = AddBDRIEntry ( & ( pre_header . fs_header ) , sizeof ( TickDBHeader ) - sizeof ( BDRIFsHeader ) , title_id ,
2021-10-23 14:19:13 +03:00
( const u8 * ) te , entry_size , replace ) ) = = 1 ) | |
2026-06-26 23:56:28 +03:00
( add_bdri_res = = REPLACE_SIZE_MISMATCH & & ( ( RemoveBDRIEntry ( & ( pre_header . fs_header ) , sizeof ( TickDBHeader ) - sizeof ( BDRIFsHeader ) , title_id ) ! = 0 ) | |
( AddBDRIEntry ( & ( pre_header . fs_header ) , sizeof ( TickDBHeader ) - sizeof ( BDRIFsHeader ) , title_id ,
2021-10-23 14:19:13 +03:00
( const u8 * ) te , entry_size , replace ) ! = 0 ) ) ) ) {
2020-01-23 00:29:11 +00:00
free ( te ) ;
2019-09-21 10:30:18 -04:00
fvx_close ( bdrifp ) ;
bdrifp = NULL ;
2019-07-13 23:00:10 -04:00
return 1 ;
}
2020-05-23 19:57:47 -04:00
2020-01-23 00:29:11 +00:00
free ( te ) ;
2019-09-21 10:30:18 -04:00
fvx_close ( bdrifp ) ;
bdrifp = NULL ;
2019-07-13 23:00:10 -04:00
return 0 ;
2020-05-23 19:57:47 -04:00
}
2026-06-26 23:56:28 +03:00
static u32 CalcNumHashBuckets ( u32 num_entries )
{
if ( num_entries < = 3 )
return 3 ;
if ( num_entries < = 19 )
return num_entries | 1 ;
for ( int i = 0 ; i < 100 ; + + i )
{
u32 ret = num_entries + i ;
if ( ret & 1 & & ret % 3 & & ret % 5 & & ret % 7 & & ret % 11 & & ret % 13 & & ret % 17 )
return ret ;
}
return num_entries | 1 ;
}
u32 CreateBDRI ( const char * path , u64 image_offset , u64 image_size , u32 blocksize , u32 num_files ) {
BDRIFsHeader bdri ;
memset ( & bdri , 0 , sizeof ( BDRIFsHeader ) ) ;
IFatPreHeader * pre_header = & bdri . pre_header ;
IFatFsInfo * fs_info = & bdri . fs_info ;
const u64 img_num_blocks = image_size / blocksize ;
const u32 file_hashbucket_count = CalcNumHashBuckets ( num_files ) ;
memcpy ( pre_header - > magic_str , " BDRI " , 4 ) ;
pre_header - > version = 0x30000 ;
pre_header - > fs_image_blocksize = blocksize ;
pre_header - > fs_image_size_blocks = img_num_blocks ;
pre_header - > fs_info_offset = sizeof ( IFatPreHeader ) ;
const u64 full_image_size = pre_header - > fs_image_blocksize * pre_header - > fs_image_size_blocks ;
u32 num_data_blocks = img_num_blocks ;
u32 data_region_offs = 0 ;
// dynamically adjust the number of data region blocks to fit inside the given image size
while ( 1 ) {
if ( ! num_data_blocks )
return 1 ; // image will not fit inside given size
data_region_offs =
align_pow2 (
sizeof ( IFatPreHeader ) + sizeof ( IFatFsInfo ) + /* headers */
( 4 * 1 ) + ( 4 * file_hashbucket_count ) + /* hashtables (dir, file) */
( num_data_blocks + 1 ) * sizeof ( IFatEntry ) , /* FAT entries, including the freelist entry */
blocksize ) ;
if ( full_image_size > = ( num_data_blocks * blocksize ) + data_region_offs )
break ; // found a satisfactory number of data blocks
- - num_data_blocks ;
}
// create file system info
fs_info - > data_region_blocksize = blocksize ;
fs_info - > dir_hashtbl . outfat_offset = pre_header - > fs_info_offset + sizeof ( IFatFsInfo ) ;
fs_info - > dir_hashtbl . outfat_count = 1 ;
fs_info - > file_hashtbl . outfat_offset = fs_info - > dir_hashtbl . outfat_offset + 4 ;
fs_info - > file_hashtbl . outfat_count = file_hashbucket_count ;
fs_info - > fat . outfat_offset = fs_info - > file_hashtbl . outfat_offset + file_hashbucket_count * 4 ;
fs_info - > fat . outfat_count = num_data_blocks ;
fs_info - > data_region . outfat_offset = data_region_offs ;
fs_info - > data_region . outfat_count = num_data_blocks ;
// dummy freelist dir entry [0] + root dir entry [1]
uint32_t dir_entries_num_blocks = ceil_div ( 2 * sizeof ( BDRIDirectoryEntry ) , blocksize ) ;
// dummy freelist file entry [0] + file entries [n]
uint32_t file_entries_num_blocks = ceil_div ( ( num_files + 1 ) * sizeof ( BDRIFileEntry ) , blocksize ) ;
fs_info - > dirtable_info . max_entry_count = 1 ;
fs_info - > dirtable_info . block_count = dir_entries_num_blocks ;
fs_info - > dirtable_info . starting_block_index = 0xFFFFFFFF ; // to be allocated in FAT later using above block_count
fs_info - > filetable_info . max_entry_count = num_files ;
fs_info - > filetable_info . block_count = file_entries_num_blocks ;
fs_info - > filetable_info . starting_block_index = 0xFFFFFFFF ; // to be allocated in FAT later using above block_count
FIL file ;
if ( fvx_open ( & file , path , FA_READ | FA_WRITE | FA_OPEN_EXISTING ) ! = FR_OK ) {
bdrifp = NULL ;
return 1 ;
}
bdrifp = & file ;
// create new FAT
u32 fatentries [ 2 ] [ 2 ] ;
fatentries [ 0 ] [ 0 ] = buildfatuv ( 0 , 0 ) ;
fatentries [ 0 ] [ 1 ] = buildfatuv ( 1 , 0 ) ;
fatentries [ 1 ] [ 0 ] = buildfatuv ( 0 , true ) ;
fatentries [ 1 ] [ 1 ] = buildfatuv ( 0 , num_data_blocks > 1 ) ;
# define FATOFFSET(n) (image_offset + fs_info->fat.outfat_offset + (n) * FAT_ENTRY_SIZE)
if ( BDRIWrite ( FATOFFSET ( 0 ) , FAT_ENTRY_SIZE * 2 , fatentries ) ! = FR_OK ) {
fvx_close ( & file ) ;
bdrifp = NULL ;
return 1 ;
}
if ( num_data_blocks > 1 ) {
u32 * fatentry = fatentries [ 0 ] ;
fatentry [ 0 ] = buildfatuv ( 1 , true ) ;
fatentry [ 1 ] = buildfatuv ( num_data_blocks , false ) ;
if ( BDRIWrite ( FATOFFSET ( 2 ) , FAT_ENTRY_SIZE , fatentry ) ! = FR_OK | |
BDRIWrite ( FATOFFSET ( num_data_blocks ) , FAT_ENTRY_SIZE , fatentry ) ! = FR_OK ) {
fvx_close ( & file ) ;
bdrifp = NULL ;
return 1 ;
}
}
// allocate file and directory entry tables in FAT
if ( AllocateBDRIBlocks ( & bdri , image_offset , dir_entries_num_blocks , & fs_info - > dirtable_info . starting_block_index ) ! = 0 | |
AllocateBDRIBlocks ( & bdri , image_offset , file_entries_num_blocks , & fs_info - > filetable_info . starting_block_index ) ! = 0 ) {
fvx_close ( & file ) ;
bdrifp = NULL ;
return 1 ;
}
// because of the extra freelist sentinel in fat[0], the actual data region index is fat_index - 1
fs_info - > dirtable_info . starting_block_index - = 1 ;
fs_info - > filetable_info . starting_block_index - = 1 ;
if ( BDRIWrite ( image_offset , sizeof ( BDRIFsHeader ) , & bdri ) ! = FR_OK ) {
fvx_close ( & file ) ;
bdrifp = NULL ;
return 1 ;
}
// initialize file/dir hash tables & entries
// the directory hashtable has just one entry, pointing to the root directory
// no need to do the calculation
u32 dir_hash_value = 1 ;
if ( BDRIWrite ( image_offset + fs_info - > dir_hashtbl . outfat_offset , sizeof ( u32 ) , & dir_hash_value ) ! = FR_OK ) {
fvx_close ( & file ) ;
bdrifp = NULL ;
return 1 ;
}
u8 zero [ 0x200 ] ;
memset ( zero , 0 , sizeof ( zero ) ) ;
u32 remaining_fht = file_hashbucket_count * 4 ;
u32 fht_cur_offset = fs_info - > file_hashtbl . outfat_offset ;
// fill the file hashtable with zeros
while ( remaining_fht ) {
u32 writesize = min ( remaining_fht , sizeof ( zero ) ) ;
if ( BDRIWrite ( image_offset + fht_cur_offset , writesize , zero ) ! = FR_OK ) {
fvx_close ( & file ) ;
bdrifp = NULL ;
return 1 ;
}
remaining_fht - = writesize ;
fht_cur_offset + = writesize ;
}
// initialize directory entry table
BDRIDummyDirectoryEntry dummy_dir ;
BDRIDirectoryEntry root_dir ;
memset ( & dummy_dir , 0 , sizeof ( dummy_dir ) ) ;
memset ( & root_dir , 0 , sizeof ( root_dir ) ) ;
dummy_dir . next_dummy_index = 0 ;
dummy_dir . total_entry_count = 2 ; // this (dummy), and root (the entry after)
dummy_dir . max_entry_count = 3 ; // even though only two are "used"
u64 det_offset = fs_info - > data_region . outfat_offset + fs_info - > dirtable_info . starting_block_index * fs_info - > data_region_blocksize ;
if ( BDRIWrite ( image_offset + det_offset , sizeof ( dummy_dir ) , & dummy_dir ) ! = FR_OK | |
BDRIWrite ( image_offset + det_offset + sizeof ( dummy_dir ) , sizeof ( root_dir ) , & root_dir ) ! = FR_OK ) {
fvx_close ( & file ) ;
bdrifp = NULL ;
return 1 ;
}
// initialize file entry table
BDRIDummyFileEntry dummy_file ;
memset ( & dummy_file , 0 , sizeof ( dummy_file ) ) ;
dummy_file . next_dummy_index = 0 ;
dummy_file . total_entry_count = 1 ; // currently, just this (dummy)
dummy_file . max_entry_count = num_files + 1 ; // this (dummy), and however many file entries should exist
u64 fet_offset = fs_info - > data_region . outfat_offset + fs_info - > filetable_info . starting_block_index * fs_info - > data_region_blocksize ;
if ( BDRIWrite ( image_offset + fet_offset , sizeof ( dummy_file ) , & dummy_file ) ! = FR_OK ) {
fvx_close ( & file ) ;
bdrifp = NULL ;
return 1 ;
}
fvx_close ( & file ) ;
bdrifp = NULL ;
return 0 ;
}
typedef struct DbFileConfig {
const char * path ;
const char * magic ;
u32 version ;
u32 full_data_size ;
u32 image_offset ; // also pre-header size
u32 image_size ;
u32 image_blocksize ;
u32 num_entries ;
} DbFileConfig ;
typedef enum DbType {
NAND_TICKET_DB = 0 , NAND_IMPORT_DB , NAND_TITLE_DB , NAND_TMP_I_DB , NAND_TMP_T_DB ,
SDMC_TITLE_DB , SDMC_IMPORT_DB ,
} DbType ;
static const DbFileConfig DbConfigs [ 7 ] = {
// NAND dbs
{ " /dbs/ticket.db " , " TICK " , 1 , 0x10A2010 , 0x10 , 0x10A2000 , 0x200 , 8192 } , // ticket.db is the only db where the image size is correct...
{ " /dbs/import.db " , " NANDIDB " , 0 , 0x17800 , 0x80 , 0x17800 , 0x80 , 512 } , // ---
{ " /dbs/title.db " , " NANDTDB " , 0 , 0x2ED80 , 0x80 , 0x2ED80 , 0x80 , 1024 } , // | *all* of these are missing 0x80 bytes at the end.
{ " /dbs/tmp_i.db " , " TEMPIDB " , 0 , 0xE00 , 0x80 , 0xE00 , 0x80 , 16 } , // | the image size should be file size - 0x80 because
{ " /dbs/tmp_t.db " , " TEMPIDB " , 0 , 0x6000 , 0x80 , 0x6000 , 0x80 , 128 } , // | of the pre-header. keeping it the same just for
// SDMC dbs // | consistency with Process9, really.
{ " /dbs/title.db " , " TEMPTDB " , 0 , 0x175A80 , 0x80 , 0x175A80 , 0x80 , 8192 } , // |
{ " /dbs/import.db " , " TEMPTDB " , 0 , 0x175A80 , 0x80 , 0x175A80 , 0x80 , 8192 } , // ---
} ;
static const DbFileConfig * GetDbFileConfigBuildPath ( char * outpath , u32 pathsize , u32 type , bool emunand ) {
if ( type > = countof ( DbConfigs ) )
return NULL ;
bool nand = type < = NAND_TMP_T_DB ;
char dest_drive =
nand ?
emunand ? ' 4 ' : ' 1 ' :
emunand ? ' B ' : ' A ' ;
const DbFileConfig * config = & DbConfigs [ type ] ;
snprintf ( outpath , pathsize , " %c:%s " , dest_drive , config - > path ) ;
return config ;
}
u32 CreateDbFile ( u32 type , bool emunand ) {
char path [ 256 ] = { 0 } ;
const DbFileConfig * config = GetDbFileConfigBuildPath ( path , sizeof ( path ) , type , emunand ) ;
if ( ! config )
return 1 ;
u64 reqsize = BuildDiffCalcRequiredSize ( config - > full_data_size , false , true ) ;
if ( ! CheckWritePermissions ( path ) )
return 1 ;
// ensure remounting the old mount path
char path_store [ 256 ] = { 0 } ;
char * path_bak = NULL ;
strncpy ( path_store , GetMountPath ( ) , 256 ) ;
if ( * path_store ) path_bak = path_store ;
// unmount any mounted image since we're about to create a DIFF that will need to be mounted
InitImgFS ( NULL ) ;
// create the raw file
if ( fvx_qcreate ( path , reqsize ) ! = FR_OK ) {
InitImgFS ( path_bak ) ;
return 1 ;
}
// create the DIFF container and mount it
if ( CreateDiff ( path , config - > full_data_size , false , true , NULL ) ! = 0 | |
! InitImgFS ( path ) ) {
InitImgFS ( path_bak ) ;
fvx_unlink ( path ) ;
return 1 ;
}
char db_hdr [ 0x80 ] ;
memset ( db_hdr , 0 , sizeof ( db_hdr ) ) ;
u32 version_offset = align_pow2 ( strlen ( config - > magic ) , 4 ) ;
memcpy ( db_hdr , config - > magic , strlen ( config - > magic ) ) ;
memcpy ( & db_hdr [ version_offset ] , & config - > version , 4 ) ;
UINT written = 0 ;
// write the BDRI filesystem image
if ( fvx_qwrite ( " D:/partitionA.bin " , db_hdr , 0 , config - > image_offset , & written ) ! = 0 | |
CreateBDRI ( " D:/partitionA.bin " , config - > image_offset , config - > image_size , config - > image_blocksize , config - > num_entries ) ! = 0 ) {
InitImgFS ( path_bak ) ;
fvx_unlink ( path ) ;
return 1 ;
}
// remount previously mounted image, if there was one
InitImgFS ( path_bak ) ;
// make sure CMAC is fixed
if ( FixFileCmac ( path , true ) ! = 0 ) {
fvx_unlink ( path ) ;
return 1 ;
}
return 0 ;
}
u32 CreateDbFilesForDrive ( const char * destdrv , bool silent , bool force_overwrite ) {
static const DbType db_types_nand [ 5 ] = { NAND_TICKET_DB , NAND_IMPORT_DB , NAND_TITLE_DB , NAND_TMP_I_DB , NAND_TMP_T_DB } ;
static const DbType db_types_sdmc [ 2 ] = { SDMC_TITLE_DB , SDMC_IMPORT_DB } ;
if ( * destdrv ! = ' A ' & & * destdrv ! = ' B ' & & * destdrv ! = ' 1 ' & & * destdrv ! = ' 4 ' ) {
return 1 ;
}
bool emunand = * destdrv = = ' B ' /* EmuNAND SD */ | | * destdrv = = ' 4 ' /* EmuNAND CTRNAND */ ;
bool sd = * destdrv = = ' A ' | | * destdrv = = ' B ' /* Sys/EmuNAND SD */ ;
const DbType * dbs_to_check = sd ? db_types_sdmc : db_types_nand ;
u32 db_count = sd ? countof ( db_types_sdmc ) : countof ( db_types_nand ) ;
u32 num_to_create = db_count ;
u32 num_already_exist = 0 ;
u32 num_failed = 0 ;
u32 num_created_ok = 0 ;
char db_path [ 256 ] = { 0 } ;
const DbFileConfig * config = NULL ;
for ( u32 i = 0 ; i < db_count ; i + + ) {
if ( ! ( config = GetDbFileConfigBuildPath ( db_path , sizeof ( db_path ) , dbs_to_check [ i ] , emunand ) ) ) {
return 1 ;
}
if ( fvx_stat ( db_path , NULL ) = = FR_OK ) {
if ( ! force_overwrite & & fvx_qsize ( db_path ) = = BuildDiffCalcRequiredSize ( config - > full_data_size , false , true ) ) {
+ + num_already_exist ;
- - num_to_create ;
continue ;
}
}
if ( CreateDbFile ( dbs_to_check [ i ] , emunand ) = = 0 ) {
+ + num_created_ok ;
} else {
+ + num_failed ;
if ( ! silent ) {
ShowPrompt ( false , STR_PATH_CREATE_DB_FILE_FAILED , db_path ) ;
}
}
}
if ( ! silent ) {
if ( num_already_exist ) {
if ( num_already_exist = = db_count ) {
ShowPrompt ( false , " %s " , STR_NO_MISSING_DB_FILES_DETECTED ) ;
} else {
ShowPrompt ( false , STR_CREATE_DB_FILES_N_N_N_OK_FAILED_ALREADY_EXIST , num_created_ok , num_failed , num_already_exist ) ;
}
} else {
if ( num_created_ok = = db_count ) {
ShowPrompt ( false , " %s " , STR_SUCCESSFULLY_CREATED_DB_FILES_FOR_DRIVE ) ;
} else {
ShowPrompt ( false , STR_CREATE_DB_FILES_N_N_OK_FAILED , num_created_ok , num_failed ) ;
}
}
}
return num_to_create = = num_created_ok ? 0 : 1 ;
}