campctf2023-chall-tis256

Zachtronics TIS100-inspired reversing challenge for CampCTF 2023
git clone https://git.sinitax.com/sinitax/campctf2023-chall-tis256
Log | Files | Refs | Submodules | README | sfeed.txt

tis256-curses.c (13830B)


      1#include <bits/time.h>
      2#include <sys/select.h>
      3#define NCURSES_WIDECHAR 1
      4
      5#include "tpu.h"
      6#include "util.h"
      7#include "asm.h"
      8
      9#include <ncurses.h>
     10#include <sys/inotify.h>
     11#include <time.h>
     12#include <unistd.h>
     13#include <locale.h>
     14#include <errno.h>
     15#include <stdarg.h>
     16#include <string.h>
     17#include <stdio.h>
     18#include <stdbool.h>
     19#include <stdlib.h>
     20
     21#define KEY_ESC 0x1b
     22#define KEY_CTRL(c) ((c) & ~0x60)
     23
     24#define TPU_INPUT_ROWS 14
     25#define TPU_INPUT_COLS 21
     26#define TPU_INFO_W 7
     27#define TPU_INFO_H 4
     28#define TPU_W (1 + TPU_INPUT_COLS + TPU_INFO_W)
     29#define TPU_H (1 + TPU_INPUT_ROWS + 1)
     30
     31enum input_mode {
     32	MAIN,
     33	TPU_NAV
     34};
     35
     36enum {
     37	NONE, MIN, MAX, MID
     38};
     39
     40enum {
     41	COLOR_WARN = 1
     42};
     43
     44static const char *mode_repr[] = {
     45	"IDL", "RUN", "REA", "WRI"
     46};
     47
     48static int scrx = 0;
     49static int scry = 0;
     50static int scrw = 80;
     51static int scrh = 40;
     52
     53static struct tis tis;
     54
     55static struct timespec show_reloaded = { 0 };
     56static bool showing_reloaded = false;
     57
     58static enum input_mode input_mode = MAIN;
     59
     60static struct tpu *tpu_sel = NULL;
     61
     62int (*cleanup)(void) = endwin;
     63const char *progname = "tis256-curses";
     64
     65static int64_t
     66ms_since(struct timespec *ts)
     67{
     68	struct timespec now;
     69
     70	if (clock_gettime(CLOCK_REALTIME, &now) < 0)
     71		die("clock_gettime:");
     72
     73	return (now.tv_sec - ts->tv_sec) * 1000
     74		+ (now.tv_nsec - ts->tv_nsec) / 1000000;
     75}
     76
     77static enum tpu_port_dir
     78key_to_dir(int key)
     79{
     80	switch (key) {
     81	case KEY_LEFT:
     82	case 'A':
     83		return DIR_LEFT;
     84	case KEY_RIGHT:
     85	case 'D':
     86		return DIR_RIGHT;
     87	case KEY_UP:
     88	case 'W':
     89		return DIR_UP;
     90	case KEY_DOWN:
     91	case 'S':
     92		return DIR_DOWN;
     93	default:
     94		abort();
     95	}
     96}
     97
     98static const cchar_t *
     99dir_to_arrow(enum tpu_port_dir dir)
    100{
    101	switch (dir) {
    102	case DIR_UP:
    103		return WACS_UARROW;
    104	case DIR_DOWN:
    105		return WACS_DARROW;
    106	case DIR_LEFT:
    107		return WACS_LARROW;
    108	case DIR_RIGHT:
    109		return WACS_RARROW;
    110	}
    111}
    112
    113static int
    114tpu_pos_x(struct tpu *tpu)
    115{
    116	return 2 + (int) tpu->x * (TPU_W + 4);
    117}
    118
    119static int
    120tpu_pos_y(struct tpu *tpu)
    121{
    122	return 2 + (int) tpu->y * (TPU_H + 2);
    123}
    124
    125static void
    126tui_draw_box(int sx, int sy, int w, int h, attr_t attr,
    127	const cchar_t *ul, const cchar_t *ur,
    128	const cchar_t *ll, const cchar_t *lr)
    129{
    130	int x, y;
    131
    132	if (sx + w < scrx || sx >= scrx + scrw) return;
    133	if (sy + h < scry || sy >= scry + scrh) return;
    134
    135	sx -= scrx;
    136	sy -= scry;
    137
    138	attron(attr);
    139	mvadd_wch(sy, sx, ul);
    140	mvadd_wch(sy, sx + w - 1, ur);
    141	mvadd_wch(sy + h - 1, sx, ll);
    142	mvadd_wch(sy + h - 1, sx + w - 1, lr);
    143	for (x = sx + 1; x < sx + w - 1; x++)
    144		mvadd_wch(sy, x, WACS_D_HLINE);
    145	for (x = sx + 1; x < sx + w - 1; x++)
    146		mvadd_wch(sy + h - 1, x, WACS_D_HLINE);
    147	for (y = sy + 1; y < sy + h - 1; y++)
    148		mvadd_wch(y, sx, WACS_D_VLINE);
    149	for (y = sy + 1; y < sy + h - 1; y++)
    150		mvadd_wch(y, sx + w - 1, WACS_D_VLINE);
    151	attroff(attr);
    152}
    153
    154static void
    155__attribute__((format(printf, 4, 5)))
    156tui_draw_text(int x, int y, attr_t attr, const char *fmt, ...)
    157{
    158	char buf[512];
    159	va_list ap;
    160	int i;
    161
    162	va_start(ap, fmt);
    163	vsnprintf(buf, 512, fmt, ap);
    164	va_end(ap);
    165
    166	attron(attr);
    167	for (i = 0; i < strlen(buf) && x + i < scrx + scrw; i++)
    168		mvaddch(y - scry, x + i - scrx, (chtype) buf[i]);
    169	attroff(attr);
    170}
    171
    172static void
    173tui_draw_wch(int x, int y, attr_t attr, const cchar_t *c)
    174{
    175	attron(attr);
    176	mvadd_wch(y - scry, x - scrx, c);
    177	attroff(attr);
    178}
    179
    180static void
    181tui_draw_tpu(struct tpu *tpu)
    182{
    183	char linebuf[TPU_INPUT_COLS + 1];
    184	struct tpu_port *port;
    185	int sx, sy, x, y, w, h;
    186	int off, start, inst;
    187	size_t len;
    188	attr_t attr;
    189	int idle;
    190
    191	attr = (tpu_sel == tpu && input_mode == TPU_NAV) ? A_BOLD : 0;
    192
    193	sx = tpu_pos_x(tpu);
    194	sy = tpu_pos_y(tpu);
    195	tui_draw_box(sx, sy, TPU_W, TPU_H, attr,
    196		WACS_D_ULCORNER, WACS_D_URCORNER,
    197		WACS_D_LLCORNER, WACS_D_LRCORNER);
    198
    199	if (tpu->inst_cnt > 0) {
    200		start = MAX(0, (int) tpu->pc - 6);
    201		for (off = 0, inst = start; inst < TPU_MAX_INST; inst++) {
    202			if (tpu->labels[inst]) {
    203				len = strlen(tpu->labels[inst]);
    204				if (len > TPU_INPUT_COLS - 1) {
    205					tui_draw_text(sx + 1, sy + 1 + off, A_DIM,
    206						"%.*s..:", TPU_INPUT_COLS - 3,
    207						tpu->labels[inst]);
    208				} else {
    209					tui_draw_text(sx + 1, sy + 1 + off, A_DIM,
    210						"%s:", tpu->labels[inst]);
    211				}
    212				if (++off >= TPU_INPUT_ROWS) break;
    213			}
    214			if (inst < tpu->inst_cnt) {
    215				asm_print_inst(linebuf, sizeof(linebuf),
    216					&tpu->insts[inst]);
    217				tui_draw_text(sx + 1, sy + 1 + off,
    218					inst == tpu->pc ? A_STANDOUT : 0, "%-*.*s",
    219					TPU_INPUT_COLS, TPU_INPUT_COLS, linebuf);
    220				if (++off >= TPU_INPUT_ROWS) break;
    221			}
    222		}
    223	}
    224
    225	x = sx + TPU_W - TPU_INFO_W;
    226	y = sy;
    227	w = TPU_INFO_W;
    228	h = TPU_INFO_H;
    229	tui_draw_box(x, y, w, h, attr,
    230		WACS_D_TTEE, WACS_D_URCORNER, WACS_D_LTEE, WACS_D_RTEE);
    231	tui_draw_text(x + 2, y + 1, A_BOLD, "ACC");
    232	tui_draw_text(x + 2, y + 2, 0, "%03i", tpu->acc);
    233
    234	tui_draw_box(x, (y += TPU_INFO_H - 1), w, h, attr,
    235		WACS_D_LTEE, WACS_D_RTEE, WACS_D_LTEE, WACS_D_RTEE);
    236	tui_draw_text(x + 2, y + 1, A_BOLD, "BAK");
    237	tui_draw_text(x + 2, y + 2, 0, "%03i", tpu->bak);
    238
    239	tui_draw_box(x, (y += TPU_INFO_H - 1), w, h, attr,
    240		WACS_D_LTEE, WACS_D_RTEE, WACS_D_LTEE, WACS_D_RTEE);
    241	tui_draw_text(x + 2, y + 1, A_BOLD, "LST");
    242	if (tpu->last < 0) {
    243		tui_draw_text(x + 2, y + 2, 0, "N/A");
    244	} else {
    245		tui_draw_wch(x + 2, y + 2, 0,
    246			dir_to_arrow((enum tpu_port_dir) tpu->last));
    247		tui_draw_wch(x + 3, y + 2, 0,
    248			dir_to_arrow((enum tpu_port_dir) tpu->last));
    249		tui_draw_wch(x + 4, y + 2, 0,
    250			dir_to_arrow((enum tpu_port_dir) tpu->last));
    251	}
    252
    253	tui_draw_box(x, (y += TPU_INFO_H - 1), w, h, attr,
    254		WACS_D_LTEE, WACS_D_RTEE, WACS_D_LTEE, WACS_D_RTEE);
    255	tui_draw_text(x + 2, y + 1, A_BOLD, "MOD");
    256	tui_draw_text(x + 2, y + 2, 0, "%s", mode_repr[tpu->status]);
    257
    258	tui_draw_box(x, (y += TPU_INFO_H - 1), w, h, attr,
    259		WACS_D_LTEE, WACS_D_RTEE, WACS_D_BTEE, WACS_D_LRCORNER);
    260	tui_draw_text(x + 2, y + 1, A_BOLD, "IDL");
    261	if (tpu->steps > 0)
    262		idle = (int) ((double) tpu->idle_steps * 100 / (double) tpu->steps);
    263	else
    264		idle = 100;
    265	tui_draw_text(x + 2, y + 2, 0, "%03i", idle);
    266
    267	port = &tpu->ports[DIR_LEFT];
    268	if (!port->dst_port || !(port->dst_port->type & PORT_OUT))
    269		attron(COLOR_PAIR(COLOR_WARN));
    270	if (port->in >= 0)
    271		tui_draw_text(sx - 3, sy + 6, A_BOLD, "%03i", port->in);
    272	if (port->type & PORT_IN)
    273		tui_draw_wch(sx - 1, sy + 7,
    274			port->in >= 0 ? A_BOLD : 0, WACS_RARROW);
    275	attroff(COLOR_PAIR(COLOR_WARN));
    276	if (!port->dst_port || !(port->dst_port->type & PORT_IN))
    277		attron(COLOR_PAIR(COLOR_WARN));
    278	if (port->type & PORT_OUT)
    279		tui_draw_wch(sx - 1, sy + 8,
    280			port->out_pending >= 0 ? A_BOLD : 0, WACS_LARROW);
    281	if (port->out_pending >= 0)
    282		tui_draw_text(sx - 3, sy + 10, A_BOLD, "%03i", port->out_pending);
    283	attroff(COLOR_PAIR(COLOR_WARN));
    284
    285	port = &tpu->ports[DIR_RIGHT];
    286	if (!port->dst_port || !(port->dst_port->type & PORT_IN))
    287		attron(COLOR_PAIR(COLOR_WARN));
    288	if (port->out_pending >= 0)
    289		tui_draw_text(sx + TPU_W, sy + 5, A_BOLD, "%03i", port->out_pending);
    290	if (port->type & PORT_OUT)
    291		tui_draw_wch(sx + TPU_W, sy + 7,
    292			port->out_pending >= 0 ? A_BOLD : 0, WACS_RARROW);
    293	attroff(COLOR_PAIR(COLOR_WARN));
    294	if (!port->dst_port || !(port->dst_port->type & PORT_OUT))
    295		attron(COLOR_PAIR(COLOR_WARN));
    296	if (port->type & PORT_IN)
    297		tui_draw_wch(sx + TPU_W, sy + 8,
    298			port->in >= 0 ? A_BOLD : 0, WACS_LARROW);
    299	if (port->in >= 0)
    300		tui_draw_text(sx + TPU_W, sy + 9, A_BOLD, "%03i", port->in);
    301	attroff(COLOR_PAIR(COLOR_WARN));
    302
    303	port = &tpu->ports[DIR_UP];
    304	if (!port->dst_port || !(port->dst_port->type & PORT_IN))
    305		attron(COLOR_PAIR(COLOR_WARN));
    306	if (port->out_pending >= 0)
    307		tui_draw_text(sx + 9, sy - 1, A_BOLD, "%03i", port->out_pending);
    308	if (port->type & PORT_OUT)
    309		tui_draw_wch(sx + 13, sy - 1,
    310			port->out_pending >= 0 ? A_BOLD : 0, WACS_UARROW);
    311	attroff(COLOR_PAIR(COLOR_WARN));
    312	if (!port->dst_port || !(port->dst_port->type & PORT_OUT))
    313		attron(COLOR_PAIR(COLOR_WARN));
    314	if (port->type & PORT_IN)
    315		tui_draw_wch(sx + 15, sy - 1,
    316			port->in >= 0 ? A_BOLD : 0, WACS_DARROW);
    317	if (port->in >= 0)
    318		tui_draw_text(sx + 17, sy - 1, A_BOLD, "%03i", port->in);
    319	attroff(COLOR_PAIR(COLOR_WARN));
    320
    321	port = &tpu->ports[DIR_DOWN];
    322	if (!port->dst_port || !(port->dst_port->type & PORT_OUT))
    323		attron(COLOR_PAIR(COLOR_WARN));
    324	if (port->in >= 0)
    325		tui_draw_text(sx + 9, sy + TPU_H, A_BOLD, "%03i", port->in);
    326	if (port->type & PORT_IN)
    327		tui_draw_wch(sx + 13, sy + TPU_H,
    328			port->in >= 0 ? A_BOLD : 0, WACS_UARROW);
    329	attroff(COLOR_PAIR(COLOR_WARN));
    330	if (!port->dst_port || !(port->dst_port->type & PORT_IN))
    331		attron(COLOR_PAIR(COLOR_WARN));
    332	if (port->type & PORT_OUT)
    333		tui_draw_wch(sx + 15, sy + TPU_H,
    334			port->out_pending >= 0 ? A_BOLD : 0, WACS_DARROW);
    335	if (port->out_pending >= 0)
    336		tui_draw_text(sx + 17, sy + TPU_H, A_BOLD, "%03i", port->out_pending);
    337	attroff(COLOR_PAIR(COLOR_WARN));
    338}
    339
    340static void
    341tui_draw(void)
    342{
    343	struct tpu *tpu;
    344	size_t i;
    345	int tx;
    346
    347	clear();
    348	for (tpu = tis.tpu_vec.tpus, i = 0; i < tis.tpu_vec.cnt; i++, tpu++)
    349		tui_draw_tpu(tpu);
    350	if (showing_reloaded) {
    351		tx = scrx + scrw / 2 - 4;
    352		tui_draw_text(scrx, scry, A_STANDOUT, "%*s", scrw, "");
    353		tui_draw_text(tx, scry, A_STANDOUT, "RELOADED");
    354	}
    355	refresh();
    356}
    357
    358static void
    359tui_resize(void)
    360{
    361	scrw = getmaxx(stdscr);
    362	scrh = getmaxy(stdscr);
    363}
    364
    365static void
    366tpu_seek_running(void)
    367{
    368	struct tpu *tpu;
    369	size_t i;
    370
    371	for (tpu = tis.tpu_vec.tpus, i = 0; i < tis.tpu_vec.cnt; i++, tpu++) {
    372		if (tpu->status == STATUS_RUN) tpu_sel = tpu;
    373	}
    374}
    375
    376static void
    377tui_seek(struct tpu *tpu, int dx, int dy)
    378{
    379	int minx, miny, maxx, maxy;
    380	int x, y;
    381	size_t i;
    382
    383	if (tpu) {
    384		minx = maxx = tpu_pos_x(tpu);
    385		miny = maxy = tpu_pos_y(tpu);
    386 	} else {
    387		minx = miny = maxx = maxy = -1;
    388		for (tpu = tis.tpu_vec.tpus, i = 0; i < tis.tpu_vec.cnt; i++, tpu++) {
    389			x = tpu_pos_x(tpu);
    390			if (minx == -1 || x < minx) minx = x;
    391			if (maxx == -1 || x > maxx) maxx = x;
    392			y = tpu_pos_y(tpu);
    393			if (miny == -1 || y < miny) miny = y;
    394			if (maxy == -1 || y > maxy) maxy = y;
    395		}
    396		if (minx == -1 || miny == -1) return;
    397	}
    398
    399	if (dx == MIN) scrx = minx - 2;
    400	else if (dx == MAX) scrx = maxx + TPU_W + 2 - scrw;
    401	else if (dx == MID) scrx = (minx + maxx + TPU_W - scrw) / 2;
    402
    403	if (dy == MIN) scry = miny - 2;
    404	else if (dy == MAX) scry = maxy + TPU_H + 2 - scrh;
    405	else if (dy == MID) scry = (miny + maxy + TPU_H - scrh) / 2;
    406}
    407
    408static void
    409handlekey(int key)
    410{
    411	enum tpu_port_dir dir;
    412
    413	if (input_mode == MAIN) {
    414		switch (key) {
    415		case 'I':
    416			input_mode = TPU_NAV;
    417			break;
    418		case KEY_UP:
    419		case 'W':
    420			scry -= 2;
    421			break;
    422		case KEY_DOWN:
    423		case 'S':
    424			scry += 2;
    425			break;
    426		case KEY_LEFT:
    427		case 'A':
    428			scrx -= 4;
    429			break;
    430		case KEY_RIGHT:
    431		case 'D':
    432			scrx += 4;
    433			break;
    434		}
    435	} else {
    436		switch (key) {
    437		case KEY_ESC:
    438			input_mode = MAIN;
    439			break;
    440		case KEY_UP:
    441		case 'W':
    442		case KEY_DOWN:
    443		case 'A':
    444		case KEY_LEFT:
    445		case 'S':
    446		case KEY_RIGHT:
    447		case 'D':
    448			dir = key_to_dir(key);
    449			if (tpu_sel && tpu_sel->ports[dir].dst_tpu)
    450				tpu_sel = tpu_sel->ports[dir].dst_tpu;
    451			tui_seek(tpu_sel, MID, MID);
    452			break;
    453		case 'c':
    454			if (!tpu_sel) return;
    455			do {
    456				tis_step(&tis);
    457			} while (tpu_sel->status != STATUS_RUN && tis.alive);
    458			break;
    459		case 'p':
    460			tpu_seek_running();
    461			break;
    462		}
    463	}
    464}
    465
    466static void
    467reset(int ifd, int argc, char **argv, bool watch)
    468{
    469	FILE *tis_stdin, *tis_stdout;
    470
    471	tis_stdin = NULL;
    472	if (argc >= 3) {
    473		tis_stdin = fopen(argv[2], "r");
    474		if (!tis_stdin) die("fopen '%s':", argv[2]);
    475	}
    476
    477	tis_stdout = NULL;
    478	if (argc >= 4) {
    479		tis_stdout = fopen(argv[3], "w+");
    480		if (!tis_stdout) die("fopen '%s':", argv[3]);
    481	}
    482
    483	tis_load(&tis, argv[1], tis_stdin, tis_stdout);
    484
    485	if (watch)
    486		if (inotify_add_watch(ifd, argv[1], IN_CLOSE_WRITE) < 0)
    487			die("inotify_add_watch '%s':", argv[1]);
    488
    489	if (tis.stdin_port.attached) {
    490		tpu_sel = tis.stdin_port.dst_tpu;
    491	} else {
    492		tpu_sel = tis.tpu_vec.cnt ? &tis.tpu_vec.tpus[0] : NULL;
    493	}
    494}
    495
    496int
    497main(int argc, char **argv)
    498{
    499	struct timeval timeout;
    500	struct inotify_event event;
    501	ssize_t len;
    502	fd_set fds;
    503	bool quit;
    504	int key;
    505	int ifd;
    506	int rc;
    507
    508	if (argc < 2 || argc > 4) {
    509		fprintf(stderr, "Usage: tis256-curses FILE [STDIN] [STDOUT]\n");
    510		exit(1);
    511	}
    512
    513	setlocale(LC_ALL, "");
    514
    515	initscr();
    516	raw();
    517	noecho();
    518	keypad(stdscr, TRUE);
    519	start_color();
    520	curs_set(0);
    521	tui_resize();
    522	ESCDELAY = 0;
    523
    524	init_pair(COLOR_WARN, COLOR_RED, COLOR_BLACK);
    525
    526	tis_init(&tis, NULL, NULL);
    527
    528	ifd = inotify_init1(IN_NONBLOCK);
    529
    530	reset(ifd, argc, argv, true);
    531
    532	tui_seek(NULL, MID, MID);
    533
    534	quit = false;
    535	while (!quit) {
    536		tui_draw();
    537
    538		FD_ZERO(&fds);
    539		FD_SET(ifd, &fds);
    540		FD_SET(0, &fds);
    541		timeout.tv_sec = 0;
    542		timeout.tv_usec = 500000;
    543		rc = select(ifd+1, &fds, NULL, NULL,
    544			showing_reloaded ? &timeout : NULL);
    545		if (rc < 0 && errno == EINTR) continue;
    546		if (rc < 0) die("select:");
    547
    548		if (FD_ISSET(ifd, &fds)) {
    549			len = read(ifd, &event, sizeof(event));
    550			if (len < 0 && errno != EAGAIN)
    551				die("inotify_read:");
    552			if (len >= 0)
    553				reset(ifd, argc, argv, true);
    554			if (clock_gettime(CLOCK_REALTIME, &show_reloaded) < 0)
    555				die("clock_gettime:");
    556			showing_reloaded = true;
    557		}
    558
    559		if (!showing_reloaded || ms_since(&show_reloaded) < 500) {
    560			if (!FD_ISSET(ifd, &fds) && !FD_ISSET(0, &fds))
    561				continue;
    562		} else {
    563			showing_reloaded = false;
    564		}
    565
    566		if (!FD_ISSET(0, &fds)) continue;
    567		key = getch();
    568		switch (key) {
    569		case KEY_RESIZE:
    570			tui_resize();
    571			break;
    572		case 'g':
    573			tui_seek(tpu_sel, MID, MID);
    574			break;
    575		case 'h':
    576			tui_seek(NULL, MID, MID);
    577			break;
    578		case 'i':
    579			if (tis.stdin_port.dst_tpu) {
    580				tpu_sel = tis.stdin_port.dst_tpu;
    581				tui_seek(tis.stdin_port.dst_tpu, MID, MID);
    582			}
    583			break;
    584		case 'o':
    585			if (tis.stdout_port.dst_tpu) {
    586				tpu_sel = tis.stdout_port.dst_tpu;
    587				tui_seek(tis.stdout_port.dst_tpu, MID, MID);
    588			}
    589			break;
    590		case 'r':
    591			reset(ifd, argc, argv, false);
    592			break;
    593		case 's':
    594			tis_step(&tis);
    595			break;
    596		case KEY_CTRL('c'):
    597			quit = true;
    598			break;
    599		default:
    600			handlekey(key);
    601			break;
    602		}
    603	}
    604
    605	tis_deinit(&tis);
    606
    607	close(ifd);
    608
    609	endwin();
    610}