/*

* 0. Make sure your savefiles are in the correct layout under sdcard:/Saves/YOUR_GAME_TID_HERE/

* 1. Boot game

* 2. Wait a second or so (for the game tid to register but before the code mounts the savedata)

* 3. Home button to pause game

* 4. Run this script

* 5. Look at debug log output to see if it restored everything correctly and then commited

*/

var tid = '0000000000000000' ; //Change Title ID here

var commitAfterEveryFile = false ; //set this to true if you get strange errors after some amount of files written (needed for BotW and possibly other games which have lots of large save files)

if ( IFileSystem. prototype . OpenDir === undefined || IFileSystem. prototype . CreateDir === undefined || IFileSystem. prototype . DeleteDir === undefined ||

IFileSystem. prototype . DeleteDir === undefined || IFileSystem. prototype . Commit === undefined )

{

throw new Error ( "Missing IFileSystem functions, please update your PegaSwitch from git!" ) ;

}

IFile. prototype . SetSize = function ( size ) {

return this . sc . ipcMsg ( 3 ) . datau64 ( size ) . sendTo ( this . handle ) . asResult ( ) ;

} ;

foreachDirEntry = function ( dir_obj , callback )

{

var entryCount = utils. trunc32 ( dir_obj. GetEntryCount ( ) . assertOk ( ) ) ;

if ( entryCount > 0 && callback !== undefined )

{

var entryBuf = new Uint32Array ( 0x310 * entryCount ) ;

dir_obj. GetEntries ( entryBuf , entryCount ) . assertOk ( ) ;

for ( var entryIdx = 0 ; entryIdx < entryCount ; entryIdx ++ )

{

var fn = utils. u8a2str ( new Uint8Array ( entryBuf. buffer , 0x310 * entryIdx , 0x300 ) ) ;

for ( var strIdx = 0 ; strIdx < fn. length ; strIdx ++ )

{

if ( fn. charCodeAt ( strIdx ) === 0 )

{

fn = fn. substring ( 0 , strIdx ) ;

break ;

}

}

var eType = entryBuf [ ( 0x310 * entryIdx + 0x304 ) >> 2 ] ;

callback ( fn , eType ) ;

}

}

return entryCount ;

} ;

copyFiles = function ( src_dir , dst_dir )

{

var thefiles = [ ] ;

foreachDirEntry ( src_dir , function ( fn , eType )

{

if ( eType === 1 ) { thefiles. push ( fn ) ; }

} ) ;

//dont need handle to dst_dir anymore (and it will interfere with Commit ops)

var dst_dir_fs = dst_dir. fs ;

var dst_dir_path = dst_dir. path ;

dst_dir. Close ( ) ; dst_dir = null ;

var size = [ ] ;

var buf = null ;

for ( var fileIdx = 0 ; fileIdx < thefiles. length ; fileIdx ++ )

{

var fn = thefiles [ fileIdx ] ;

utils. log ( '[Opening existing file] ' + src_dir. path + fn ) ;

var f_src = src_dir. fs . OpenFile ( src_dir. path + fn ) . assertOk ( ) ;

var f_dst = null ;

try

{

size = f_src. GetSize ( ) . assertOk ( ) ;

buf = f_src. Read ( new ArrayBuffer ( utils. trunc32 ( size ) ) , 0 , size ) . assertOk ( ) ;

f_src. Close ( ) ; f_src = null ;

utils. log ( '[Read file complete] ' + src_dir. path + fn + ' = ' + size + ' bytes' ) ;

//try open existing file

f_dst = dst_dir_fs. OpenFile ( dst_dir_path + fn ) ;

if ( f_dst. isOk ) //file exists, must be deleted

{

f_dst = f_dst. assertOk ( ) ;

f_dst. Close ( ) ; f_dst = null ;

utils. log ( '[Deleting existing file] ' + dst_dir_path + fn ) ;

dst_dir_fs. DeleteFile ( dst_dir_path + fn ) . assertOk ( ) ;

}

utils. log ( '[Creating new file] ' + dst_dir_path + fn + ' with size ' + size + ' bytes' ) ;

dst_dir_fs. CreateFile ( dst_dir_path + fn , size ) . assertOk ( ) ;

utils. log ( '[Opening new file] ' + dst_dir_path + fn ) ;

f_dst = dst_dir_fs. OpenFile ( dst_dir_path + fn ) . assertOk ( ) ;

utils. log ( '[Open file OK] ' + dst_dir_path + fn ) ;

f_dst. Write ( 0 , buf , size ) . assertOk ( ) ;

utils. log ( '[Write file OK] ' + dst_dir_path + fn + ' -> ' + size + ' bytes' ) ;

buf = null ;

f_dst. Close ( ) ; f_dst = null ;

utils. log ( '[Close file OK] ' + dst_dir_path + fn ) ;

if ( commitAfterEveryFile )

{

utils. log ( '[Commiting to fs ' + dst_dir_fs. partition + ' handle ' + dst_dir_fs. handle + '] ' + dst_dir_path + fn ) ;

dst_dir_fs. Commit ( ) . assertOk ( ) ;

utils. log ( '[Commit OK] ' + dst_dir_path + fn ) ;

}

}

finally

{

if ( f_dst !== null && f_dst instanceof IFile ) { f_dst. Close ( ) ; f_dst = null ; }

if ( f_src !== null ) { f_src. Close ( ) ; f_src = null ; }

}

}

return dst_dir ;

} ;

