added verbosity command to shell
[dcpu16] / vm-dcpu16.c
1 #include <stdlib.h>
2 #include <stdio.h>
3 #include <stdarg.h>
4 #include <unistd.h>
5 #include <string.h>
6 #include <strings.h>
7 #include <signal.h>
8 #include <errno.h>
9 #include <assert.h>
10 #include <sysexits.h>
11 #include <time.h>
12 #include <sys/time.h>
13
14 #include <readline/readline.h>
15 #ifdef HAVE_LIBVNCSERVER
16 #include "rfb/rfb.h"
17 #endif /* HAVE_LIBVNCSERVER */
18
19 #include "dcpu16.h"
20 #include "common.h"
21
22 #include "hw_lem1802.h"
23 #include "hw_keyboard.h"
24
25 /*
26 * shell-like driver for dcpu16 core
27 * provides a basic interface to control a single emulation instance
28 *
29 * Justin Wind <justin.wind@gmail.com>
30 * 2012 04 10 - implementation started
31 * 2012 04 12 - cleanup, better shell loop
32 * 2012 05 12 - support v1.7 style devices
33 *
34 * TODO
35 * handle quotes in shell command parsing
36 * use readline/history.h, since we're using readline anyhow
37 * ncurses windowing or something, for future display capabilities
38 */
39
40 static const char * const src_id_ = "$Id$";
41
42 /* global invocation options */
43 struct options {
44 unsigned int verbose;
45 } opt_ = {
46 .verbose = 0,
47 };
48
49 /* global run state, first sigint caught will drop out of run loop and back into shell */
50 static volatile unsigned int running_ = 0;
51 static
52 void sigint_handler_(int sig) {
53 (void)sig;
54 running_ = 0;
55 }
56
57 #define VERBOSE_PRINTF(...) do { if (opt_.verbose) printf(__VA_ARGS__); } while (0)
58 #ifdef DEBUG
59 #define DEBUG_PRINTF(...) do { if (opt_.verbose > 4) fprintf(stderr, __VA_ARGS__); } while (0)
60 #else /* DEBUG */
61 #define DEBUG_PRINTF(...) do { } while (0)
62 #endif /* DEBUG */
63
64 static
65 void usage_(char *prog, unsigned int full) {
66 FILE *f = full ? stdout : stderr;
67 char *x = strrchr(prog, '/');
68
69 if (x && *(x + 1))
70 prog = x + 1;
71
72 if (full)
73 fprintf(f, "%s -- dcpu16 emulator core shell\n\n",
74 prog);
75
76 fprintf(f, "Usage: %s [-v] [file]\n",
77 prog);
78
79 if (full) {
80 fprintf(f, "\nOptions:\n"
81 "\t [file] -- ram image to load initially\n"
82 "\t -v -- prints slightly more information while operating\n"
83 "\t -h -- this screen\n");
84
85 fprintf(f, "\n%78s\n", src_id_);
86 }
87 }
88
89
90 /* flense a buffer into a newly-allocated argument list */
91 static
92 int buf_tok_vect_(char ***v, int *c, char *buf) {
93 const char *sep = " \t";
94 const char *quot = "\"'`";
95 const size_t v_grow = 32;
96 size_t v_sz = 32;
97 char *st, *qt;
98
99 *c = 0;
100 *v = malloc(v_sz * sizeof **v);
101 if (*v == NULL) {
102 fprintf(stderr, "%s():%s\n", "malloc", strerror(errno));
103 return -1;
104 }
105
106 for ( (*v)[*c] = strqtok_r(buf, sep, '\\', quot, &qt, &st);
107 (*v)[*c];
108 (*v)[*c] = strqtok_r(NULL, sep, '\\', quot, &qt, &st)
109 ) {
110 (*c)++;
111
112 if ((size_t)(*c) == v_sz) {
113 void *tmp_ptr = realloc(*v, (v_sz + v_grow) * sizeof **v);
114 if (tmp_ptr == NULL) {
115 fprintf(stderr, "%s():%s\n", "realloc", strerror(errno));
116 free(*v);
117 *v = NULL;
118 return -1;
119 }
120 v_sz += v_grow;
121 }
122 }
123
124 return 0;
125 }
126
127 /*
128 resets the vm if addr is zero then
129 loads an image from filename into ram starting at addr
130 */
131 static
132 int file_load_(struct dcpu16 *vm, char *filename, DCPU16_WORD addr) {
133 FILE *f;
134 size_t r;
135
136 if (!addr)
137 dcpu16_reset(vm);
138
139 f = fopen(filename, "rb");
140 if (f == NULL) {
141 fprintf(stderr, "%s('%s'):%s\n", "fopen", filename, strerror(errno));
142 return -1;
143 }
144
145 r = fread(vm->ram + addr, sizeof(DCPU16_WORD), DCPU16_RAM - addr, f);
146 VERBOSE_PRINTF("read %zu words", r);
147 if (addr) VERBOSE_PRINTF(" starting at 0x%04x", addr);
148 VERBOSE_PRINTF("\n");
149
150 if (ferror(f))
151 fprintf(stderr, "%s('%s'):%s\n", "fread", filename, strerror(errno));
152
153 fclose(f);
154 return 0;
155 }
156
157 /* dump_ram_
158 * print raw ram contents from start to stop
159 */
160 static
161 void dump_ram_(struct dcpu16 *vm, DCPU16_WORD start, DCPU16_WORD end) {
162 unsigned int i, j;
163 const unsigned int n = 8; /* words per line */
164
165 if (!vm) return;
166
167 for (i = start, j = 0; i <= end; i++, j++) {
168 if (j % n == 0)
169 printf("0x%04x:\t", i);
170 printf(" %04x%s", vm->ram[i], (j % n) == (n - 1) ? "\n" : "");
171 }
172 if ((j % n) != (n - 1))
173 printf("\n");
174 }
175
176
177 /*
178 print the current state of the machine
179 shows current cycle count, registers, and next instruction
180 */
181 static
182 void state_print_(struct dcpu16 *vm) {
183 unsigned int i;
184
185 if (!vm) return;
186
187 printf(" ");
188 for (i = 0; i < 8; i++)
189 printf(" %s:0x%04x", dcpu16_reg_names[i], vm->reg[i]);
190 printf("\n");
191
192 printf("(0x%08llx) %2s:0x%04x %2s:0x%04x %2s:0x%04x %2s:0x%04x [%2s]:",
193 vm->cycle_,
194 dcpu16_reg_names[DCPU16_REG_EX], vm->reg[DCPU16_REG_EX],
195 dcpu16_reg_names[DCPU16_REG_SP], vm->reg[DCPU16_REG_SP],
196 dcpu16_reg_names[DCPU16_REG_PC], vm->reg[DCPU16_REG_PC],
197 dcpu16_reg_names[DCPU16_REG_IA], vm->reg[DCPU16_REG_IA],
198 "PC");
199
200 dcpu16_disassemble_print(vm, vm->reg[DCPU16_REG_PC]);
201 printf("\n");
202 }
203
204
205 #ifdef HAVE_LIBVNCSERVER
206 static struct dynamic_array rfbScreens_;
207 /* wups, kbdAddEvent isn't null by default, so I guess track associations externally */
208 struct rfb_instance_ {
209 rfbScreenInfoPtr screen;
210 struct dcpu16_hw *attached_display;
211 struct dcpu16_hw *attached_keyboard;
212 };
213
214 enum rfbscreen_next_what_ {
215 NEXT_DISPLAY,
216 NEXT_KEYBOARD,
217 };
218 /* locate next rfb not paired to requested type */
219 static
220 struct rfb_instance_ *rfbScreen_next_available_(enum rfbscreen_next_what_ what, struct dynamic_array *rfbScreens, int argc, char *argv[]) {
221 size_t i;
222 struct rfb_instance_ new_instance, *s;
223 struct packed_args_ {
224 int argc;
225 char **argv;
226 } parg = { argc, argv };
227
228 for (i = 0; i < rfbScreens->entries; i++) {
229 struct dcpu16_hw *attached;
230
231 s = (struct rfb_instance_ *)DYNARRAY_ITEM(*rfbScreens, i);
232 switch (what) {
233 case NEXT_DISPLAY: attached = s->attached_display; break;
234 case NEXT_KEYBOARD: attached = s->attached_keyboard; break;
235 }
236 if (attached == NULL)
237 return s;
238 }
239
240 /* no available rfb, create new */
241 if (dcpu16_hw_module_lem1802.ctl(NULL, "new_rfbScreen", &parg, &new_instance.screen)) {
242 fprintf(stderr, "failed to allocate new rfbScreen\n");
243 return NULL;
244 }
245
246 new_instance.attached_display = NULL;
247 new_instance.attached_keyboard = NULL;
248
249 new_instance.screen->port += rfbScreens->entries;
250 new_instance.screen->ipv6port += rfbScreens->entries;
251
252 DEBUG_PRINTF("%s>> port:%u\n", __func__, new_instance.screen->port);
253
254 s = dynarray_add(rfbScreens, &new_instance);
255
256 return s;
257 }
258
259
260 /* begin serving a screen */
261 void rfbScreen_start(rfbScreenInfoPtr s) {
262 rfbInitServer(s);
263 rfbRunEventLoop(s, -1, TRUE);
264 }
265 #endif /* HAVE_LIBVNCSERVER */
266
267 /*
268 Here follows the various commands the shell can execute.
269
270 At invocation, a command function will have already had its
271 number of arguments vetted, but will need command-specific
272 argument verifications done.
273
274 The arg_vector contains the command as the first entry, and
275 as such, arg_count will always be at least 1.
276 However, the args_min and args_max entries in struct command_
277 only refer to the counts of arguments, not the entries in the
278 argv.
279 */
280
281 struct command_ {
282 char *name;
283 int args_min;
284 int args_max;
285 int (*func)(struct dcpu16 *, int c, char **v);
286 void (*help)(FILE *f, unsigned int);
287 };
288
289 #define COMMAND_IMPL(x) static int command_##x##_(struct dcpu16 *vm, int arg_count, char **arg_vector)
290 #define COMMAND_HELP(x) static void command_##x##_help_(FILE *f, unsigned int summary)
291 #define COMMAND_ENTRY(x, y, z) { #x, y, z, command_##x##_, command_##x##_help_ }
292
293
294 COMMAND_IMPL(quit) {
295 (void)vm, (void)arg_count, (void)arg_vector;
296
297 return -1;
298 }
299 COMMAND_HELP(quit) {
300 fprintf(f, "\tquit\n");
301 if (summary) return;
302
303 fprintf(f, "Exits the emulator.\n");
304 }
305
306
307 COMMAND_IMPL(reset) {
308 (void)arg_count, (void)arg_vector;
309
310 dcpu16_reset(vm);
311 printf("initialized\n");
312
313 return 0;
314 }
315 COMMAND_HELP(reset) {
316 fprintf(f, "\treset\n");
317 if (summary) return;
318
319 fprintf(f, "Clears and reinitializes emulator.\n");
320 }
321
322
323 COMMAND_IMPL(verbosity) {
324 int l;
325 (void)vm, (void)arg_count;
326
327 l = str_to_word(arg_vector[1]);
328 if (l < 0) {
329 fprintf(stderr, "invalid level\n");
330 return 0;
331 }
332
333 opt_.verbose = l;
334
335 return 0;
336 }
337 COMMAND_HELP(verbosity) {
338 fprintf(f, "\tverbosity level\n");
339 if (summary) return;
340
341 fprintf(f, "sets the verbosity level\n");
342 }
343
344 COMMAND_IMPL(load) {
345 int addr = 0;
346
347 if (arg_count > 2) {
348 addr = str_to_word(arg_vector[2]);
349 if (addr < 0) {
350 fprintf(stderr, "address '%s' is not a valid word: %s\n", arg_vector[2], strerror(errno));
351 return 0;
352 }
353 }
354
355 if (file_load_(vm, arg_vector[1], addr)) {
356 fprintf(stderr, "failed to load '%s'\n", arg_vector[1]);
357 return 0;
358 }
359 printf("loaded '%s'", arg_vector[1]);
360 if (addr) printf(" starting at 0x%04x", addr);
361 printf("\n");
362
363 return 0;
364 }
365 COMMAND_HELP(load) {
366 fprintf(f, "\tload file [addr]\n");
367 if (summary) return;
368
369 fprintf(f, "Load binary image from 'file' into ram.\n");
370 }
371
372
373 COMMAND_IMPL(dump) {
374 int addr[2];
375 int i;
376
377 for (i = 1; i < arg_count; i++) {
378 addr[i-1] = str_to_word(arg_vector[i]);
379 if (addr[i-1] < 0) {
380 fprintf(stderr, "address '%s' is not a valid word: %s\n", arg_vector[i], strerror(errno));
381 return 0;
382 }
383 }
384 if (arg_count < 2) addr[0] = vm->reg[DCPU16_REG_PC];
385 if (arg_count < 3) addr[1] = addr[0];
386
387 if (addr[1] < addr[0]) {
388 fprintf(stderr, "\t'addr_start' must be before addr_end\n");
389 return 0;
390 }
391
392 dump_ram_(vm, addr[0], addr[1]);
393
394 return 0;
395 }
396 COMMAND_HELP(dump) {
397 fprintf(f, "\tdump [addr_start [addr_end]]\n");
398 if (summary) return;
399
400 fprintf(f, "Displays contents of ram from addr_start to addr_end.\n");
401 }
402
403
404 COMMAND_IMPL(disassemble) {
405 int addr[2];
406 int i;
407
408 for (i = 1; i < arg_count; i++) {
409 addr[i-1] = str_to_word(arg_vector[i]);
410 if (addr[i-1] < 0) {
411 fprintf(stderr, "address '%s' is not a valid word: %s\n", arg_vector[i], strerror(errno));
412 return 0;
413 }
414 }
415 if (arg_count < 2) addr[0] = vm->reg[DCPU16_REG_PC];
416 if (arg_count < 3) addr[1] = addr[0];
417
418 if (addr[1] < addr[0]) {
419 fprintf(stderr, "\t'addr_start' must be before addr_end\n");
420 return 0;
421 }
422
423 for (i = addr[0]; i <= addr[1]; /* */ ) {
424 printf("0x%04x: ", i);
425 i += dcpu16_disassemble_print(vm, i);
426 printf("\n");
427 }
428
429 return 0;
430 }
431 COMMAND_HELP(disassemble) {
432 fprintf(f, "\tdisassemble [addr_start [addr_end]]\n");
433 if (summary) return;
434
435 fprintf(f, "Displays contents of ram parsed into instructions.\n");
436 }
437
438
439 COMMAND_IMPL(step) {
440 unsigned long count = 1;
441 char *ep;
442
443 if (arg_count == 2) {
444 errno = 0;
445 count = strtoul(arg_vector[1], &ep, 0);
446 if (errno
447 || !(*arg_vector[1] && *ep == '\0') ) {
448 fprintf(stderr, "count '%s' is not a valid number: %s\n", arg_vector[1], strerror(errno));
449 return 0;
450 }
451
452 if (count <= 0) {
453 fprintf(stderr, "count must be positive\n");
454 return 0;
455 }
456 }
457
458 while (count--) {
459 dcpu16_disassemble_print(vm, vm->reg[DCPU16_REG_PC]);
460 printf("\n");
461 dcpu16_step(vm);
462
463 if (count > 1 && opt_.verbose)
464 state_print_(vm);
465 }
466
467 return 0;
468 }
469 COMMAND_HELP(step) {
470 fprintf(f, "\tstep [count]\n");
471 if (summary) return;
472
473 fprintf(f, "Executes the next instruction, or the next count instructions.\n");
474 }
475
476
477 COMMAND_IMPL(set) {
478 int addr, value;
479 DCPU16_WORD *v;
480
481 (void)arg_count;
482
483 /* check if addr is a register */
484 for (addr = 0; dcpu16_reg_names[addr]; addr++) {
485 if (strcasecmp(arg_vector[1], dcpu16_reg_names[addr]) == 0)
486 break;
487 }
488 if (addr < DCPU16_REG__NUM) {
489 v = vm->reg + addr;
490 } else {
491 addr = str_to_word(arg_vector[1]);
492 if (addr < 0) {
493 fprintf(stderr, "address '%s' is not a valid word: %s\n", arg_vector[1], strerror(errno));
494 return 0;
495 }
496 v = vm->ram + addr;
497 }
498
499 value = str_to_word(arg_vector[2]);
500 if (value < 0) {
501 fprintf(stderr, "address '%s' is not a valid word: %s\n", arg_vector[2], strerror(errno));
502 return 0;
503 }
504
505 *v = value;
506
507 return 0;
508 }
509
510 COMMAND_HELP(set) {
511 fprintf(f, "\tset addr value\n");
512 if (summary) return;
513
514 fprintf(f, "Sets addr to value.");
515 }
516
517 #define NANOSECONDS_PER_CYCLE 10000
518 #define MIN_NANOSLEEP 31000
519 COMMAND_IMPL(run) {
520 struct sigaction act;
521 long long run_cycle_start, run_cycle_end;
522 long long cycle_start, cycles_to_wait;
523
524 struct timespec ts_run_start, ts_run_end, ts_run_diff;
525 struct timespec ts_cycle_start, ts_cycle_end_target, ts_cycle_end, ts_cycle_waste, ts_cycle_rem;
526 const struct timespec ts_cycle_time = { .tv_sec = 0, .tv_nsec = NANOSECONDS_PER_CYCLE };
527
528 (void)arg_count, (void)arg_vector;
529
530 running_ = 1;
531 gettimespecofday(&ts_run_start);
532 run_cycle_start = vm->cycle_;
533
534 memset(&act, 0, sizeof act);
535 act.sa_handler = sigint_handler_;
536 act.sa_flags = SA_RESETHAND;
537
538 if (sigaction(SIGINT, &act, NULL)) {
539 fprintf(stderr, "%s():%s\n", "sigaction", strerror(errno));
540 return -1;
541 }
542
543 while (running_) {
544 gettimespecofday(&ts_cycle_start);
545 ts_cycle_end_target = ts_cycle_start;
546
547 cycle_start = vm->cycle_;
548
549 dcpu16_step(vm);
550 if (opt_.verbose > 1)
551 state_print_(vm);
552 else if (opt_.verbose) {
553 dcpu16_disassemble_print(vm, vm->reg[DCPU16_REG_PC]);
554 printf("\n");
555 }
556
557 /* how many cycles did this instr use? */
558 cycles_to_wait = vm->cycle_ - cycle_start;
559
560 /* each cycle wants to take 10 microseconds */
561 while (cycles_to_wait--)
562 timespec_add(&ts_cycle_end_target, &ts_cycle_time);
563
564 /* how much of that did we spend already */
565 gettimespecofday(&ts_cycle_end);
566
567 /* do we have time to kill? */
568 if (timespec_subtract(&ts_cycle_waste, &ts_cycle_end_target, &ts_cycle_end) == 0) {
569 /* nanosleep doesn't interfere with libvncserver, unlike usleep */
570 if (ts_cycle_waste.tv_sec == 0 && ts_cycle_waste.tv_nsec >= MIN_NANOSLEEP)
571 while ( nanosleep(&ts_cycle_waste, &ts_cycle_rem) )
572 ts_cycle_waste = ts_cycle_rem;
573 } else {
574 /* negative, we've already blown our time */
575 DEBUG_PRINTF("cycle time overrun %ld.%09lds\n", ts_cycle_waste.tv_sec, ts_cycle_waste.tv_nsec);
576 }
577
578 #ifdef DEBUG
579 /* how did we do */
580 gettimespecofday(&ts_cycle_end);
581 timespec_subtract(&ts_cycle_rem, &ts_cycle_end_target, &ts_cycle_end);
582 DEBUG_PRINTF("projected end: %ld.%09ld actual end: %ld.%09ld diff: %ld.%09ld\n",
583 ts_cycle_end_target.tv_sec, ts_cycle_end_target.tv_nsec,
584 ts_cycle_end.tv_sec, ts_cycle_end.tv_nsec,
585 ts_cycle_rem.tv_sec, ts_cycle_rem.tv_nsec);
586 #endif /* DEBUG */
587
588 }
589
590 run_cycle_end = vm->cycle_;
591 gettimespecofday(&ts_run_end);
592 timespec_subtract(&ts_run_diff, &ts_run_end, &ts_run_start);
593 VERBOSE_PRINTF("ran %lld cycles in %ld.%09lds\n",
594 run_cycle_end - run_cycle_start,
595 ts_run_diff.tv_sec, ts_run_diff.tv_nsec);
596
597 printf("interrupted...\n");
598
599 return 0;
600 }
601 COMMAND_HELP(run) {
602 fprintf(f, "\trun\n");
603 if (summary) return;
604
605 fprintf(f, "Begins executing continuously.\n"
606 "May be interrupted with SIGINT.\n");
607 }
608
609 static const char * const display_filename_default_ =
610 #ifdef HAVE_LIBPNG
611 "dcpu16-display.png"
612 #else /* HAVE_LIBPNG */
613 "dcpu16-display.pnm"
614 #endif /* HAVE_LIBPNG */
615 ;
616 COMMAND_IMPL(display) {
617 struct dcpu16_hw *hw;
618 const char *renderer = arg_vector[1];
619 const char *renderer_arg = NULL;
620 void *renderer_data;
621
622 if (arg_count == 3)
623 renderer_arg = arg_vector[2];
624
625 hw = dcpu16_hw_new(vm, &dcpu16_hw_module_lem1802, NULL);
626 if (hw == NULL) {
627 fprintf(stderr, "failed to initialize new display\n");
628 return 0;
629 }
630
631 /* handle per-renderer setup of data.. */
632 /* FIXME: these are awkward */
633 if (strcmp(renderer, "pnm") == 0) {
634 renderer_data = (void *)(renderer_arg ? renderer_arg : display_filename_default_);
635 }
636
637 #ifdef HAVE_LIBPNG
638 if (strcmp(renderer, "png") == 0) {
639 renderer_data = (void *)(renderer_arg ? renderer_arg : display_filename_default_);
640 }
641 #endif /* HAVE_LIBPNG */
642
643 #ifdef HAVE_LIBVNCSERVER
644 if (strcmp(renderer, "vnc") == 0) {
645 int argc = 1;
646 char *argv[] = { "vm-dcpu16", NULL };
647 struct rfb_instance_ *s;
648
649 s = rfbScreen_next_available_(NEXT_DISPLAY, &rfbScreens_, argc, argv);
650 if (s == NULL) {
651 fprintf(stderr, "failed to initialize vnc\n");
652 dcpu16_hw_del(&hw);
653 return 0;
654 }
655
656 if (dcpu16_hw_ctl(hw, "associate_rfbScreen", s->screen, NULL)) {
657 fprintf(stderr, "failed to configure display/vnc");
658 dcpu16_hw_del(&hw);
659 return 0;
660 }
661 s->attached_display = hw;
662 rfbScreen_start(s->screen);
663 renderer_data = s->screen;
664
665 DEBUG_PRINTF("attached display to rfb (frameBuffer:%p)\n", s->screen->frameBuffer);
666 }
667 #endif /* HAVE_LIBVNCSERVER */
668
669 dcpu16_hw_ctl(hw, "renderer", (char *)renderer, NULL);
670 dcpu16_hw_ctl(hw, "renderer_data", renderer_data, NULL);
671
672 if (dcpu16_hw_attach(vm, hw)) {
673 fprintf(stderr, "failed to attach new display\n");
674 dcpu16_hw_del(&hw);
675 return 0;
676 }
677
678 return 0;
679 }
680 COMMAND_HELP(display) {
681 struct renderer_ {
682 char *name;
683 char *args;
684 } renderer;
685 void *iter;
686
687 fprintf(f, "\tdisplay renderer [renderer data]\n");
688 if (summary) return;
689
690 fprintf(f, "Attaches new display unit, using 'renderer' as back-end output.\n"
691 );
692
693 fprintf(f, "Supported renderers:\n");
694
695 iter = NULL;
696 do {
697 if (dcpu16_hw_module_lem1802.ctl(NULL, "renderers_iter", &iter, &renderer)) {
698 fprintf(stderr, "error fetching next renderer\n");
699 break;
700 }
701 if (iter == NULL || renderer.name == NULL)
702 break;
703
704 fprintf(f, "\t%s %s\n", renderer.name, renderer.args);
705 } while (iter);
706 }
707
708 COMMAND_IMPL(keyboard) {
709 struct dcpu16_hw *hw;
710
711 (void)arg_count, (void)arg_vector;
712
713 hw = dcpu16_hw_new(vm, &dcpu16_hw_module_keyboard, NULL);
714 if (hw == NULL) {
715 fprintf(stderr, "failed to initialize new keyboard\n");
716 return 0;
717 }
718
719 #ifdef HAVE_LIBVNCSERVER
720 struct rfb_instance_ *s;
721 int argc = 1;
722 char *argv[] = { "vm-dcpu16", NULL };
723
724 s = rfbScreen_next_available_(NEXT_KEYBOARD, &rfbScreens_, argc, argv);
725 if (s == NULL) {
726 fprintf(stderr, "failed to initialize vnc\n");
727 dcpu16_hw_del(&hw);
728 return 0;
729 }
730 if (dcpu16_hw_ctl(hw, "associate_rfbScreen", s->screen, NULL)) {
731 fprintf(stderr, "failed to configure keyboard/vnc\n");
732 dcpu16_hw_del(&hw);
733 return 0;
734 }
735 s->attached_keyboard = hw;
736
737 if (dcpu16_hw_attach(vm, hw)) {
738 fprintf(stderr, "failed to attach new keyboard\n");
739 dcpu16_hw_del(&hw);
740 return 0;
741 }
742 #endif /* HAVE_LIBVNCSERVER */
743
744 return 0;
745 }
746 COMMAND_HELP(keyboard) {
747 fprintf(f, "\tkeyboard\n");
748 if (summary) return;
749
750 fprintf(f, "Attaches new keyboard unit.\n");
751 }
752
753 /* gather all these together into a searchable table */
754
755 /* help command gets some assistance in declarations */
756 COMMAND_IMPL(help);
757 COMMAND_HELP(help);
758
759 static struct command_ command_table_[] = {
760 COMMAND_ENTRY(help, 0, -1),
761 COMMAND_ENTRY(quit, 0, -1),
762 COMMAND_ENTRY(verbosity, 1, 1),
763 COMMAND_ENTRY(load, 1, 2),
764 COMMAND_ENTRY(dump, 0, 2),
765 COMMAND_ENTRY(disassemble, 0, 2),
766 COMMAND_ENTRY(step, 0, 1),
767 COMMAND_ENTRY(run, 0, 0),
768 COMMAND_ENTRY(set, 2, 2),
769 COMMAND_ENTRY(reset, 0, 0),
770 COMMAND_ENTRY(display, 1, 2),
771 COMMAND_ENTRY(keyboard, 0, 0),
772 { NULL, 0, 0, NULL, NULL }
773 };
774
775 COMMAND_IMPL(help) {
776 struct command_ *c;
777 (void)vm;
778
779 if (arg_count == 2) {
780 for (c = command_table_; c->func; c++) {
781 if (strcasecmp(arg_vector[1], c->name) == 0) {
782 if (c->help)
783 c->help(stdout, 0);
784 break;
785 }
786 }
787 return 0;
788 }
789
790 for (c = command_table_; c->func; c++) {
791 if (c->help)
792 c->help(stdout, 1);
793 }
794 return 0;
795 }
796 COMMAND_HELP(help) {
797 fprintf(f, "\thelp [command]\n");
798 if (summary) return;
799
800 fprintf(f, "Displays a list of available commands, or detailed help on a specific command.\n");
801 }
802
803 static
804 void msg_verbose_filter_(unsigned int level, char *fmt, ...) {
805 static const char * const levels[] = { "error", "info", "debug" };
806 FILE *f = (level <= MSG_ERROR) ? stderr : stdout;
807 va_list ap;
808
809 if (level + 2 > opt_.verbose)
810 return;
811
812 if (level < sizeof levels / sizeof *levels)
813 fprintf(f, "[%s %s] ", "dcpu16", levels[level]);
814 else
815 fprintf(f, "[%s %u] ", "dcpu16", level);
816
817 va_start(ap, fmt);
818 vfprintf(f, fmt, ap);
819 va_end(ap);
820
821 fprintf(f, "\n");
822
823 fflush(f);
824 }
825
826 int main(int argc, char **argv) {
827 const char prompt_fmt[] = "PC:%04x> ";
828 char prompt[32];
829 struct dcpu16 *vm;
830 char *line, *line_prev;
831 char **tok_v, **tok_v_prev;
832 int tok_c, tok_c_prev;
833 int c;
834
835 while ( (c = getopt(argc, argv, "hv")) != EOF) {
836 switch (c) {
837 case 'v':
838 opt_.verbose++;
839 break;
840
841 case 'h':
842 usage_(argv[0], 1);
843 exit(EX_OK);
844
845 default:
846 usage_(argv[0], 0);
847 exit(EX_USAGE);
848 }
849 }
850 argc -= optind;
851 argv += optind;
852
853 dcpu16_msg_set_default(msg_verbose_filter_);
854
855 if ((vm = dcpu16_new()) == NULL) {
856 fprintf(stderr, "could not allocate new dcpu16 instance\n");
857 exit(EX_UNAVAILABLE);
858 }
859
860 #ifdef HAVE_LIBVNCSERVER
861 if (dynarray_init(&rfbScreens_, sizeof(struct rfb_instance_), 4)) {
862 fprintf(stderr, "could not allocate rfb container\n");
863 exit(EX_UNAVAILABLE);
864 }
865 #endif /* HAVE_LIBVNCSERVER */
866
867 if (argc) {
868 if (file_load_(vm, *argv, 0)) {
869 fprintf(stderr, "couldn't load '%s'\n", *argv);
870 exit(EX_NOINPUT);
871 }
872 }
873
874 /* show state, read commands */
875 for (line = line_prev = NULL,
876 tok_v = tok_v_prev = NULL,
877 tok_c = tok_c_prev= 0,
878 snprintf(prompt, sizeof prompt, prompt_fmt, vm->reg[DCPU16_REG_PC]),
879 state_print_(vm);
880
881 (line = readline(prompt));
882
883 printf("\n"),
884 snprintf(prompt, sizeof prompt, prompt_fmt, vm->reg[DCPU16_REG_PC]),
885 state_print_(vm)) {
886 const char whitespace[] = " \t";
887 char *line_start;
888 struct command_ *c;
889 int r = 0;
890
891 /* skip whitespaces */
892 line_start = line + strspn(line, whitespace);
893
894 if (*line_start) {
895 /* a new command, remember previous for possible repetition */
896
897 /* turn new line into new arg array */
898 if (buf_tok_vect_(&tok_v, &tok_c, line_start)) {
899 fprintf(stderr, "failed to process command\n");
900 continue;
901 }
902
903 /* and keep track if it all for the next time around */
904 if (line_prev) free(line_prev);
905 line_prev = line;
906
907 if (tok_v_prev) free(tok_v_prev);
908 tok_v_prev = tok_v;
909 tok_c_prev = tok_c;
910 } else {
911 /* blank new command, but no prior command to repeat? ask again */
912 if (tok_v_prev == NULL || tok_v_prev[0] == NULL || *(tok_v_prev[0]) == '\0') {
913 free(line);
914 continue;
915 }
916
917 /* otherwise discard new line and promote prior */
918 free(line);
919 tok_v = tok_v_prev;
920 tok_c = tok_c_prev;
921 line = line_prev;
922 }
923
924 /* look up command */
925 for (c = command_table_; c->name; c++) {
926 if (strcasecmp(tok_v[0], c->name) == 0) {
927 if (c->args_min > tok_c - 1) {
928 fprintf(stderr, "%s: not enough arguments\n", c->name);
929 c->help(stderr, 1);
930 break;
931 }
932
933 if (c->args_max > 0
934 && tok_c - 1 > c->args_max) {
935 fprintf(stderr, "%s: too many arguments\n", c->name);
936 c->help(stderr, 1);
937 break;
938 }
939
940 r = c->func(vm, tok_c, tok_v);
941 break;
942 }
943 }
944 if (r)
945 break;
946
947 if (!c->func)
948 fprintf(stderr, "didn't recognize '%s'\n", tok_v[0]);
949 }
950
951 printf("\nfinished\n");
952
953 dcpu16_delete(&vm);
954
955 exit(EX_OK);
956 }