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-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"
2017-12-19 02:58:58 +01:00
# define _SKIP_BLOCK 1
# define _SKIP_TILL_END 2
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))
2017-06-09 01:45:00 +02:00
# define _FLG(c) (1 << (c - 'a'))
2017-12-20 02:08:40 +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))
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-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 ,
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 ,
2017-06-09 01:45:00 +02:00
CMD_ID_SET ,
2017-12-20 12:33:18 +09:00
CMD_ID_STRSPLIT ,
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 ,
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 ,
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
{ 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 } ,
{ 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 } ,
2017-09-13 02:45:00 +02:00
{ CMD_ID_FILESEL , " filesel " , 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-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 ' ) } ,
{ 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 } ,
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
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
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 ] ;
snprintf ( env_date , _VAR_CNT_LEN , " %02lX%02lX%02lX " , ( u32 ) dstime . bcd_Y , ( u32 ) dstime . bcd_M , ( u32 ) dstime . bcd_D ) ;
snprintf ( env_time , _VAR_CNT_LEN , " %02lX%02lX%02lX " , ( u32 ) dstime . bcd_h , ( u32 ) dstime . bcd_m , ( u32 ) dstime . bcd_s ) ;
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
2017-10-27 19:25:26 +02:00
set_var ( " HAX " , IS_SIGHAX ? " 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
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 ' ;
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 ' ;
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 ;
}
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 ) ;
2017-12-19 02:58:58 +01:00
} // otherwise: irrelevant line
2017-10-29 14:00:02 +09:00
}
return NULL ;
}
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-19 02:58:58 +01:00
// special handling for "if" and "elif"
2017-10-29 14:00:02 +09:00
if ( MATCH_STR ( cmd , cmd_len , _CMD_IF ) ) {
* 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 ;
2017-10-02 16:10:17 +02:00
if ( ( id = = CMD_ID_SHA ) | | ( id = = CMD_ID_SHAGET ) | | ( id = = CMD_ID_INJECT ) ) {
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-10-29 14:00:02 +09:00
if ( id = = CMD_ID_IF ) {
// 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 ;
}
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 ) {
u8 qrcode [ qrcodegen_BUFFER_LEN_MAX ] ;
u8 temp [ qrcodegen_BUFFER_LEN_MAX ] ;
ret = qrcodegen_encodeText ( argv [ 1 ] , temp , qrcode , qrcodegen_Ecc_LOW ,
qrcodegen_VERSION_MIN , qrcodegen_VERSION_MAX , qrcodegen_Mask_AUTO , true ) ;
if ( ret ) {
memcpy ( TEMP_BUFFER , ALT_SCREEN , ( SCREEN_HEIGHT * SCREEN_WIDTH_ALT * 3 ) ) ;
DrawQrCode ( ALT_SCREEN , qrcode ) ;
ShowPrompt ( false , argv [ 0 ] ) ;
memcpy ( ALT_SCREEN , TEMP_BUFFER , ( SCREEN_HEIGHT * SCREEN_WIDTH_ALT * 3 ) ) ;
}
}
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
}
else if ( id = = CMD_ID_FILESEL ) {
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 ) ;
char * npattern = strrchr ( path , ' / ' ) ;
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 " ) ;
} else if ( ! npattern ) {
2017-09-13 02:45:00 +02:00
ret = false ;
if ( err_str ) snprintf ( err_str , _ERR_STR_LEN , " invalid path " ) ;
} else {
* ( npattern + + ) = ' \0 ' ;
ret = FileSelector ( choice , argv [ 0 ] , path , npattern , false , true ) ;
if ( err_str ) snprintf ( err_str , _ERR_STR_LEN , " fileselect abort " ) ;
}
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-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
}
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
}
else if ( id = = CMD_ID_BOOT ) {
2017-08-21 21:33:22 +02:00
size_t firm_size = FileGetData ( argv [ 0 ] , TEMP_BUFFER , TEMP_BUFFER_SIZE , 0 ) ;
2017-09-29 03:14:06 +02:00
ret = firm_size & & IsBootableFirm ( TEMP_BUFFER , firm_size ) ;
2017-08-21 21:33:22 +02:00
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 * ) TEMP_BUFFER , fixpath ) ;
while ( 1 ) ;
} else if ( err_str ) snprintf ( err_str , _ERR_STR_LEN , " not a bootable 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 \n Eject SD card... " , argv [ 0 ] ) ;
while ( ! ( ( pad_state = InputWait ( 0 ) ) & ( BUTTON_B | SD_EJECT ) ) ) ;
if ( pad_state & SD_EJECT ) {
ShowString ( " %s \n \n Insert 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 ) {
asm ( " bkpt \n \t " ) ;
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 " ) ;
}
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
if ( IS_CTRLFLOW_CMD ( cmdid ) ) {
// block out of control flow commands
if ( if_cond ) {
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-20 01:06:56 +01:00
// shortcuts for "elif" / "else"
if ( ( ( cmdid = = CMD_ID_ELIF ) | | ( cmdid = = CMD_ID_ELSE ) ) & & ! skip_state ) {
2017-12-19 22:44:54 +01:00
skip_state = _SKIP_TILL_END ;
2017-12-20 01:06:56 +01:00
cmdid = 0 ;
2017-12-19 02:58:58 +01:00
}
2017-12-20 01:06:56 +01:00
// handle "if" / "elif"
if ( ( cmdid = = CMD_ID_IF ) | | ( cmdid = = CMD_ID_ELIF ) ) {
// set defaults
argc = 1 ;
strncpy ( argv [ 0 ] , _ARG_FALSE , _ARG_MAX_LEN ) ;
// skip to behind the "if"/"elif" command
char * line_start_next = ( char * ) line_start ;
for ( ; IS_WHITESPACE ( * line_start_next ) ; line_start_next + + ) ;
line_start_next + = strlen ( ( cmdid = = CMD_ID_IF ) ? _CMD_IF : _CMD_ELIF ) ;
// 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 ) ;
bool ar = ! ww & & ( ( int ) llen > off_disp + TV_LLEN_DISP ) ;
// 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
2017-09-25 13:21:54 +02:00
if ( ( cmt_start > 0 ) & & ( cmt_start < TV_LLEN_DISP ) ) {
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 \n R+Y - Toggle wordwrap \n R+X - Goto line # \n B - 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 \n Enter 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 + + ;
}
if ( off_disp + TV_LLEN_DISP > ( int ) llen_max ) off_disp = llen_max - TV_LLEN_DISP ;
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)
char * text = ( char * ) TEMP_BUFFER ;
u32 flen = FileGetData ( path , text , TEMP_BUFFER_SIZE , 0 ) ;
u32 len = 0 ; // actual length may be shorter due to zero symbol
for ( len = 0 ; ( len < flen ) & & text [ len ] ; len + + ) ;
// let MemTextViewer take over
2017-10-02 03:08:47 +02:00
return MemTextViewer ( text , len , 1 , as_script ) ;
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 ;
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 ) ;
u8 * pcx = TEMP_BUFFER + TEMP_BUFFER_SIZE / 2 ;
u32 pcx_size = FileGetData ( preview_str , pcx , TEMP_BUFFER_SIZE / 2 , 0 ) ;
if ( ( pcx_size > 0 ) & & ( pcx_size < TEMP_BUFFER_SIZE / 2 ) & &
( PCX_Decompress ( TEMP_BUFFER , TEMP_BUFFER_SIZE / 2 , pcx , pcx_size ) ) ) {
PCXHdr * hdr = ( PCXHdr * ) ( void * ) pcx ;
DrawBitmap ( TOP_SCREEN , - 1 , - 1 , PCX_Width ( hdr ) , PCX_Height ( hdr ) , TEMP_BUFFER ) ;
} 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 ) ;
}
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 ) ;
// skip state handling
char * skip_ptr = ptr ;
if ( skip_state ) {
2017-12-19 02:58:58 +01:00
skip_ptr = skip_block ( line_end + 1 , ( skip_state = = _SKIP_TILL_END ) , false ) ;
2017-10-29 14:00:02 +09:00
if ( ! skip_ptr ) snprintf ( err_str , _ERR_STR_LEN , " unclosed conditional " ) ;
}
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 \n line %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 ;
} else {
ptr = line_end + 1 ;
lno + + ;
}
}
// check for unresolved if here
if ( ifcnt ) {
ShowPrompt ( false , " %s \n end of script: unresolved 'if' " , path_str ) ;
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 ;
}