evaluateDirs = function ( fs_obj , root_dir )

{

var folders = [ ] ;

var dir_obj = fs_obj. OpenDir ( root_dir , 1 ) ; //directories only

if ( ! dir_obj. isOk ) { return folders ; } //no dirs

else { dir_obj = dir_obj. assertOk ( ) ; }

folders. push ( dir_obj. path ) ; //this dir

for ( var evalIdx = 0 ; evalIdx < folders. length ; evalIdx ++ )

{

if ( evalIdx !== 0 )

{

dir_obj = fs_obj. OpenDir ( folders [ evalIdx ] , 1 ) ;

if ( ! dir_obj. isOk ) { throw new Error ( 'Unable to open dir ' + folders [ evalIdx ] + ' for listing' ) ; }

else { dir_obj = dir_obj. assertOk ( ) ; }

}

foreachDirEntry ( dir_obj , function ( fn ) { folders. push ( dir_obj. path + fn + '/' ) ; } ) ;

dir_obj. Close ( ) ; dir_obj = null ;

}

return folders ;

} ;

utils. log ( "stage1, hijack fsppr and set perms" ) ;

if ( SploitCore. prototype . escalateFilesystemPrivileges === undefined )

{

throw new Error ( "Missing escalateFilesystemPrivileges function, please update your PegaSwitch from git!" ) ;

}

sc. escalateFilesystemPrivileges ( ) ;

utils. log ( "GetLastUserProfile" ) ;

var userID = sc. ipcMsg ( 4 ) . sendTo ( 'acc:u1' ) . assertOk ( ) . data ;

