D
Deleted-442439
Guest
OP
It is now possible to backup and and restore save data from the SD card via Pegaswitch. This is only possible on 1.0.0-3.0.0.
Backup script:
Restore script:
Backup script:
Code:
unction GetUsers() {
return sc.getService("acc:u0", (acc) => {
var numAccounts = sc.ipcMsg(0).sendTo(acc).assertOk().dataBuffer[0];
var obuf = new Uint32Array(4*8);
sc.ipcMsg(2).datau32().cDescriptor(obuf, obuf.length * 4, true).sendTo(acc).assertOk();
var ids = [];
for(var i = 0; i < numAccounts; i++) {
ids[i] = [obuf[i*4+0], obuf[i*4+1], obuf[i*4+2], obuf[i*4+3]];
}
return ids;
});
}
function GetProfile(id) {
return sc.getService("acc:u0", (acc) => {
return sc.withHandle(sc.ipcMsg(5).datau32(id[0], id[1], id[2], id[3]).sendTo(acc).assertOk().movedHandles[0], (iProfile) => {
var userData = new Uint8Array(0x80);
var profileBase = new Uint32Array(
sc.ipcMsg(0).cDescriptor(userData).sendTo(iProfile).assertOk().data);
var imageSize = sc.ipcMsg(10).sendTo(iProfile).assertOk().data[0];
var image = new Uint8Array(imageSize);
sc.ipcMsg(11).bDescriptor(image, image.byteLength, 0).sendTo(iProfile).assertOk();
return {
name: utils.u8a2nullstr(new Uint8Array(profileBase.buffer, 0x18, 10)),
imageData: image
};
});
});
}
function GetIApplicationManager(cb) {
if(sc.hasService("ns:am")) {
return sc.getService("ns:am", cb);
} else {
return sc.getService("ns:am2", (ns) => {
return sc.ipcMsg(7996).sendTo(ns).assertOk().withHandles((r, m, c) => { // GetIApplicationManager
return cb(m[0]);
});
});
}
}
function GetTitleIds() {
return GetIApplicationManager((iam) => {
var buf = new Uint32Array(0x18*32); // 32 games max
var count = sc.ipcMsg(0).datau32(0).bDescriptor(buf).sendTo(iam).assertOk().data[0];
var tids = [];
for(var i = 0; i < count; i++) {
tids[i] = [buf[6*i+0], buf[6*i+1]];
}
return tids;
});
}
sc.enableTurbo();
sc.escalateFilesystemAccess();
var date = new Date();
var prefix = "saves-" + date.getFullYear() + "-" + (date.getMonth()+1) + "-" + date.getDate() + "-" + date.getHours() + "-" + date.getMinutes();
var tids = GetTitleIds();
sc.getService("fsp-srv", (serv) => {
GetUsers().forEach((userId) => {
var profile = GetProfile(userId);
utils.log("dump saves for " + profile.name);
sc.ipcMsg(1).sendPid().data(0).sendTo(serv);
tids.forEach((tid) => {
utils.log("dump saves for " + profile.name + " game " + utils.paddr(tid));
var msg = sc.ipcMsg(51).datau64(0x1, utils.pad64(tid), [userId[0], userId[1]], [userId[2], userId[3]], [0x0, 0x0], 0x1, 0x0, 0x0, 0x0).sendTo(serv);
if(!msg.success) {
utils.log("failed to mount save data for " + profile.name + " game " + utils.paddr(tid) + ": 0x" + msg.resultCode.toString(16));
return;
}
sc.withHandle(msg.movedHandles[0], (h) => {
var fs = new sc.IFileSystem(sc, h);
try {
var curDir = fs.OpenDir("/").assertOk();
try {
curDir.DirDump(prefix + "/" + profile.name + "/" + utils.paddr(tid) + "/");
} finally {
curDir.Close();
}
} finally {
fs.Close();
}
utils.log("finished dumping saves for " + profile.name + " game " + utils.paddr(tid));
});
});
utils.log("finished dumping saves for " + profile.name);
});
});
Restore script:
Code:
/*
* 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 = '00000000000000000'; //Change Title ID here
var commitAfterEveryFile = false; //set this to true if you get strange errors after some amount of files written (BoTW large saves trigger this)
IFileSystem.prototype.OpenDir = function (dir, flags) {
if (flags === undefined) { flags = 3; }
var path = utils.str2ab(dir);
var self = this;
return this.sc.ipcMsg(9).datau64(flags).xDescriptor(path, path.byteLength, 0).sendTo(this.handle).asResult()
.map((r) => new self.sc.IDirectory(self.sc, dir, r.movedHandles[0], self));
};
IFileSystem.prototype.CreateDir = function (dir) {
var path = utils.str2ab(dir);
return this.sc.ipcMsg(2).xDescriptor(path, path.byteLength, 0).sendTo(this.handle).asResult();
};
IFileSystem.prototype.DeleteDir = function (dir, recursive) {
if (recursive === undefined) { recursive = false; }
var path = utils.str2ab(dir);
return this.sc.ipcMsg(recursive ? 4 : 3).xDescriptor(path, path.byteLength, 0).sendTo(this.handle).asResult();
};
IFileSystem.prototype.Commit = function () {
return sc.ipcMsg(10).sendTo(this.handle).asResult();
};
IFile.prototype.SetSize = function (size) {
return this.sc.ipcMsg(3).datau64(size).sendTo(this.handle).asResult();
};
utils.log("stage1, hijack fsppr and set perms");
sc.getFSPPR = function () {
if (sc.closed_pr !== undefined) {
return;
}
sc.enableTurbo();
var i = 0;
var FspPr_Handle = null;
while (true) {
sc.ipcMsg(2).setType(5).sendTo('pm:shell');
var FspPr = sc.getService('fsp-pr');
if(FspPr.isOk) {
FspPr_Handle = FspPr.getValue();
break;
}
i++;
}
utils.log('Got fsp-pr handle after ' + i + ' iterations: ');
utils.log('fsp-pr handle: 0x' + FspPr_Handle.toString(16));
sc.svcCloseHandle(FspPr_Handle).assertOk();
sc.closed_pr = true;
};
sc.getFSPPR();
sc.ipcMsg(1).sendPid().data(0).sendTo('fsp-srv').assertOk();
var pid = sc.read4(sc.ipcBufAddr, 0xC >> 2);
utils.log('Got process PID: '+pid.toString(16));
var buf1_sz = 0x1C;
var buf2_sz = 0x2C;
var buf = sc.malloc(buf1_sz + buf2_sz);
var buf2 = utils.add2(buf, buf1_sz);
//buffer init
sc.write4(1, buf, 0x0>>2);
sc.write8([0xFFFFFFFF, 0xFFFFFFFF], buf, 0x4 >> 2); //This is the permissions value.
sc.write4(buf1_sz, buf, 0xC >> 2);
sc.write4(buf1_sz, buf, 0x14 >> 2);
sc.write4(1, buf2, 0x0 >> 2);
sc.write8([0xFFFFFFFF, 0xFFFFFFFF], buf2, 0x4 >> 2); //This is the permissions value -- actual perms = buf2_val & buf1_val
sc.write4(0xFFFFFFFF, buf2, 0x14 >> 2);
sc.write4(0xFFFFFFFF, buf2, 0x18 >> 2);
sc.write4(0xFFFFFFFF, buf2, 0x24 >> 2);
sc.write4(0xFFFFFFFF, buf2, 0x28 >> 2);
sc.ipcMsg(256).data(0).sendTo('fsp-pr').assertOk().show();
sc.ipcMsg(1).data(pid).sendTo('fsp-pr').assertOk().show();
sc.ipcMsg(0).data(2, [pid,0], utils.parseAddr(tid), buf1_sz, buf2_sz, pid, pid, 0, 0, 0, 0, 0).aDescriptor(buf, buf1_sz).aDescriptor(buf2, buf2_sz).sendTo('fsp-pr').assertOk().show();
sc.free(buf);
sc.free(buf2);
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(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("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-srv').asResult()
.map((r) => new self.sc.IFileSystem(self.sc, r.movedHandles[0]));
};
mountSDCard = function () {
var self = this;
return sc.ipcMsg(18).sendTo('fsp-srv').asResult()
.map((r) => new self.sc.IFileSystem(self.sc, r.movedHandles[0]));
};
utils.log("GetLastUserProfile");
var userID = sc.ipcMsg(4).sendTo('acc:u1').assertOk().data;
utils.log('MountSaveData');
var fs_sd = null;
var fs_data = mountSaveData(userID, tid).assertOk();
fs_data.partition = "SAVEDATA";
utils.log('SAVEDATA handle is ' + fs_data.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_data.Close(); fs_data = 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_data.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_data.DeleteDir(currSaveDir, true).assertOk();
utils.log('[SAVEDATA dir deleted] ' + currSaveDir)
}
save_root_dir = fs_data.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_data.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_data.OpenDir(right_side, 2);
if (!dir_data.isOk)
{
utils.log('[Making folder in savedata] '+ right_side);
fs_data.CreateDir(right_side).assertOk();
utils.log('[Opening newly made folder] ' + right_side);
dir_data = fs_data.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_data.Commit().assertOk();
utils.log('[Closing file systems]');
fs_data.Close(); fs_data = null;
fs_sd.Close(); fs_sd = null;
}
finally
{
if (fs_data !== null) { fs_data.Close(); fs_data = null; }
if (fs_sd !== null) { fs_sd.Close(); fs_sd = null; }
}