mirror of
https://gitlab.com/Theopse/fbi-i18n-zh.git
synced 2025-06-04 06:09:17 +08:00
Add DS save support.
This commit is contained in:
parent
8583d62ec3
commit
abf82057ef
@ -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
463
source/core/spi.c
Normal 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
7
source/core/spi.h
Normal 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);
|
@ -469,4 +469,4 @@ Result util_close_archive(FS_Archive archive) {
|
||||
}
|
||||
|
||||
return FSUSER_CloseArchive(archive);
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
|
@ -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)
|
||||
|
@ -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);
|
||||
|
205
source/ui/section/action/exporttwlsave.c
Normal file
205
source/ui/section/action/exporttwlsave.c
Normal 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);
|
||||
}
|
191
source/ui/section/action/importtwlsave.c
Normal file
191
source/ui/section/action/importtwlsave.c
Normal 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);
|
||||
}
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -91,6 +91,7 @@ typedef struct data_op_info_s {
|
||||
data_op op;
|
||||
|
||||
// Copy
|
||||
u32 copyBufferSize;
|
||||
bool copyEmpty;
|
||||
|
||||
u32 processed;
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
Loading…
x
Reference in New Issue
Block a user