sc. getService ( 'fsp-srv' , ( fsp_handle ) =>

{

utils. log ( 'initialized fsp-srv handle: 0x' + fsp_handle. toString ( 16 ) ) ;

sc. ipcMsg ( 1 ) . sendPid ( ) . datau64 ( 0 ) . sendTo ( fsp_handle ) . assertOk ( ) ;

utils. log ( "stage2, open save data" ) ;

mountSaveData = function ( userID , tid ) {

var self = this ;

return sc. ipcMsg ( 51 ) . datau64 ( 1 , utils. parseAddr ( tid ) , [ userID [ 0 ] , userID [ 1 ] ] , [ userID [ 2 ] , userID [ 3 ] ] , [ 0 , 0 ] , 1 , 0 , 0 , 0 ) . sendTo ( fsp_handle ) . asResult ( )

. map ( ( r ) => new self. sc . IFileSystem ( self. sc , r. movedHandles [ 0 ] ) ) ;

} ;

mountSDCard = function ( ) {

var self = this ;

return sc. ipcMsg ( 18 ) . sendTo ( fsp_handle ) . asResult ( )

. map ( ( r ) => new self. sc . IFileSystem ( self. sc , r. movedHandles [ 0 ] ) ) ;

} ;

utils. log ( 'MountSaveData' ) ;

var fs_sd = null ;

var fs_user = mountSaveData ( userID , tid ) . assertOk ( ) ;

fs_user. partition = "SAVEDATA" ;

utils. log ( 'SAVEDATA handle is ' + fs_user. handle ) ;

try {

utils. log ( "Mounting SD card" ) ;

try { fs_sd = mountSDCard ( ) . assertOk ( ) ; }

catch ( e ) { throw new Error ( "Failed to open SD card. Is it inserted?" ) ; }

fs_sd. partition = "SDCARD" ;

utils. log ( 'SDCARD handle is ' + fs_sd. handle ) ;

var sd_folder_root = '/Saves/' + tid + '/' ;

utils. log ( '[Opening SD dir for listing] ' + sd_folder_root ) ;

var dirList = evaluateDirs ( fs_sd , sd_folder_root ) ;

if ( dirList. length < 1 )

{

fs_sd. Close ( ) ; fs_sd = null ;

fs_user. Close ( ) ; fs_user = null ;

throw new Error ( "Unable to open SD saves dir " + sd_folder_root + ' or its empty' ) ;

}

utils. log ( '[Opening SAVEDATA dir for listing]' ) ;

var save_root_dir = fs_user. OpenDir ( '/' , 1 ) . assertOk ( ) ; //directories only

var saveEntries = [ ] ;

foreachDirEntry ( save_root_dir , function ( fn ) { saveEntries. push ( save_root_dir. path + fn + '/' ) ; } ) ;

save_root_dir. Close ( ) ; save_root_dir = null ;

for ( var saveDirIdx = 0 ; saveDirIdx < saveEntries. length ; saveDirIdx ++ )

{

var currSaveDir = saveEntries [ saveDirIdx ] ;

utils. log ( '[Deleting SAVEDATA dir] ' + currSaveDir )

fs_user. DeleteDir ( currSaveDir , true ) . assertOk ( ) ;

utils. log ( '[SAVEDATA dir deleted] ' + currSaveDir )

}

save_root_dir = fs_user. OpenDir ( '/' , 2 ) . assertOk ( ) ; //files only

saveEntries = [ ] ;

foreachDirEntry ( save_root_dir , function ( fn ) { saveEntries. push ( save_root_dir. path + fn ) ; } ) ;

save_root_dir. Close ( ) ; save_root_dir = null ;

for ( var saveFnIdx = 0 ; saveFnIdx < saveEntries. length ; saveFnIdx ++ )

{

var currSaveFile = saveEntries [ saveFnIdx ] ;

utils. log ( '[Deleting SAVEDATA file] ' + currSaveFile )

fs_user. DeleteFile ( currSaveFile ) . assertOk ( ) ;

utils. log ( '[SAVEDATA file deleted] ' + currSaveFile )

}

saveEntries = [ ] ;

for ( var dirIndex = 0 ; dirIndex < dirList. length ; dirIndex ++ )

{

var left_side = dirList [ dirIndex ] ;

var right_side = '/' + dirList [ dirIndex ] . substring ( sd_folder_root. length ) ;

utils. log ( '[Opening folder on SD] ' + left_side ) ;

var dir_sd = fs_sd. OpenDir ( left_side , 2 ) . assertOk ( ) ;

utils. log ( '[Opening folder in savedata] ' + right_side ) ;

var dir_data = fs_user. OpenDir ( right_side , 2 ) ;

if ( ! dir_data. isOk )

{

utils. log ( '[Making folder in savedata] ' + right_side ) ;

fs_user. CreateDir ( right_side ) . assertOk ( ) ;

utils. log ( '[Opening newly made folder] ' + right_side ) ;

dir_data = fs_user. OpenDir ( right_side , 2 ) . assertOk ( ) ;

utils. log ( '[New folder opened OK] ' + right_side ) ;

}

else

{

dir_data = dir_data. assertOk ( ) ;

utils. log ( '[Existing folder opened OK] ' + right_side ) ;

}

try

{

dir_data = copyFiles ( dir_sd , dir_data ) ; //because we might change dst_dir inside

}

finally

{

if ( dir_data !== null ) { dir_data. Close ( ) ; dir_data = null ; }

if ( dir_sd !== null ) { dir_sd. Close ( ) ; dir_sd = null ; }

}

}

utils. log ( '[Commiting savedata changes]' ) ;

fs_user. Commit ( ) . assertOk ( ) ;

utils. log ( '[Closing file systems]' ) ;

fs_user. Close ( ) ; fs_user = null ;

fs_sd. Close ( ) ; fs_sd = null ;

}

finally

{

if ( fs_user !== null ) { fs_user. Close ( ) ; fs_user = null ; }

if ( fs_sd !== null ) { fs_sd. Close ( ) ; fs_sd = null ; }

}