Add DS save support.

This commit is contained in:
Steven Smith 2016-06-03 15:57:18 -07:00
parent 8583d62ec3
commit abf82057ef
20 changed files with 895 additions and 6 deletions

View File

@ -2,8 +2,12 @@
FBI is an open source title manager for the 3DS.
Banner credit: [OctopusRift](http://gbatemp.net/members/octopusrift.356526/), [Apache Thunder](https://gbatemp.net/members/apache-thunder.105648/)
Download: https://github.com/Steveice10/FBI/releases
Requires [devkitARM](http://sourceforge.net/projects/devkitpro/files/devkitARM/) and [citro3d](https://github.com/fincs/citro3d) to build.
# Credit
Banner: [OctopusRift](http://gbatemp.net/members/octopusrift.356526/), [Apache Thunder](https://gbatemp.net/members/apache-thunder.105648/)
SPI Protocol Information: [TuxSH](https://github.com/TuxSH/) ([TWLSaveTool](https://github.com/TuxSH/TWLSaveTool))

463
source/core/spi.c Normal file
View File

@ -0,0 +1,463 @@
#include <malloc.h>
#include <string.h>
#include <3ds.h>
#include "spi.h"
#include "../ui/error.h"
#define SPI_CMD_RDSR 5
#define SPI_CMD_WREN 6
#define SPI_CMD_RDID 0x9F
#define SPI_EEPROM_512B_CMD_WRLO 2
#define SPI_EEPROM_512B_CMD_RDLO 3
#define SPI_EEPROM_512B_CMD_WRHI 10
#define SPI_EEPROM_512B_CMD_RDHI 11
#define SPI_EEPROM_CMD_WRITE 2
#define SPI_EEPROM_CMD_READ 3
#define SPI_FLASH_CMD_READ 3
#define SPI_FLASH_CMD_PW 10
#define SPI_STAT_WIP 1
#define SPI_STAT_WEL 2
typedef enum {
CHIP_NONE = 0,
CHIP_EEPROM_512B = 1,
CHIP_EEPROM_8KB = 2,
CHIP_EEPROM_64KB = 3,
CHIP_EEPROM_128KB = 4,
CHIP_FLASH_256KB = 5,
CHIP_FLASH_512KB = 6,
CHIP_FLASH_1MB = 7,
CHIP_FLASH_8MB = 8,
CHIP_FLASH_256KB_INFRARED = 9,
CHIP_FLASH_512KB_INFRARED = 10,
CHIP_FLASH_1MB_INFRARED = 11,
CHIP_FLASH_8MB_INFRARED = 12
} SaveChip;
static Result spi_get_page_size(SaveChip chip, u32* pageSize) {
Result res = 0;
u32 size = 0;
switch(chip) {
case CHIP_EEPROM_512B:
size = 16;
break;
case CHIP_EEPROM_8KB:
size = 32;
break;
case CHIP_EEPROM_64KB:
size = 128;
break;
case CHIP_EEPROM_128KB:
case CHIP_FLASH_256KB:
case CHIP_FLASH_512KB:
case CHIP_FLASH_1MB:
case CHIP_FLASH_8MB:
case CHIP_FLASH_256KB_INFRARED:
case CHIP_FLASH_512KB_INFRARED:
case CHIP_FLASH_1MB_INFRARED:
case CHIP_FLASH_8MB_INFRARED:
size = 256;
break;
default:
res = R_FBI_UNSUPPORTED_OPERATION;
break;
}
if(R_SUCCEEDED(res) && pageSize != NULL) {
*pageSize = size;
}
return res;
}
static Result spi_get_capacity(SaveChip chip, u32* capacity) {
Result res = 0;
u32 cap = 0;
switch(chip) {
case CHIP_EEPROM_512B:
cap = 512;
break;
case CHIP_EEPROM_8KB:
cap = 8 * 1024;
break;
case CHIP_EEPROM_64KB:
cap = 64 * 1024;
break;
case CHIP_EEPROM_128KB:
cap = 128 * 1024;
break;
case CHIP_FLASH_256KB:
case CHIP_FLASH_256KB_INFRARED:
cap = 256 * 1024;
break;
case CHIP_FLASH_512KB:
case CHIP_FLASH_512KB_INFRARED:
cap = 512 * 1024;
break;
case CHIP_FLASH_1MB:
case CHIP_FLASH_1MB_INFRARED:
cap = 1024 * 1024;
break;
case CHIP_FLASH_8MB:
case CHIP_FLASH_8MB_INFRARED:
cap = 8 * 1024 * 1024;
break;
default:
res = R_FBI_UNSUPPORTED_OPERATION;
break;
}
if(R_SUCCEEDED(res) && capacity != NULL) {
*capacity = cap;
}
return res;
}
static Result spi_execute_command(SaveChip chip, void* cmd, u32 cmdSize, void* answer, u32 answerSize, void* data, u32 dataSize) {
if(chip == CHIP_NONE) {
return R_FBI_UNSUPPORTED_OPERATION;
}
bool infrared = chip == CHIP_FLASH_256KB_INFRARED || chip == CHIP_FLASH_512KB_INFRARED || chip == CHIP_FLASH_1MB_INFRARED || chip == CHIP_FLASH_8MB_INFRARED;
u8 transferOp = pxiDevMakeTransferOption(BAUDRATE_4MHZ, BUSMODE_1BIT);
u64 waitOp = pxiDevMakeWaitOperation(WAIT_NONE, DEASSERT_NONE, 0);
u8 dummy = 0;
PXIDEV_SPIBuffer header = {infrared ? &dummy : NULL, infrared ? 1 : 0, infrared ? pxiDevMakeTransferOption(BAUDRATE_1MHZ, BUSMODE_1BIT) : transferOp, waitOp};
PXIDEV_SPIBuffer writeBuffer1 = {cmd, cmdSize, transferOp, waitOp};
PXIDEV_SPIBuffer readBuffer1 = {answer, answerSize, transferOp, waitOp};
PXIDEV_SPIBuffer writeBuffer2 = {data, dataSize, transferOp, waitOp};
PXIDEV_SPIBuffer readBuffer2 = {NULL, 0, transferOp, waitOp};
PXIDEV_SPIBuffer footer = {NULL, 0, transferOp, waitOp};
return PXIDEV_SPIMultiWriteRead(&header, &writeBuffer1, &readBuffer1, &writeBuffer2, &readBuffer2, &footer);
}
static Result spi_wait_write_finish(SaveChip chip) {
Result res = 0;
u8 cmd = SPI_CMD_RDSR;
u8 status = 0;
while(R_SUCCEEDED(res = spi_execute_command(chip, &cmd, sizeof(cmd), &status, sizeof(status), NULL, 0)) && (status & SPI_STAT_WIP));
return res;
}
static Result spi_read_jedec_id_status(SaveChip chip, u32* jecedId, u8* status) {
Result res = 0;
u8 cmd = SPI_CMD_RDID;
u8 idData[3] = {0};
if(R_SUCCEEDED(res = spi_wait_write_finish(chip)) && R_SUCCEEDED(res = spi_execute_command(chip, &cmd, sizeof(cmd), idData, sizeof(idData), NULL, 0))) {
cmd = SPI_CMD_RDSR;
u8 stat = 0;
if(R_SUCCEEDED(res = spi_execute_command(chip, &cmd, 1, &stat, 1, 0, 0))) {
if(jecedId != NULL) {
*jecedId = (idData[0] << 16) | (idData[1] << 8) | idData[2];
}
if(status != NULL) {
*status = stat;
}
}
}
return res;
}
static Result spi_read_data(SaveChip chip, u32* bytesRead, void* data, u32 offset, u32 size) {
Result res = 0;
u32 capacity = 0;
if(R_SUCCEEDED(res = spi_get_capacity(chip, &capacity))) {
if(size > capacity - offset) {
size = capacity - offset;
}
u32 pos = offset;
if(size > 0 && R_SUCCEEDED(res = spi_wait_write_finish(chip))) {
u8 cmd[4] = {0};
u32 cmdSize = 0;
switch(chip) {
case CHIP_EEPROM_512B:
if(pos < 0x100) {
u32 len = size > 0x100 - pos ? 0x100 - pos : size;
cmdSize = 2;
cmd[0] = SPI_EEPROM_512B_CMD_RDLO;
cmd[1] = (u8) pos;
res = spi_execute_command(chip, cmd, cmdSize, data, len, NULL, 0);
pos += len;
data = (u8*) data + len;
size = size - len;
}
if(R_SUCCEEDED(res) && pos >= 0x100 && size > 0) {
u32 len = size > 0x200 - pos ? 0x200 - pos : size;
cmdSize = 2;
cmd[0] = SPI_EEPROM_512B_CMD_RDHI;
cmd[1] = (u8) pos;
res = spi_execute_command(chip, cmd, cmdSize, data, len, NULL, 0);
pos += len;
}
break;
case CHIP_EEPROM_8KB:
case CHIP_EEPROM_64KB:
case CHIP_EEPROM_128KB:
cmdSize = 3;
cmd[0] = SPI_EEPROM_CMD_READ;
cmd[1] = (u8) (pos >> 8);
cmd[2] = (u8) pos;
res = spi_execute_command(chip, cmd, cmdSize, data, size, NULL, 0);
pos += size;
break;
case CHIP_FLASH_256KB:
case CHIP_FLASH_512KB:
case CHIP_FLASH_1MB:
case CHIP_FLASH_8MB:
case CHIP_FLASH_256KB_INFRARED:
case CHIP_FLASH_512KB_INFRARED:
case CHIP_FLASH_1MB_INFRARED:
case CHIP_FLASH_8MB_INFRARED:
cmdSize = 4;
cmd[0] = SPI_FLASH_CMD_READ;
cmd[1] = (u8) (pos >> 16);
cmd[2] = (u8) (pos >> 8);
cmd[3] = (u8) pos;
res = spi_execute_command(chip, cmd, cmdSize, data, size, NULL, 0);
pos += size;
break;
default:
res = R_FBI_UNSUPPORTED_OPERATION;
break;
}
}
if(R_SUCCEEDED(res) && bytesRead != NULL) {
*bytesRead = pos - offset;
}
}
return res;
}
static Result spi_write_data(SaveChip chip, u32* bytesWritten, void* data, u32 offset, u32 size) {
Result res = 0;
u32 pageSize = 0;
u32 capacity = 0;
if(R_SUCCEEDED(res = spi_get_page_size(chip, &pageSize)) && R_SUCCEEDED(res = spi_get_capacity(chip, &capacity))) {
if(size > capacity - offset) {
size = capacity - offset;
}
u32 pos = offset;
if(size > 0 && R_SUCCEEDED(res = spi_wait_write_finish(chip))) {
while(pos < offset + size) {
u8 cmd[4] = {0};
u32 cmdSize = 0;
switch(chip) {
case CHIP_EEPROM_512B:
cmdSize = 2;
cmd[0] = (pos >= 0x100) ? (u8) SPI_EEPROM_512B_CMD_WRHI : (u8) SPI_EEPROM_512B_CMD_WRLO;
cmd[1] = (u8) pos;
break;
case CHIP_EEPROM_8KB:
case CHIP_EEPROM_64KB:
case CHIP_EEPROM_128KB:
cmdSize = 3;
cmd[0] = SPI_EEPROM_CMD_WRITE;
cmd[1] = (u8) (pos >> 8);
cmd[2] = (u8) pos;
break;
case CHIP_FLASH_256KB:
case CHIP_FLASH_512KB:
case CHIP_FLASH_1MB:
case CHIP_FLASH_256KB_INFRARED:
case CHIP_FLASH_512KB_INFRARED:
case CHIP_FLASH_1MB_INFRARED:
cmdSize = 4;
cmd[0] = SPI_FLASH_CMD_PW;
cmd[1] = (u8) (pos >> 16);
cmd[2] = (u8) (pos >> 8);
cmd[3] = (u8) pos;
break;
case CHIP_FLASH_8MB:
case CHIP_FLASH_8MB_INFRARED:
// TODO
default:
res = R_FBI_UNSUPPORTED_OPERATION;
break;
}
if(R_FAILED(res)) {
break;
}
u32 pagePos = pos & ~(pageSize - 1);
u32 currSize = size - (pos - offset);
if(currSize > pageSize - (pos - pagePos)) {
currSize = pageSize - (pos - pagePos);
}
u8 ewCmd = SPI_CMD_WREN;
if(R_SUCCEEDED(res = spi_execute_command(chip, &ewCmd, sizeof(ewCmd), NULL, 0, NULL, 0))) {
if(chip != CHIP_EEPROM_512B) {
ewCmd = SPI_CMD_RDSR;
u8 status = 0;
while(R_SUCCEEDED(res = spi_execute_command(chip, &ewCmd, sizeof(ewCmd), &status, sizeof(status), NULL, 0)) && (status & ~SPI_STAT_WEL));
}
if(R_SUCCEEDED(res) && R_SUCCEEDED(res = spi_execute_command(chip, cmd, cmdSize, NULL, 0, (u8*) data + (pos - offset), currSize))) {
res = spi_wait_write_finish(chip);
}
}
if(R_FAILED(res)) {
break;
}
pos = pagePos + pageSize;
}
}
if(R_SUCCEEDED(res) && bytesWritten != NULL) {
*bytesWritten = pos - offset;
}
}
return res;
}
static Result spi_is_data_mirrored(SaveChip chip, u32 size, bool* mirrored) {
Result res = 0;
u8 original = 0;
u8 oldMirror = 0;
if(R_SUCCEEDED(res = spi_read_data(chip, NULL, &original, size - 1, sizeof(original)))
&& R_SUCCEEDED(res = spi_read_data(chip, NULL, &oldMirror, 2 * size - 1, sizeof(oldMirror)))) {
u8 modified = ~original;
u8 newMirror = 0;
if(R_SUCCEEDED(res = spi_write_data(chip, NULL, &modified, size - 1, sizeof(modified)))
&& R_SUCCEEDED(res = spi_read_data(chip, NULL, &newMirror, 2 * size - 1, sizeof(newMirror)))
&& R_SUCCEEDED(res = spi_write_data(chip, NULL, &original, size - 1, sizeof(original)))) {
if(mirrored != NULL) {
*mirrored = oldMirror != newMirror;
}
}
}
return res;
}
static Result spi_get_save_chip_internal(SaveChip* chip, SaveChip base) {
Result res = 0;
u32 jedecId = 0;
u8 status = 0;
if(R_SUCCEEDED(res = spi_read_jedec_id_status(base, &jedecId, &status))) {
SaveChip c = CHIP_NONE;
if(jedecId == 0xFFFFFF && ((status & 0xFD) == 0xF0 || (status & 0xFD) == 0x00)) {
if((status & 0xFD) == 0xF0) {
c = CHIP_EEPROM_512B;
} else if((status & 0xFD) == 0x00) {
bool mirrored = false;
if(R_SUCCEEDED(res = spi_is_data_mirrored(CHIP_EEPROM_8KB, 8 * 1024, &mirrored))) {
if(mirrored) {
c = CHIP_EEPROM_8KB;
} else {
if(R_SUCCEEDED(res = spi_is_data_mirrored(CHIP_EEPROM_64KB, 64 * 1024, &mirrored))) {
if(mirrored) {
c = CHIP_EEPROM_64KB;
} else {
c = CHIP_EEPROM_128KB;
}
}
}
}
}
} else {
c = base < CHIP_FLASH_256KB_INFRARED ? CHIP_FLASH_256KB : CHIP_FLASH_256KB_INFRARED;
switch(jedecId) {
case 0x204012:
case 0x621600:
c += CHIP_FLASH_256KB - CHIP_FLASH_256KB;
break;
case 0x204013:
case 0x621100:
c += CHIP_FLASH_512KB - CHIP_FLASH_256KB;
break;
case 0x204014:
c += CHIP_FLASH_1MB - CHIP_FLASH_256KB;
break;
case 0x202017:
case 0x204017:
c += CHIP_FLASH_8MB - CHIP_FLASH_256KB;
break;
default:
if(base < CHIP_FLASH_256KB_INFRARED) {
res = spi_get_save_chip_internal(&c, CHIP_FLASH_256KB_INFRARED);
} else {
res = R_FBI_UNSUPPORTED_OPERATION;
}
break;
}
}
if(R_SUCCEEDED(res) && chip != NULL) {
*chip = c;
}
}
return res;
}
static Result spi_get_save_chip(SaveChip* chip) {
return spi_get_save_chip_internal(chip, CHIP_EEPROM_512B);
}
static SaveChip curr_chip = CHIP_NONE;
Result spi_init_card() {
return spi_get_save_chip(&curr_chip);
}
Result spi_deinit_card() {
curr_chip = CHIP_NONE;
return 0;
}
Result spi_get_save_size(u32* size) {
return spi_get_capacity(curr_chip, size);
}
Result spi_read_save(u32* bytesRead, void* data, u32 offset, u32 size) {
return spi_read_data(curr_chip, bytesRead, data, offset, size);
}
Result spi_write_save(u32* bytesWritten, void* data, u32 offset, u32 size) {
return spi_write_data(curr_chip, bytesWritten, data, offset, size);
}

7
source/core/spi.h Normal file
View File

@ -0,0 +1,7 @@
#pragma once
Result spi_init_card();
Result spi_deinit_card();
Result spi_get_save_size(u32* size);
Result spi_read_save(u32* bytesRead, void* data, u32 offset, u32 size);
Result spi_write_save(u32* bytesWritten, void* data, u32 offset, u32 size);

View File

@ -469,4 +469,4 @@ Result util_close_archive(FS_Archive archive) {
}
return FSUSER_CloseArchive(archive);
}
}

View File

@ -27,6 +27,7 @@ void cleanup() {
amExit();
httpcExit();
pxiDevExit();
ptmuExit();
acExit();
cfguExit();
@ -60,6 +61,7 @@ int main(int argc, const char* argv[]) {
cfguInit();
acInit();
ptmuInit();
pxiDevInit();
httpcInit(0);
amInit();

View File

@ -8,6 +8,7 @@
#define R_FBI_THREAD_CREATE_FAILED MAKERESULT(RL_PERMANENT, RS_INTERNAL, RM_APPLICATION, 6)
#define R_FBI_PARSE_FAILED MAKERESULT(RL_PERMANENT, RS_INTERNAL, RM_APPLICATION, 7)
#define R_FBI_BAD_DATA MAKERESULT(RL_PERMANENT, RS_INTERNAL, RM_APPLICATION, 8)
#define R_FBI_UNSUPPORTED_OPERATION MAKERESULT(RL_PERMANENT, RS_INTERNAL, RM_APPLICATION, RD_NOT_IMPLEMENTED)
#define R_FBI_OUT_OF_MEMORY MAKERESULT(RL_FATAL, RS_OUTOFRESOURCE, RM_APPLICATION, RD_OUT_OF_MEMORY)
#define R_FBI_OUT_OF_RANGE MAKERESULT(RL_PERMANENT, RS_INVALIDARG, RM_APPLICATION, RD_OUT_OF_RANGE)

View File

@ -39,6 +39,8 @@ void action_delete_title(linked_list* items, list_item* selected);
void action_launch_title(linked_list* items, list_item* selected);
void action_extract_smdh(linked_list* items, list_item* selected);
void action_import_seed(linked_list* items, list_item* selected);
void action_export_twl_save(linked_list* items, list_item* selected);
void action_import_twl_save(linked_list* items, list_item* selected);
void action_browse_title_save_data(linked_list* items, list_item* selected);
void action_import_secure_value(linked_list* items, list_item* selected);
void action_export_secure_value(linked_list* items, list_item* selected);

View File

@ -0,0 +1,205 @@
#include <stdio.h>
#include <3ds.h>
#include <malloc.h>
#include "action.h"
#include "../task/task.h"
#include "../../error.h"
#include "../../info.h"
#include "../../list.h"
#include "../../prompt.h"
#include "../../ui.h"
#include "../../../core/linkedlist.h"
#include "../../../core/screen.h"
#include "../../../core/spi.h"
#include "../../../core/util.h"
typedef struct {
title_info* title;
data_op_data exportInfo;
} export_twl_save_data;
static void action_export_twl_save_draw_top(ui_view* view, void* data, float x1, float y1, float x2, float y2) {
ui_draw_title_info(view, ((export_twl_save_data*) data)->title, x1, y1, x2, y2);
}
static Result action_export_twl_save_is_src_directory(void* data, u32 index, bool* isDirectory) {
*isDirectory = false;
return 0;
}
static Result action_export_twl_save_make_dst_directory(void* data, u32 index) {
return 0;
}
static Result action_export_twl_save_open_src(void* data, u32 index, u32* handle) {
return spi_init_card();
}
static Result action_export_twl_save_close_src(void* data, u32 index, bool succeeded, u32 handle) {
return spi_deinit_card();
}
static Result action_export_twl_save_get_src_size(void* data, u32 handle, u64* size) {
Result res = 0;
u32 saveSize = 0;
if(R_SUCCEEDED(res = spi_get_save_size(&saveSize))) {
*size = saveSize;
}
return res;
}
static Result action_export_twl_save_read_src(void* data, u32 handle, u32* bytesRead, void* buffer, u64 offset, u32 size) {
return spi_read_save(bytesRead, buffer, (u32) offset, size);
}
static Result action_export_twl_save_open_dst(void* data, u32 index, void* initialReadBlock, u32* handle) {
export_twl_save_data* exportData = (export_twl_save_data*) data;
Result res = 0;
FS_Archive sdmcArchive = 0;
if(R_SUCCEEDED(res = FSUSER_OpenArchive(&sdmcArchive, ARCHIVE_SDMC, fsMakePath(PATH_EMPTY, "")))) {
if(R_SUCCEEDED(res = util_ensure_dir(sdmcArchive, "/fbi/")) && R_SUCCEEDED(res = util_ensure_dir(sdmcArchive, "/fbi/save/"))) {
char path[FILE_PATH_MAX];
snprintf(path, sizeof(path), "/fbi/save/%s.sav", exportData->title->productCode);
FS_Path* fsPath = util_make_path_utf8(path);
if(fsPath != NULL) {
res = FSUSER_OpenFileDirectly(handle, ARCHIVE_SDMC, fsMakePath(PATH_EMPTY, ""), *fsPath, FS_OPEN_WRITE | FS_OPEN_CREATE, 0);
util_free_path_utf8(fsPath);
} else {
res = R_FBI_OUT_OF_MEMORY;
}
}
FSUSER_CloseArchive(sdmcArchive);
}
return res;
}
static Result action_export_twl_save_close_dst(void* data, u32 index, bool succeeded, u32 handle) {
return FSFILE_Close(handle);
}
static Result action_export_twl_save_write_dst(void* data, u32 handle, u32* bytesWritten, void* buffer, u64 offset, u32 size) {
return FSFILE_Write(handle, bytesWritten, offset, buffer, size, 0);
}
static Result action_export_twl_save_suspend_copy(void* data, u32 index, u32* srcHandle, u32* dstHandle) {
return 0;
}
static Result action_export_twl_save_restore_copy(void* data, u32 index, u32* srcHandle, u32* dstHandle) {
return 0;
}
static Result action_export_twl_save_suspend(void* data, u32 index) {
return 0;
}
static Result action_export_twl_save_restore(void* data, u32 index) {
return 0;
}
static bool action_export_twl_save_error(void* data, u32 index, Result res) {
export_twl_save_data* exportData = (export_twl_save_data*) data;
if(res == R_FBI_CANCELLED) {
prompt_display("Failure", "Export cancelled.", COLOR_TEXT, false, exportData->title, ui_draw_title_info, NULL);
} else {
error_display_res(NULL, exportData->title, ui_draw_title_info, res, "Failed to export save.");
}
return false;
}
static void action_export_twl_save_update(ui_view* view, void* data, float* progress, char* text) {
export_twl_save_data* exportData = (export_twl_save_data*) data;
if(exportData->exportInfo.finished) {
ui_pop();
info_destroy(view);
if(R_SUCCEEDED(exportData->exportInfo.result)) {
prompt_display("Success", "Save exported.", COLOR_TEXT, false, exportData->title, ui_draw_title_info, NULL);
}
free(data);
return;
}
if(hidKeysDown() & KEY_B) {
svcSignalEvent(exportData->exportInfo.cancelEvent);
}
*progress = exportData->exportInfo.currTotal != 0 ? (float) ((double) exportData->exportInfo.currProcessed / (double) exportData->exportInfo.currTotal) : 0;
snprintf(text, PROGRESS_TEXT_MAX, "%.2f MiB / %.2f MiB", exportData->exportInfo.currProcessed / 1024.0f / 1024.0f, exportData->exportInfo.currTotal / 1024.0f / 1024.0f);
}
static void action_export_twl_save_onresponse(ui_view* view, void* data, bool response) {
if(response) {
export_twl_save_data* exportData = (export_twl_save_data*) data;
Result res = task_data_op(&exportData->exportInfo);
if(R_SUCCEEDED(res)) {
info_display("Exporting Save", "Press B to cancel.", true, data, action_export_twl_save_update, action_export_twl_save_draw_top);
} else {
error_display_res(NULL, exportData->title, ui_draw_title_info, res, "Failed to initiate save export.");
free(data);
}
} else {
free(data);
}
}
void action_export_twl_save(linked_list* items, list_item* selected) {
export_twl_save_data* data = (export_twl_save_data*) calloc(1, sizeof(export_twl_save_data));
if(data == NULL) {
error_display(NULL, NULL, NULL, "Failed to allocate export TWL save data.");
return;
}
data->title = (title_info*) selected->data;
data->exportInfo.data = data;
data->exportInfo.op = DATAOP_COPY;
data->exportInfo.copyBufferSize = 128 * 1024;
data->exportInfo.copyEmpty = true;
data->exportInfo.total = 1;
data->exportInfo.isSrcDirectory = action_export_twl_save_is_src_directory;
data->exportInfo.makeDstDirectory = action_export_twl_save_make_dst_directory;
data->exportInfo.openSrc = action_export_twl_save_open_src;
data->exportInfo.closeSrc = action_export_twl_save_close_src;
data->exportInfo.getSrcSize = action_export_twl_save_get_src_size;
data->exportInfo.readSrc = action_export_twl_save_read_src;
data->exportInfo.openDst = action_export_twl_save_open_dst;
data->exportInfo.closeDst = action_export_twl_save_close_dst;
data->exportInfo.writeDst = action_export_twl_save_write_dst;
data->exportInfo.suspendCopy = action_export_twl_save_suspend_copy;
data->exportInfo.restoreCopy = action_export_twl_save_restore_copy;
data->exportInfo.suspend = action_export_twl_save_suspend;
data->exportInfo.restore = action_export_twl_save_restore;
data->exportInfo.error = action_export_twl_save_error;
data->exportInfo.finished = true;
prompt_display("Confirmation", "Export the save of the selected title?", COLOR_TEXT, true, data, action_export_twl_save_draw_top, action_export_twl_save_onresponse);
}

View File

@ -0,0 +1,191 @@
#include <stdio.h>
#include <3ds.h>
#include <malloc.h>
#include "action.h"
#include "../task/task.h"
#include "../../error.h"
#include "../../info.h"
#include "../../list.h"
#include "../../prompt.h"
#include "../../ui.h"
#include "../../../core/linkedlist.h"
#include "../../../core/screen.h"
#include "../../../core/spi.h"
#include "../../../core/util.h"
typedef struct {
title_info* title;
data_op_data importInfo;
} import_twl_save_data;
static void action_import_twl_save_draw_top(ui_view* view, void* data, float x1, float y1, float x2, float y2) {
ui_draw_title_info(view, ((import_twl_save_data*) data)->title, x1, y1, x2, y2);
}
static Result action_import_twl_save_is_src_directory(void* data, u32 index, bool* isDirectory) {
*isDirectory = false;
return 0;
}
static Result action_import_twl_save_make_dst_directory(void* data, u32 index) {
return 0;
}
static Result action_import_twl_save_open_src(void* data, u32 index, u32* handle) {
import_twl_save_data* importData = (import_twl_save_data*) data;
Result res = 0;
char path[FILE_PATH_MAX];
snprintf(path, sizeof(path), "/fbi/save/%s.sav", importData->title->productCode);
FS_Path* fsPath = util_make_path_utf8(path);
if(fsPath != NULL) {
res = FSUSER_OpenFileDirectly(handle, ARCHIVE_SDMC, fsMakePath(PATH_EMPTY, ""), *fsPath, FS_OPEN_READ, 0);
util_free_path_utf8(fsPath);
} else {
res = R_FBI_OUT_OF_MEMORY;
}
return res;
}
static Result action_import_twl_save_close_src(void* data, u32 index, bool succeeded, u32 handle) {
return FSFILE_Close(handle);
}
static Result action_import_twl_save_get_src_size(void* data, u32 handle, u64* size) {
return FSFILE_GetSize(handle, size);
}
static Result action_import_twl_save_read_src(void* data, u32 handle, u32* bytesRead, void* buffer, u64 offset, u32 size) {
return FSFILE_Read(handle, bytesRead, offset, buffer, size);
}
static Result action_import_twl_save_open_dst(void* data, u32 index, void* initialReadBlock, u32* handle) {
return spi_init_card();
}
static Result action_import_twl_save_close_dst(void* data, u32 index, bool succeeded, u32 handle) {
return spi_deinit_card();
}
static Result action_import_twl_save_write_dst(void* data, u32 handle, u32* bytesWritten, void* buffer, u64 offset, u32 size) {
return spi_write_save(bytesWritten, buffer, (u32) offset, size);
}
static Result action_import_twl_save_suspend_copy(void* data, u32 index, u32* srcHandle, u32* dstHandle) {
return 0;
}
static Result action_import_twl_save_restore_copy(void* data, u32 index, u32* srcHandle, u32* dstHandle) {
return 0;
}
static Result action_import_twl_save_suspend(void* data, u32 index) {
return 0;
}
static Result action_import_twl_save_restore(void* data, u32 index) {
return 0;
}
static bool action_import_twl_save_error(void* data, u32 index, Result res) {
import_twl_save_data* importData = (import_twl_save_data*) data;
if(res == R_FBI_CANCELLED) {
prompt_display("Failure", "Import cancelled.", COLOR_TEXT, false, importData->title, ui_draw_title_info, NULL);
} else {
error_display_res(NULL, importData->title, ui_draw_title_info, res, "Failed to import save.");
}
return false;
}
static void action_import_twl_save_update(ui_view* view, void* data, float* progress, char* text) {
import_twl_save_data* importData = (import_twl_save_data*) data;
if(importData->importInfo.finished) {
ui_pop();
info_destroy(view);
if(R_SUCCEEDED(importData->importInfo.result)) {
prompt_display("Success", "Save imported.", COLOR_TEXT, false, importData->title, ui_draw_title_info, NULL);
}
free(data);
return;
}
if(hidKeysDown() & KEY_B) {
svcSignalEvent(importData->importInfo.cancelEvent);
}
*progress = importData->importInfo.currTotal != 0 ? (float) ((double) importData->importInfo.currProcessed / (double) importData->importInfo.currTotal) : 0;
snprintf(text, PROGRESS_TEXT_MAX, "%.2f MiB / %.2f MiB", importData->importInfo.currProcessed / 1024.0f / 1024.0f, importData->importInfo.currTotal / 1024.0f / 1024.0f);
}
static void action_import_twl_save_onresponse(ui_view* view, void* data, bool response) {
if(response) {
import_twl_save_data* importData = (import_twl_save_data*) data;
Result res = task_data_op(&importData->importInfo);
if(R_SUCCEEDED(res)) {
info_display("Importing Save", "Press B to cancel.", true, data, action_import_twl_save_update, action_import_twl_save_draw_top);
} else {
error_display_res(NULL, importData->title, ui_draw_title_info, res, "Failed to initiate save import.");
free(data);
}
} else {
free(data);
}
}
void action_import_twl_save(linked_list* items, list_item* selected) {
import_twl_save_data* data = (import_twl_save_data*) calloc(1, sizeof(import_twl_save_data));
if(data == NULL) {
error_display(NULL, NULL, NULL, "Failed to allocate import TWL save data.");
return;
}
data->title = (title_info*) selected->data;
data->importInfo.data = data;
data->importInfo.op = DATAOP_COPY;
data->importInfo.copyBufferSize = 16 * 1024;
data->importInfo.copyEmpty = true;
data->importInfo.total = 1;
data->importInfo.isSrcDirectory = action_import_twl_save_is_src_directory;
data->importInfo.makeDstDirectory = action_import_twl_save_make_dst_directory;
data->importInfo.openSrc = action_import_twl_save_open_src;
data->importInfo.closeSrc = action_import_twl_save_close_src;
data->importInfo.getSrcSize = action_import_twl_save_get_src_size;
data->importInfo.readSrc = action_import_twl_save_read_src;
data->importInfo.openDst = action_import_twl_save_open_dst;
data->importInfo.closeDst = action_import_twl_save_close_dst;
data->importInfo.writeDst = action_import_twl_save_write_dst;
data->importInfo.suspendCopy = action_import_twl_save_suspend_copy;
data->importInfo.restoreCopy = action_import_twl_save_restore_copy;
data->importInfo.suspend = action_import_twl_save_suspend;
data->importInfo.restore = action_import_twl_save_restore;
data->importInfo.error = action_import_twl_save_error;
data->importInfo.finished = true;
prompt_display("Confirmation", "Import the save of the selected title?", COLOR_TEXT, true, data, action_import_twl_save_draw_top, action_import_twl_save_onresponse);
}

View File

@ -267,6 +267,7 @@ void action_install_cdn_noprompt(volatile bool* done, ticket_info* info, bool fi
data->installInfo.op = DATAOP_COPY;
data->installInfo.copyBufferSize = 256 * 1024;
data->installInfo.copyEmpty = false;
data->installInfo.total = 1;

View File

@ -268,6 +268,7 @@ static void action_install_cias_internal(linked_list* items, list_item* selected
data->installInfo.op = DATAOP_COPY;
data->installInfo.copyBufferSize = 256 * 1024;
data->installInfo.copyEmpty = false;
data->installInfo.isSrcDirectory = action_install_cias_is_src_directory;

View File

@ -245,6 +245,7 @@ static void action_install_tickets_internal(linked_list* items, list_item* selec
data->installInfo.op = DATAOP_COPY;
data->installInfo.copyBufferSize = 256 * 1024;
data->installInfo.copyEmpty = false;
data->installInfo.isSrcDirectory = action_install_tickets_is_src_directory;

View File

@ -285,6 +285,7 @@ void action_paste_contents(linked_list* items, list_item* selected) {
data->pasteInfo.op = DATAOP_COPY;
data->pasteInfo.copyBufferSize = 256 * 1024;
data->pasteInfo.copyEmpty = true;
data->pasteInfo.isSrcDirectory = action_paste_files_is_src_directory;

View File

@ -107,6 +107,7 @@ static void dumpnand_onresponse(ui_view* view, void* data, bool response) {
info_display("Dumping NAND", "Press B to cancel.", true, data, dumpnand_update, NULL);
} else {
error_display_res(NULL, NULL, NULL, res, "Failed to initiate NAND dump.");
free(data);
}
} else {
free(data);
@ -125,6 +126,7 @@ void dumpnand_open() {
data->op = DATAOP_COPY;
data->copyBufferSize = 256 * 1024;
data->copyEmpty = true;
data->total = 1;

View File

@ -349,6 +349,7 @@ void networkinstall_open() {
data->installInfo.op = DATAOP_COPY;
data->installInfo.copyBufferSize = 256 * 1024;
data->installInfo.copyEmpty = false;
data->installInfo.isSrcDirectory = networkinstall_is_src_directory;

View File

@ -454,6 +454,7 @@ void qrinstall_open() {
data->installInfo.op = DATAOP_COPY;
data->installInfo.copyBufferSize = 256 * 1024;
data->installInfo.copyEmpty = false;
data->installInfo.total = 0;

View File

@ -62,8 +62,7 @@ static Result task_data_op_copy(data_op_data* data, u32 index) {
res = R_FBI_BAD_DATA;
}
} else {
u32 bufferSize = 1024 * 256;
u8* buffer = (u8*) calloc(1, bufferSize);
u8* buffer = (u8*) calloc(1, data->copyBufferSize);
if(buffer != NULL) {
u32 dstHandle = 0;
@ -73,7 +72,7 @@ static Result task_data_op_copy(data_op_data* data, u32 index) {
break;
}
u32 currSize = bufferSize;
u32 currSize = data->copyBufferSize;
if((u64) currSize > data->currTotal - data->currProcessed) {
currSize = (u32) (data->currTotal - data->currProcessed);
}

View File

@ -91,6 +91,7 @@ typedef struct data_op_info_s {
data_op op;
// Copy
u32 copyBufferSize;
bool copyEmpty;
u32 processed;

View File

@ -17,6 +17,8 @@ static list_item delete_title = {"Delete Title", COLOR_TEXT, action_delete_title
static list_item extract_smdh = {"Extract SMDH", COLOR_TEXT, action_extract_smdh};
static list_item import_seed = {"Import Seed", COLOR_TEXT, action_import_seed};
static list_item browse_save_data = {"Browse Save Data", COLOR_TEXT, action_browse_title_save_data};
static list_item import_save_data = {"Import Save Data", COLOR_TEXT, action_import_twl_save};
static list_item export_save_data = {"Export Save Data", COLOR_TEXT, action_export_twl_save};
static list_item import_secure_value = {"Import Secure Value", COLOR_TEXT, action_import_secure_value};
static list_item export_secure_value = {"Export Secure Value", COLOR_TEXT, action_export_secure_value};
static list_item delete_secure_value = {"Delete Secure Value", COLOR_TEXT, action_delete_secure_value};
@ -88,6 +90,9 @@ static void titles_action_update(ui_view* view, void* data, linked_list* items,
linked_list_add(items, &export_secure_value);
linked_list_add(items, &delete_secure_value);
}
} else if(info->mediaType == MEDIATYPE_GAME_CARD) {
linked_list_add(items, &import_save_data);
linked_list_add(items, &export_save_data);
}
}
}

View File

@ -313,6 +313,7 @@ void update_open() {
data->installInfo.op = DATAOP_COPY;
data->installInfo.copyBufferSize = 256 * 1024;
data->installInfo.copyEmpty = false;
data->installInfo.total = 1;