From 78d9f1747a56aae47ba530197c9869105cf78b0f Mon Sep 17 00:00:00 2001 From: d0k3 Date: Fri, 9 Jun 2017 01:45:00 +0200 Subject: [PATCH] Godmode9 scripting support --- source/common/common.h | 18 +- source/common/power.h | 12 + source/filesys/filetype.c | 3 + source/filesys/filetype.h | 2 + source/filesys/fsscript.c | 490 ++++++++++++++++++++++++++++++++++++++ source/filesys/fsscript.h | 10 + source/filesys/fsutil.c | 71 +++--- source/filesys/fsutil.h | 13 +- source/filesys/vff.c | 32 +++ source/filesys/vff.h | 3 +- source/godmode.c | 16 +- source/main.c | 15 +- 12 files changed, 619 insertions(+), 66 deletions(-) create mode 100644 source/common/power.h create mode 100644 source/filesys/fsscript.c create mode 100644 source/filesys/fsscript.h diff --git a/source/common/common.h b/source/common/common.h index e713d2a..f5b2c64 100644 --- a/source/common/common.h +++ b/source/common/common.h @@ -49,7 +49,7 @@ #endif // GodMode9 version -#define VERSION "1.2.3" +#define VERSION "1.2.4" // input / output paths #define INPUT_PATHS "0:", "0:/files9", "1:/rw/files9" @@ -82,22 +82,16 @@ // buffer area defines (in use by sddata.c) #define SDCRYPT_BUFFER ((u8*)0x21400000) #define SDCRYPT_BUFFER_SIZE (0x100000) +// buffer area defines (in use by fsscript.c) +#define SCRIPT_BUFFER ((u8*)0x21500000) +#define SCRIPT_BUFFER_SIZE (0x100000) // buffer area defines (in use by vgame.c) -#define VGAME_BUFFER ((u8*)0x21500000) +#define VGAME_BUFFER ((u8*)0x21600000) #define VGAME_BUFFER_SIZE (0x200000) // 2MB, big RomFS // buffer area defines (in use by vcart.c) -#define VCART_BUFFER ((u8*)0x21600000) +#define VCART_BUFFER ((u8*)0x21800000) #define VCART_BUFFER_SIZE (0x20000) // 128kB, this is more than enough // buffer area defines (in use by image.c, for RAMdrive) #define RAMDRV_BUFFER ((u8*)0x24000000) // top half of FCRAM #define RAMDRV_SIZE_O3DS (0x04000000) // 64MB #define RAMDRV_SIZE_N3DS (0x0C000000) // 192MB - -inline u32 strchrcount(const char* str, char symbol) { - u32 count = 0; - for (u32 i = 0; str[i] != '\0'; i++) { - if (str[i] == symbol) - count++; - } - return count; -} diff --git a/source/common/power.h b/source/common/power.h new file mode 100644 index 0000000..12c86b3 --- /dev/null +++ b/source/common/power.h @@ -0,0 +1,12 @@ +#include "i2c.h" + +inline static void Reboot() { + i2cWriteRegister(I2C_DEV_MCU, 0x20, 1 << 2); + while(true); +} + +inline static void PowerOff() +{ + i2cWriteRegister(I2C_DEV_MCU, 0x20, 1 << 0); + while (true); +} diff --git a/source/filesys/filetype.c b/source/filesys/filetype.c index 64f9b13..299fcbd 100644 --- a/source/filesys/filetype.c +++ b/source/filesys/filetype.c @@ -6,6 +6,7 @@ #include "keydb.h" #include "ctrtransfer.h" #include "chainload.h" +#include "fsscript.h" u32 IdentifyFileType(const char* path) { const u8 romfs_magic[] = { ROMFS_MAGIC }; @@ -105,6 +106,8 @@ u32 IdentifyFileType(const char* path) { } else if ((fsize <= PAYLOAD_MAX_SIZE) && ext && (strncasecmp(ext, "bin", 4) == 0)) { return BIN_LAUNCH; // assume it's an ARM9 payload #endif + } else if ((fsize <= SCRIPT_MAX_SIZE) && ext && (strncasecmp(ext, SCRIPT_EXT, strnlen(SCRIPT_EXT, 16) + 1) == 0)) { + return TXT_SCRIPT; // should be a script } return 0; diff --git a/source/filesys/filetype.h b/source/filesys/filetype.h index 9a4df2b..d6dda02 100644 --- a/source/filesys/filetype.h +++ b/source/filesys/filetype.h @@ -22,6 +22,7 @@ #define BIN_TIKDB (1UL<<17) #define BIN_KEYDB (1UL<<18) #define BIN_LEGKEY (1UL<<19) +#define TXT_SCRIPT (1UL<<20) #define TYPE_BASE 0x00FFFFFF // 24 bit reserved for base types #define FLAG_ENC (1UL<<28) @@ -45,6 +46,7 @@ #define FTYPE_RESTORABLE(tp) (tp&(IMG_NAND)) #define FTYPE_EBACKUP(tp) (tp&(IMG_NAND)) #define FTYPE_XORPAD(tp) (tp&(BIN_NCCHNFO)) +#define FTYPE_SCRIPT(tp) (tp&(TXT_SCRIPT)) #define FTYPE_PAYLOAD(tp) (tp&(BIN_LAUNCH)) u32 IdentifyFileType(const char* path); diff --git a/source/filesys/fsscript.c b/source/filesys/fsscript.c new file mode 100644 index 0000000..ab276a4 --- /dev/null +++ b/source/filesys/fsscript.c @@ -0,0 +1,490 @@ +#include "fsscript.h" +#include "fsutil.h" +#include "fsinit.h" +#include "fsperm.h" +#include "nandutil.h" +#include "gameutil.h" +#include "filetype.h" +#include "power.h" +#include "vff.h" +#include "ui.h" + +#define _MAX_ARGS 2 +#define _VAR_CNT_LEN 256 +#define _VAR_NAME_LEN 32 +#define _ERR_STR_LEN 32 + +#define VAR_BUFFER (SCRIPT_BUFFER + SCRIPT_BUFFER_SIZE - VAR_BUFFER_SIZE) + +// some useful macros +#define IS_WHITESPACE(c) ((c == ' ') || (c == '\t') || (c == '\r') || (c == '\n')) +#define _FLG(c) (1 << (c - 'a')) + +// command ids +typedef enum { + CMD_ID_NONE = 0, + CMD_ID_ECHO, + CMD_ID_ASK, + CMD_ID_SET, + CMD_ID_ALLOW, + CMD_ID_CP, + CMD_ID_MV, + CMD_ID_INJECT, + CMD_ID_RM, + CMD_ID_MKDIR, + CMD_ID_MOUNT, + CMD_ID_UMOUNT, + CMD_ID_FIND, + CMD_ID_FINDNOT, + CMD_ID_SHA, + CMD_ID_VERIFY, + CMD_ID_REBOOT, + CMD_ID_POWEROFF +} cmd_id; + +typedef struct { + cmd_id id; + char cmd[16]; + u32 n_args; + u32 allowed_flags; +} Gm9ScriptCmd; + +typedef struct { + char name[_VAR_NAME_LEN]; // variable name + char content[_VAR_CNT_LEN]; +} Gm9ScriptVar; + +Gm9ScriptCmd cmd_list[] = { + { CMD_ID_ECHO , "echo" , 1, 0 }, + { CMD_ID_ASK , "ask" , 1, 0 }, + { CMD_ID_SET , "set" , 2, 0 }, + { CMD_ID_ALLOW , "allow" , 1, _FLG('a') }, + { CMD_ID_CP , "cp" , 2, _FLG('h') | _FLG('w') | _FLG('k') | _FLG('s') | _FLG('n')}, + { CMD_ID_MV , "mv" , 2, _FLG('w') | _FLG('k') | _FLG('s') | _FLG('n') }, + { CMD_ID_INJECT , "inject" , 2, _FLG('n') }, + { CMD_ID_RM , "rm" , 1, 0 }, + { CMD_ID_MKDIR , "mkdir" , 1, 0 }, + { CMD_ID_MOUNT , "mount" , 1, 0 }, + { CMD_ID_UMOUNT , "umount" , 0, 0 }, + { CMD_ID_FIND , "find" , 2, 0 }, + { CMD_ID_FINDNOT , "findnot" , 2, 0 }, + { CMD_ID_SHA , "sha" , 2, 0 }, + { CMD_ID_VERIFY , "verify" , 1, 0 }, + { CMD_ID_REBOOT , "reboot" , 0, 0 }, + { CMD_ID_POWEROFF, "poweroff", 0, 0 } +}; + +inline bool strntohex(const char* str, u8* hex, u32 len) { + if (!len) { + len = strlen(str); + if (len%1) return false; + else len >>= 1; + } else if (len*2 != strnlen(str, (len*2)+1)) { + return false; + } + for (u32 i = 0; i < len; i++) { + char bytestr[2+1] = { 0 }; + u32 bytehex; + memcpy(bytestr, str + (i*2), 2); + if (sscanf(bytestr, "%02lx", &bytehex) != 1) + return false; + hex[i] = (u8) bytehex; + } + return true; +} + +char* get_var(const char* name, char** endptr) { + Gm9ScriptVar* vars = (Gm9ScriptVar*) VAR_BUFFER; + u32 max_vars = VAR_BUFFER_SIZE / sizeof(Gm9ScriptVar); + + u32 name_len = 0; + char* vname = (char*) name + 1; + if (*name != '[') return NULL; + for (name_len = 0; vname[name_len] != ']'; name_len++) + if ((name_len >= _VAR_NAME_LEN) || !vname[name_len]) return NULL; + if (endptr) *endptr = vname + name_len + 1; + + u32 n_var = 0; + for (Gm9ScriptVar* var = vars; n_var < max_vars; n_var++, var++) { + if (!*(var->name) || (strncmp(var->name, vname, name_len) == 0)) break; + } + + if (n_var >= max_vars || !*(vars[n_var].name)) n_var = 0; + + return vars[n_var].content; +} + +char* set_var(const char* name, const char* content) { + Gm9ScriptVar* vars = (Gm9ScriptVar*) VAR_BUFFER; + u32 max_vars = VAR_BUFFER_SIZE / sizeof(Gm9ScriptVar); + + if ((strnlen(name, _VAR_NAME_LEN) > (_VAR_NAME_LEN-1)) || (strnlen(content, _VAR_CNT_LEN) > (_VAR_CNT_LEN-1)) || + (strchr(name, '[') || strchr(name, ']'))) + return NULL; + + u32 n_var = 0; + for (Gm9ScriptVar* var = vars; n_var < max_vars; n_var++, var++) + if (!*(var->name) || (strncmp(var->name, name, _VAR_NAME_LEN) == 0)) break; + if (n_var >= max_vars) return NULL; + strncpy(vars[n_var].name, name, _VAR_NAME_LEN); + strncpy(vars[n_var].content, content, _VAR_CNT_LEN); + if (!n_var) *(vars[n_var].content) = '\0'; // NULL var + + return vars[n_var].content; +} + +bool init_vars(void) { + // reset var buffer + memset(VAR_BUFFER, 0x00, VAR_BUFFER_SIZE); + + // set env vars + char env_serial[16] = { 0 }; + if ((FileGetData("1:/rw/sys/SecureInfo_A", (u8*) env_serial, 0xF, 0x102) != 0xF) && + (FileGetData("1:/rw/sys/SecureInfo_B", (u8*) env_serial, 0xF, 0x102) != 0xF)) + snprintf(env_serial, 0xF, "UNKNOWN"); + set_var("NULL", ""); // this one is special and should not be changed later + set_var("SERIAL", env_serial); + set_var("GM9OUT", OUTPUT_PATH); + + return true; +} + +bool expand_arg(char* argex, const char* arg, u32 len) { + char* out = argex; + + for (char* in = (char*) arg; in - arg < (int) len; in++) { + u32 out_len = out - argex; + if (out_len >= (_VAR_CNT_LEN-1)) return false; // maximum arglen reached + + if (*in == '\\') { // escape line breaks (no other escape is handled) + if (*(++in) == 'n') *(out++) = '\n'; + else { + *(out++) = '\\'; + *(out++) = *in; + } + } else if (*in == '$') { // replace vars + char* content = get_var(in + 1, &in); + if (content) { + u32 clen = strnlen(content, 256); + strncpy(out, content, clen); + out += clen; + in--; // go back one char + } else *(out++) = *in; + } else *(out++) = *in; + } + *out = '\0'; + + return true; +} + +cmd_id get_cmd_id(char* cmd, u32 len, u32 flags, u32 argc, char* err_str) { + Gm9ScriptCmd* cmd_entry = NULL; + + for (u32 i = 0; i < (sizeof(cmd_list)/sizeof(Gm9ScriptCmd)); i++) { + if (strncmp(cmd_list[i].cmd, cmd, len) == 0) { + cmd_entry = cmd_list + i; + break; + } + } + + if (!cmd_entry) { + if (err_str) snprintf(err_str, _ERR_STR_LEN, "unknown cmd"); + } else if (cmd_entry->n_args != argc) { + if (err_str) snprintf(err_str, _ERR_STR_LEN, "bad # of args"); + } else if (~(cmd_entry->allowed_flags|_FLG('o')|_FLG('s')) & flags) { + if (err_str) snprintf(err_str, _ERR_STR_LEN, "unrecognized flags"); + } else return cmd_entry->id; + + return 0; +} + +u32 get_flag(char* str, u32 len, char* err_str) { + char flag_char = '\0'; + + if ((len < 2) || (*str != '-')) flag_char = '\0'; + else if (len == 2) flag_char = str[1]; + else if (strncmp(str, "--all", len) == 0) flag_char = 'a'; + else if (strncmp(str, "--hash", len) == 0) flag_char = 'h'; + else if (strncmp(str, "--skip", len) == 0) flag_char = 'k'; + else if (strncmp(str, "--no_cancel", len) == 0) flag_char = 'n'; + else if (strncmp(str, "--optional", len) == 0) flag_char = 'o'; + else if (strncmp(str, "--silent", len) == 0) flag_char = 's'; + else if (strncmp(str, "--overwrite", len) == 0) flag_char = 'w'; + + if ((flag_char < 'a') && (flag_char > 'z')) { + if (err_str) snprintf(err_str, _ERR_STR_LEN, "illegal flag"); + return 0; + } + + return _FLG(flag_char); +} + +char* get_string(char* ptr, const char* line_end, u32* len, char** next, char* err_str) { + char* str = NULL; + *len = 0; + + // skip whitespaces + for (; IS_WHITESPACE(*ptr) && (ptr < line_end); ptr++); + if (ptr >= line_end) return (*next = (char*) line_end); // end reached, all whitespaces + + // handle string + if (*ptr == '\"') { // quotes + str = ++ptr; + for (; (*ptr != '\"') && (ptr < line_end); ptr++, (*len)++); + if (ptr >= line_end) { // failed if unresolved quotes + if (err_str) snprintf(err_str, _ERR_STR_LEN, "unresolved quotes"); + return NULL; + } + *next = ptr + 1; + } else { // no quotes, no whitespace + str = ptr; + for (; !IS_WHITESPACE(*ptr) && (ptr < line_end); ptr++, (*len)++); + *next = ptr; + } + + return str; +} + +bool parse_line(const char* line_start, const char* line_end, cmd_id* cmdid, u32* flags, u32* argc, char** argv, char* err_str) { + char* ptr = (char*) line_start; + char* str; + u32 len; + + // set everything to initial values + *cmdid = 0; + *flags = 0; + *argc = 0; + + // search for cmd + char* cmd = NULL; + u32 cmd_len = 0; + if (!(cmd = get_string(ptr, line_end, &cmd_len, &ptr, err_str))) return false; // string error + if ((cmd >= line_end) || (*cmd == '#')) return true; // empty line or comment + + // got cmd, now parse flags & args + while ((str = get_string(ptr, line_end, &len, &ptr, err_str))) { + if ((str >= line_end) || (*str == '#')) // end of line or comment + return (*cmdid = get_cmd_id(cmd, cmd_len, *flags, *argc, err_str)); + if (*str == '-') { // flag + u32 flag_add = get_flag(str, len, err_str); + if (!flag_add) return false; // not a proper flag + *flags |= flag_add; + } else if (*argc >= _MAX_ARGS) { + if (err_str) snprintf(err_str, _ERR_STR_LEN, "too many arguments"); + return false; // too many arguments + } else if (!expand_arg(argv[(*argc)++], str, len)) { + if (err_str) snprintf(err_str, _ERR_STR_LEN, "argument expand failed"); + return false; // arg expand failed + } + } + + // end reached with a failed get_string() + return false; +} + +bool run_cmd(cmd_id id, u32 flags, char** argv, char* err_str) { + bool ret = true; // true unless some cmd messes up + + // perform command + if (id == CMD_ID_ECHO) { + ShowPrompt(false, argv[0]); + } else if (id == CMD_ID_ASK) { + ret = ShowPrompt(true, argv[0]); + if (err_str) snprintf(err_str, _ERR_STR_LEN, "user abort"); + } else if (id == CMD_ID_SET) { + ret = set_var(argv[0], argv[1]); + if (err_str) snprintf(err_str, _ERR_STR_LEN, "set fail"); + } else if (id == CMD_ID_ALLOW) { + if (flags & _FLG('a')) ret = CheckDirWritePermissions(argv[0]); + else ret = CheckWritePermissions(argv[0]); + if (err_str) snprintf(err_str, _ERR_STR_LEN, "permission fail"); + } else if (id == CMD_ID_CP) { + u32 flags_ext = BUILD_PATH; + if (flags && _FLG('h')) flags_ext |= CALC_SHA; + if (flags && _FLG('n')) flags_ext |= NO_CANCEL; + if (flags && _FLG('s')) flags_ext |= SILENT; + if (flags && _FLG('w')) flags_ext |= OVERWRITE_ALL; + else if (flags && _FLG('k')) flags_ext |= SKIP_ALL; + ret = PathMoveCopy(argv[1], argv[0], &flags_ext, false); + if (err_str) snprintf(err_str, _ERR_STR_LEN, "copy fail"); + } else if (id == CMD_ID_MV) { + u32 flags_ext = BUILD_PATH; + if (flags && _FLG('n')) flags_ext |= NO_CANCEL; + if (flags && _FLG('s')) flags_ext |= SILENT; + if (flags && _FLG('w')) flags_ext |= OVERWRITE_ALL; + else if (flags && _FLG('k')) flags_ext |= SKIP_ALL; + ret = PathMoveCopy(argv[1], argv[0], &flags_ext, true); + if (err_str) snprintf(err_str, _ERR_STR_LEN, "move fail"); + } else if (id == CMD_ID_INJECT) { + char* atstr_dst = strrchr(argv[1], '@'); + char* atstr_org = strrchr(argv[0], '@'); + u64 at_org = 0; + u64 sz_org = 0; + if (atstr_org) { + *(atstr_org++) = '\0'; + if (sscanf(atstr_org, "%llX:%llX", &at_org, &sz_org) != 2) { + if (sscanf(atstr_org, "%llX", &at_org) != 1) at_org = 0; + sz_org = 0; + } + } + if (!atstr_dst) { + ret = false; + if (err_str) snprintf(err_str, _ERR_STR_LEN, "missing dest offset"); + } else { + u64 at_dst = 0; + *(atstr_dst++) = '\0'; + ret = ((sscanf(atstr_dst, "%llX", &at_dst) == 1) && FileInjectFile(argv[1], argv[0], at_dst, at_org, sz_org, NULL)); + if (err_str) snprintf(err_str, _ERR_STR_LEN, "inject fail"); + } + } else if (id == CMD_ID_RM) { + char pathstr[_ERR_STR_LEN]; + TruncateString(pathstr, argv[0], 24, 8); + ShowString("Deleting %s...", pathstr); + ret = PathDelete(argv[0]); + if (err_str) snprintf(err_str, _ERR_STR_LEN, "remove fail"); + } else if (id == CMD_ID_MKDIR) { + ret = (fvx_rmkdir(argv[0]) == FR_OK); + if (err_str) snprintf(err_str, _ERR_STR_LEN, "makedir fail"); + } else if (id == CMD_ID_MOUNT) { + ret = InitImgFS(argv[0]); + if (err_str) snprintf(err_str, _ERR_STR_LEN, "mount fail"); + } else if (id == CMD_ID_UMOUNT) { + InitImgFS(NULL); + } else if (id == CMD_ID_FIND) { + char* path = set_var(argv[1], ""); // setup the variable, get pointer + if (!path) { + ret = false; + if (err_str) snprintf(err_str, _ERR_STR_LEN, "var fail"); + } else { + ret = (fvx_findpath(path, argv[0]) == FR_OK); + if (err_str) snprintf(err_str, _ERR_STR_LEN, "find fail"); + } + } else if (id == CMD_ID_FINDNOT) { + char* path = set_var(argv[1], ""); // setup the variable, get pointer + if (!path) { + ret = false; + if (err_str) snprintf(err_str, _ERR_STR_LEN, "var fail"); + } else { + ret = (fvx_findnopath(path, argv[0]) == FR_OK); + if (err_str) snprintf(err_str, _ERR_STR_LEN, "findnot fail"); + } + } else if (id == CMD_ID_SHA) { + u8 sha256_fil[0x20]; + u8 sha256_cmp[0x20]; + if (!FileGetSha256(argv[0], sha256_fil)) { + ret = false; + if (err_str) snprintf(err_str, _ERR_STR_LEN, "sha arg0 fail"); + } else if ((FileGetData(argv[1], sha256_cmp, 0x20, 0) != 0x20) && !strntohex(argv[1], sha256_cmp, 0x20)) { + ret = false; + if (err_str) snprintf(err_str, _ERR_STR_LEN, "sha arg1 fail"); + } else { + ret = (memcmp(sha256_fil, sha256_cmp, 0x20) == 0); + if (err_str) snprintf(err_str, _ERR_STR_LEN, "sha does not match"); + } + } else if (id == CMD_ID_VERIFY) { + u32 filetype = IdentifyFileType(argv[0]); + if (filetype & IMG_NAND) ret = (ValidateNandDump(argv[0]) == 0); + else ret = (VerifyGameFile(argv[0]) == 0); + if (err_str) snprintf(err_str, _ERR_STR_LEN, "verification failed"); + } else if (id == CMD_ID_REBOOT) { + Reboot(); + } else if (id == CMD_ID_POWEROFF) { + PowerOff(); + } else { // command not recognized / bad number of arguments + ret = false; + if (err_str) snprintf(err_str, _ERR_STR_LEN, "unknown error"); + } + + return ret; +} + +bool run_line(const char* line_start, const char* line_end, u32* flags, char* err_str) { + char* argv[_MAX_ARGS] = { NULL }; + u32 argc = 0; + cmd_id cmdid; + + // flags handling (if no pointer given) + u32 lflags; + if (!flags) flags = &lflags; + *flags = 0; + + // set up argv vars (if not already done) + for (u32 i = 0; i < _MAX_ARGS; i++) { + char name[16]; + snprintf(name, 16, "ARGV%01lu", i); + argv[i] = set_var(name, ""); + if (!argv[i]) return false; + } + + // parse current line, grab cmd / flags / args + if (!parse_line(line_start, line_end, &cmdid, flags, &argc, argv, err_str)) { + *flags &= ~(_FLG('o')|_FLG('s')); // parsing errors are never silent or optional + return false; + } + + // run the command (if available) + if (cmdid && !run_cmd(cmdid, *flags, argv, err_str)) { + char* msg_fail = get_var("[ERRORMSG]", NULL); + if (msg_fail && *msg_fail) *err_str = '\0'; // use custom error message + return false; + } + + // success if we arrive here + return true; +} + +bool ExecuteGM9Script(const char* path_script) { + // revert mount state? + char* script = (char*) SCRIPT_BUFFER; + char* ptr = script; + + // fetch script + u32 script_size; + if (!(script_size = FileGetData(path_script, (u8*) script, SCRIPT_MAX_SIZE, 0))) + return false; + char* end = script + script_size; + *end = '\0'; + + // initialise variables + init_vars(); + + for (u32 line = 1; ptr < end; line++) { + u32 flags = 0; + + // find line end + char* line_end = strchr(ptr, '\n'); + if (!line_end) line_end = ptr + strlen(ptr); + + // run command + char err_str[_ERR_STR_LEN+1] = { 0 }; + if (!run_line(ptr, line_end, &flags, err_str)) { // error handling + if (!(flags & _FLG('s'))) { // not silent + if (!*err_str) { + char* msg_fail = get_var("[ERRORMSG]", NULL); + if (msg_fail && *msg_fail) ShowPrompt(false, msg_fail); + else snprintf(err_str, _ERR_STR_LEN, "error message fail"); + } + if (*err_str) { + char line_str[32+1]; + char* lptr0 = ptr; + char* lptr1 = line_end; + for (; IS_WHITESPACE(*lptr0) && (lptr0 < lptr1); lptr0++); // skip whitespaces + if ((lptr1 > lptr0) && (*(lptr1-1) == '\r')) lptr1--; // handle \r + if (lptr1 - lptr0 > 32) snprintf(line_str, 32+1, "%.29s...", lptr0); + else snprintf(line_str, 32+1, "%.*s", lptr1 - lptr0, lptr0); + char path_str[32+1]; + TruncateString(path_str, path_script, 32, 12); + ShowPrompt(false, "%s\nline %lu: %s\n%s", path_str, line, err_str, line_str); + } + } + if (!(flags & _FLG('o'))) return false; // failed if not optional + } + + // reposition pointer + ptr = line_end + 1; + } + + char* msg_okay = get_var("[SUCCESSMSG]", NULL); + if (msg_okay && *msg_okay) ShowPrompt(false, msg_okay); + return true; +} diff --git a/source/filesys/fsscript.h b/source/filesys/fsscript.h new file mode 100644 index 0000000..b9ada3c --- /dev/null +++ b/source/filesys/fsscript.h @@ -0,0 +1,10 @@ +#pragma once + +#include "common.h" + +#define VAR_BUFFER_SIZE (72 * 1024) // enough for exactly 256 vars + +#define SCRIPT_EXT "gm9" +#define SCRIPT_MAX_SIZE (SCRIPT_BUFFER_SIZE-VAR_BUFFER_SIZE-1) + +bool ExecuteGM9Script(const char* path_script); diff --git a/source/filesys/fsutil.c b/source/filesys/fsutil.c index 9dba996..311f3ac 100644 --- a/source/filesys/fsutil.c +++ b/source/filesys/fsutil.c @@ -11,8 +11,8 @@ #include "ff.h" #include "ui.h" -#define SKIP_CUR (1UL<<7) -#define OVERWRITE_CUR (1UL<<8) +#define SKIP_CUR (1UL<<8) +#define OVERWRITE_CUR (1UL<<9) // Volume2Partition resolution table PARTITION VolToPart[] = { @@ -199,13 +199,9 @@ u32 FileFindData(const char* path, u8* data, u32 size_data, u32 offset_file) { return found; } -bool FileInjectFile(const char* dest, const char* orig, u32 offset, u32* flags) { +bool FileInjectFile(const char* dest, const char* orig, u64 off_dest, u64 off_orig, u64 size, u32* flags) { FIL ofile; FIL dfile; - u64 osize; - u64 dsize; - bool no_cancel = (flags && (*flags & NO_CANCEL)); - bool ret = true; if (!CheckWritePermissions(dest)) return false; if (strncmp(dest, orig, 256) == 0) { @@ -221,32 +217,41 @@ bool FileInjectFile(const char* dest, const char* orig, u32 offset, u32* flags) fvx_close(&dfile); return false; } - fvx_lseek(&dfile, offset); - fvx_lseek(&ofile, 0); - dsize = fvx_size(&dfile); - osize = f_size(&ofile); + fvx_lseek(&dfile, off_dest); + fvx_lseek(&ofile, off_orig); + if (!size && (off_orig < fvx_size(&ofile))) + size = fvx_size(&ofile) - off_orig; // check file limits - if (offset + osize > dsize) { + if (off_dest + size > fvx_size(&dfile)) { ShowPrompt(false, "Operation would write beyond end of file"); fvx_close(&dfile); fvx_close(&ofile); return false; + } else if (off_orig + size > fvx_size(&ofile)) { + ShowPrompt(false, "Not enough data in file"); + fvx_close(&dfile); + fvx_close(&ofile); + return false; } + bool ret = true; ShowProgress(0, 0, orig); - for (u64 pos = 0; (pos < osize) && ret; pos += MAIN_BUFFER_SIZE) { - UINT read_bytes = min(MAIN_BUFFER_SIZE, osize - pos); + for (u64 pos = 0; (pos < size) && ret; pos += MAIN_BUFFER_SIZE) { + UINT read_bytes = min(MAIN_BUFFER_SIZE, size - pos); UINT bytes_read = read_bytes; UINT bytes_written = read_bytes; - if (fvx_read(&ofile, MAIN_BUFFER, read_bytes, &bytes_read) != FR_OK) { - ret = false; - break; - } - if ((!ShowProgress(pos + (bytes_read / 2), osize, orig) && !no_cancel) || + if ((fvx_read(&ofile, MAIN_BUFFER, read_bytes, &bytes_read) != FR_OK) || (fvx_write(&dfile, MAIN_BUFFER, read_bytes, &bytes_written) != FR_OK) || (bytes_read != bytes_written)) ret = false; + if (ret && !ShowProgress(pos + bytes_read, size, orig)) { + if (flags && (*flags & NO_CANCEL)) { + ShowPrompt(false, "Cancel is now allowed here"); + ShowProgress(0, 0, orig); + ShowProgress(pos + bytes_read, size, orig); + } else ret = false; + } } ShowProgress(1, 1, orig); @@ -319,6 +324,7 @@ bool DirInfo(const char* path, u64* tsize, u32* tdirs, u32* tfiles) { bool PathMoveCopyRec(char* dest, char* orig, u32* flags, bool move) { bool to_virtual = GetVirtualSource(dest); + bool silent = (flags && (*flags & SILENT)); bool ret = false; // check destination write permission (special paths only) @@ -335,8 +341,7 @@ bool PathMoveCopyRec(char* dest, char* orig, u32* flags, bool move) { TruncateString(deststr, dest, 36, 8); // the copy process takes place here - bool no_cancel = (flags && (*flags & NO_CANCEL)); - if (!ShowProgress(0, 0, orig) && !no_cancel) return false; + if (!ShowProgress(0, 0, orig) && !(flags && (*flags & NO_CANCEL))) return false; if (move && fvx_stat(dest, NULL) != FR_OK) { // moving if dest not existing ret = (fvx_rename(orig, dest) == FR_OK); } else if (fno.fattrib & AM_DIR) { // processing folders (same for move & copy) @@ -346,7 +351,7 @@ bool PathMoveCopyRec(char* dest, char* orig, u32* flags, bool move) { // create the destination folder if it does not already exist if (fvx_opendir(&pdir, dest) != FR_OK) { if (fvx_mkdir(dest) != FR_OK) { - ShowPrompt(false, "%s\nError: Overwriting file with dir", deststr); + if (!silent) ShowPrompt(false, "%s\nError: Overwriting file with dir", deststr); return false; } } else fvx_closedir(&pdir); @@ -378,7 +383,7 @@ bool PathMoveCopyRec(char* dest, char* orig, u32* flags, bool move) { } else if (move) { // moving if destination exists if (fvx_stat(dest, &fno) != FR_OK) return false; if (fno.fattrib & AM_DIR) { - ShowPrompt(false, "%s\nError: Overwriting dir with file", deststr); + if (!silent) ShowPrompt(false, "%s\nError: Overwriting dir with file", deststr); return false; } if (fvx_unlink(dest) != FR_OK) return false; @@ -396,19 +401,19 @@ bool PathMoveCopyRec(char* dest, char* orig, u32* flags, bool move) { fsize = fvx_size(&ofile); if (!to_virtual && (GetFreeSpace(dest) < fsize)) { - ShowPrompt(false, "%s\nError: Not enough space in drive", deststr); + if (!silent) ShowPrompt(false, "%s\nError: Not enough space in drive", deststr); fvx_close(&ofile); return false; } if (fvx_open(&dfile, dest, FA_WRITE | FA_CREATE_ALWAYS) != FR_OK) { - ShowPrompt(false, "%s\nError: Cannot open destination file", deststr); + if (!silent) ShowPrompt(false, "%s\nError: Cannot open destination file", deststr); fvx_close(&ofile); return false; } if (to_virtual && (fvx_size(&dfile) < fsize)) { - ShowPrompt(false, "%s\nError: Not enough virtual space", deststr); + if (!silent) ShowPrompt(false, "%s\nError: Not enough virtual space", deststr); fvx_close(&ofile); fvx_close(&dfile); return false; @@ -426,9 +431,15 @@ bool PathMoveCopyRec(char* dest, char* orig, u32* flags, bool move) { UINT bytes_written = 0; if ((fvx_read(&ofile, MAIN_BUFFER, MAIN_BUFFER_SIZE, &bytes_read) != FR_OK) || (fvx_write(&dfile, MAIN_BUFFER, bytes_read, &bytes_written) != FR_OK) || - (bytes_read != bytes_written) || - (!ShowProgress(pos + bytes_read, fsize, orig) && !no_cancel)) + (bytes_read != bytes_written)) ret = false; + if (ret && !ShowProgress(pos + bytes_read, fsize, orig)) { + if (flags && (*flags & NO_CANCEL)) { + ShowPrompt(false, "Cancel is now allowed here"); + ShowProgress(0, 0, orig); + ShowProgress(pos + bytes_read, fsize, orig); + } else ret = false; + } if (flags && (*flags & CALC_SHA)) sha_update(MAIN_BUFFER, bytes_read); } @@ -529,8 +540,8 @@ bool PathMoveCopy(const char* dest, const char* orig, u32* flags, bool move) { // actual move / copy operation bool same_drv = (strncmp(lorig, ldest, 2) == 0); - bool res = PathMoveCopyRec(ldest, lorig, flags, move & same_drv); - if (move & res && (!flags || !(*flags&SKIP_CUR))) PathDelete(lorig); + bool res = PathMoveCopyRec(ldest, lorig, flags, move && same_drv); + if (move && res && (!flags || !(*flags&SKIP_CUR))) PathDelete(lorig); return res; } else { // virtual destination handling // can't write an SHA file to a virtual destination diff --git a/source/filesys/fsutil.h b/source/filesys/fsutil.h index c1e2efd..d4e7f1d 100644 --- a/source/filesys/fsutil.h +++ b/source/filesys/fsutil.h @@ -5,11 +5,12 @@ // move / copy flags #define OVERRIDE_PERM (1UL<<0) #define NO_CANCEL (1UL<<1) -#define CALC_SHA (1UL<<2) -#define BUILD_PATH (1UL<<3) -#define ASK_ALL (1UL<<4) -#define SKIP_ALL (1UL<<5) -#define OVERWRITE_ALL (1UL<<6) +#define SILENT (1UL<<2) +#define CALC_SHA (1UL<<3) +#define BUILD_PATH (1UL<<4) +#define ASK_ALL (1UL<<5) +#define SKIP_ALL (1UL<<6) +#define OVERWRITE_ALL (1UL<<7) /** Return total size of SD card **/ uint64_t GetSDCardSize(); @@ -39,7 +40,7 @@ bool FileGetSha256(const char* path, u8* sha256); u32 FileFindData(const char* path, u8* data, u32 size_data, u32 offset_file); /** Inject file into file @offset **/ -bool FileInjectFile(const char* dest, const char* orig, u32 offset, u32* flags); +bool FileInjectFile(const char* dest, const char* orig, u64 off_dest, u64 off_orig, u64 size, u32* flags); /** Create a new directory in cpath **/ bool DirCreate(const char* cpath, const char* dirname); diff --git a/source/filesys/vff.c b/source/filesys/vff.c index 022be24..096c958 100644 --- a/source/filesys/vff.c +++ b/source/filesys/vff.c @@ -325,5 +325,37 @@ FRESULT fvx_findpath (TCHAR* path, const TCHAR* pattern) { strncpy(fname, fno.fname, _MAX_FN_LEN - (fname - path)); if (!*(fno.fname)) return FR_NO_PATH; + return res; +} + +FRESULT fvx_findnopath (TCHAR* path, const TCHAR* pattern) { + strncpy(path, pattern, _MAX_FN_LEN); + TCHAR* fname = strrchr(path, '/'); + if (!fname) return FR_DENIED; + fname++; + + TCHAR* rep[16]; + u32 n_rep = 0; + for (u32 i = 0; fname[i]; i++) { + if (fname[i] == '?') { + rep[n_rep] = &(fname[i]); + *rep[n_rep++] = '0'; + } + if (n_rep >= 16) return FR_DENIED; + } + if (!n_rep) return fvx_stat(path, NULL); + + while (fvx_stat(path, NULL) == FR_OK) { + for (INT i = n_rep - 1; (i >= 0); i--) { + if (*(rep[i]) == '9') { + if (!i) return FR_NO_PATH; + *(rep[i]) = '0'; + } else { + (*(rep[i]))++; + break; + } + } + } + return FR_OK; } diff --git a/source/filesys/vff.h b/source/filesys/vff.h index 8182086..b42459a 100644 --- a/source/filesys/vff.h +++ b/source/filesys/vff.h @@ -36,4 +36,5 @@ FRESULT fvx_runlink (const TCHAR* path); // additional wildcard based functions FRESULT fvx_match_name(const TCHAR* path, const TCHAR* pattern); FRESULT fvx_preaddir (DIR* dp, FILINFO* fno, const TCHAR* pattern); -FRESULT fvx_findfile (const TCHAR* path); +FRESULT fvx_findpath (TCHAR* path, const TCHAR* pattern); +FRESULT fvx_findnopath (TCHAR* path, const TCHAR* pattern); diff --git a/source/godmode.c b/source/godmode.c index 4a1d364..232e4aa 100644 --- a/source/godmode.c +++ b/source/godmode.c @@ -6,6 +6,7 @@ #include "fsutil.h" #include "fsperm.h" #include "fsgame.h" +#include "fsscript.h" #include "gameutil.h" #include "keydbutil.h" #include "nandutil.h" @@ -686,10 +687,11 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, DirStruct* cur bool restorable = (FTYPE_RESTORABLE(filetype) && IS_A9LH && !(drvtype & DRV_SYSNAND)); bool ebackupable = (FTYPE_EBACKUP(filetype)); bool xorpadable = (FTYPE_XORPAD(filetype)); + bool scriptable = (FTYPE_SCRIPT(filetype)); bool launchable = ((FTYPE_PAYLOAD(filetype)) && (drvtype & DRV_FAT) && !IS_SIGHAX); bool special_opt = mountable || verificable || decryptable || encryptable || cia_buildable || cia_buildable_legit || cxi_dumpable || tik_buildable || key_buildable || titleinfo || renamable || transferable || hsinjectable || restorable || xorpadable || - launchable || ebackupable; + ebackupable || launchable || scriptable; char pathstr[32+1]; TruncateString(pathstr, curr_entry->path, 32, 8); @@ -733,7 +735,8 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, DirStruct* cur (filetype & BIN_KEYDB) ? "AESkeydb options..." : (filetype & BIN_LEGKEY) ? "Build " KEYDB_NAME : (filetype & BIN_NCCHNFO)? "NCCHinfo options..." : - (filetype & BIN_LAUNCH) ? "Launch as arm9 payload" : "???"; + (filetype & BIN_LAUNCH) ? "Launch as arm9 payload" : + (filetype & TXT_SCRIPT) ? "Execute GM9 script" : "???"; optionstr[hexviewer-1] = "Show in Hexeditor"; optionstr[calcsha-1] = "Calculate SHA-256"; if (calccmac > 0) optionstr[calccmac-1] = "Calculate CMAC"; @@ -801,7 +804,7 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, DirStruct* cur TruncateString(origstr, clipboard->entry[0].name, 18, 10); u64 offset = ShowHexPrompt(0, 8, "Inject data from %s?\nSpecifiy offset below.", origstr); if (offset != (u64) -1) { - if (!FileInjectFile(curr_entry->path, clipboard->entry[0].path, (u32) offset, NULL)) + if (!FileInjectFile(curr_entry->path, clipboard->entry[0].path, (u32) offset, 0, 0, NULL)) ShowPrompt(false, "Failed injecting %s", origstr); clipboard->n_entries = 0; } @@ -840,6 +843,7 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, DirStruct* cur int xorpad = (xorpadable) ? ++n_opt : -1; int xorpad_inplace = (xorpadable) ? ++n_opt : -1; int launch = (launchable) ? ++n_opt : -1; + int script = (scriptable) ? ++n_opt : -1; if (mount > 0) optionstr[mount-1] = "Mount image to drive"; if (restore > 0) optionstr[restore-1] = "Restore SysNAND (safe)"; if (ebackup > 0) optionstr[ebackup-1] = "Update embedded backup"; @@ -859,6 +863,7 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, DirStruct* cur if (xorpad > 0) optionstr[xorpad-1] = "Build XORpads (SD output)"; if (xorpad_inplace > 0) optionstr[xorpad_inplace-1] = "Build XORpads (inplace)"; if (launch > 0) optionstr[launch-1] = "Launch as ARM9 payload"; + if (script > 0) optionstr[script-1] = "Execute GM9 script"; // auto select when there is only one option user_select = (n_opt <= 1) ? n_opt : (int) ShowSelectPrompt(n_opt, optionstr, (n_marked > 1) ? @@ -1200,6 +1205,11 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, DirStruct* cur } // failed load is basically impossible here } return 0; + } else if ((user_select == script)) { + if (ShowPrompt(true, "%s\nExecute script?", pathstr)) + ShowPrompt(false, "%s\nScript execute %s", pathstr, ExecuteGM9Script(curr_entry->path) ? "success" : "failure"); + GetDirContents(current_dir, current_path); + return 0; } return FileHandlerMenu(current_path, cursor, scroll, current_dir, clipboard); diff --git a/source/main.c b/source/main.c index e12794e..7fb3384 100644 --- a/source/main.c +++ b/source/main.c @@ -1,20 +1,7 @@ #include "common.h" #include "godmode.h" #include "ui.h" -#include "i2c.h" - -void Reboot() -{ - i2cWriteRegister(I2C_DEV_MCU, 0x20, 1 << 2); - while(true); -} - - -void PowerOff() -{ - i2cWriteRegister(I2C_DEV_MCU, 0x20, 1 << 0); - while (true); -} +#include "power.h" u8 *top_screen, *bottom_screen;