2017-09-04 16:18:07 +02:00
|
|
|
#include "scripting.h"
|
2017-11-03 16:11:29 +01:00
|
|
|
#include "fs.h"
|
|
|
|
#include "utils.h"
|
2017-09-04 02:15:45 +02:00
|
|
|
#include "nand.h"
|
2017-08-21 21:33:22 +02:00
|
|
|
#include "bootfirm.h"
|
2017-09-19 15:57:29 +02:00
|
|
|
#include "qrcodegen.h"
|
2017-08-21 21:33:22 +02:00
|
|
|
#include "firm.h"
|
2017-06-09 01:45:00 +02:00
|
|
|
#include "power.h"
|
2017-09-21 01:21:02 +02:00
|
|
|
#include "unittype.h"
|
2017-11-03 16:11:29 +01:00
|
|
|
#include "region.h"
|
2017-08-04 18:21:56 +02:00
|
|
|
#include "rtc.h"
|
2017-06-23 02:13:22 +02:00
|
|
|
#include "sha.h"
|
2017-09-04 02:15:45 +02:00
|
|
|
#include "hid.h"
|
2017-06-09 01:45:00 +02:00
|
|
|
#include "ui.h"
|
2017-12-20 00:13:31 +01:00
|
|
|
#include "pcx.h"
|
2017-06-09 01:45:00 +02:00
|
|
|
|
2017-10-29 14:00:02 +09:00
|
|
|
|
|
|
|
#define _MAX_ARGS 4
|
2017-09-21 01:21:02 +02:00
|
|
|
#define _ARG_MAX_LEN 512
|
2017-06-09 01:45:00 +02:00
|
|
|
#define _VAR_CNT_LEN 256
|
|
|
|
#define _VAR_NAME_LEN 32
|
|
|
|
#define _ERR_STR_LEN 32
|
|
|
|
|
2017-12-20 02:08:40 +01:00
|
|
|
#define _CHOICE_STR_LEN 32
|
|
|
|
#define _CHOICE_MAX_N 12
|
|
|
|
|
2017-12-31 03:26:50 +01:00
|
|
|
#define _CMD_NOT "not"
|
2017-10-29 14:00:02 +09:00
|
|
|
#define _CMD_IF "if"
|
2017-12-19 02:58:58 +01:00
|
|
|
#define _CMD_ELIF "elif"
|
2017-10-29 14:00:02 +09:00
|
|
|
#define _CMD_ELSE "else"
|
|
|
|
#define _CMD_END "end"
|
|
|
|
#define _CMD_FOR "for"
|
|
|
|
#define _CMD_NEXT "next"
|
2017-12-19 02:58:58 +01:00
|
|
|
|
2017-10-29 14:00:02 +09:00
|
|
|
#define _ARG_TRUE "TRUE"
|
|
|
|
#define _ARG_FALSE "FALSE"
|
2018-01-12 03:19:04 +01:00
|
|
|
#define _VAR_FORPATH "FORPATH"
|
2017-10-29 14:00:02 +09:00
|
|
|
|
2017-12-19 02:58:58 +01:00
|
|
|
#define _SKIP_BLOCK 1
|
|
|
|
#define _SKIP_TILL_END 2
|
2018-01-12 03:19:04 +01:00
|
|
|
#define _SKIP_TO_NEXT 3
|
|
|
|
#define _SKIP_TO_FOR 4
|
2017-12-19 02:58:58 +01:00
|
|
|
|
2018-01-18 02:23:46 +01:00
|
|
|
#define _MAX_FOR_DEPTH 16
|
|
|
|
|
2017-06-09 01:45:00 +02:00
|
|
|
#define VAR_BUFFER (SCRIPT_BUFFER + SCRIPT_BUFFER_SIZE - VAR_BUFFER_SIZE)
|
|
|
|
|
2017-09-06 00:46:01 +02:00
|
|
|
// macros for textviewer
|
|
|
|
#define TV_VPAD 1 // vertical padding per line (above / below)
|
|
|
|
#define TV_HPAD 0 // horizontal padding per line (left)
|
|
|
|
#define TV_LNOS 4 // # of digits in line numbers (0 to disable)
|
|
|
|
|
|
|
|
#define TV_NLIN_DISP (SCREEN_HEIGHT / (FONT_HEIGHT_EXT + (2*TV_VPAD)))
|
|
|
|
#define TV_LLEN_DISP (((SCREEN_WIDTH_TOP - (2*TV_HPAD)) / FONT_WIDTH_EXT) - (TV_LNOS + 1))
|
|
|
|
|
2017-06-09 01:45:00 +02:00
|
|
|
// some useful macros
|
|
|
|
#define IS_WHITESPACE(c) ((c == ' ') || (c == '\t') || (c == '\r') || (c == '\n'))
|
2017-10-29 14:00:02 +09:00
|
|
|
#define MATCH_STR(s,l,c) ((l == strlen(c)) && (strncmp(s, c, l) == 0))
|
2018-02-06 00:29:21 +01:00
|
|
|
#define _FLG(c) ((c >= 'a') ? (1 << (c - 'a')) : 0)
|
2017-06-09 01:45:00 +02:00
|
|
|
|
2018-01-10 00:49:28 +01:00
|
|
|
#define IS_CTRLFLOW_CMD(id) ((id == CMD_ID_IF) || (id == CMD_ID_ELIF) || (id == CMD_ID_ELSE) || (id == CMD_ID_END) || \
|
|
|
|
(id == CMD_ID_GOTO) || (id == CMD_ID_LABELSEL) || \
|
|
|
|
(id == CMD_ID_FOR) || (id == CMD_ID_NEXT))
|
2017-12-20 02:08:40 +01:00
|
|
|
|
2017-10-29 14:00:02 +09:00
|
|
|
// command ids (also entry into the cmd_list aray below)
|
2017-06-09 01:45:00 +02:00
|
|
|
typedef enum {
|
|
|
|
CMD_ID_NONE = 0,
|
2017-12-31 03:26:50 +01:00
|
|
|
CMD_ID_NOT,
|
2017-10-29 14:00:02 +09:00
|
|
|
CMD_ID_IF,
|
2017-12-19 02:58:58 +01:00
|
|
|
CMD_ID_ELIF,
|
2017-10-29 14:00:02 +09:00
|
|
|
CMD_ID_ELSE,
|
|
|
|
CMD_ID_END,
|
2018-01-10 00:49:28 +01:00
|
|
|
CMD_ID_FOR,
|
|
|
|
CMD_ID_NEXT,
|
2017-10-29 14:00:02 +09:00
|
|
|
CMD_ID_GOTO,
|
2017-12-20 02:08:40 +01:00
|
|
|
CMD_ID_LABELSEL,
|
2017-06-09 01:45:00 +02:00
|
|
|
CMD_ID_ECHO,
|
2017-09-19 15:57:29 +02:00
|
|
|
CMD_ID_QR,
|
2017-06-09 01:45:00 +02:00
|
|
|
CMD_ID_ASK,
|
2017-07-26 14:14:12 +02:00
|
|
|
CMD_ID_INPUT,
|
2017-09-13 02:45:00 +02:00
|
|
|
CMD_ID_FILESEL,
|
2018-01-09 01:39:37 +01:00
|
|
|
CMD_ID_DIRSEL,
|
2017-06-09 01:45:00 +02:00
|
|
|
CMD_ID_SET,
|
2017-12-20 12:33:18 +09:00
|
|
|
CMD_ID_STRSPLIT,
|
2017-12-31 02:09:43 +01:00
|
|
|
CMD_ID_STRREP,
|
2017-09-21 01:21:02 +02:00
|
|
|
CMD_ID_CHK,
|
2017-06-09 01:45:00 +02:00
|
|
|
CMD_ID_ALLOW,
|
|
|
|
CMD_ID_CP,
|
|
|
|
CMD_ID_MV,
|
|
|
|
CMD_ID_INJECT,
|
2018-01-02 02:26:49 +01:00
|
|
|
CMD_ID_FILL,
|
|
|
|
CMD_ID_FDUMMY,
|
2017-06-09 01:45:00 +02:00
|
|
|
CMD_ID_RM,
|
|
|
|
CMD_ID_MKDIR,
|
|
|
|
CMD_ID_MOUNT,
|
|
|
|
CMD_ID_UMOUNT,
|
|
|
|
CMD_ID_FIND,
|
|
|
|
CMD_ID_FINDNOT,
|
|
|
|
CMD_ID_SHA,
|
2017-09-04 02:15:45 +02:00
|
|
|
CMD_ID_SHAGET,
|
2017-07-27 14:29:03 +02:00
|
|
|
CMD_ID_FIXCMAC,
|
2017-06-09 01:45:00 +02:00
|
|
|
CMD_ID_VERIFY,
|
2017-07-27 14:29:03 +02:00
|
|
|
CMD_ID_DECRYPT,
|
|
|
|
CMD_ID_ENCRYPT,
|
|
|
|
CMD_ID_BUILDCIA,
|
2017-09-08 15:27:10 +02:00
|
|
|
CMD_ID_EXTRCODE,
|
2018-01-12 02:52:39 +01:00
|
|
|
CMD_ID_ISDIR,
|
|
|
|
CMD_ID_EXIST,
|
2017-08-21 19:56:30 +02:00
|
|
|
CMD_ID_BOOT,
|
2017-09-04 02:15:45 +02:00
|
|
|
CMD_ID_SWITCHSD,
|
2017-06-09 01:45:00 +02:00
|
|
|
CMD_ID_REBOOT,
|
2017-09-19 15:57:29 +02:00
|
|
|
CMD_ID_POWEROFF,
|
|
|
|
CMD_ID_BKPT
|
2017-06-09 01:45:00 +02:00
|
|
|
} 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[] = {
|
2017-10-29 14:00:02 +09:00
|
|
|
{ CMD_ID_NONE , "#" , 0, 0 }, // dummy entry
|
2017-12-31 03:26:50 +01:00
|
|
|
{ CMD_ID_NOT , _CMD_NOT , 0, 0 }, // inverts the output of the following command
|
2017-10-29 14:00:02 +09:00
|
|
|
{ CMD_ID_IF , _CMD_IF , 1, 0 }, // control flow commands at the top of the list
|
2017-12-19 02:58:58 +01:00
|
|
|
{ CMD_ID_ELIF , _CMD_ELIF , 1, 0 },
|
2017-10-29 14:00:02 +09:00
|
|
|
{ CMD_ID_ELSE , _CMD_ELSE , 0, 0 },
|
|
|
|
{ CMD_ID_END , _CMD_END , 0, 0 },
|
2018-01-18 02:23:46 +01:00
|
|
|
{ CMD_ID_FOR , _CMD_FOR , 2, _FLG('r') },
|
2018-01-10 00:49:28 +01:00
|
|
|
{ CMD_ID_NEXT , _CMD_NEXT , 0, 0 },
|
2017-10-29 14:00:02 +09:00
|
|
|
{ CMD_ID_GOTO , "goto" , 1, 0 },
|
2017-12-20 02:08:40 +01:00
|
|
|
{ CMD_ID_LABELSEL, "labelsel", 2, 0 },
|
2017-06-09 01:45:00 +02:00
|
|
|
{ CMD_ID_ECHO , "echo" , 1, 0 },
|
2017-09-19 15:57:29 +02:00
|
|
|
{ CMD_ID_QR , "qr" , 2, 0 },
|
2017-06-09 01:45:00 +02:00
|
|
|
{ CMD_ID_ASK , "ask" , 1, 0 },
|
2017-07-26 14:14:12 +02:00
|
|
|
{ CMD_ID_INPUT , "input" , 2, 0 },
|
2018-01-02 01:02:09 +01:00
|
|
|
{ CMD_ID_FILESEL , "filesel" , 3, _FLG('d') },
|
2018-01-09 01:39:37 +01:00
|
|
|
{ CMD_ID_DIRSEL , "dirsel" , 3, 0 },
|
2017-06-09 01:45:00 +02:00
|
|
|
{ CMD_ID_SET , "set" , 2, 0 },
|
2017-12-20 12:33:18 +09:00
|
|
|
{ CMD_ID_STRSPLIT, "strsplit", 3, _FLG('b') | _FLG('f')},
|
2017-12-31 02:09:43 +01:00
|
|
|
{ CMD_ID_STRREP , "strrep" , 3, 0 },
|
2017-09-21 01:21:02 +02:00
|
|
|
{ CMD_ID_CHK , "chk" , 2, _FLG('u') },
|
2017-06-09 01:45:00 +02:00
|
|
|
{ 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') },
|
2018-01-02 02:26:49 +01:00
|
|
|
{ CMD_ID_FILL , "fill" , 2, 0 },
|
|
|
|
{ CMD_ID_FDUMMY , "fdummy" , 2, 0 },
|
2017-06-09 01:45:00 +02:00
|
|
|
{ CMD_ID_RM , "rm" , 1, 0 },
|
|
|
|
{ CMD_ID_MKDIR , "mkdir" , 1, 0 },
|
2017-07-11 20:54:30 +02:00
|
|
|
{ CMD_ID_MOUNT , "imgmount", 1, 0 },
|
|
|
|
{ CMD_ID_UMOUNT , "imgumount",0, 0 },
|
2017-09-06 02:01:42 +02:00
|
|
|
{ CMD_ID_FIND , "find" , 2, _FLG('f') },
|
2017-06-09 01:45:00 +02:00
|
|
|
{ CMD_ID_FINDNOT , "findnot" , 2, 0 },
|
|
|
|
{ CMD_ID_SHA , "sha" , 2, 0 },
|
2017-09-04 02:15:45 +02:00
|
|
|
{ CMD_ID_SHAGET , "shaget" , 2, 0 },
|
2017-08-21 19:56:30 +02:00
|
|
|
{ CMD_ID_FIXCMAC , "fixcmac" , 1, 0 },
|
2017-06-09 01:45:00 +02:00
|
|
|
{ CMD_ID_VERIFY , "verify" , 1, 0 },
|
2017-07-27 14:29:03 +02:00
|
|
|
{ CMD_ID_DECRYPT , "decrypt" , 1, 0 },
|
|
|
|
{ CMD_ID_ENCRYPT , "encrypt" , 1, 0 },
|
|
|
|
{ CMD_ID_BUILDCIA, "buildcia", 1, _FLG('l') },
|
2017-09-08 15:27:10 +02:00
|
|
|
{ CMD_ID_EXTRCODE, "extrcode", 2, 0 },
|
2018-01-12 02:52:39 +01:00
|
|
|
{ CMD_ID_ISDIR, "isdir" , 1, 0 },
|
|
|
|
{ CMD_ID_EXIST, "exist" , 1, 0 },
|
2017-08-21 21:33:22 +02:00
|
|
|
{ CMD_ID_BOOT , "boot" , 1, 0 },
|
2017-09-04 02:15:45 +02:00
|
|
|
{ CMD_ID_SWITCHSD, "switchsd", 1, 0 },
|
2017-06-09 01:45:00 +02:00
|
|
|
{ CMD_ID_REBOOT , "reboot" , 0, 0 },
|
2017-09-19 15:57:29 +02:00
|
|
|
{ CMD_ID_POWEROFF, "poweroff", 0, 0 },
|
|
|
|
{ CMD_ID_BKPT , "bkpt" , 0, 0 }
|
2017-09-16 17:08:26 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
// global vars for preview
|
|
|
|
static u32 preview_mode = 0; // 0 -> off 1 -> quick 2 -> full
|
2017-09-25 00:50:44 +02:00
|
|
|
static u32 script_color_active = 0;
|
|
|
|
static u32 script_color_comment = 0;
|
|
|
|
static u32 script_color_code = 0;
|
2017-06-09 01:45:00 +02:00
|
|
|
|
2017-10-29 14:00:02 +09:00
|
|
|
// global vars for control flow
|
|
|
|
static bool syntax_error = false; // if true, severe error, script has to stop
|
|
|
|
static char* jump_ptr = NULL; // next position after a jump
|
2018-01-12 03:19:04 +01:00
|
|
|
static char* for_ptr = NULL; // position of the active 'for' command
|
2017-12-19 02:58:58 +01:00
|
|
|
static u32 skip_state = 0; // zero, _SKIP_BLOCK, _SKIP_TILL_END
|
2017-10-29 14:00:02 +09:00
|
|
|
static u32 ifcnt = 0; // current # of 'if' nesting
|
|
|
|
|
|
|
|
|
2018-01-03 02:21:56 +01:00
|
|
|
static inline bool isntrboot(void) {
|
|
|
|
// taken over from Luma 3DS:
|
|
|
|
// https://github.com/AuroraWright/Luma3DS/blob/bb5518b0f68d89bcd8efaf326355a770d5e57856/source/main.c#L58-L62
|
|
|
|
const vu8 *bootMediaStatus = (const vu8 *) 0x1FFFE00C;
|
|
|
|
const vu32 *bootPartitionsStatus = (const vu32 *) 0x1FFFE010;
|
|
|
|
|
|
|
|
// shell closed, no error booting NTRCARD, NAND partitions not even considered
|
|
|
|
return (bootMediaStatus[3] == 2) && !bootMediaStatus[1] && !bootPartitionsStatus[0] && !bootPartitionsStatus[1];
|
|
|
|
}
|
|
|
|
|
2017-07-17 19:26:03 +01:00
|
|
|
static inline bool strntohex(const char* str, u8* hex, u32 len) {
|
2017-06-09 01:45:00 +02:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2017-09-06 00:46:01 +02:00
|
|
|
static inline u32 line_len(const char* text, u32 len, u32 ww, const char* line) {
|
|
|
|
char* line0 = (char*) line;
|
|
|
|
char* line1 = (char*) line;
|
|
|
|
u32 llen = 0;
|
|
|
|
|
|
|
|
// non wordwrapped length
|
|
|
|
while ((line1 < (text + len)) && (*line1 != '\n') && *line1) line1++;
|
|
|
|
while ((line1 > line0) && (*(line1-1) <= ' ')) line1--;
|
|
|
|
llen = line1 - line0;
|
|
|
|
if (ww && (llen > ww)) { // wordwrapped length
|
|
|
|
for (llen = ww; (llen > 0) && (line[llen] != ' '); llen--);
|
|
|
|
if (!llen) llen = ww; // workaround for long strings
|
|
|
|
}
|
|
|
|
return llen;
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline char* line_seek(const char* text, u32 len, u32 ww, const char* line, int add) {
|
|
|
|
// safety checks /
|
|
|
|
if (line < text) return NULL;
|
|
|
|
if ((line >= (text + len)) && (add >= 0)) return (char*) line;
|
|
|
|
|
|
|
|
if (!ww) { // non wordwrapped mode
|
|
|
|
char* lf = ((char*) line - 1);
|
|
|
|
|
|
|
|
// ensure we are at the start of the line
|
|
|
|
while ((lf > text) && (*lf != '\n')) lf--;
|
|
|
|
|
|
|
|
// handle backwards search
|
|
|
|
for (; (add < 0) && (lf >= text); add++)
|
|
|
|
for (lf--; (lf >= text) && (*lf != '\n'); lf--);
|
|
|
|
|
|
|
|
// handle forwards search
|
|
|
|
for (; (add > 0) && (lf < text + len); add--)
|
|
|
|
for (lf++; (lf < text + len) && (*lf != '\n'); lf++);
|
|
|
|
|
|
|
|
return lf + 1;
|
|
|
|
} else { // wordwrapped mode
|
|
|
|
char* l0 = (char*) line;
|
|
|
|
|
|
|
|
// handle forwards wordwrapped search
|
|
|
|
while ((add > 0) && (l0 < text + len)) {
|
|
|
|
u32 llen = line_len(text, len, 0, l0);
|
|
|
|
for (; (add > 0) && (llen > ww); add--) {
|
|
|
|
u32 llenww = line_len(text, len, ww, l0);
|
|
|
|
llen -= llenww;
|
|
|
|
l0 += llenww;
|
|
|
|
}
|
|
|
|
if (add > 0) {
|
|
|
|
l0 = line_seek(text, len, 0, l0, 1);
|
|
|
|
add--;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// handle backwards wordwrapped search
|
|
|
|
while ((add < 0) && (l0 > text)) {
|
|
|
|
char* l1 = line_seek(text, len, 0, l0, -1);
|
|
|
|
int nlww = 0; // count wordwrapped lines in paragraph
|
|
|
|
for (char* ld = l1; ld < l0; ld = line_seek(text, len, ww, ld, 1), nlww++);
|
|
|
|
if (add + nlww < 0) {
|
|
|
|
add += nlww;
|
|
|
|
l0 = l1;
|
|
|
|
} else {
|
|
|
|
l0 = line_seek(text, len, ww, l1, nlww + add);
|
|
|
|
add = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return l0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-10-29 14:00:02 +09:00
|
|
|
static inline u32 get_lno(const char* text, u32 len, const char* line) {
|
|
|
|
u32 lno = 1;
|
|
|
|
|
|
|
|
for (u32 i = 0; i < len; i++) {
|
|
|
|
if (line <= text + i) return lno;
|
|
|
|
else if (text[i] == '\n') lno++;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2017-09-16 17:08:26 +02:00
|
|
|
void set_preview(const char* name, const char* content) {
|
|
|
|
if (strncmp(name, "PREVIEW_MODE", _VAR_NAME_LEN) == 0) {
|
2017-12-20 00:13:31 +01:00
|
|
|
if (strncasecmp(content, "quick", _VAR_CNT_LEN) == 0) preview_mode = 1;
|
2017-09-16 17:08:26 +02:00
|
|
|
else if (strncasecmp(content, "full", _VAR_CNT_LEN) == 0) preview_mode = 2;
|
2017-12-20 00:13:31 +01:00
|
|
|
else preview_mode = 0xFF; // unknown preview mode
|
2017-09-16 17:08:26 +02:00
|
|
|
} else if (strncmp(name, "PREVIEW_COLOR_ACTIVE", _VAR_NAME_LEN) == 0) {
|
|
|
|
u8 rgb[4] = { 0 };
|
|
|
|
if (strntohex(content, rgb, 3))
|
2017-09-25 00:50:44 +02:00
|
|
|
script_color_active = getle32(rgb);
|
2017-09-16 17:08:26 +02:00
|
|
|
} else if (strncmp(name, "PREVIEW_COLOR_COMMENT", _VAR_NAME_LEN) == 0) {
|
|
|
|
u8 rgb[4] = { 0 };
|
|
|
|
if (strntohex(content, rgb, 3))
|
2017-09-25 00:50:44 +02:00
|
|
|
script_color_comment = getle32(rgb);
|
2017-09-16 17:08:26 +02:00
|
|
|
} else if (strncmp(name, "PREVIEW_COLOR_CODE", _VAR_NAME_LEN) == 0) {
|
|
|
|
u8 rgb[4] = { 0 };
|
|
|
|
if (strntohex(content, rgb, 3))
|
2017-09-25 00:50:44 +02:00
|
|
|
script_color_code = getle32(rgb);
|
2017-09-16 17:08:26 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-08-24 15:46:36 +02:00
|
|
|
char* set_var(const char* name, const char* content) {
|
2017-06-09 01:45:00 +02:00
|
|
|
Gm9ScriptVar* vars = (Gm9ScriptVar*) VAR_BUFFER;
|
|
|
|
u32 max_vars = VAR_BUFFER_SIZE / sizeof(Gm9ScriptVar);
|
|
|
|
|
2017-08-24 15:46:36 +02:00
|
|
|
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;
|
2017-06-09 01:45:00 +02:00
|
|
|
|
|
|
|
u32 n_var = 0;
|
2017-08-24 15:46:36 +02:00
|
|
|
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
|
|
|
|
|
2017-09-16 17:08:26 +02:00
|
|
|
// update preview stuff
|
|
|
|
set_preview(name, content);
|
|
|
|
|
2017-08-24 15:46:36 +02:00
|
|
|
return vars[n_var].content;
|
|
|
|
}
|
|
|
|
|
|
|
|
void upd_var(const char* name) {
|
2017-10-25 15:17:09 +01:00
|
|
|
// device serial / region
|
|
|
|
if (!name || (strncmp(name, "SERIAL", _VAR_NAME_LEN) == 0) ||
|
|
|
|
(strncmp(name, "REGION", _VAR_NAME_LEN) == 0)) {
|
|
|
|
u8 secinfo_data[1 + 1 + 16] = { 0 };
|
|
|
|
char* env_serial = (char*) secinfo_data + 2;
|
|
|
|
char env_region[3 + 1];
|
|
|
|
|
|
|
|
snprintf(env_region, 0x4, "UNK");
|
|
|
|
if ((FileGetData("1:/rw/sys/SecureInfo_A", secinfo_data, 0x11, 0x100) != 0x11) &&
|
|
|
|
(FileGetData("1:/rw/sys/SecureInfo_B", secinfo_data, 0x11, 0x100) != 0x11))
|
2017-08-24 15:46:36 +02:00
|
|
|
snprintf(env_serial, 0xF, "UNKNOWN");
|
2017-10-25 15:17:09 +01:00
|
|
|
else if (*secinfo_data < SMDH_NUM_REGIONS)
|
2017-10-29 14:00:02 +09:00
|
|
|
strncpy(env_region, g_regionNamesShort[*secinfo_data], countof(env_region));
|
2017-10-25 15:17:09 +01:00
|
|
|
|
2017-08-24 15:46:36 +02:00
|
|
|
set_var("SERIAL", env_serial);
|
2017-10-25 15:17:09 +01:00
|
|
|
set_var("REGION", env_region);
|
2017-06-09 01:45:00 +02:00
|
|
|
}
|
|
|
|
|
2017-08-24 15:46:36 +02:00
|
|
|
// device sysnand / emunand id0
|
|
|
|
for (u32 emu = 0; emu <= 1; emu++) {
|
|
|
|
const char* env_id0_name = (emu) ? "EMUID0" : "SYSID0";
|
|
|
|
if (!name || (strncmp(name, env_id0_name, _VAR_NAME_LEN) == 0)) {
|
|
|
|
const char* path = emu ? "4:/private/movable.sed" : "1:/private/movable.sed";
|
|
|
|
char env_id0[32+1];
|
|
|
|
u8 sd_keyy[0x10];
|
|
|
|
if (FileGetData(path, sd_keyy, 0x10, 0x110) == 0x10) {
|
|
|
|
u32 sha256sum[8];
|
|
|
|
sha_quick(sha256sum, sd_keyy, 0x10, SHA256_MODE);
|
|
|
|
snprintf(env_id0, 32+1, "%08lx%08lx%08lx%08lx",
|
|
|
|
sha256sum[0], sha256sum[1], sha256sum[2], sha256sum[3]);
|
|
|
|
} else snprintf(env_id0, 0xF, "UNKNOWN");
|
|
|
|
set_var(env_id0_name, env_id0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// datestamp & timestamp
|
|
|
|
if (!name || (strncmp(name, "DATESTAMP", _VAR_NAME_LEN) == 0) || (strncmp(name, "TIMESTAMP", _VAR_NAME_LEN) == 0)) {
|
2017-08-04 18:21:56 +02:00
|
|
|
DsTime dstime;
|
|
|
|
get_dstime(&dstime);
|
2017-08-24 15:46:36 +02:00
|
|
|
char env_date[16+1];
|
|
|
|
char env_time[16+1];
|
2018-02-06 00:29:21 +01:00
|
|
|
snprintf(env_date, 16, "%02lX%02lX%02lX", (u32) dstime.bcd_Y, (u32) dstime.bcd_M, (u32) dstime.bcd_D);
|
|
|
|
snprintf(env_time, 16, "%02lX%02lX%02lX", (u32) dstime.bcd_h, (u32) dstime.bcd_m, (u32) dstime.bcd_s);
|
2017-08-24 15:46:36 +02:00
|
|
|
if (!name || (strncmp(name, "DATESTAMP", _VAR_NAME_LEN) == 0)) set_var("DATESTAMP", env_date);
|
|
|
|
if (!name || (strncmp(name, "TIMESTAMP", _VAR_NAME_LEN) == 0)) set_var("TIMESTAMP", env_time);
|
2017-08-04 18:21:56 +02:00
|
|
|
}
|
2017-06-09 01:45:00 +02:00
|
|
|
}
|
|
|
|
|
2017-08-24 15:46:36 +02:00
|
|
|
char* get_var(const char* name, char** endptr) {
|
2017-06-09 01:45:00 +02:00
|
|
|
Gm9ScriptVar* vars = (Gm9ScriptVar*) VAR_BUFFER;
|
|
|
|
u32 max_vars = VAR_BUFFER_SIZE / sizeof(Gm9ScriptVar);
|
|
|
|
|
2017-08-24 15:46:36 +02:00
|
|
|
u32 name_len = 0;
|
|
|
|
char* pname = NULL;
|
|
|
|
if (!endptr) { // no endptr, varname is verbatim
|
|
|
|
pname = (char*) name;
|
|
|
|
name_len = strnlen(pname, _VAR_NAME_LEN);
|
|
|
|
} else { // endptr given, varname is in [VAR] format
|
|
|
|
pname = (char*) name + 1;
|
|
|
|
if (*name != '[') return NULL;
|
|
|
|
for (name_len = 0; pname[name_len] != ']'; name_len++)
|
|
|
|
if ((name_len >= _VAR_NAME_LEN) || !pname[name_len]) return NULL;
|
|
|
|
*endptr = pname + name_len + 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
char vname[_VAR_NAME_LEN];
|
|
|
|
strncpy(vname, pname, name_len);
|
2017-12-26 18:24:25 +01:00
|
|
|
vname[name_len] = '\0';
|
2017-08-24 15:46:36 +02:00
|
|
|
upd_var(vname); // handle dynamic env vars
|
2017-06-09 01:45:00 +02:00
|
|
|
|
|
|
|
u32 n_var = 0;
|
2017-08-24 15:46:36 +02:00
|
|
|
for (Gm9ScriptVar* var = vars; n_var < max_vars; n_var++, var++) {
|
2017-12-26 18:24:25 +01:00
|
|
|
if (!*(var->name) || (strncmp(var->name, vname, _VAR_NAME_LEN) == 0)) break;
|
2017-08-24 15:46:36 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if (n_var >= max_vars || !*(vars[n_var].name)) n_var = 0;
|
2017-06-09 01:45:00 +02:00
|
|
|
|
|
|
|
return vars[n_var].content;
|
|
|
|
}
|
|
|
|
|
2017-07-26 14:08:29 +02:00
|
|
|
bool init_vars(const char* path_script) {
|
2017-06-09 01:45:00 +02:00
|
|
|
// reset var buffer
|
|
|
|
memset(VAR_BUFFER, 0x00, VAR_BUFFER_SIZE);
|
|
|
|
|
2017-07-26 14:08:29 +02:00
|
|
|
// current path
|
|
|
|
char curr_dir[_VAR_CNT_LEN];
|
2017-09-21 01:21:02 +02:00
|
|
|
if (path_script) {
|
|
|
|
strncpy(curr_dir, path_script, _VAR_CNT_LEN);
|
|
|
|
char* slash = strrchr(curr_dir, '/');
|
|
|
|
if (slash) *slash = '\0';
|
|
|
|
} else strncpy(curr_dir, "(null)", _VAR_CNT_LEN);
|
2017-07-26 14:08:29 +02:00
|
|
|
|
2017-06-23 02:13:22 +02:00
|
|
|
// set env vars
|
2017-06-09 01:45:00 +02:00
|
|
|
set_var("NULL", ""); // this one is special and should not be changed later
|
2017-08-24 15:46:36 +02:00
|
|
|
set_var("CURRDIR", curr_dir); // script path, never changes
|
|
|
|
set_var("GM9OUT", OUTPUT_PATH); // output path, never changes
|
2018-01-03 02:21:56 +01:00
|
|
|
set_var("HAX", IS_SIGHAX ? (isntrboot() ? "ntrboot" : "sighax") : IS_A9LH ? "a9lh" : ""); // type of hax running from
|
2017-09-21 01:21:02 +02:00
|
|
|
set_var("ONTYPE", IS_O3DS ? "O3DS" : "N3DS"); // type of the console
|
|
|
|
set_var("RDTYPE", IS_DEVKIT ? "devkit" : "retail"); // devkit / retail
|
2018-01-18 15:55:37 +01:00
|
|
|
char* ptr = set_var("GM9VER", VERSION); // GodMode9 version, truncated below
|
2018-01-18 15:13:24 +01:00
|
|
|
while (*(ptr++) != '\0') if (*ptr == '-') *ptr = '\0';
|
2017-08-24 15:46:36 +02:00
|
|
|
upd_var(NULL); // set all dynamic environment vars
|
2017-06-09 01:45:00 +02:00
|
|
|
|
|
|
|
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;
|
2017-09-21 01:21:02 +02:00
|
|
|
if (out_len >= (_ARG_MAX_LEN-1)) return false; // maximum arglen reached
|
2017-06-09 01:45:00 +02:00
|
|
|
|
|
|
|
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;
|
|
|
|
|
2017-10-29 14:00:02 +09:00
|
|
|
return CMD_ID_NONE;
|
2017-06-09 01:45:00 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
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';
|
2017-12-20 12:33:18 +09:00
|
|
|
else if (strncmp(str, "--before", len) == 0) flag_char = 'b';
|
2018-01-02 01:02:09 +01:00
|
|
|
else if (strncmp(str, "--include_dirs", len) == 0) flag_char = 'd';
|
2017-09-06 02:01:42 +02:00
|
|
|
else if (strncmp(str, "--first", len) == 0) flag_char = 'f';
|
2017-06-09 01:45:00 +02:00
|
|
|
else if (strncmp(str, "--hash", len) == 0) flag_char = 'h';
|
|
|
|
else if (strncmp(str, "--skip", len) == 0) flag_char = 'k';
|
2017-07-27 14:29:03 +02:00
|
|
|
else if (strncmp(str, "--legit", len) == 0) flag_char = 'l';
|
2017-06-09 01:45:00 +02:00
|
|
|
else if (strncmp(str, "--no_cancel", len) == 0) flag_char = 'n';
|
|
|
|
else if (strncmp(str, "--optional", len) == 0) flag_char = 'o';
|
2018-01-18 02:23:46 +01:00
|
|
|
else if (strncmp(str, "--recursive", len) == 0) flag_char = 'r';
|
2017-06-09 01:45:00 +02:00
|
|
|
else if (strncmp(str, "--silent", len) == 0) flag_char = 's';
|
2017-09-21 01:21:02 +02:00
|
|
|
else if (strncmp(str, "--unequal", len) == 0) flag_char = 'u';
|
2017-06-09 01:45:00 +02:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2017-10-29 14:00:02 +09:00
|
|
|
char* skip_block(char* ptr, bool ignore_else, bool stop_after_end) {
|
|
|
|
while (*ptr) {
|
|
|
|
// store line start / line end
|
|
|
|
char* line_start = ptr;
|
|
|
|
char* line_end = strchr(ptr, '\n');
|
|
|
|
if (!line_end) line_end = ptr + strlen(ptr);
|
|
|
|
|
|
|
|
// grab first string
|
|
|
|
char* str = NULL;
|
|
|
|
u32 str_len = 0;
|
|
|
|
if (!(str = get_string(ptr, line_end, &str_len, &ptr, NULL)) || (str >= line_end)) {
|
|
|
|
// string error or empty line
|
|
|
|
ptr = line_end + 1;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// check string
|
|
|
|
if (MATCH_STR(str, str_len, _CMD_END)) { // stop at end
|
|
|
|
return line_start; // end of block found
|
|
|
|
} else if (!ignore_else && MATCH_STR(str, str_len, _CMD_ELSE)) { // stop at else
|
|
|
|
return line_start; // end of block found
|
2017-12-19 02:58:58 +01:00
|
|
|
} else if (!ignore_else && MATCH_STR(str, str_len, _CMD_ELIF)) { // stop at elif
|
|
|
|
return line_start; // end of block found
|
2017-10-29 14:00:02 +09:00
|
|
|
} else if (MATCH_STR(str, str_len, _CMD_IF)) {
|
|
|
|
ptr = line_start = skip_block(line_end + 1, true, false);
|
|
|
|
if (ptr == NULL) return NULL;
|
|
|
|
|
|
|
|
line_end = strchr(ptr, '\n');
|
|
|
|
if (!line_end) line_end = ptr + strlen(ptr);
|
|
|
|
|
|
|
|
str = get_string(ptr, line_end, &str_len, &ptr, NULL);
|
|
|
|
if (!(MATCH_STR(str, str_len, _CMD_END))) return NULL;
|
|
|
|
if (stop_after_end) return line_end + 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
// move on to the next line
|
|
|
|
ptr = line_end + 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
// end of block not found
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2018-01-12 03:19:04 +01:00
|
|
|
char* find_next(char* ptr) {
|
|
|
|
while (ptr && *ptr) {
|
|
|
|
// store line start / line end
|
|
|
|
char* line_start = ptr;
|
|
|
|
char* line_end = strchr(ptr, '\n');
|
|
|
|
if (!line_end) line_end = ptr + strlen(ptr);
|
|
|
|
|
|
|
|
// grab first string
|
|
|
|
char* str = NULL;
|
|
|
|
u32 str_len = 0;
|
|
|
|
if (!(str = get_string(ptr, line_end, &str_len, &ptr, NULL)) || (str >= line_end)) {
|
|
|
|
// string error or empty line
|
|
|
|
ptr = line_end + 1;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// check string
|
|
|
|
if (MATCH_STR(str, str_len, _CMD_IF)) { // skip 'if' blocks
|
|
|
|
ptr = skip_block(ptr, true, true);
|
|
|
|
} else if (MATCH_STR(str, str_len, _CMD_END) || MATCH_STR(str, str_len, _CMD_FOR)) {
|
|
|
|
ptr = NULL; // this should not happen here
|
|
|
|
} else if (MATCH_STR(str, str_len, _CMD_NEXT)) {
|
|
|
|
return line_start;
|
|
|
|
}
|
|
|
|
|
|
|
|
// move on to the next line
|
|
|
|
ptr = line_end + 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
// 'next' not found
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2017-10-29 14:00:02 +09:00
|
|
|
char* find_label(const char* label, const char* last_found) {
|
|
|
|
char* script = (char*) SCRIPT_BUFFER; // equals global, not a good solution
|
|
|
|
char* ptr = script;
|
|
|
|
|
|
|
|
if (last_found) {
|
|
|
|
ptr = strchr(last_found, '\n');
|
|
|
|
if (!ptr) return NULL;
|
|
|
|
ptr++;
|
|
|
|
}
|
|
|
|
|
|
|
|
char* next = ptr;
|
|
|
|
for (; next && *ptr; ptr = next) {
|
|
|
|
// store line start / get line end
|
|
|
|
char* line_start = ptr;
|
|
|
|
char* line_end = strchr(ptr, '\n');
|
|
|
|
if (!line_end) line_end = ptr + strlen(ptr);
|
|
|
|
next = line_end + 1;
|
|
|
|
|
|
|
|
// search for label
|
|
|
|
char* str = NULL;
|
|
|
|
u32 str_len = 0;
|
|
|
|
if (!(str = get_string(ptr, line_end, &str_len, &ptr, NULL))) continue; // string error, ignore line
|
|
|
|
else if (str >= line_end) continue; // empty line
|
|
|
|
|
|
|
|
if (*str == '@') {
|
|
|
|
// label found
|
|
|
|
str++; str_len--;
|
|
|
|
|
|
|
|
// compare it manually (also check for '*' at end)
|
|
|
|
u32 pdiff = 0;
|
|
|
|
for (; (pdiff < str_len) && (label[pdiff] == str[pdiff]); pdiff++);
|
|
|
|
if ((pdiff != str_len) && (label[pdiff] != '*')) continue; // no match
|
|
|
|
// otherwise: potential regular or wildcard match
|
|
|
|
|
|
|
|
// may be a match, see if there are more strings after it
|
|
|
|
if (!(str = get_string(ptr, line_end, &str_len, &ptr, NULL))) continue; // string error, ignore line
|
|
|
|
else if ((str < line_end) && (*str != '#')) continue; // neither end of line nor comment
|
|
|
|
|
|
|
|
return line_start; // match found
|
|
|
|
} else if (MATCH_STR(str, str_len, _CMD_IF)) {
|
|
|
|
next = skip_block(line_start, true, true);
|
2018-01-12 03:19:04 +01:00
|
|
|
} else if (MATCH_STR(str, str_len, _CMD_FOR)) {
|
|
|
|
next = find_next(line_start);
|
2017-12-19 02:58:58 +01:00
|
|
|
} // otherwise: irrelevant line
|
2017-10-29 14:00:02 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2018-01-18 02:23:46 +01:00
|
|
|
bool for_handler(char* path, const char* dir, const char* pattern, bool recursive) {
|
|
|
|
static DIR fdir[_MAX_FOR_DEPTH];
|
2018-01-12 03:19:04 +01:00
|
|
|
static DIR* dp = NULL;
|
|
|
|
static char ldir[256];
|
|
|
|
static char lpattern[64];
|
2018-01-18 02:23:46 +01:00
|
|
|
static bool rec = false;
|
2018-01-12 03:19:04 +01:00
|
|
|
|
2018-01-18 02:23:46 +01:00
|
|
|
if (!path && !dir && !pattern) { // close all dirs
|
|
|
|
while (dp >= fdir) fvx_closedir(dp--);
|
2018-01-12 03:19:04 +01:00
|
|
|
dp = NULL;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2018-01-18 02:23:46 +01:00
|
|
|
if (dir) { // open a dir
|
2018-01-12 03:19:04 +01:00
|
|
|
snprintf(lpattern, 64, pattern);
|
|
|
|
snprintf(ldir, 256, dir);
|
|
|
|
if (dp) return false; // <- this should never happen
|
2018-01-18 02:23:46 +01:00
|
|
|
if (fvx_opendir(&fdir[0], dir) != FR_OK)
|
2018-01-12 03:19:04 +01:00
|
|
|
return false;
|
2018-01-18 02:23:46 +01:00
|
|
|
dp = &fdir[0];
|
|
|
|
rec = recursive;
|
|
|
|
} else if (dp) { // traverse dir
|
2018-01-12 03:19:04 +01:00
|
|
|
FILINFO fno;
|
2018-01-18 02:23:46 +01:00
|
|
|
while ((fvx_preaddir(dp, &fno, lpattern) != FR_OK) || !*(fno.fname)) {
|
|
|
|
*path = '\0';
|
|
|
|
if (dp == fdir) return true;
|
|
|
|
fvx_closedir(dp--);
|
|
|
|
char* slash = strrchr(ldir, '/');
|
|
|
|
if (!slash) return false;
|
|
|
|
*slash = '\0';
|
|
|
|
}
|
|
|
|
|
|
|
|
snprintf(path, 256, "%s/%.254s", ldir, fno.fname);
|
|
|
|
if (rec && (fno.fattrib & AM_DIR) && (dp - fdir < _MAX_FOR_DEPTH - 1)) {
|
|
|
|
if (fvx_opendir(++dp, path) != FR_OK) dp--;
|
|
|
|
else strncpy(ldir, path, 255);
|
|
|
|
}
|
2018-01-12 03:19:04 +01:00
|
|
|
} else return false;
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2017-06-09 01:45:00 +02:00
|
|
|
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
|
2017-10-29 14:00:02 +09:00
|
|
|
if ((cmd >= line_end) || (*cmd == '#') || (*cmd == '@')) return true; // empty line or comment or label
|
|
|
|
|
2017-12-31 03:26:50 +01:00
|
|
|
// special handling for "if", "elif" and "not"
|
|
|
|
if (MATCH_STR(cmd, cmd_len, _CMD_NOT)) {
|
|
|
|
*cmdid = CMD_ID_NOT;
|
|
|
|
return true;
|
|
|
|
} else if (MATCH_STR(cmd, cmd_len, _CMD_IF)) {
|
2017-10-29 14:00:02 +09:00
|
|
|
*cmdid = CMD_ID_IF;
|
|
|
|
return true;
|
2017-12-19 02:58:58 +01:00
|
|
|
} else if (MATCH_STR(cmd, cmd_len, _CMD_ELIF)) {
|
|
|
|
*cmdid = CMD_ID_ELIF;
|
|
|
|
return true;
|
2017-10-29 14:00:02 +09:00
|
|
|
}
|
2017-06-09 01:45:00 +02:00
|
|
|
|
|
|
|
// 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
|
|
|
|
|
2017-08-23 02:04:53 +02:00
|
|
|
// process arg0 @string
|
|
|
|
u64 at_org = 0;
|
|
|
|
u64 sz_org = 0;
|
2018-01-02 02:26:49 +01:00
|
|
|
if ((id == CMD_ID_SHA) || (id == CMD_ID_SHAGET) || (id == CMD_ID_INJECT) || (id == CMD_ID_FILL)) {
|
2017-08-23 02:04:53 +02:00
|
|
|
char* atstr_org = strrchr(argv[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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-06-09 01:45:00 +02:00
|
|
|
// perform command
|
2017-12-31 03:26:50 +01:00
|
|
|
if (id == CMD_ID_NOT) {
|
|
|
|
// check the argument
|
|
|
|
// "not true" or "not false"
|
|
|
|
ret = (strncmp(argv[0], _ARG_FALSE, _ARG_MAX_LEN) == 0);
|
|
|
|
if (err_str) snprintf(err_str, _ERR_STR_LEN, "'not' an error");
|
|
|
|
}
|
|
|
|
else if (id == CMD_ID_IF) {
|
2017-10-29 14:00:02 +09:00
|
|
|
// check the argument
|
2017-12-19 02:58:58 +01:00
|
|
|
// "if true" or "if false"
|
|
|
|
skip_state = (strncmp(argv[0], _ARG_TRUE, _ARG_MAX_LEN) == 0) ? 0 : _SKIP_BLOCK;
|
2017-10-29 14:00:02 +09:00
|
|
|
ifcnt++;
|
|
|
|
|
|
|
|
if (syntax_error && err_str)
|
|
|
|
snprintf(err_str, _ERR_STR_LEN, "syntax error after 'if'");
|
|
|
|
ret = !syntax_error;
|
|
|
|
}
|
2017-12-19 02:58:58 +01:00
|
|
|
else if (id == CMD_ID_ELIF) {
|
|
|
|
// check syntax errors
|
|
|
|
if (ifcnt == 0) {
|
|
|
|
if (err_str) snprintf(err_str, _ERR_STR_LEN, "'elif' without 'if'");
|
|
|
|
syntax_error = true;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// skip state handling, check the argument if required
|
|
|
|
// "if true" or "if false"
|
|
|
|
skip_state = !skip_state ? _SKIP_TILL_END :
|
|
|
|
((strncmp(argv[0], _ARG_TRUE, _ARG_MAX_LEN) == 0) ? 0 : _SKIP_BLOCK);
|
|
|
|
|
|
|
|
if (syntax_error && err_str)
|
|
|
|
snprintf(err_str, _ERR_STR_LEN, "syntax error after 'elif'");
|
|
|
|
ret = !syntax_error;
|
|
|
|
}
|
2017-10-29 14:00:02 +09:00
|
|
|
else if (id == CMD_ID_ELSE) {
|
|
|
|
// check syntax errors
|
|
|
|
if (ifcnt == 0) {
|
|
|
|
if (err_str) snprintf(err_str, _ERR_STR_LEN, "'else' without 'if'");
|
|
|
|
syntax_error = true;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// turn the skip state
|
2017-12-19 02:58:58 +01:00
|
|
|
skip_state = skip_state ? 0 : _SKIP_TILL_END;
|
2017-10-29 14:00:02 +09:00
|
|
|
|
|
|
|
ret = true;
|
|
|
|
}
|
|
|
|
else if (id == CMD_ID_END) {
|
|
|
|
// check syntax errors
|
|
|
|
if (ifcnt == 0){
|
|
|
|
if (err_str) snprintf(err_str, _ERR_STR_LEN, "'end' without 'if'");
|
|
|
|
syntax_error = true;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// close last "if"
|
2017-12-19 02:58:58 +01:00
|
|
|
skip_state = 0;
|
2017-10-29 14:00:02 +09:00
|
|
|
ifcnt--;
|
|
|
|
|
|
|
|
ret = true;
|
|
|
|
}
|
2018-01-12 03:19:04 +01:00
|
|
|
else if (id == CMD_ID_FOR) {
|
|
|
|
// cheating alert(!): actually this does nothing much
|
|
|
|
// just sets up the for_handler and skips to 'next'
|
|
|
|
if (for_ptr) {
|
|
|
|
if (err_str) snprintf(err_str, _ERR_STR_LEN, "'for' inside 'for'");
|
|
|
|
syntax_error = true;
|
|
|
|
return false;
|
2018-01-18 02:23:46 +01:00
|
|
|
} else if (!for_handler(NULL, argv[0], argv[1], flags & _FLG('r'))) {
|
2018-01-12 03:19:04 +01:00
|
|
|
if (err_str) snprintf(err_str, _ERR_STR_LEN, "dir not found");
|
|
|
|
skip_state = _SKIP_TO_NEXT;
|
|
|
|
ret = false;
|
|
|
|
} else {
|
|
|
|
skip_state = _SKIP_TO_NEXT;
|
|
|
|
ret = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (id == CMD_ID_NEXT) {
|
|
|
|
// actual work is done here
|
|
|
|
char* var = set_var(_VAR_FORPATH, "");
|
|
|
|
ret = true;
|
|
|
|
if (!for_ptr) {
|
|
|
|
if (err_str) snprintf(err_str, _ERR_STR_LEN, "'next' without 'for'");
|
|
|
|
syntax_error = true;
|
|
|
|
return false;
|
|
|
|
} else if (!var) {
|
|
|
|
if (err_str) snprintf(err_str, _ERR_STR_LEN, "forpath error");
|
|
|
|
ret = false;
|
|
|
|
} else {
|
2018-01-18 02:23:46 +01:00
|
|
|
if (!for_handler(var, NULL, NULL, false)) *var = '\0';
|
2018-01-12 03:19:04 +01:00
|
|
|
if (!*var) {
|
2018-01-18 02:23:46 +01:00
|
|
|
for_handler(NULL, NULL, NULL, false); // finish for_handler
|
2018-01-12 03:19:04 +01:00
|
|
|
for_ptr = NULL;
|
|
|
|
skip_state = 0;
|
|
|
|
} else {
|
|
|
|
skip_state = _SKIP_TO_FOR;
|
|
|
|
}
|
|
|
|
ret = true;
|
|
|
|
}
|
|
|
|
}
|
2017-10-29 14:00:02 +09:00
|
|
|
else if (id == CMD_ID_GOTO) {
|
|
|
|
jump_ptr = find_label(argv[0], NULL);
|
|
|
|
if (!jump_ptr) {
|
|
|
|
ret = false;
|
|
|
|
if (err_str) snprintf(err_str, _ERR_STR_LEN, "label not found");
|
|
|
|
}
|
|
|
|
}
|
2017-12-20 02:08:40 +01:00
|
|
|
else if (id == CMD_ID_LABELSEL) {
|
|
|
|
const char* options[_CHOICE_MAX_N] = { NULL };
|
|
|
|
char* options_jmp[_CHOICE_MAX_N] = { NULL };
|
|
|
|
char options_str[_CHOICE_MAX_N][_CHOICE_STR_LEN+1];
|
|
|
|
|
|
|
|
char* ast = strchr(argv[1], '*');
|
|
|
|
char* ptr = NULL;
|
|
|
|
u32 n_opt = 0;
|
|
|
|
while ((ptr = find_label(argv[1], ptr))) {
|
|
|
|
options[n_opt] = options_str[n_opt];
|
|
|
|
options_jmp[n_opt] = ptr;
|
|
|
|
|
|
|
|
while (*(ptr++) != '@');
|
|
|
|
if (ast) ptr += (ast - argv[1]);
|
|
|
|
|
|
|
|
char* choice = options_str[n_opt];
|
|
|
|
for (u32 i = 0; i < _CHOICE_STR_LEN; choice[++i] = '\0') {
|
|
|
|
if (IS_WHITESPACE(ptr[i])) break;
|
|
|
|
else if (ptr[i] == '_') choice[i] = ' ';
|
|
|
|
else choice[i] = ptr[i];
|
|
|
|
}
|
|
|
|
if (++n_opt >= _CHOICE_MAX_N) break;
|
|
|
|
}
|
|
|
|
|
|
|
|
u32 result = ShowSelectPrompt(n_opt, options, argv[0]);
|
|
|
|
if (!result) {
|
|
|
|
ret = false;
|
|
|
|
if (err_str) snprintf(err_str, _ERR_STR_LEN, "user abort");
|
|
|
|
} else jump_ptr = options_jmp[result-1];
|
|
|
|
}
|
2017-10-29 14:00:02 +09:00
|
|
|
else if (id == CMD_ID_ECHO) {
|
2017-06-09 01:45:00 +02:00
|
|
|
ShowPrompt(false, argv[0]);
|
2017-09-13 02:45:00 +02:00
|
|
|
}
|
2017-09-19 15:57:29 +02:00
|
|
|
else if (id == CMD_ID_QR) {
|
2018-01-24 23:32:06 +01:00
|
|
|
const u32 screen_size = SCREEN_SIZE(ALT_SCREEN);
|
|
|
|
u8* screen_copy = (u8*) malloc(screen_size);
|
2017-09-19 15:57:29 +02:00
|
|
|
u8 qrcode[qrcodegen_BUFFER_LEN_MAX];
|
|
|
|
u8 temp[qrcodegen_BUFFER_LEN_MAX];
|
2018-01-24 23:32:06 +01:00
|
|
|
ret = screen_copy && qrcodegen_encodeText(argv[1], temp, qrcode, qrcodegen_Ecc_LOW,
|
2017-09-19 15:57:29 +02:00
|
|
|
qrcodegen_VERSION_MIN, qrcodegen_VERSION_MAX, qrcodegen_Mask_AUTO, true);
|
|
|
|
if (ret) {
|
2018-01-24 23:32:06 +01:00
|
|
|
memcpy(screen_copy, ALT_SCREEN, screen_size);
|
2017-09-19 15:57:29 +02:00
|
|
|
DrawQrCode(ALT_SCREEN, qrcode);
|
|
|
|
ShowPrompt(false, argv[0]);
|
2018-01-24 23:32:06 +01:00
|
|
|
memcpy(ALT_SCREEN, screen_copy, screen_size);
|
|
|
|
} else if (err_str) snprintf(err_str, _ERR_STR_LEN, "out of memory");
|
|
|
|
free(screen_copy);
|
2017-09-19 15:57:29 +02:00
|
|
|
}
|
2017-09-13 02:45:00 +02:00
|
|
|
else if (id == CMD_ID_ASK) {
|
2017-06-09 01:45:00 +02:00
|
|
|
ret = ShowPrompt(true, argv[0]);
|
|
|
|
if (err_str) snprintf(err_str, _ERR_STR_LEN, "user abort");
|
2017-09-13 02:45:00 +02:00
|
|
|
}
|
|
|
|
else if (id == CMD_ID_INPUT) {
|
2017-08-25 00:20:50 +02:00
|
|
|
char input[_VAR_CNT_LEN] = { 0 };
|
2017-07-26 14:14:12 +02:00
|
|
|
char* var = get_var(argv[1], NULL);
|
2017-08-25 00:20:50 +02:00
|
|
|
strncpy(input, var, _VAR_CNT_LEN);
|
|
|
|
ret = ShowStringPrompt(input, _VAR_CNT_LEN, argv[0]);
|
|
|
|
if (ret) set_var(argv[1], "");
|
2017-07-26 14:14:12 +02:00
|
|
|
if (err_str) snprintf(err_str, _ERR_STR_LEN, "user abort");
|
2017-08-25 00:20:50 +02:00
|
|
|
if (ret) {
|
|
|
|
ret = set_var(argv[1], input);
|
|
|
|
if (err_str) snprintf(err_str, _ERR_STR_LEN, "var fail");
|
|
|
|
}
|
2017-09-13 02:45:00 +02:00
|
|
|
}
|
2018-01-09 01:39:37 +01:00
|
|
|
else if ((id == CMD_ID_FILESEL) || (id == CMD_ID_DIRSEL)) {
|
2017-09-13 02:45:00 +02:00
|
|
|
char choice[_VAR_CNT_LEN] = { 0 };
|
|
|
|
char* var = get_var(argv[2], NULL);
|
|
|
|
strncpy(choice, var, _VAR_CNT_LEN);
|
|
|
|
|
|
|
|
char path[_VAR_CNT_LEN];
|
|
|
|
strncpy(path, argv[1], _VAR_CNT_LEN);
|
2017-10-25 00:36:58 +02:00
|
|
|
if (strncmp(path, "Z:", 2) == 0) {
|
|
|
|
ret = false;
|
|
|
|
if (err_str) snprintf(err_str, _ERR_STR_LEN, "forbidden drive");
|
2018-01-09 01:39:37 +01:00
|
|
|
} else if (id == CMD_ID_FILESEL) {
|
2018-01-09 15:46:37 +01:00
|
|
|
char* npattern = strrchr(path, '/');
|
|
|
|
if (!npattern) {
|
|
|
|
ret = false;
|
|
|
|
if (err_str) snprintf(err_str, _ERR_STR_LEN, "invalid path");
|
|
|
|
} else {
|
|
|
|
u32 flags_ext = (flags & _FLG('d')) ? 0 : NO_DIRS;
|
|
|
|
*(npattern++) = '\0';
|
|
|
|
ret = FileSelector(choice, argv[0], path, npattern, flags_ext);
|
|
|
|
if (err_str) snprintf(err_str, _ERR_STR_LEN, "fileselect abort");
|
|
|
|
}
|
2018-01-09 01:39:37 +01:00
|
|
|
} else {
|
2018-01-09 15:46:37 +01:00
|
|
|
ret = FileSelector(choice, argv[0], path, NULL, NO_FILES | SELECT_DIRS);
|
2018-01-09 01:39:37 +01:00
|
|
|
if (err_str) snprintf(err_str, _ERR_STR_LEN, "dirselect abort");
|
2017-09-13 02:45:00 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if (ret) {
|
|
|
|
ret = set_var(argv[2], choice);
|
|
|
|
if (err_str) snprintf(err_str, _ERR_STR_LEN, "var fail");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (id == CMD_ID_SET) {
|
2017-06-09 01:45:00 +02:00
|
|
|
ret = set_var(argv[0], argv[1]);
|
|
|
|
if (err_str) snprintf(err_str, _ERR_STR_LEN, "set fail");
|
2017-09-13 02:45:00 +02:00
|
|
|
}
|
2017-12-20 12:33:18 +09:00
|
|
|
else if (id == CMD_ID_STRSPLIT) {
|
|
|
|
char str[_ARG_MAX_LEN];
|
|
|
|
strncpy(str, argv[1], _ARG_MAX_LEN);
|
|
|
|
|
|
|
|
ret = false;
|
|
|
|
if (strlen(argv[2]) == 1) { // argv[2] must be one char
|
|
|
|
char* found;
|
|
|
|
if (flags & _FLG('f')) found = strchr(str, *argv[2]);
|
|
|
|
else found = strrchr(str, *argv[2]);
|
|
|
|
if (!found && err_str) snprintf(err_str, _ERR_STR_LEN, "char not found");
|
|
|
|
|
|
|
|
if (found) {
|
|
|
|
if (flags & _FLG('b')) {
|
|
|
|
*found = '\0';
|
|
|
|
ret = set_var(argv[0], str);
|
|
|
|
} else ret = set_var(argv[0], found+1);
|
|
|
|
if (err_str) snprintf(err_str, _ERR_STR_LEN, "var fail");
|
|
|
|
}
|
|
|
|
} else if (err_str) snprintf(err_str, _ERR_STR_LEN, "argv[2] is not a char");
|
|
|
|
}
|
2017-12-31 02:09:43 +01:00
|
|
|
else if (id == CMD_ID_STRREP) {
|
|
|
|
char str[_ARG_MAX_LEN];
|
|
|
|
strncpy(str, argv[1], _ARG_MAX_LEN);
|
|
|
|
|
|
|
|
if (strnlen(argv[2], _ARG_MAX_LEN) != 2) {
|
|
|
|
if (err_str) snprintf(err_str, _ERR_STR_LEN, "argv[2] must be 2 chars");
|
|
|
|
ret = false;
|
|
|
|
} else {
|
2018-02-06 00:29:21 +01:00
|
|
|
for (u32 i = 0; (i < _ARG_MAX_LEN) && str[i]; i++) {
|
2017-12-31 02:09:43 +01:00
|
|
|
if (str[i] == argv[2][0]) str[i] = argv[2][1];
|
|
|
|
}
|
|
|
|
ret = set_var(argv[0], str);
|
|
|
|
if (err_str) snprintf(err_str, _ERR_STR_LEN, "var fail");
|
|
|
|
}
|
|
|
|
}
|
2017-09-21 01:21:02 +02:00
|
|
|
else if (id == CMD_ID_CHK) {
|
|
|
|
if (flags & _FLG('u')) {
|
|
|
|
ret = (strncasecmp(argv[0], argv[1], _VAR_CNT_LEN) != 0);
|
|
|
|
if (err_str) snprintf(err_str, _ERR_STR_LEN, "arg match");
|
|
|
|
} else {
|
|
|
|
ret = (strncasecmp(argv[0], argv[1], _VAR_CNT_LEN) == 0);
|
|
|
|
if (err_str) snprintf(err_str, _ERR_STR_LEN, "no arg match");
|
|
|
|
}
|
|
|
|
}
|
2017-09-13 02:45:00 +02:00
|
|
|
else if (id == CMD_ID_ALLOW) {
|
2017-06-09 01:45:00 +02:00
|
|
|
if (flags & _FLG('a')) ret = CheckDirWritePermissions(argv[0]);
|
|
|
|
else ret = CheckWritePermissions(argv[0]);
|
|
|
|
if (err_str) snprintf(err_str, _ERR_STR_LEN, "permission fail");
|
2017-09-13 02:45:00 +02:00
|
|
|
}
|
|
|
|
else if (id == CMD_ID_CP) {
|
2017-06-09 01:45:00 +02:00
|
|
|
u32 flags_ext = BUILD_PATH;
|
2017-06-23 00:28:45 +02:00
|
|
|
if (flags & _FLG('h')) flags_ext |= CALC_SHA;
|
2017-06-23 02:13:22 +02:00
|
|
|
if (flags & _FLG('n')) flags_ext |= NO_CANCEL;
|
2017-06-23 00:28:45 +02:00
|
|
|
if (flags & _FLG('s')) flags_ext |= SILENT;
|
|
|
|
if (flags & _FLG('w')) flags_ext |= OVERWRITE_ALL;
|
|
|
|
else if (flags & _FLG('k')) flags_ext |= SKIP_ALL;
|
2017-06-09 01:45:00 +02:00
|
|
|
ret = PathMoveCopy(argv[1], argv[0], &flags_ext, false);
|
|
|
|
if (err_str) snprintf(err_str, _ERR_STR_LEN, "copy fail");
|
2017-09-13 02:45:00 +02:00
|
|
|
}
|
|
|
|
else if (id == CMD_ID_MV) {
|
2017-06-09 01:45:00 +02:00
|
|
|
u32 flags_ext = BUILD_PATH;
|
2017-06-23 00:28:45 +02:00
|
|
|
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;
|
2017-06-09 01:45:00 +02:00
|
|
|
ret = PathMoveCopy(argv[1], argv[0], &flags_ext, true);
|
|
|
|
if (err_str) snprintf(err_str, _ERR_STR_LEN, "move fail");
|
2017-09-13 02:45:00 +02:00
|
|
|
}
|
|
|
|
else if (id == CMD_ID_INJECT) {
|
2017-06-09 01:45:00 +02:00
|
|
|
char* atstr_dst = strrchr(argv[1], '@');
|
2017-08-23 01:24:55 +02:00
|
|
|
u64 at_dst = 0;
|
|
|
|
if (atstr_dst) {
|
2017-06-09 01:45:00 +02:00
|
|
|
*(atstr_dst++) = '\0';
|
2017-08-23 01:24:55 +02:00
|
|
|
if (sscanf(atstr_dst, "%llX", &at_dst) != 1) at_dst = 0;
|
|
|
|
} else fvx_unlink(argv[1]); // force new file when no offset is given
|
|
|
|
u32 flags_ext = ALLOW_EXPAND;
|
|
|
|
if (flags & _FLG('n')) flags_ext |= NO_CANCEL;
|
|
|
|
ret = FileInjectFile(argv[1], argv[0], at_dst, at_org, sz_org, &flags_ext);
|
|
|
|
if (err_str) snprintf(err_str, _ERR_STR_LEN, "inject fail");
|
2017-09-13 02:45:00 +02:00
|
|
|
}
|
2018-01-02 02:26:49 +01:00
|
|
|
else if (id == CMD_ID_FILL) {
|
|
|
|
u32 flags_ext = ALLOW_EXPAND;
|
|
|
|
u8 fillbyte = 0;
|
|
|
|
if ((strnlen(argv[1], _ARG_MAX_LEN) != 2) || !strntohex(argv[1], &fillbyte, 1)) {
|
|
|
|
ret = false;
|
|
|
|
if (err_str) snprintf(err_str, _ERR_STR_LEN, "fillbyte fail");
|
|
|
|
} else {
|
|
|
|
if (flags & _FLG('n')) flags_ext |= NO_CANCEL;
|
|
|
|
ret = FileSetByte(argv[0], at_org, sz_org, fillbyte, &flags_ext);
|
|
|
|
if (err_str) snprintf(err_str, _ERR_STR_LEN, "fill fail");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (id == CMD_ID_FDUMMY) {
|
|
|
|
u32 fsize;
|
|
|
|
if (sscanf(argv[1], "%lX", &fsize) != 1) {
|
|
|
|
ret = false;
|
|
|
|
if (err_str) snprintf(err_str, _ERR_STR_LEN, "bad filesize");
|
|
|
|
} else {
|
|
|
|
ret = FileCreateDummy(argv[0], NULL, fsize);
|
|
|
|
if (err_str) snprintf(err_str, _ERR_STR_LEN, "create dummy fail");
|
|
|
|
}
|
|
|
|
}
|
2017-09-13 02:45:00 +02:00
|
|
|
else if (id == CMD_ID_RM) {
|
2017-06-09 01:45:00 +02:00
|
|
|
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");
|
2017-09-13 02:45:00 +02:00
|
|
|
}
|
|
|
|
else if (id == CMD_ID_MKDIR) {
|
2017-09-08 20:58:20 +02:00
|
|
|
ret = (CheckWritePermissions(argv[0])) && (fvx_rmkdir(argv[0]) == FR_OK);
|
2017-06-09 01:45:00 +02:00
|
|
|
if (err_str) snprintf(err_str, _ERR_STR_LEN, "makedir fail");
|
2017-09-13 02:45:00 +02:00
|
|
|
}
|
|
|
|
else if (id == CMD_ID_MOUNT) {
|
2017-06-09 01:45:00 +02:00
|
|
|
ret = InitImgFS(argv[0]);
|
|
|
|
if (err_str) snprintf(err_str, _ERR_STR_LEN, "mount fail");
|
2017-09-13 02:45:00 +02:00
|
|
|
}
|
|
|
|
else if (id == CMD_ID_UMOUNT) {
|
2017-06-09 01:45:00 +02:00
|
|
|
InitImgFS(NULL);
|
2017-09-13 02:45:00 +02:00
|
|
|
}
|
|
|
|
else if (id == CMD_ID_FIND) {
|
2017-08-25 00:20:50 +02:00
|
|
|
char path[_VAR_CNT_LEN];
|
2017-09-06 02:01:42 +02:00
|
|
|
u8 mode = (flags & _FLG('f')) ? FN_LOWEST : FN_HIGHEST;
|
|
|
|
ret = (fvx_findpath(path, argv[0], mode) == FR_OK);
|
2017-08-25 00:20:50 +02:00
|
|
|
if (err_str) snprintf(err_str, _ERR_STR_LEN, "find fail");
|
|
|
|
if (ret) {
|
|
|
|
ret = set_var(argv[1], path);
|
2017-06-09 01:45:00 +02:00
|
|
|
if (err_str) snprintf(err_str, _ERR_STR_LEN, "var fail");
|
|
|
|
}
|
2017-09-13 02:45:00 +02:00
|
|
|
}
|
|
|
|
else if (id == CMD_ID_FINDNOT) {
|
2017-08-25 00:20:50 +02:00
|
|
|
char path[_VAR_CNT_LEN];
|
|
|
|
ret = (fvx_findnopath(path, argv[0]) == FR_OK);
|
|
|
|
if (err_str) snprintf(err_str, _ERR_STR_LEN, "findnot fail");
|
|
|
|
if (ret) {
|
|
|
|
ret = set_var(argv[1], path);
|
2017-06-09 01:45:00 +02:00
|
|
|
if (err_str) snprintf(err_str, _ERR_STR_LEN, "var fail");
|
|
|
|
}
|
2017-09-13 02:45:00 +02:00
|
|
|
}
|
|
|
|
else if (id == CMD_ID_SHA) {
|
2017-06-09 01:45:00 +02:00
|
|
|
u8 sha256_fil[0x20];
|
|
|
|
u8 sha256_cmp[0x20];
|
2017-08-23 02:04:53 +02:00
|
|
|
if (!FileGetSha256(argv[0], sha256_fil, at_org, sz_org)) {
|
2017-06-09 01:45:00 +02:00
|
|
|
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");
|
|
|
|
}
|
2017-09-13 02:45:00 +02:00
|
|
|
}
|
|
|
|
else if (id == CMD_ID_SHAGET) {
|
2017-09-04 02:15:45 +02:00
|
|
|
u8 sha256_fil[0x20];
|
|
|
|
if (!(ret = FileGetSha256(argv[0], sha256_fil, at_org, sz_org))) {
|
|
|
|
if (err_str) snprintf(err_str, _ERR_STR_LEN, "sha arg0 fail");
|
2017-09-24 13:44:49 +02:00
|
|
|
} else if (!strchr(argv[1], ':')) {
|
|
|
|
char sha256_str[64+1];
|
|
|
|
snprintf(sha256_str, 64+1, "%016llX%016llX%016llX%016llX", getbe64(sha256_fil + 0), getbe64(sha256_fil + 8),
|
|
|
|
getbe64(sha256_fil + 16), getbe64(sha256_fil + 24));
|
|
|
|
ret = set_var(argv[1], sha256_str);
|
|
|
|
if (err_str) snprintf(err_str, _ERR_STR_LEN, "var fail");
|
2017-09-04 02:15:45 +02:00
|
|
|
} else if (!(ret = FileSetData(argv[1], sha256_fil, 0x20, 0, true))) {
|
|
|
|
if (err_str) snprintf(err_str, _ERR_STR_LEN, "sha write fail");
|
|
|
|
}
|
2017-09-13 02:45:00 +02:00
|
|
|
}
|
|
|
|
else if (id == CMD_ID_FIXCMAC) {
|
2017-08-21 19:56:30 +02:00
|
|
|
ShowString("Fixing CMACs...");
|
|
|
|
ret = (RecursiveFixFileCmac(argv[0]) == 0);
|
|
|
|
if (err_str) snprintf(err_str, _ERR_STR_LEN, "fixcmac failed");
|
2017-09-13 02:45:00 +02:00
|
|
|
}
|
|
|
|
else if (id == CMD_ID_VERIFY) {
|
2017-10-16 02:02:24 +02:00
|
|
|
u64 filetype = IdentifyFileType(argv[0]);
|
2017-06-09 01:45:00 +02:00
|
|
|
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");
|
2017-09-13 02:45:00 +02:00
|
|
|
}
|
|
|
|
else if (id == CMD_ID_DECRYPT) {
|
2017-10-16 02:02:24 +02:00
|
|
|
u64 filetype = IdentifyFileType(argv[0]);
|
2017-07-27 14:29:03 +02:00
|
|
|
if (filetype & BIN_KEYDB) ret = (CryptAesKeyDb(argv[0], true, false) == 0);
|
|
|
|
else ret = (CryptGameFile(argv[0], true, false) == 0);
|
|
|
|
if (err_str) snprintf(err_str, _ERR_STR_LEN, "decrypt failed");
|
2017-09-13 02:45:00 +02:00
|
|
|
}
|
|
|
|
else if (id == CMD_ID_ENCRYPT) {
|
2017-10-16 02:02:24 +02:00
|
|
|
u64 filetype = IdentifyFileType(argv[0]);
|
2017-07-27 14:29:03 +02:00
|
|
|
if (filetype & BIN_KEYDB) ret = (CryptAesKeyDb(argv[0], true, true) == 0);
|
|
|
|
else ret = (CryptGameFile(argv[0], true, true) == 0);
|
|
|
|
if (err_str) snprintf(err_str, _ERR_STR_LEN, "encrypt failed");
|
2017-09-13 02:45:00 +02:00
|
|
|
}
|
|
|
|
else if (id == CMD_ID_BUILDCIA) {
|
2017-07-27 14:29:03 +02:00
|
|
|
ret = (BuildCiaFromGameFile(argv[0], (flags & _FLG('n'))) == 0);
|
|
|
|
if (err_str) snprintf(err_str, _ERR_STR_LEN, "build CIA failed");
|
2017-09-13 02:45:00 +02:00
|
|
|
}
|
|
|
|
else if (id == CMD_ID_EXTRCODE) {
|
2017-10-16 02:02:24 +02:00
|
|
|
u64 filetype = IdentifyFileType(argv[0]);
|
2017-09-08 15:27:10 +02:00
|
|
|
if ((filetype&(GAME_NCCH|FLAG_CXI)) != (GAME_NCCH|FLAG_CXI)) {
|
|
|
|
ret = false;
|
|
|
|
if (err_str) snprintf(err_str, _ERR_STR_LEN, "not a CXI file");
|
|
|
|
} else {
|
|
|
|
ShowString("Extracting .code, please wait...");
|
2017-10-16 02:02:24 +02:00
|
|
|
ret = (ExtractCodeFromCxiFile(argv[0], argv[1], NULL) == 0);
|
2017-09-08 15:27:10 +02:00
|
|
|
if (err_str) snprintf(err_str, _ERR_STR_LEN, "extract .code failed");
|
|
|
|
}
|
2017-09-13 02:45:00 +02:00
|
|
|
}
|
2018-01-12 02:52:39 +01:00
|
|
|
else if (id == CMD_ID_ISDIR) {
|
|
|
|
DIR fdir;
|
|
|
|
if (fvx_opendir(&fdir, argv[0]) == FR_OK) {
|
|
|
|
fvx_closedir(&fdir);
|
|
|
|
ret = true;
|
|
|
|
} else {
|
|
|
|
if (err_str) snprintf(err_str, _ERR_STR_LEN, "not a dir");
|
|
|
|
ret = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (id == CMD_ID_EXIST) {
|
|
|
|
if (fvx_stat(argv[0], NULL) == FR_OK) {
|
|
|
|
ret = true;
|
|
|
|
} else {
|
|
|
|
if (err_str) snprintf(err_str, _ERR_STR_LEN, "file not found");
|
|
|
|
ret = false;
|
|
|
|
}
|
|
|
|
}
|
2017-09-13 02:45:00 +02:00
|
|
|
else if (id == CMD_ID_BOOT) {
|
2018-01-24 23:32:06 +01:00
|
|
|
u8* firm = (u8*) malloc(FIRM_MAX_SIZE);
|
|
|
|
if (!firm) {
|
|
|
|
ret = false;
|
|
|
|
if (err_str) snprintf(err_str, _ERR_STR_LEN, "out of memory");
|
|
|
|
} else {
|
|
|
|
size_t firm_size = FileGetData(argv[0], firm, FIRM_MAX_SIZE, 0);
|
|
|
|
ret = firm_size && IsBootableFirm(firm, firm_size);
|
|
|
|
if (ret) {
|
|
|
|
char fixpath[256] = { 0 };
|
|
|
|
if ((*argv[0] == '0') || (*argv[0] == '1'))
|
|
|
|
snprintf(fixpath, 256, "%s%s", (*argv[0] == '0') ? "sdmc" : "nand", argv[0] + 1);
|
|
|
|
else strncpy(fixpath, argv[0], 256);
|
|
|
|
BootFirm((FirmHeader*)(void*)firm, fixpath);
|
|
|
|
while(1);
|
|
|
|
} else if (err_str) snprintf(err_str, _ERR_STR_LEN, "not a bootable firm");
|
|
|
|
free(firm);
|
|
|
|
}
|
2017-09-13 02:45:00 +02:00
|
|
|
}
|
|
|
|
else if (id == CMD_ID_SWITCHSD) {
|
2017-09-04 02:15:45 +02:00
|
|
|
DeinitExtFS();
|
|
|
|
if (!(ret = CheckSDMountState())) {
|
|
|
|
if (err_str) snprintf(err_str, _ERR_STR_LEN, "SD not mounted");
|
|
|
|
} else {
|
|
|
|
u32 pad_state;
|
|
|
|
DeinitSDCardFS();
|
|
|
|
ShowString("%s\n \nEject SD card...", argv[0]);
|
|
|
|
while (!((pad_state = InputWait(0)) & (BUTTON_B|SD_EJECT)));
|
|
|
|
if (pad_state & SD_EJECT) {
|
|
|
|
ShowString("%s\n \nInsert SD card...", argv[0]);
|
|
|
|
while (!((pad_state = InputWait(0)) & (BUTTON_B|SD_INSERT)));
|
|
|
|
}
|
|
|
|
if (pad_state & BUTTON_B) {
|
|
|
|
ret = false;
|
|
|
|
if (err_str) snprintf(err_str, _ERR_STR_LEN, "user abort");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
InitSDCardFS();
|
|
|
|
AutoEmuNandBase(true);
|
|
|
|
InitExtFS();
|
2017-09-13 02:45:00 +02:00
|
|
|
}
|
|
|
|
else if (id == CMD_ID_REBOOT) {
|
2017-09-08 15:24:29 +02:00
|
|
|
DeinitExtFS();
|
|
|
|
DeinitSDCardFS();
|
2017-06-09 01:45:00 +02:00
|
|
|
Reboot();
|
2017-09-13 02:45:00 +02:00
|
|
|
}
|
|
|
|
else if (id == CMD_ID_POWEROFF) {
|
2017-09-08 15:24:29 +02:00
|
|
|
DeinitExtFS();
|
|
|
|
DeinitSDCardFS();
|
2017-06-09 01:45:00 +02:00
|
|
|
PowerOff();
|
2017-09-13 02:45:00 +02:00
|
|
|
}
|
2017-09-19 15:57:29 +02:00
|
|
|
else if (id == CMD_ID_BKPT) {
|
2018-01-24 23:32:06 +01:00
|
|
|
bkpt;
|
2017-09-19 15:57:29 +02:00
|
|
|
while(1);
|
|
|
|
}
|
2017-09-13 02:45:00 +02:00
|
|
|
else { // command not recognized / bad number of arguments
|
2017-06-09 01:45:00 +02:00
|
|
|
ret = false;
|
|
|
|
if (err_str) snprintf(err_str, _ERR_STR_LEN, "unknown error");
|
|
|
|
}
|
|
|
|
|
2017-12-31 03:26:50 +01:00
|
|
|
if (ret && err_str) snprintf(err_str, _ERR_STR_LEN, "command success");
|
2017-06-09 01:45:00 +02:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2017-10-29 14:00:02 +09:00
|
|
|
bool run_line(const char* line_start, const char* line_end, u32* flags, char* err_str, bool if_cond) {
|
2017-09-21 01:21:02 +02:00
|
|
|
char args[_MAX_ARGS][_ARG_MAX_LEN];
|
|
|
|
char* argv[_MAX_ARGS];
|
2017-06-09 01:45:00 +02:00
|
|
|
u32 argc = 0;
|
|
|
|
cmd_id cmdid;
|
2017-10-29 14:00:02 +09:00
|
|
|
|
2017-09-21 01:21:02 +02:00
|
|
|
// set up argv array
|
|
|
|
for (u32 i = 0; i < _MAX_ARGS; i++)
|
|
|
|
argv[i] = args[i];
|
|
|
|
|
2017-06-09 01:45:00 +02:00
|
|
|
// flags handling (if no pointer given)
|
|
|
|
u32 lflags;
|
|
|
|
if (!flags) flags = &lflags;
|
|
|
|
*flags = 0;
|
|
|
|
|
|
|
|
// parse current line, grab cmd / flags / args
|
|
|
|
if (!parse_line(line_start, line_end, &cmdid, flags, &argc, argv, err_str)) {
|
2017-10-29 14:00:02 +09:00
|
|
|
syntax_error = true;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2017-12-20 01:06:56 +01:00
|
|
|
// control flow command handling
|
2017-12-31 03:26:50 +01:00
|
|
|
// block out of control flow commands
|
|
|
|
if (if_cond && IS_CTRLFLOW_CMD(cmdid)) {
|
|
|
|
if (err_str) snprintf(err_str, _ERR_STR_LEN, "control flow error");
|
|
|
|
syntax_error = true;
|
|
|
|
return false;
|
|
|
|
}
|
2017-10-29 14:00:02 +09:00
|
|
|
|
2017-12-31 03:26:50 +01:00
|
|
|
// shortcuts for "elif" / "else"
|
|
|
|
if (((cmdid == CMD_ID_ELIF) || (cmdid == CMD_ID_ELSE)) && !skip_state) {
|
|
|
|
skip_state = _SKIP_TILL_END;
|
|
|
|
cmdid = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
// handle "if" / "elif" / "not"
|
|
|
|
if ((cmdid == CMD_ID_IF) || (cmdid == CMD_ID_ELIF) || (cmdid == CMD_ID_NOT)) {
|
|
|
|
// set defaults
|
|
|
|
argc = 1;
|
|
|
|
strncpy(argv[0], _ARG_FALSE, _ARG_MAX_LEN);
|
2017-12-19 02:58:58 +01:00
|
|
|
|
2017-12-31 03:26:50 +01:00
|
|
|
// skip to behind the command
|
|
|
|
char* line_start_next = (char*) line_start;
|
|
|
|
for (; IS_WHITESPACE(*line_start_next); line_start_next++);
|
|
|
|
for (; *line_start_next && !IS_WHITESPACE(*line_start_next); line_start_next++);
|
|
|
|
|
|
|
|
// run condition, take over result
|
|
|
|
if (run_line(line_start_next, line_end, flags, err_str, true))
|
|
|
|
strncpy(argv[0], _ARG_TRUE, _ARG_MAX_LEN);
|
2017-06-09 01:45:00 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// run the command (if available)
|
|
|
|
if (cmdid && !run_cmd(cmdid, *flags, argv, err_str)) {
|
2017-07-26 14:14:12 +02:00
|
|
|
char* msg_fail = get_var("ERRORMSG", NULL);
|
2017-06-09 01:45:00 +02:00
|
|
|
if (msg_fail && *msg_fail) *err_str = '\0'; // use custom error message
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// success if we arrive here
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2017-06-26 01:44:16 +02:00
|
|
|
// checks for illegal ASCII symbols
|
|
|
|
bool ValidateText(const char* text, u32 len) {
|
2017-11-01 18:05:30 +01:00
|
|
|
if (!len) return false;
|
2017-06-26 01:44:16 +02:00
|
|
|
for (u32 i = 0; i < len; i++) {
|
|
|
|
char c = text[i];
|
|
|
|
if ((c == '\r') && ((i+1) < len) && (text[i+1] != '\n')) return false; // CR without LF
|
|
|
|
if ((c < 0x20) && (c != '\t') && (c != '\r') && (c != '\n')) return false; // illegal control char
|
2017-11-01 18:05:30 +01:00
|
|
|
if (c == 0xFF) return false; // 0xFF illegal char
|
2017-06-26 01:44:16 +02:00
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2017-09-06 00:46:01 +02:00
|
|
|
void MemTextView(const char* text, u32 len, char* line0, int off_disp, int lno, u32 ww, u32 mno, bool is_script) {
|
|
|
|
// block placements
|
|
|
|
const char* al_str = "<< ";
|
|
|
|
const char* ar_str = " >>";
|
|
|
|
u32 x_txt = (TV_LNOS >= 0) ? TV_HPAD + ((TV_LNOS+1)*FONT_WIDTH_EXT) : TV_HPAD;
|
|
|
|
u32 x_lno = TV_HPAD;
|
|
|
|
u32 p_al = 0;
|
|
|
|
u32 p_ar = TV_LLEN_DISP - strnlen(ar_str, 16);
|
|
|
|
u32 x_al = x_txt + (p_al * FONT_WIDTH_EXT);
|
|
|
|
u32 x_ar = x_txt + (p_ar * FONT_WIDTH_EXT);
|
|
|
|
|
|
|
|
// display text on screen
|
2017-09-25 13:21:54 +02:00
|
|
|
char txtstr[TV_LLEN_DISP + 1];
|
2017-09-06 00:46:01 +02:00
|
|
|
char* ptr = line0;
|
|
|
|
u32 nln = lno;
|
|
|
|
for (u32 y = TV_VPAD; y < SCREEN_HEIGHT; y += FONT_HEIGHT_EXT + (2*TV_VPAD)) {
|
|
|
|
char* ptr_next = line_seek(text, len, ww, ptr, 1);
|
|
|
|
u32 llen = line_len(text, len, ww, ptr);
|
|
|
|
u32 ncpy = ((int) llen < off_disp) ? 0 : (llen - off_disp);
|
|
|
|
if (ncpy > TV_LLEN_DISP) ncpy = TV_LLEN_DISP;
|
|
|
|
bool al = !ww && off_disp && (ptr != ptr_next);
|
2018-01-12 15:01:55 +01:00
|
|
|
bool ar = !ww && (llen > off_disp + TV_LLEN_DISP);
|
2017-09-06 00:46:01 +02:00
|
|
|
|
|
|
|
// set text color / find start of comment of scripts
|
2017-10-09 15:20:45 +02:00
|
|
|
u32 color_text = (nln == mno) ? script_color_active : (is_script) ? script_color_code : (u32) COLOR_TVTEXT;
|
2017-09-25 13:21:54 +02:00
|
|
|
int cmt_start = TV_LLEN_DISP; // start of comment in current displayed line (may be negative)
|
2017-09-06 00:46:01 +02:00
|
|
|
if (is_script && (nln != mno)) {
|
|
|
|
char* hash = line_seek(text, len, 0, ptr, 0);
|
|
|
|
for (; *hash != '#' && (hash - ptr < (int) llen); hash++);
|
|
|
|
cmt_start = (hash - ptr) - off_disp;
|
|
|
|
}
|
2017-09-25 00:50:44 +02:00
|
|
|
if (cmt_start <= 0) color_text = script_color_comment;
|
2017-09-06 00:46:01 +02:00
|
|
|
|
|
|
|
// build text string
|
|
|
|
snprintf(txtstr, TV_LLEN_DISP + 1, "%-*.*s", (int) TV_LLEN_DISP, (int) TV_LLEN_DISP, "");
|
|
|
|
if (ncpy) memcpy(txtstr, ptr + off_disp, ncpy);
|
|
|
|
for (char* d = txtstr; *d; d++) if (*d < ' ') *d = ' ';
|
|
|
|
if (al) memcpy(txtstr + p_al, al_str, strnlen(al_str, 16));
|
|
|
|
if (ar) memcpy(txtstr + p_ar, ar_str, strnlen(ar_str, 16));
|
|
|
|
|
|
|
|
// draw line number & text
|
2017-10-02 16:09:55 +02:00
|
|
|
DrawString(TOP_SCREEN, txtstr, x_txt, y, color_text, COLOR_STD_BG);
|
2017-09-06 00:46:01 +02:00
|
|
|
if (TV_LNOS > 0) { // line number
|
|
|
|
if (ptr != ptr_next)
|
|
|
|
DrawStringF(TOP_SCREEN, x_lno, y, ((ptr == text) || (*(ptr-1) == '\n')) ? COLOR_TVOFFS : COLOR_TVOFFSL, COLOR_STD_BG, "%0*lu", TV_LNOS, nln);
|
|
|
|
else DrawStringF(TOP_SCREEN, x_lno, y, COLOR_TVOFFSL, COLOR_STD_BG, "%*.*s", TV_LNOS, TV_LNOS, " ");
|
|
|
|
}
|
|
|
|
|
|
|
|
// colorize comment if is_script
|
2018-01-12 15:01:55 +01:00
|
|
|
if ((cmt_start > 0) && ((u32) cmt_start < TV_LLEN_DISP)) {
|
2017-09-25 13:21:54 +02:00
|
|
|
memset(txtstr, ' ', cmt_start);
|
2017-10-02 16:09:55 +02:00
|
|
|
DrawString(TOP_SCREEN, txtstr, x_txt, y, script_color_comment, COLOR_TRANSPARENT);
|
2017-09-06 00:46:01 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// colorize arrows
|
|
|
|
if (al) DrawStringF(TOP_SCREEN, x_al, y, COLOR_TVOFFS, COLOR_TRANSPARENT, al_str);
|
|
|
|
if (ar) DrawStringF(TOP_SCREEN, x_ar, y, COLOR_TVOFFS, COLOR_TRANSPARENT, ar_str);
|
|
|
|
|
|
|
|
// advance pointer / line number
|
|
|
|
for (char* c = ptr; c < ptr_next; c++) if (*c == '\n') ++nln;
|
|
|
|
ptr = ptr_next;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-10-02 03:08:47 +02:00
|
|
|
bool MemTextViewer(const char* text, u32 len, u32 start, bool as_script) {
|
|
|
|
u32 ww = TV_LLEN_DISP;
|
2017-09-06 00:46:01 +02:00
|
|
|
|
|
|
|
// check if this really is text
|
|
|
|
if (!ValidateText(text, len)) {
|
|
|
|
ShowPrompt(false, "Error: Invalid text data");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// clear screens
|
|
|
|
ClearScreenF(true, true, COLOR_STD_BG);
|
|
|
|
|
|
|
|
// instructions
|
|
|
|
static const char* instr = "Textviewer Controls:\n \n\x18\x19\x1A\x1B(+R) - Scroll\nR+Y - Toggle wordwrap\nR+X - Goto line #\nB - Exit\n";
|
|
|
|
ShowString(instr);
|
|
|
|
|
2017-09-25 00:50:44 +02:00
|
|
|
// set script colors
|
|
|
|
if (as_script) {
|
|
|
|
script_color_active = COLOR_TVRUN;
|
|
|
|
script_color_comment = COLOR_TVCMT;
|
|
|
|
script_color_code = COLOR_TVCMD;
|
|
|
|
}
|
|
|
|
|
2017-09-06 00:46:01 +02:00
|
|
|
// find maximum line len
|
|
|
|
u32 llen_max = 0;
|
|
|
|
for (char* ptr = (char*) text; ptr < (text + len); ptr = line_seek(text, len, 0, ptr, 1)) {
|
|
|
|
u32 llen = line_len(text, len, 0, ptr);
|
|
|
|
if (llen > llen_max) llen_max = llen;
|
|
|
|
}
|
|
|
|
|
|
|
|
// find last allowed lines (ww and nonww)
|
|
|
|
char* llast_nww = line_seek(text, len, 0, text + len, -TV_NLIN_DISP);
|
|
|
|
char* llast_ww = line_seek(text, len, TV_LLEN_DISP, text + len, -TV_NLIN_DISP);
|
|
|
|
|
|
|
|
char* line0 = (char*) text;
|
|
|
|
int lcurr = 1;
|
|
|
|
int off_disp = 0;
|
2017-10-02 03:08:47 +02:00
|
|
|
for (; lcurr < (int) start; line0 = line_seek(text, len, 0, line0, 1), lcurr++);
|
2017-09-06 00:46:01 +02:00
|
|
|
while (true) {
|
|
|
|
// display text on screen
|
|
|
|
MemTextView(text, len, line0, off_disp, lcurr, ww, 0, as_script);
|
|
|
|
|
|
|
|
// handle user input
|
|
|
|
u32 pad_state = InputWait(0);
|
|
|
|
if ((pad_state & BUTTON_R1) && (pad_state & BUTTON_L1)) CreateScreenshot();
|
|
|
|
else { // standard viewer mode
|
|
|
|
char* line0_next = line0;
|
|
|
|
u32 step_ud = (pad_state & BUTTON_R1) ? TV_NLIN_DISP : 1;
|
|
|
|
u32 step_lr = (pad_state & BUTTON_R1) ? TV_LLEN_DISP : 1;
|
|
|
|
bool switched = (pad_state & BUTTON_R1);
|
|
|
|
if (pad_state & BUTTON_DOWN) line0_next = line_seek(text, len, ww, line0, step_ud);
|
|
|
|
else if (pad_state & BUTTON_UP) line0_next = line_seek(text, len, ww, line0, -step_ud);
|
|
|
|
else if (pad_state & BUTTON_RIGHT) off_disp += step_lr;
|
|
|
|
else if (pad_state & BUTTON_LEFT) off_disp -= step_lr;
|
|
|
|
else if (switched && (pad_state & BUTTON_X)) {
|
|
|
|
u64 lnext64 = ShowNumberPrompt(lcurr, "Current line: %i\nEnter new line below.", lcurr);
|
|
|
|
if (lnext64 && (lnext64 != (u64) -1)) line0_next = line_seek(text, len, 0, line0, (int) lnext64 - lcurr);
|
|
|
|
ShowString(instr);
|
|
|
|
} else if (switched && (pad_state & BUTTON_Y)) {
|
|
|
|
ww = ww ? 0 : TV_LLEN_DISP;
|
|
|
|
line0_next = line_seek(text, len, ww, line0, 0);
|
|
|
|
} else if (pad_state & (BUTTON_B|BUTTON_START)) break;
|
|
|
|
|
|
|
|
// check for problems, apply changes
|
|
|
|
if (!ww && (line0_next > llast_nww)) line0_next = llast_nww;
|
|
|
|
else if (ww && (line0_next > llast_ww)) line0_next = llast_ww;
|
|
|
|
if (line0_next < line0) { // fix line number for decrease
|
|
|
|
do if (*(--line0) == '\n') lcurr--;
|
|
|
|
while (line0 > line0_next);
|
|
|
|
} else { // fix line number for increase / same
|
|
|
|
for (; line0_next > line0; line0++)
|
|
|
|
if (*line0 == '\n') lcurr++;
|
|
|
|
}
|
2018-01-12 15:01:55 +01:00
|
|
|
if (off_disp + TV_LLEN_DISP > llen_max) off_disp = llen_max - TV_LLEN_DISP;
|
2017-09-06 00:46:01 +02:00
|
|
|
if ((off_disp < 0) || ww) off_disp = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// clear screens
|
|
|
|
ClearScreenF(true, true, COLOR_STD_BG);
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2017-10-02 03:08:47 +02:00
|
|
|
// right now really only intended for use with the GodMode9 readme
|
|
|
|
// (misses safety checks for wider compatibility)
|
|
|
|
bool MemToCViewer(const char* text, u32 len, const char* title) {
|
|
|
|
const u32 max_captions = 24; // we assume this is enough
|
|
|
|
char* captions[max_captions];
|
|
|
|
u32 lineno[max_captions];
|
|
|
|
u32 ww = TV_LLEN_DISP;
|
|
|
|
|
|
|
|
// check if this really is text
|
|
|
|
if (!ValidateText(text, len)) {
|
|
|
|
ShowPrompt(false, "Error: Invalid text data");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// clear screens / view start of readme on top
|
|
|
|
ClearScreenF(true, true, COLOR_STD_BG);
|
|
|
|
MemTextView(text, len, (char*) text, 0, 1, ww, 0, false);
|
|
|
|
|
|
|
|
// parse text for markdown captions
|
|
|
|
u32 n_captions = 0;
|
|
|
|
char* ptr = (char*) text;
|
|
|
|
for (u32 lno = 1;; lno++) {
|
|
|
|
char* ptr_next = line_seek(text, len, 0, ptr, 1);
|
|
|
|
if (ptr == ptr_next) break;
|
|
|
|
if (*ptr == '#') {
|
|
|
|
captions[n_captions] = ptr;
|
|
|
|
lineno[n_captions] = lno;
|
|
|
|
if ((lno > 1) && (++n_captions >= max_captions)) break;
|
|
|
|
}
|
|
|
|
ptr = ptr_next;
|
|
|
|
}
|
|
|
|
|
|
|
|
int cursor = -1;
|
|
|
|
while (true) {
|
|
|
|
// display ToC
|
|
|
|
u32 y0 = TV_VPAD;
|
|
|
|
u32 x0 = (SCREEN_WIDTH_BOT - GetDrawStringWidth(title)) / 2;
|
|
|
|
DrawStringF(BOT_SCREEN, x0, y0, COLOR_TVTEXT, COLOR_STD_BG, "%s\n%*.*s", title,
|
|
|
|
strnlen(title, 40), strnlen(title, 40), "========================================");
|
|
|
|
y0 += 2 * (FONT_HEIGHT_EXT + (2*TV_VPAD));
|
|
|
|
for (u32 i = 0; (i < n_captions) && (y0 < SCREEN_HEIGHT); i++) {
|
|
|
|
u32 text_color = ((int) i == cursor) ? COLOR_TVRUN : COLOR_TVTEXT;
|
|
|
|
char* caption = captions[i];
|
|
|
|
u32 len = 0;
|
|
|
|
u32 lvl = 0;
|
|
|
|
for (; *caption == '#'; caption++, lvl++);
|
|
|
|
for (; IS_WHITESPACE(*caption); caption++);
|
|
|
|
for (; caption[len] != '\n' && caption[len] != '\r'; len++);
|
|
|
|
DrawStringF(BOT_SCREEN, x0 + (lvl-1) * (FONT_WIDTH_EXT/2), y0, text_color, COLOR_STD_BG,
|
|
|
|
"%*.*s", len, len, caption);
|
|
|
|
y0 += FONT_HEIGHT_EXT + (2*TV_VPAD);
|
|
|
|
}
|
|
|
|
|
|
|
|
// handle user input
|
|
|
|
u32 pad_state = InputWait(0);
|
|
|
|
if ((cursor >= 0) && (pad_state & BUTTON_A)) {
|
2017-12-02 12:28:20 +01:00
|
|
|
if (!MemTextViewer(text, len, lineno[cursor], false)) return false;
|
|
|
|
MemTextView(text, len, captions[cursor], 0, lineno[cursor], ww, 0, false);
|
2017-10-02 03:08:47 +02:00
|
|
|
} else if (pad_state & BUTTON_B) {
|
|
|
|
break;
|
|
|
|
} else if (pad_state & BUTTON_UP) {
|
|
|
|
cursor = (cursor <= 0) ? ((int) n_captions - 1) : cursor - 1;
|
|
|
|
MemTextView(text, len, captions[cursor], 0, lineno[cursor], ww, 0, false);
|
|
|
|
} else if (pad_state & BUTTON_DOWN) {
|
|
|
|
if (++cursor >= (int) n_captions) cursor = 0;
|
|
|
|
MemTextView(text, len, captions[cursor], 0, lineno[cursor], ww, 0, false);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// clear screens
|
|
|
|
ClearScreenF(true, true, COLOR_STD_BG);
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2017-09-06 00:46:01 +02:00
|
|
|
bool FileTextViewer(const char* path, bool as_script) {
|
|
|
|
// load text file (completely into memory)
|
2018-01-24 23:32:06 +01:00
|
|
|
// text file needs to fit inside the STD_BUFFER_SIZE
|
|
|
|
char* text = (char*) malloc(STD_BUFFER_SIZE);
|
|
|
|
if (!text) return false;
|
|
|
|
|
|
|
|
u32 flen = FileGetData(path, text, STD_BUFFER_SIZE, 0);
|
2017-09-06 00:46:01 +02:00
|
|
|
u32 len = 0; // actual length may be shorter due to zero symbol
|
|
|
|
for (len = 0; (len < flen) && text[len]; len++);
|
|
|
|
|
|
|
|
// let MemTextViewer take over
|
2018-01-24 23:32:06 +01:00
|
|
|
bool result = MemTextViewer(text, len, 1, as_script);
|
|
|
|
|
|
|
|
free(text);
|
|
|
|
return result;
|
2017-09-06 00:46:01 +02:00
|
|
|
}
|
|
|
|
|
2017-06-09 01:45:00 +02:00
|
|
|
bool ExecuteGM9Script(const char* path_script) {
|
|
|
|
char* script = (char*) SCRIPT_BUFFER;
|
|
|
|
char* ptr = script;
|
2017-10-29 14:00:02 +09:00
|
|
|
char path_str[32+1];
|
|
|
|
TruncateString(path_str, path_script, 32, 12);
|
|
|
|
|
|
|
|
// reset control flow global vars
|
|
|
|
ifcnt = 0;
|
2018-01-12 03:19:04 +01:00
|
|
|
jump_ptr = NULL;
|
|
|
|
for_ptr = NULL;
|
2017-12-19 02:58:58 +01:00
|
|
|
skip_state = 0;
|
2017-10-29 14:00:02 +09:00
|
|
|
syntax_error = false;
|
2017-06-09 01:45:00 +02:00
|
|
|
|
2017-09-08 15:39:06 +02:00
|
|
|
// fetch script - if no path is given, assume script already in script buffer
|
|
|
|
u32 script_size = (path_script) ? FileGetData(path_script, (u8*) script, SCRIPT_MAX_SIZE, 0) : strnlen(script, SCRIPT_BUFFER_SIZE);
|
|
|
|
if (!script_size || (script_size >= SCRIPT_BUFFER_SIZE))
|
2017-06-09 01:45:00 +02:00
|
|
|
return false;
|
|
|
|
char* end = script + script_size;
|
|
|
|
*end = '\0';
|
|
|
|
|
|
|
|
// initialise variables
|
2017-07-26 14:08:29 +02:00
|
|
|
init_vars(path_script);
|
2017-06-09 01:45:00 +02:00
|
|
|
|
2017-09-16 17:08:26 +02:00
|
|
|
// setup script preview (only if used)
|
|
|
|
u32 preview_mode_local = 0;
|
|
|
|
if (MAIN_SCREEN != TOP_SCREEN) {
|
2017-09-06 00:46:01 +02:00
|
|
|
ClearScreen(TOP_SCREEN, COLOR_STD_BG);
|
2017-09-16 17:08:26 +02:00
|
|
|
preview_mode = 2; // 0 -> off 1 -> quick 2 -> full
|
2017-09-25 00:50:44 +02:00
|
|
|
script_color_active = COLOR_TVRUN;
|
|
|
|
script_color_comment = COLOR_TVCMT;
|
|
|
|
script_color_code = COLOR_TVCMD;
|
2017-09-16 17:08:26 +02:00
|
|
|
}
|
2017-09-06 00:46:01 +02:00
|
|
|
|
|
|
|
// script execute loop
|
2017-10-29 14:00:02 +09:00
|
|
|
u32 lno = 1;
|
|
|
|
while (ptr < end) {
|
2017-06-09 01:45:00 +02:00
|
|
|
u32 flags = 0;
|
|
|
|
|
|
|
|
// find line end
|
|
|
|
char* line_end = strchr(ptr, '\n');
|
|
|
|
if (!line_end) line_end = ptr + strlen(ptr);
|
|
|
|
|
2017-09-06 00:46:01 +02:00
|
|
|
// update script viewer
|
|
|
|
if (MAIN_SCREEN != TOP_SCREEN) {
|
2017-09-16 17:08:26 +02:00
|
|
|
if (preview_mode != preview_mode_local) {
|
2017-12-20 00:13:31 +01:00
|
|
|
if (!preview_mode || (preview_mode > 2) || !preview_mode_local)
|
|
|
|
ClearScreen(TOP_SCREEN, COLOR_STD_BG);
|
|
|
|
if (preview_mode > 2) {
|
|
|
|
char* preview_str = get_var("PREVIEW_MODE", NULL);
|
2018-01-24 23:32:06 +01:00
|
|
|
u32 pcx_size = fvx_qsize(preview_str);
|
|
|
|
u8* pcx = (u8*) malloc(SCREEN_SIZE_TOP);
|
|
|
|
u8* bitmap = (u8*) malloc(SCREEN_SIZE_TOP);
|
|
|
|
if (pcx && bitmap && pcx_size && (pcx_size < SCREEN_SIZE_TOP) &&
|
|
|
|
(pcx_size == FileGetData(preview_str, pcx, pcx_size, 0)) &&
|
|
|
|
(PCX_Decompress(bitmap, SCREEN_SIZE_TOP, pcx, pcx_size))) {
|
2017-12-20 00:13:31 +01:00
|
|
|
PCXHdr* hdr = (PCXHdr*) (void*) pcx;
|
2018-01-24 23:32:06 +01:00
|
|
|
DrawBitmap(TOP_SCREEN, -1, -1, PCX_Width(hdr), PCX_Height(hdr), bitmap);
|
2017-12-20 00:13:31 +01:00
|
|
|
} else {
|
|
|
|
if (strncmp(preview_str, "off", _VAR_CNT_LEN) == 0) preview_str = "(preview disabled)";
|
|
|
|
DrawStringCenter(TOP_SCREEN, COLOR_STD_FONT, COLOR_STD_BG, preview_str);
|
|
|
|
}
|
2018-01-24 23:32:06 +01:00
|
|
|
if (pcx) free(pcx);
|
|
|
|
if (bitmap) free(bitmap);
|
2017-12-20 00:13:31 +01:00
|
|
|
preview_mode = 0;
|
|
|
|
}
|
2017-09-16 17:08:26 +02:00
|
|
|
preview_mode_local = preview_mode;
|
|
|
|
}
|
2017-12-20 00:13:31 +01:00
|
|
|
|
|
|
|
bool show_preview = preview_mode;
|
2017-09-16 17:08:26 +02:00
|
|
|
if (preview_mode == 1) {
|
|
|
|
show_preview = false;
|
|
|
|
for (char* c = ptr; (c < line_end) && !show_preview; c++) {
|
2017-12-20 00:13:31 +01:00
|
|
|
// check for comments / labels
|
2017-09-16 17:08:26 +02:00
|
|
|
if (IS_WHITESPACE(*c)) continue;
|
2017-12-20 00:13:31 +01:00
|
|
|
else if ((*c == '#') || (*c == '@')) break;
|
2017-09-16 17:08:26 +02:00
|
|
|
else show_preview = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (show_preview) {
|
|
|
|
if (lno <= (TV_NLIN_DISP/2)) {
|
|
|
|
MemTextView(script, script_size, script, 0, 1, 0, lno, true);
|
|
|
|
} else {
|
|
|
|
char* ptr_view = line_seek(script, script_size, 0, ptr, -(TV_NLIN_DISP/2));
|
|
|
|
u32 lno_view = lno - (TV_NLIN_DISP/2);
|
|
|
|
MemTextView(script, script_size, ptr_view, 0, lno_view, 0, lno, true);
|
|
|
|
}
|
2017-09-06 00:46:01 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-06-09 01:45:00 +02:00
|
|
|
// run command
|
|
|
|
char err_str[_ERR_STR_LEN+1] = { 0 };
|
2017-10-29 14:00:02 +09:00
|
|
|
bool result = run_line(ptr, line_end, &flags, err_str, false);
|
|
|
|
|
2018-01-12 03:19:04 +01:00
|
|
|
|
2017-10-29 14:00:02 +09:00
|
|
|
// skip state handling
|
|
|
|
char* skip_ptr = ptr;
|
2018-01-12 03:19:04 +01:00
|
|
|
if ((skip_state == _SKIP_BLOCK) || (skip_state == _SKIP_TILL_END)) {
|
2017-12-19 02:58:58 +01:00
|
|
|
skip_ptr = skip_block(line_end + 1, (skip_state == _SKIP_TILL_END), false);
|
2018-01-10 00:49:28 +01:00
|
|
|
if (!skip_ptr) {
|
|
|
|
snprintf(err_str, _ERR_STR_LEN, "unclosed conditional");
|
|
|
|
result = false;
|
|
|
|
syntax_error = true;
|
|
|
|
}
|
2018-01-12 03:19:04 +01:00
|
|
|
} else if (skip_state == _SKIP_TO_NEXT) {
|
|
|
|
skip_ptr = find_next(ptr);
|
|
|
|
if (!skip_ptr) {
|
|
|
|
snprintf(err_str, _ERR_STR_LEN, "'for' without 'next'");
|
|
|
|
result = false;
|
|
|
|
syntax_error = true;
|
|
|
|
}
|
|
|
|
for_ptr = (char*) line_end + 1;
|
|
|
|
} else if (skip_state == _SKIP_TO_FOR) {
|
|
|
|
skip_ptr = for_ptr;
|
|
|
|
if (!skip_ptr) {
|
|
|
|
snprintf(err_str, _ERR_STR_LEN, "'next' without 'for'");
|
|
|
|
result = false;
|
|
|
|
syntax_error = true;
|
|
|
|
}
|
|
|
|
skip_state = 0;
|
2017-10-29 14:00:02 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!result) { // error handling
|
|
|
|
if (syntax_error) // severe error, can't continue
|
|
|
|
flags &= ~(_FLG('o')|_FLG('s')); // never silent or optional
|
|
|
|
|
2017-06-09 01:45:00 +02:00
|
|
|
if (!(flags & _FLG('s'))) { // not silent
|
|
|
|
if (!*err_str) {
|
2017-07-26 14:14:12 +02:00
|
|
|
char* msg_fail = get_var("ERRORMSG", NULL);
|
2017-06-09 01:45:00 +02:00
|
|
|
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);
|
2017-09-06 00:46:01 +02:00
|
|
|
ShowPrompt(false, "%s\nline %lu: %s\n%s", path_str, lno, err_str, line_str);
|
2017-06-09 01:45:00 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if (!(flags & _FLG('o'))) return false; // failed if not optional
|
|
|
|
}
|
|
|
|
|
|
|
|
// reposition pointer
|
2017-10-29 14:00:02 +09:00
|
|
|
if (skip_ptr != ptr) {
|
|
|
|
ptr = skip_ptr;
|
|
|
|
lno = get_lno(script, script_size, ptr);
|
|
|
|
} else if (jump_ptr) {
|
|
|
|
ptr = jump_ptr;
|
|
|
|
lno = get_lno(script, script_size, ptr);
|
|
|
|
ifcnt = 0; // jumping into conditional block is unexpected/unsupported
|
|
|
|
jump_ptr = NULL;
|
2018-01-12 03:19:04 +01:00
|
|
|
for_ptr = NULL;
|
2018-01-18 02:23:46 +01:00
|
|
|
for_handler(NULL, NULL, NULL, false);
|
2017-10-29 14:00:02 +09:00
|
|
|
} else {
|
|
|
|
ptr = line_end + 1;
|
|
|
|
lno++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// check for unresolved if here
|
|
|
|
if (ifcnt) {
|
|
|
|
ShowPrompt(false, "%s\nend of script: unresolved 'if'", path_str);
|
|
|
|
return false;
|
2018-01-12 03:19:04 +01:00
|
|
|
} else if (for_ptr) {
|
|
|
|
ShowPrompt(false, "%s\nend of script: unresolved 'for'", path_str);
|
2018-01-18 02:23:46 +01:00
|
|
|
for_handler(NULL, NULL, NULL, false);
|
2018-01-12 03:19:04 +01:00
|
|
|
return false;
|
2017-06-09 01:45:00 +02:00
|
|
|
}
|
|
|
|
|
2017-10-29 14:00:02 +09:00
|
|
|
// success message if applicable
|
2017-07-26 14:14:12 +02:00
|
|
|
char* msg_okay = get_var("SUCCESSMSG", NULL);
|
2017-06-09 01:45:00 +02:00
|
|
|
if (msg_okay && *msg_okay) ShowPrompt(false, msg_okay);
|
2017-09-06 00:46:01 +02:00
|
|
|
|
2017-06-09 01:45:00 +02:00
|
|
|
return true;
|
|
|
|
}
|