forked from opencode.energia.arti.ee/vt1200
feat(termtris): run the game on the VT1200 X server
Board, score panel, preview and help/scores toggles draw through the ROM Xlib cell grid; keys arrive as real X KeyPress events. The backend is now monochrome (the VT1200 raster is one bit deep) and flushes whole rows so the in-machine server can drain the request stream.
This commit is contained in:
1 parent
c1945cfc73
commit
41d3cecd49
23 files changed
+1151
-84
No files matched your search
@@ -91,7 +91,7 @@ run() {
|
||||
compile_c() {
|
||||
base=$1
|
||||
rm -f "$WORK/$base.IF" "$WORK/$base.OPT" "$WORK/$base.ASM" "$WORK/$base.obj"
|
||||
run "$base.IF" ./gspac.exe -k "$base.C" "$base.IF"
|
||||
run "$base.IF" ./gspac.exe -k -i. "$base.C" "$base.IF"
|
||||
run "$base.OPT" ./gspopt.exe -k -O2 "$base.IF" "$base.OPT"
|
||||
run "$base.ASM" ./gspcg.exe -m "$base.OPT" "$base.ASM" "$base.TMP"
|
||||
run "$base.obj" ./gspa.exe "$base.ASM"
|
||||
@@ -120,7 +120,7 @@ compile_asm ENTRY
|
||||
compile_asm CINIT
|
||||
compile_asm CINITZ
|
||||
|
||||
OUTNAME=$(printf '%s' "$MAIN" | tr 'A-Z' 'a-z')
|
||||
OUTNAME=${OUTNAME:-$(printf '%s' "$MAIN" | tr 'A-Z' 'a-z')}
|
||||
CMD="$WORK/$MAIN.CMD"
|
||||
{
|
||||
printf '%s\n' '-a'
|
||||
|
||||
Binary file not shown.
@@ -1,7 +1,7 @@
|
||||
*****************************************************
|
||||
TMS340 COFF Linker Version 5.01
|
||||
*****************************************************
|
||||
Tue Nov 03 19:00:04 2026
|
||||
Tue Nov 03 19:54:29 2026
|
||||
|
||||
OUTPUT FILE NAME: <RENDER.out>
|
||||
ENTRY POINT SYMBOL: "_start" address: 018a0000
|
||||
@@ -20,21 +20,21 @@ SECTION ALLOCATION MAP
|
||||
output attributes/
|
||||
section page origin length input sections
|
||||
-------- ---- ---------- ---------- ----------------
|
||||
.text 0 018a0000 00003af0
|
||||
.text 0 018a0000 00003f20
|
||||
018a0000 00000150 ENTRY.obj (.text)
|
||||
018a0150 00001280 RENDER.obj (.text)
|
||||
018a13d0 00002510 X11.obj (.text)
|
||||
018a38e0 00000210 XGFIX.obj (.text)
|
||||
018a13d0 00002940 X11.obj (.text)
|
||||
018a3d10 00000210 XGFIX.obj (.text)
|
||||
|
||||
.cinit 0 018a3af0 000000a0
|
||||
018a3af0 00000050 RENDER.obj (.cinit)
|
||||
018a3b40 00000050 X11.obj (.cinit)
|
||||
.cinit 0 018a3f20 000000a0
|
||||
018a3f20 00000050 RENDER.obj (.cinit)
|
||||
018a3f70 00000050 X11.obj (.cinit)
|
||||
|
||||
.bss 0 01a00000 0001d080 UNINITIALIZED
|
||||
.bss 0 01a00000 00019480 UNINITIALIZED
|
||||
01a00000 00008000 ENTRY.obj (.bss)
|
||||
01a08000 00000260 RENDER.obj (.bss)
|
||||
01a08260 00012e20 X11.obj (.bss)
|
||||
01a1b080 00002000 XGFIX.obj (.bss)
|
||||
01a08260 0000f220 X11.obj (.bss)
|
||||
01a17480 00002000 XGFIX.obj (.bss)
|
||||
|
||||
.data 0 018a0000 00000000 UNINITIALIZED
|
||||
|
||||
@@ -49,37 +49,37 @@ address name address name
|
||||
018a0370 _main 018a0000 _start
|
||||
01a08000 _mark 018a0370 _main
|
||||
018a0000 _start 018a13d0 _vt_root_window
|
||||
01a1afa0 _term_clearscr 018a14b0 _vt_x11_init
|
||||
01a1afe0 _term_cursor 018a1b60 _x11_clearscr
|
||||
01a08220 _term_height 018a1d50 _x11_reset
|
||||
01a1b020 _term_ibmchar 018a1d90 _x11_cursor
|
||||
018a3870 _term_init 018a1df0 _x11_setcolor
|
||||
018a2600 _term_putstr 018a1f10 _x11_setcursor
|
||||
01a1af80 _term_reset 018a2160 _x11_ibmchar
|
||||
01a1b000 _term_setcolor 018a2600 _term_putstr
|
||||
01a1afc0 _term_setcursor 018a27a0 _vt_x11_flush
|
||||
01a1b060 _term_type 018a3550 _vt_x11_term_init
|
||||
01a08200 _term_width 018a3870 _term_init
|
||||
01a1b040 _termenv 018a38e0 _vt_install_xg
|
||||
01a1aee0 _vt_dpy 018a3af0 etext
|
||||
01a1af20 _vt_gc 018a3af0 cinit
|
||||
018a38e0 _vt_install_xg 01a00000 .bss
|
||||
01a173a0 _term_clearscr 018a14b0 _vt_x11_init
|
||||
01a173e0 _term_cursor 018a1b60 _x11_clearscr
|
||||
01a08220 _term_height 018a1d20 _x11_reset
|
||||
01a17420 _term_ibmchar 018a1d60 _x11_cursor
|
||||
018a3ca0 _term_init 018a1dc0 _x11_setcolor
|
||||
018a25b0 _term_putstr 018a1ee0 _x11_setcursor
|
||||
01a17380 _term_reset 018a2130 _x11_ibmchar
|
||||
01a17400 _term_setcolor 018a25b0 _term_putstr
|
||||
01a173c0 _term_setcursor 018a2750 _vt_x11_flush
|
||||
01a17460 _term_type 018a3980 _vt_x11_term_init
|
||||
01a08200 _term_width 018a3ca0 _term_init
|
||||
01a17440 _termenv 018a3d10 _vt_install_xg
|
||||
01a172e0 _vt_dpy 018a3f20 etext
|
||||
01a17320 _vt_gc 018a3f20 cinit
|
||||
018a3d10 _vt_install_xg 01a00000 .bss
|
||||
018a13d0 _vt_root_window 01a08000 _mark
|
||||
01a1af00 _vt_win 01a08200 _term_width
|
||||
01a1af60 _vt_x11_drain_events 01a08220 _term_height
|
||||
018a27a0 _vt_x11_flush 01a1aee0 _vt_dpy
|
||||
018a14b0 _vt_x11_init 01a1af00 _vt_win
|
||||
01a1af40 _vt_x11_progress 01a1af20 _vt_gc
|
||||
018a3550 _vt_x11_term_init 01a1af40 _vt_x11_progress
|
||||
018a1b60 _x11_clearscr 01a1af60 _vt_x11_drain_events
|
||||
018a1d90 _x11_cursor 01a1af80 _term_reset
|
||||
018a2160 _x11_ibmchar 01a1afa0 _term_clearscr
|
||||
018a1d50 _x11_reset 01a1afc0 _term_setcursor
|
||||
018a1df0 _x11_setcolor 01a1afe0 _term_cursor
|
||||
018a1f10 _x11_setcursor 01a1b000 _term_setcolor
|
||||
018a3af0 cinit 01a1b020 _term_ibmchar
|
||||
018a0000 edata 01a1b040 _termenv
|
||||
01a1d080 end 01a1b060 _term_type
|
||||
018a3af0 etext 01a1d080 end
|
||||
01a17300 _vt_win 01a08200 _term_width
|
||||
01a17360 _vt_x11_drain_events 01a08220 _term_height
|
||||
018a2750 _vt_x11_flush 01a172e0 _vt_dpy
|
||||
018a14b0 _vt_x11_init 01a17300 _vt_win
|
||||
01a17340 _vt_x11_progress 01a17320 _vt_gc
|
||||
018a3980 _vt_x11_term_init 01a17340 _vt_x11_progress
|
||||
018a1b60 _x11_clearscr 01a17360 _vt_x11_drain_events
|
||||
018a1d60 _x11_cursor 01a17380 _term_reset
|
||||
018a2130 _x11_ibmchar 01a173a0 _term_clearscr
|
||||
018a1d20 _x11_reset 01a173c0 _term_setcursor
|
||||
018a1dc0 _x11_setcolor 01a173e0 _term_cursor
|
||||
018a1ee0 _x11_setcursor 01a17400 _term_setcolor
|
||||
018a3f20 cinit 01a17420 _term_ibmchar
|
||||
018a0000 edata 01a17440 _termenv
|
||||
01a19480 end 01a17460 _term_type
|
||||
018a3f20 etext 01a19480 end
|
||||
|
||||
[38 symbols]
|
||||
Binary file not shown.
@@ -0,0 +1,141 @@
|
||||
*****************************************************
|
||||
TMS340 COFF Linker Version 5.01
|
||||
*****************************************************
|
||||
Tue Nov 03 20:01:11 2026
|
||||
|
||||
OUTPUT FILE NAME: <MAIN.out>
|
||||
ENTRY POINT SYMBOL: "_start" address: 018a0000
|
||||
|
||||
|
||||
MEMORY CONFIGURATION
|
||||
|
||||
name origin length attributes
|
||||
-------- -------- --------- ----------
|
||||
prog 018a0000 000020000 RWIX
|
||||
ram 01a00000 000040000 RWIX
|
||||
|
||||
|
||||
SECTION ALLOCATION MAP
|
||||
|
||||
output attributes/
|
||||
section page origin length input sections
|
||||
-------- ---- ---------- ---------- ----------------
|
||||
.text 0 018a0000 00014940
|
||||
018a0000 00000150 ENTRY.obj (.text)
|
||||
018a0150 00001c90 MAIN.obj (.text)
|
||||
018a1de0 0000ce50 GAME.obj (.text)
|
||||
018aec30 00000d20 SCOREDB.obj (.text)
|
||||
018af950 00002a40 X11.obj (.text)
|
||||
018b2390 00001640 COMPAT.obj (.text)
|
||||
018b39d0 00000210 XGFIX.obj (.text)
|
||||
018b3be0 00000450 rts.lib (.text)
|
||||
018b4030 000001a0 rts.lib (.text)
|
||||
018b41d0 00000230 rts.lib (.text)
|
||||
018b4400 00000150 rts.lib (.text)
|
||||
018b4550 00000120 rts.lib (.text)
|
||||
018b4670 00000110 rts.lib (.text)
|
||||
018b4780 000001c0 rts.lib (.text)
|
||||
|
||||
.cinit 0 018b4940 000012d0
|
||||
018b4940 00000050 MAIN.obj (.cinit)
|
||||
018b4990 00001190 GAME.obj (.cinit)
|
||||
018b5b20 00000050 SCOREDB.obj (.cinit)
|
||||
018b5b70 00000050 X11.obj (.cinit)
|
||||
018b5bc0 00000050 rts.lib (.cinit)
|
||||
|
||||
.bss 0 01a00000 0001ec20 UNINITIALIZED
|
||||
01a00000 00008000 ENTRY.obj (.bss)
|
||||
01a08000 00000400 MAIN.obj (.bss)
|
||||
01a08400 00004a40 GAME.obj (.bss)
|
||||
01a0ce40 00000b80 SCOREDB.obj (.bss)
|
||||
01a0d9c0 0000f220 X11.obj (.bss)
|
||||
01a1cbe0 00000020 COMPAT.obj (.bss)
|
||||
01a1cc00 00002000 XGFIX.obj (.bss)
|
||||
01a1ec00 00000020 rts.lib (.bss)
|
||||
|
||||
.data 0 018a0000 00000000 UNINITIALIZED
|
||||
|
||||
|
||||
GLOBAL SYMBOLS
|
||||
|
||||
address name address name
|
||||
-------- ---- -------- ----
|
||||
01a00000 .bss 018a0000 .text
|
||||
018a0000 .data 018a0000 .data
|
||||
018a0000 .text 018a0000 edata
|
||||
01a08a00 _blackpf 018a0000 _start
|
||||
018a2f40 _cleanup_game 018a0150 _now_ms
|
||||
01a08380 _dbg_events 018a03d0 _sleep_ms
|
||||
01a083c0 _dbg_focus 018a0530 _vt_poll_event
|
||||
01a083a0 _dbg_last 018a0ea0 _vt_handle_event
|
||||
01a08360 _dbg_loops 018a1700 _wait_display
|
||||
01a083e0 _dbg_rev 018a1740 _main
|
||||
018b2390 _fflush 018a1de0 _init_game
|
||||
018a84e0 _game_input 018a2f40 _cleanup_game
|
||||
018a1de0 _init_game 018a3130 _update
|
||||
018a1740 _main 018a84e0 _game_input
|
||||
018b3be0 _memcpy 018aef90 _read_scores
|
||||
018b3d80 _memmove 018af310 _save_score
|
||||
018b4030 _memset 018af8a0 _print_scores
|
||||
01a089a0 _monochrome 018af950 _vt_root_window
|
||||
018a0150 _now_ms 018afa30 _vt_x11_init
|
||||
01a089e0 _onlyascii 018b01e0 _x11_clearscr
|
||||
018af8a0 _print_scores 018b03a0 _x11_reset
|
||||
018b2400 _putchar 018b03e0 _x11_cursor
|
||||
01a08940 _quit 018b0440 _x11_setcolor
|
||||
018b41d0 _rand 018b0560 _x11_setcursor
|
||||
018aef90 _read_scores 018b07b0 _x11_ibmchar
|
||||
01a08a20 _rotstep 018b0c30 _term_putstr
|
||||
018af310 _save_score 018b0dd0 _vt_x11_flush
|
||||
01a08a80 _scr 018b2000 _vt_x11_term_init
|
||||
018a03d0 _sleep_ms 018b2320 _term_init
|
||||
018b2c10 _sprintf 018b2390 _fflush
|
||||
018b4330 _srand 018b2400 _putchar
|
||||
018a0000 _start 018b2c10 _sprintf
|
||||
01a1cbe0 _stdout 018b39d0 _vt_install_xg
|
||||
018b4400 _strcmp 018b3be0 _memcpy
|
||||
018b4550 _strcpy 018b3d80 _memmove
|
||||
018b4670 _strlen 018b4030 _memset
|
||||
018b4780 _strncpy 018b41d0 _rand
|
||||
01a1cb00 _term_clearscr 018b4330 _srand
|
||||
01a1cb40 _term_cursor 018b4400 _strcmp
|
||||
01a08a60 _term_height 018b4550 _strcpy
|
||||
01a1cb80 _term_ibmchar 018b4670 _strlen
|
||||
018b2320 _term_init 018b4780 _strncpy
|
||||
018b0c30 _term_putstr 018b4940 cinit
|
||||
01a1cae0 _term_reset 018b4940 etext
|
||||
01a1cb60 _term_setcolor 01a00000 .bss
|
||||
01a1cb20 _term_setcursor 01a08360 _dbg_loops
|
||||
01a1cbc0 _term_type 01a08380 _dbg_events
|
||||
01a08a40 _term_width 01a083a0 _dbg_last
|
||||
01a1cba0 _termenv 01a083c0 _dbg_focus
|
||||
01a08960 _tick_interval 01a083e0 _dbg_rev
|
||||
018a3130 _update 01a08940 _quit
|
||||
01a08980 _use_bell 01a08960 _tick_interval
|
||||
01a089c0 _use_gfxchar 01a08980 _use_bell
|
||||
01a0ce40 _username 01a089a0 _monochrome
|
||||
01a1ca40 _vt_dpy 01a089c0 _use_gfxchar
|
||||
01a1ca80 _vt_gc 01a089e0 _onlyascii
|
||||
018a0ea0 _vt_handle_event 01a08a00 _blackpf
|
||||
018b39d0 _vt_install_xg 01a08a20 _rotstep
|
||||
018a0530 _vt_poll_event 01a08a40 _term_width
|
||||
018af950 _vt_root_window 01a08a60 _term_height
|
||||
01a1ca60 _vt_win 01a08a80 _scr
|
||||
01a1cac0 _vt_x11_drain_events 01a0ce40 _username
|
||||
018b0dd0 _vt_x11_flush 01a1ca40 _vt_dpy
|
||||
018afa30 _vt_x11_init 01a1ca60 _vt_win
|
||||
01a1caa0 _vt_x11_progress 01a1ca80 _vt_gc
|
||||
018b2000 _vt_x11_term_init 01a1caa0 _vt_x11_progress
|
||||
018a1700 _wait_display 01a1cac0 _vt_x11_drain_events
|
||||
018b01e0 _x11_clearscr 01a1cae0 _term_reset
|
||||
018b03e0 _x11_cursor 01a1cb00 _term_clearscr
|
||||
018b07b0 _x11_ibmchar 01a1cb20 _term_setcursor
|
||||
018b03a0 _x11_reset 01a1cb40 _term_cursor
|
||||
018b0440 _x11_setcolor 01a1cb60 _term_setcolor
|
||||
018b0560 _x11_setcursor 01a1cb80 _term_ibmchar
|
||||
018b4940 cinit 01a1cba0 _termenv
|
||||
018a0000 edata 01a1cbc0 _term_type
|
||||
01a1ec20 end 01a1cbe0 _stdout
|
||||
018b4940 etext 01a1ec20 end
|
||||
|
||||
[77 symbols]
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 19 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 20 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 19 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 22 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 19 KiB |
@@ -25,6 +25,8 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
#include "term.h"
|
||||
#include "scoredb.h"
|
||||
|
||||
extern long now_ms(void);
|
||||
|
||||
|
||||
int quit;
|
||||
long tick_interval;
|
||||
@@ -190,7 +192,7 @@ int init_game(void)
|
||||
scores = read_scores(0, 10);
|
||||
memset(&cur_score, 0, sizeof cur_score);
|
||||
|
||||
srand(time(0));
|
||||
srand((unsigned)now_ms());
|
||||
|
||||
pause = 0;
|
||||
gameover = 0;
|
||||
|
||||
@@ -17,6 +17,12 @@ int fflush(FILE *fp)
|
||||
return 0;
|
||||
}
|
||||
|
||||
int putchar(int c)
|
||||
{
|
||||
(void)c;
|
||||
return c;
|
||||
}
|
||||
|
||||
static char *fmt_num(char *out, long v, int width, int zero, int left, int unsig)
|
||||
{
|
||||
char tmp[24];
|
||||
|
||||
@@ -18,6 +18,7 @@ static vt_u32 evbuf[24];
|
||||
static long ms_base;
|
||||
static int ms_last = -1;
|
||||
static int focused;
|
||||
volatile vt_u32 dbg_loops, dbg_events, dbg_last, dbg_focus, dbg_rev;
|
||||
|
||||
long now_ms(void)
|
||||
{
|
||||
@@ -88,10 +89,18 @@ void vt_handle_event(void)
|
||||
int n;
|
||||
|
||||
VT_XNEXT(vt_dpy, (void *)evbuf);
|
||||
dbg_events = dbg_events + 1;
|
||||
dbg_last = evbuf[0];
|
||||
if(evbuf[0] == 2) { /* KeyPress */
|
||||
ks = 0;
|
||||
n = VT_XLOOKUP((void *)evbuf, buf, 8, &ks, 0);
|
||||
map_keysym(ks, n > 0 ? buf[0] : 0, n);
|
||||
} else if(evbuf[0] == 5) { /* ButtonRelease: sample focus */
|
||||
vt_u32 fw;
|
||||
int rv;
|
||||
VT_XGETFOCUS(vt_dpy, &fw, &rv);
|
||||
dbg_focus = fw;
|
||||
dbg_rev = (vt_u32)rv;
|
||||
} else if(evbuf[0] == 12) { /* Expose */
|
||||
game_input('`');
|
||||
} else if(evbuf[0] == 9) { /* FocusIn */
|
||||
@@ -111,17 +120,33 @@ int main(void)
|
||||
{
|
||||
long d;
|
||||
|
||||
if(vt_x11_init() != 0)
|
||||
return 1;
|
||||
/* The display server may still be coming up when this process is
|
||||
* created; retry for a while before giving up. */
|
||||
{
|
||||
int tries;
|
||||
for(tries = 0; tries < 40; tries++) {
|
||||
if(vt_x11_init() == 0) break;
|
||||
VT_SLEEP(50);
|
||||
}
|
||||
if(tries == 40) return 1;
|
||||
}
|
||||
onlyascii = 1;
|
||||
use_gfxchar = 0;
|
||||
monochrome = 1; /* the VT1200 raster is 1 bit deep */
|
||||
if(init_game() == -1)
|
||||
return 2;
|
||||
vt_x11_flush();
|
||||
|
||||
for(;;) {
|
||||
dbg_loops = dbg_loops + 1;
|
||||
VT_XSYNC(vt_dpy, 0);
|
||||
while(vt_poll_event()) {
|
||||
vt_handle_event();
|
||||
}
|
||||
if(quit) {
|
||||
cleanup_game();
|
||||
break;
|
||||
}
|
||||
d = update(now_ms());
|
||||
sleep_ms((int)d);
|
||||
vt_x11_flush();
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* scoredb.c -- RAM-only high scores (the VT1200 has no filesystem).
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include "scoredb.h"
|
||||
|
||||
#define NENT 10
|
||||
|
||||
char *username = "VT1200";
|
||||
|
||||
static struct score_entry list[NENT];
|
||||
static char names[NENT][16];
|
||||
static int inited;
|
||||
|
||||
static void init_list(void)
|
||||
{
|
||||
int i;
|
||||
if(inited) return;
|
||||
inited = 1;
|
||||
for(i = 0; i < NENT; i++) {
|
||||
strcpy(names[i], "VT1200");
|
||||
list[i].user = names[i];
|
||||
list[i].score = 0;
|
||||
list[i].lines = 0;
|
||||
list[i].level = 0;
|
||||
list[i].next = (i + 1 < NENT) ? &list[i + 1] : 0;
|
||||
}
|
||||
}
|
||||
|
||||
struct score_entry *read_scores(FILE *fp, int max_scores)
|
||||
{
|
||||
(void)fp;
|
||||
(void)max_scores;
|
||||
init_list();
|
||||
return &list[0];
|
||||
}
|
||||
|
||||
static void copy_entry(struct score_entry *dst, struct score_entry *src)
|
||||
{
|
||||
long sc = src->score, ln = src->lines, lv = src->level;
|
||||
char *u = (src->user && *src->user) ? src->user : username;
|
||||
strncpy(dst->user, u, 15);
|
||||
dst->user[15] = 0;
|
||||
dst->score = sc;
|
||||
dst->lines = ln;
|
||||
dst->level = lv;
|
||||
}
|
||||
|
||||
int save_score(struct score_entry *sc)
|
||||
{
|
||||
int pos, i;
|
||||
if(!sc) return 0;
|
||||
init_list();
|
||||
for(pos = 0; pos < NENT; pos++) {
|
||||
if(sc->score > list[pos].score) break;
|
||||
}
|
||||
if(pos >= NENT) return 0;
|
||||
for(i = NENT - 1; i > pos; i--) {
|
||||
long s = list[i - 1].score, l = list[i - 1].lines, v = list[i - 1].level;
|
||||
char tmp[16];
|
||||
strcpy(tmp, list[i - 1].user);
|
||||
strcpy(list[i].user, tmp);
|
||||
list[i].score = s;
|
||||
list[i].lines = l;
|
||||
list[i].level = v;
|
||||
}
|
||||
copy_entry(&list[pos], sc);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int print_scores(int num)
|
||||
{
|
||||
(void)num;
|
||||
return 0;
|
||||
}
|
||||
@@ -11,5 +11,6 @@ extern FILE *stdout;
|
||||
|
||||
int sprintf(char *buf, char *fmt, ...);
|
||||
int fflush(FILE *fp);
|
||||
int putchar(int c);
|
||||
|
||||
#endif
|
||||
@@ -19,7 +19,7 @@
|
||||
|
||||
struct vt_cell {
|
||||
unsigned short ch;
|
||||
unsigned char fg, bg, dirty;
|
||||
unsigned char inv, dirty;
|
||||
};
|
||||
|
||||
static struct vt_cell cells[ROWS * COLS];
|
||||
@@ -51,14 +51,18 @@ vt_u32 vt_root_window(void *dpy)
|
||||
|
||||
int vt_x11_init(void)
|
||||
{
|
||||
vt_x11_progress = 11;
|
||||
if(!vt_install_xg())
|
||||
return -1;
|
||||
vt_x11_progress = 12;
|
||||
vt_dpy = VT_XOPEN("0::0");
|
||||
if(!vt_dpy)
|
||||
return -1;
|
||||
vt_x11_progress = 13;
|
||||
vt_win = VT_XCREATE(vt_dpy, vt_root_window(vt_dpy), 80, 80, 480, 312, 2, 1, 0);
|
||||
if(!vt_win)
|
||||
return -1;
|
||||
vt_x11_progress = 14;
|
||||
VT_XSELECT(vt_dpy, vt_win, 0x22000FUL);
|
||||
vt_gc = VT_XGC(vt_dpy, vt_win, 0, 0);
|
||||
if(!vt_gc)
|
||||
@@ -73,8 +77,7 @@ void x11_clearscr(void)
|
||||
int i;
|
||||
for(i = 0; i < ROWS * COLS; i++) {
|
||||
cells[i].ch = ' ';
|
||||
cells[i].fg = 0;
|
||||
cells[i].bg = 0;
|
||||
cells[i].inv = 0;
|
||||
cells[i].dirty = 1;
|
||||
}
|
||||
cur_row = cur_col = 0;
|
||||
@@ -113,8 +116,9 @@ void x11_ibmchar(unsigned char c, unsigned char attr)
|
||||
if(cur_col < 0 || cur_col >= COLS) return;
|
||||
p = &cells[cur_row * COLS + cur_col];
|
||||
p->ch = c;
|
||||
p->fg = (attr >> 4) & 0xf;
|
||||
p->bg = attr & 0xf;
|
||||
/* The VT1200 raster is one bit deep, so follow the game's monochrome
|
||||
* convention (ansi.c -m): colour is dropped, the bold bit reverses. */
|
||||
p->inv = (attr & 0x80) ? 1 : 0;
|
||||
p->dirty = 1;
|
||||
cur_col++;
|
||||
if(cur_col >= COLS) {
|
||||
@@ -132,42 +136,54 @@ void term_putstr(const char *s, unsigned char attr)
|
||||
|
||||
void vt_x11_flush(void)
|
||||
{
|
||||
int i, row_dirty = 0, dirty_any = 0;
|
||||
char one[2];
|
||||
int r, c, any = 0;
|
||||
char line[COLS + 1];
|
||||
|
||||
vt_x11_progress = 1;
|
||||
for(i = 0; i < ROWS * COLS; i++) {
|
||||
struct vt_cell *p = &cells[i];
|
||||
vt_x11_progress = 0x100 + (vt_u32)i;
|
||||
if(p->dirty) {
|
||||
int x, y, bgpx, fgpx;
|
||||
x = (i % COLS) * CELL_W;
|
||||
y = (i / COLS) * CELL_H;
|
||||
bgpx = p->bg == 0 ? 0 : 1;
|
||||
fgpx = p->fg == 0 ? 0 : 1;
|
||||
VT_XSETFG(vt_dpy, vt_gc, (vt_u32)bgpx);
|
||||
VT_XFILL(vt_dpy, vt_win, vt_gc, x, y, CELL_W, CELL_H);
|
||||
VT_XSETFG(vt_dpy, vt_gc, (vt_u32)fgpx);
|
||||
one[0] = (char)p->ch;
|
||||
one[1] = 0;
|
||||
VT_XDRAW(vt_dpy, vt_win, vt_gc, x, y + BASE_OFF, one, 1);
|
||||
p->dirty = 0;
|
||||
row_dirty = 1;
|
||||
dirty_any = 1;
|
||||
for(r = 0; r < ROWS; r++) {
|
||||
struct vt_cell *row = &cells[r * COLS];
|
||||
int dirty = 0;
|
||||
|
||||
for(c = 0; c < COLS; c++) {
|
||||
if(row[c].dirty) dirty = 1;
|
||||
}
|
||||
if((i % COLS) == COLS - 1 && row_dirty) {
|
||||
vt_u32 ev[24];
|
||||
vt_x11_progress = 0x10000 + (vt_u32)(i / COLS);
|
||||
VT_XSYNC(vt_dpy, 0);
|
||||
if(vt_x11_drain_events) {
|
||||
while(*(vt_u32 *)((char *)vt_dpy + 0x54) != 0) {
|
||||
VT_XNEXT(vt_dpy, (void *)ev);
|
||||
}
|
||||
}
|
||||
row_dirty = 0;
|
||||
if(!dirty) continue;
|
||||
|
||||
vt_x11_progress = 0x100 + (vt_u32)r;
|
||||
any = 1;
|
||||
|
||||
/* repaint the whole row: black background, then the text.
|
||||
* One fill + one string per row keeps the request stream small
|
||||
* enough that the in-machine X server can drain it. */
|
||||
VT_XSETFG(vt_dpy, vt_gc, 0);
|
||||
VT_XFILL(vt_dpy, vt_win, vt_gc, 0, r * CELL_H, COLS * CELL_W, CELL_H);
|
||||
|
||||
for(c = 0; c < COLS; c++) {
|
||||
line[c] = row[c].ch ? (char)row[c].ch : ' ';
|
||||
}
|
||||
line[COLS] = 0;
|
||||
|
||||
VT_XSETFG(vt_dpy, vt_gc, 1);
|
||||
VT_XDRAW(vt_dpy, vt_win, vt_gc, 0, r * CELL_H + BASE_OFF, line, COLS);
|
||||
|
||||
/* inverse (bold) cells: white block with black text */
|
||||
for(c = 0; c < COLS; c++) {
|
||||
int start;
|
||||
if(!row[c].inv) continue;
|
||||
start = c;
|
||||
while(c < COLS && row[c].inv) c++;
|
||||
VT_XSETFG(vt_dpy, vt_gc, 1);
|
||||
VT_XFILL(vt_dpy, vt_win, vt_gc, start * CELL_W,
|
||||
r * CELL_H, (c - start) * CELL_W, CELL_H);
|
||||
VT_XSETFG(vt_dpy, vt_gc, 0);
|
||||
VT_XDRAW(vt_dpy, vt_win, vt_gc, start * CELL_W,
|
||||
r * CELL_H + BASE_OFF, line + start, c - start);
|
||||
}
|
||||
|
||||
for(c = 0; c < COLS; c++) row[c].dirty = 0;
|
||||
}
|
||||
if(dirty_any) {
|
||||
VT_XSYNC(vt_dpy, 0);
|
||||
if(any) {
|
||||
VT_XFLUSH(vt_dpy);
|
||||
}
|
||||
vt_x11_progress = 0xFFFFFFUL;
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ typedef int (*vt_xfill_t)(void *, vt_u32, vt_u32, int, int,
|
||||
typedef int (*vt_xdraw_t)(void *, vt_u32, vt_u32, int, int,
|
||||
const char *, int);
|
||||
typedef int (*vt_sleep_t)(int);
|
||||
typedef int (*vt_xgetfocus_t)(void *, vt_u32 *, int *);
|
||||
|
||||
#define VT_XOPEN ((vt_xopen_t)0x3F9C2570UL)
|
||||
#define VT_XCREATE ((vt_xcreate_t)0x3FD41B90UL)
|
||||
@@ -41,6 +42,7 @@ typedef int (*vt_sleep_t)(int);
|
||||
#define VT_XDRAW ((vt_xdraw_t)0x3FDA0000UL)
|
||||
#define VT_XPENDING ((int (*)(void *))0x3FD67620UL)
|
||||
#define VT_SLEEP ((vt_sleep_t)0x3FB33A20UL)
|
||||
#define VT_XGETFOCUS ((vt_xgetfocus_t)0x3FD55210UL)
|
||||
|
||||
/* display->screens[] is at bit 0x480 / byte 0x90; screen->root at bit 0x40 /
|
||||
* byte 8 (see dwt/notes/GUI_APPS.md and dwt/gui/xhello.asm). */
|
||||
|
||||
@@ -35,6 +35,7 @@ local key_right = ports[":lk401:K75"].fields["Right"]
|
||||
local key_zero = ports[":lk401:K10"].fields["0 )"]
|
||||
local key_ret = ports[":lk401:K68"].fields["Return"]
|
||||
local key_del = ports[":lk401:K67"].fields["Delete"]
|
||||
local key_q = ports[":lk401:K13"].fields["Q"]
|
||||
local tabx = ports[":tablet:TABLET_X"].fields["Tablet X"]
|
||||
local taby = ports[":tablet:TABLET_Y"].fields["Tablet Y"]
|
||||
local tabb = ports[":tablet:TABLET_BTN"].fields["P1 Button 1"]
|
||||
@@ -151,6 +152,8 @@ local function key_step(frames, key)
|
||||
if frames == 3230 then key_ret:set_value(0) end
|
||||
if frames == 3240 then key_del:set_value(1) end
|
||||
if frames == 3250 then key_del:set_value(0) end
|
||||
if frames == 3260 then key_q:set_value(1) end
|
||||
if frames == 3270 then key_q:set_value(0) end
|
||||
end
|
||||
|
||||
local function r32(a)
|
||||
|
||||
@@ -20,7 +20,7 @@ LOADER_OUT="$APP/build" \
|
||||
-autoboot_script "$DIR/input.lua" -autoboot_delay 0 \
|
||||
>"$LOG" 2>&1 || true
|
||||
grep 'INPUT:' "$LOG" | tail -3
|
||||
grep -q 'INPUT: .*bytes=.*61 9B 41 9B 42 9B 44 9B 43 30 0A 7F' "$LOG" || {
|
||||
grep -q 'INPUT: .*bytes=.*61 9B 41 9B 42 9B 44 9B 43 30 0A 7F 71' "$LOG" || {
|
||||
echo "input.sh: mapped key sequence missing (see $LOG)" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
@@ -0,0 +1,345 @@
|
||||
-- loader.lua -- debugger-assisted COFF loader acceptance harness.
|
||||
--
|
||||
-- Boots the firmware with VT1200_SKIP_LEVEL1=1 (the documented divergence) to
|
||||
-- the OS, then at frame LOADER_AT pokes:
|
||||
-- * the loader payload at bit 0x01880000 (raw dwt/loader/build/loader.bin)
|
||||
-- * a GSPLNK COFF image at LOADER_BUF (default bit 0x01900000)
|
||||
-- * the loader descriptor at 0x01882C00
|
||||
-- and sets PC = 0x01880000. It does not use the network, the rdebug service
|
||||
-- or any firmware transport: this is the debugger-assisted milestone.
|
||||
--
|
||||
-- The loader itself copies the COFF sections to their linked bit addresses and
|
||||
-- calls create_process(entry, "LOADER", 0x6000, 1, 0). This script only pokes
|
||||
-- the input and reads the result; it never writes the app's code or markers.
|
||||
--
|
||||
-- Env:
|
||||
-- LOADER_BIN loader payload (default dwt/loader/build/loader.bin)
|
||||
-- LOADER_COFF app COFF (default dwt/loader/build/marker.coff)
|
||||
-- LOADER_BUF COFF buffer bit address (default 0x01900000)
|
||||
-- LOADER_AT injection frame (default 2500)
|
||||
-- LOADER_END inspection/exit frame (default 2700)
|
||||
-- LOADER_OUT evidence directory (default dwt/loader/evidence)
|
||||
-- LOADER_SNAP 1 = write a PNG at LOADER_END (default 0)
|
||||
-- LOADER_VERBOSE 1 = print the result block every 25 frames
|
||||
|
||||
local function hex(v, w) return string.format("%0" .. (w or 8) .. "X", v) end
|
||||
|
||||
local machine = manager.machine
|
||||
local ports = machine.ioport.ports
|
||||
local tabx = ports[":tablet:TABLET_X"].fields["Tablet X"]
|
||||
local taby = ports[":tablet:TABLET_Y"].fields["Tablet Y"]
|
||||
local tabb = ports[":tablet:TABLET_BTN"].fields["P1 Button 1"]
|
||||
local cpu = machine.devices[':maincpu']
|
||||
local dbg = machine.debugger
|
||||
local prog = cpu.spaces['program']
|
||||
|
||||
if not dbg then
|
||||
print("LOADER: debugger not enabled; pass -debug")
|
||||
machine:exit()
|
||||
return
|
||||
end
|
||||
|
||||
local AT = tonumber(os.getenv("LOADER_AT") or "2500")
|
||||
local ENDF = tonumber(os.getenv("LOADER_END") or "2700")
|
||||
local BIN = os.getenv("LOADER_BIN") or "dwt/loader/build/loader.bin"
|
||||
local COFF = os.getenv("LOADER_COFF") or "dwt/loader/build/marker.coff"
|
||||
local BUF = tonumber(os.getenv("LOADER_BUF") or "0x01900000")
|
||||
local OUT = os.getenv("LOADER_OUT") or "dwt/loader/evidence"
|
||||
local SNAP = os.getenv("LOADER_SNAP") == "1"
|
||||
local MODE = tonumber(os.getenv("LOADER_MODE") or "0")
|
||||
local A8DESC = os.getenv("LOADER_A8_DESC") == "1"
|
||||
local VERBOSE = os.getenv("LOADER_VERBOSE") == "1"
|
||||
|
||||
local LOADER_BASE = 0x01880000
|
||||
local DESC = 0x01882C00
|
||||
local RESULT = 0x01882D00
|
||||
local RETURN_STUB = 0x01883100
|
||||
local RETURN_SP = 0x01883110
|
||||
|
||||
local function rd(p) return io.open(p, "rb") end
|
||||
local function slurp(p)
|
||||
local f, err = rd(p)
|
||||
if not f then error("cannot open " .. p .. ": " .. tostring(err)) end
|
||||
local s = f:read("*a")
|
||||
f:close()
|
||||
return s
|
||||
end
|
||||
|
||||
local function u16(s, o)
|
||||
return s:byte(o + 1) + s:byte(o + 2) * 256
|
||||
end
|
||||
|
||||
local function u32(s, o)
|
||||
return s:byte(o + 1) + s:byte(o + 2) * 256
|
||||
+ s:byte(o + 3) * 65536 + s:byte(o + 4) * 16777216
|
||||
end
|
||||
|
||||
local function parse_coff(d)
|
||||
local c = { data = d }
|
||||
c.magic = u16(d, 0)
|
||||
c.nscns = u16(d, 2)
|
||||
c.opthdr = u16(d, 16)
|
||||
c.sections = {}
|
||||
if c.opthdr > 0 then
|
||||
c.optmagic = u16(d, 20)
|
||||
c.entry = u32(d, 20 + 16)
|
||||
c.textstart = u32(d, 20 + 20)
|
||||
c.datastart = u32(d, 20 + 24)
|
||||
end
|
||||
local base = 20 + c.opthdr
|
||||
for i = 0, c.nscns - 1 do
|
||||
local o = base + i * 40
|
||||
local name = d:sub(o + 1, o + 8):gsub("%z", "")
|
||||
c.sections[#c.sections + 1] = {
|
||||
name = name,
|
||||
paddr = u32(d, o + 8),
|
||||
size = u32(d, o + 16), -- bits
|
||||
scnptr = u32(d, o + 20),
|
||||
flags = u32(d, o + 36),
|
||||
}
|
||||
end
|
||||
return c
|
||||
end
|
||||
|
||||
local function poke_bytes(bitaddr, data)
|
||||
local n = math.floor((#data + 1) / 2)
|
||||
for i = 0, n - 1 do
|
||||
local lo = data:byte(i * 2 + 1) or 0
|
||||
local hi = data:byte(i * 2 + 2) or 0
|
||||
prog:write_u16(bitaddr + i * 16, lo + hi * 256)
|
||||
end
|
||||
return n
|
||||
end
|
||||
|
||||
local function verify_bytes(bitaddr, data)
|
||||
local n = math.floor((#data + 1) / 2)
|
||||
local bad = 0
|
||||
for i = 0, n - 1 do
|
||||
local lo = data:byte(i * 2 + 1) or 0
|
||||
local hi = data:byte(i * 2 + 2) or 0
|
||||
if prog:read_u16(bitaddr + i * 16) ~= lo + hi * 256 then
|
||||
bad = bad + 1
|
||||
end
|
||||
end
|
||||
return n, bad
|
||||
end
|
||||
|
||||
local function w32(a, v) prog:write_u32(a, v) end
|
||||
local function r32(a)
|
||||
local ok, v = pcall(function() return prog:read_u32(a) end)
|
||||
if ok then return v end
|
||||
return 0xDEADBEEF
|
||||
end
|
||||
|
||||
local loader = slurp(BIN)
|
||||
local coff = slurp(COFF)
|
||||
local app = parse_coff(coff)
|
||||
|
||||
print(string.format("LOADER: payload %s (%d B) -> bit %s", BIN, #loader, hex(LOADER_BASE)))
|
||||
print(string.format("LOADER: COFF %s (%d B) -> bit %s", COFF, #coff, hex(BUF)))
|
||||
print(string.format("LOADER: file magic=%04X nscns=%d opthdr=%d optmagic=%s entry=%s textstart=%s",
|
||||
app.magic, app.nscns, app.opthdr,
|
||||
app.optmagic and string.format("%04X", app.optmagic) or "none",
|
||||
hex(app.entry or 0), hex(app.textstart or 0)))
|
||||
for _, s in ipairs(app.sections) do
|
||||
print(string.format("LOADER: sec %-8s paddr=%s size=%d bits (%d B) scnptr=%#x flags=%#x",
|
||||
s.name, hex(s.paddr), s.size, math.floor(s.size / 8), s.scnptr, s.flags))
|
||||
end
|
||||
|
||||
local TRACE = os.getenv("LOADER_TRACE") == "1"
|
||||
|
||||
local frames = 0
|
||||
local injected = false
|
||||
local done = false
|
||||
local last_count = -1
|
||||
|
||||
local function status_name(v)
|
||||
local t = {
|
||||
[0] = "OK",
|
||||
[1] = "bad descriptor",
|
||||
[2] = "truncated COFF / section headers",
|
||||
[3] = "bad COFF magic",
|
||||
[4] = "section size not whole words",
|
||||
[5] = "target outside RAM",
|
||||
[6] = "target overlaps loader page or COFF buffer",
|
||||
[7] = "relocation records present (unsupported)",
|
||||
[8] = "target not in a free-list block",
|
||||
[9] = "no entry point",
|
||||
}
|
||||
return t[v] or ("error " .. tostring(v))
|
||||
end
|
||||
|
||||
local function result_dump()
|
||||
local st = r32(RESULT + 0x000)
|
||||
print(string.format(
|
||||
"LOADER: result status=%d (%s) entry=%s text=%s/%d bits data=%s/%d bits bss=%s/%d bits relocs=%d pcb=%s nscns=%d len=%d",
|
||||
st, status_name(st), hex(r32(RESULT + 0x020)),
|
||||
hex(r32(RESULT + 0x040)), r32(RESULT + 0x060),
|
||||
hex(r32(RESULT + 0x080)), r32(RESULT + 0x0A0),
|
||||
hex(r32(RESULT + 0x0C0)), r32(RESULT + 0x0E0),
|
||||
r32(RESULT + 0x100), hex(r32(RESULT + 0x120)),
|
||||
r32(RESULT + 0x140), r32(RESULT + 0x160)))
|
||||
end
|
||||
|
||||
local function inject()
|
||||
-- 1. loader itself
|
||||
local n, bad = 0, 0
|
||||
poke_bytes(LOADER_BASE, loader)
|
||||
n, bad = verify_bytes(LOADER_BASE, loader)
|
||||
print(string.format("LOADER: loader poked %d words, readback %d bad at %s",
|
||||
n, bad, hex(LOADER_BASE)))
|
||||
|
||||
-- 2. COFF image
|
||||
poke_bytes(BUF, coff)
|
||||
n, bad = verify_bytes(BUF, coff)
|
||||
print(string.format("LOADER: COFF poked %d words, readback %d bad at %s",
|
||||
n, bad, hex(BUF)))
|
||||
|
||||
-- 3. pre-fill every .bss section with A5A5, so a loaded zero is meaningful
|
||||
for _, s in ipairs(app.sections) do
|
||||
if s.flags & 0x80 ~= 0 then
|
||||
for i = 0, math.floor(s.size / 16) - 1 do
|
||||
prog:write_u16(s.paddr + i * 16, 0xA5A5)
|
||||
end
|
||||
print(string.format("LOADER: pre-filled .bss %s..%s with A5A5",
|
||||
hex(s.paddr), hex(s.paddr + s.size)))
|
||||
end
|
||||
end
|
||||
|
||||
-- 4. descriptor: magic "LD" "R1", buf, len, mode=0 (spin)
|
||||
w32(DESC + 0x00, 0)
|
||||
prog:write_u16(DESC + 0x00, 0x4C44)
|
||||
prog:write_u16(DESC + 0x10, 0x5231)
|
||||
w32(DESC + 0x20, BUF)
|
||||
w32(DESC + 0x40, #coff)
|
||||
w32(DESC + 0x60, MODE)
|
||||
print(string.format("LOADER: descriptor @%s magic=%04X/%04X buf=%s len=%d mode=%d",
|
||||
hex(DESC), prog:read_u16(DESC + 0x00), prog:read_u16(DESC + 0x10),
|
||||
hex(r32(DESC + 0x20)), r32(DESC + 0x40), r32(DESC + 0x60)))
|
||||
|
||||
-- 5. return stub. mode 0 ignores it (the loader jumps to ORIG_PC
|
||||
-- itself); mode 1 returns here, and the stub resumes the interrupted
|
||||
-- PC so the caller yields and the scheduler can run the child.
|
||||
local orig_pc = cpu.state['PC'].value
|
||||
if MODE == 1 then
|
||||
prog:write_u16(RETURN_STUB, 0xC080) -- jauc <orig_pc>
|
||||
prog:write_u16(RETURN_STUB + 0x10, orig_pc & 0xFFFF)
|
||||
prog:write_u16(RETURN_STUB + 0x20, math.floor(orig_pc / 0x10000) & 0xFFFF)
|
||||
else
|
||||
prog:write_u16(RETURN_STUB, 0xC0FF) -- jruc self
|
||||
end
|
||||
|
||||
-- 6. enter the loader. A8=0 selects the fixed descriptor; SP points at
|
||||
-- the return slot so mode=1 would return to the spin stub.
|
||||
-- Leave SP alone for mode 0: the loader pushes onto the current
|
||||
-- process stack, then restores it and resumes at ORIG_PC. (mode 1
|
||||
-- would use the return stub.)
|
||||
w32(0x01882A20, orig_pc) -- LD_ORIG_PC
|
||||
print(string.format("LOADER: will resume at %s after create_process", hex(orig_pc)))
|
||||
cpu.state['A8'].value = A8DESC and DESC or 0
|
||||
if MODE == 1 then
|
||||
-- Simulate a CALLA from the current process: push the return
|
||||
-- address on the *firmware* stack (a real transport would do this).
|
||||
local sp = cpu.state['SP'].value - 0x20
|
||||
w32(sp, RETURN_STUB)
|
||||
cpu.state['SP'].value = sp
|
||||
end
|
||||
cpu.state['PC'].value = LOADER_BASE
|
||||
injected = true
|
||||
print(string.format("LOADER: frame %d: entered loader at %s (caller SP=%s)",
|
||||
frames, hex(LOADER_BASE), hex(cpu.state['SP'].value)))
|
||||
end
|
||||
|
||||
local function inspect()
|
||||
result_dump()
|
||||
|
||||
-- Verify the placement from RAM: .text/.data must equal the file bytes.
|
||||
for _, s in ipairs(app.sections) do
|
||||
if s.scnptr ~= 0 and s.size > 0 then
|
||||
local nb = math.floor(s.size / 8)
|
||||
local bytes = coff:sub(s.scnptr + 1, s.scnptr + nb)
|
||||
local _, bad = verify_bytes(s.paddr, bytes)
|
||||
print(string.format("LOADER: placed %s at %s: %d bytes, %d differing",
|
||||
s.name, hex(s.paddr), nb, bad))
|
||||
end
|
||||
end
|
||||
|
||||
local base = COFF:match("([^/]+)$") or COFF
|
||||
if base:find("marker") then
|
||||
print(string.format("LOADER: marker app: start=%s data=%s data2=%s bss=%s count=%d",
|
||||
hex(r32(0x018E0000)), hex(r32(0x018E0020)), hex(r32(0x018E0040)),
|
||||
hex(r32(0x018E0060)), r32(0x018E0080)))
|
||||
elseif base:find("xhello") then
|
||||
print(string.format("LOADER: xhello: stage=%d strlen=%d display=%s root=%s window=%s sync=%s",
|
||||
r32(0x01890140), r32(0x01890160), hex(r32(0x01890100)),
|
||||
hex(r32(0x01890110)), hex(r32(0x01890120)), hex(r32(0x01890130))))
|
||||
end
|
||||
print(string.format("LOADER: PC at inspection = %s", hex(cpu.state['PC'].value)))
|
||||
end
|
||||
|
||||
emu.register_frame_done(function()
|
||||
frames = frames + 1
|
||||
if frames == 2900 then tabx:set_value(700); taby:set_value(500); print("PLAY: tablet move") end
|
||||
if frames == 3000 then tabb:set_value(1); print("PLAY: click down") end
|
||||
if frames == 3020 then tabb:set_value(0); print("PLAY: click up") end
|
||||
if frames == AT and not injected then
|
||||
inject()
|
||||
if TRACE then dbg:command("trace /tmp/opencode/ld1.tr") end
|
||||
end
|
||||
if injected and TRACE and frames == AT + 1 then
|
||||
dbg:command("trace off")
|
||||
print("LOADER: trace written to /tmp/opencode/ld.tr")
|
||||
end
|
||||
if injected and not done then
|
||||
if TRACE and frames <= AT + 40 then
|
||||
print(string.format("LOADER: trace f=%d PC=%s SP=%s A14=%s status=%s entry=%s",
|
||||
frames, hex(cpu.state['PC'].value), hex(cpu.state['SP'].value),
|
||||
hex(cpu.state['A14'].value), hex(r32(RESULT + 0x000)),
|
||||
hex(r32(RESULT + 0x020))))
|
||||
end
|
||||
local st = r32(RESULT + 0x000)
|
||||
-- status 0 means OK; nonzero is final too once parsing has begun
|
||||
if st ~= 0xFFFFFFFF and not done then
|
||||
-- The loader writes 0 on success. A nonzero status is final
|
||||
-- only after it has been written once (it is 0 from power-on,
|
||||
-- so require the loader to have run: PC no longer at entry or
|
||||
-- the app counter moved).
|
||||
end
|
||||
if VERBOSE and frames % 25 == 0 then
|
||||
result_dump()
|
||||
end
|
||||
if base_contains_marker then
|
||||
local c = r32(0x018E0080)
|
||||
if c ~= last_count then
|
||||
if last_count ~= -1 then
|
||||
print(string.format("LOADER: marker counter %d -> %d (child running)",
|
||||
last_count, c))
|
||||
end
|
||||
last_count = c
|
||||
end
|
||||
end
|
||||
end
|
||||
if frames >= ENDF then
|
||||
if injected then
|
||||
done = true
|
||||
inspect()
|
||||
if SNAP then
|
||||
local screen = nil
|
||||
for _, s in pairs(machine.screens) do screen = s break end
|
||||
if screen then
|
||||
local out = OUT .. "/loader_frame" .. frames .. ".png"
|
||||
local ok, err = pcall(function() return screen:snapshot(out) end)
|
||||
print(string.format("LOADER: snapshot %s ok=%s %s",
|
||||
out, tostring(ok), tostring(err or "")))
|
||||
end
|
||||
end
|
||||
end
|
||||
machine:exit()
|
||||
end
|
||||
end, "vt1200-loader")
|
||||
|
||||
-- (Lua has no forward use of a name defined later; set a small flag here.)
|
||||
base_contains_marker = (COFF:match("([^/]+)$") or ""):find("marker") ~= nil
|
||||
|
||||
dbg.visible_cpu = cpu
|
||||
dbg.execution_state = 'run'
|
||||
Executable
+25
@@ -0,0 +1,25 @@
|
||||
#!/bin/sh
|
||||
# play.sh -- load termtris, click it active, and snapshot the board.
|
||||
set -eu
|
||||
DIR=$(cd "$(dirname "$0")" && pwd)
|
||||
APP=$(cd "$DIR/.." && pwd)
|
||||
ROOT=$(cd "$APP/../.." && pwd)
|
||||
M=${MAME:-/home/arti/code/k-space/mame0289/vt1200}
|
||||
EV="$APP/evidence"
|
||||
LOG="$APP/build/play.log"
|
||||
mkdir -p "$EV" "$APP/build"
|
||||
rm -f "$EV/loader_frame3300.png"
|
||||
VT1200_SKIP_LEVEL1=1 LOADER_SNAP=1 \
|
||||
LOADER_AT=2500 LOADER_END=3300 \
|
||||
LOADER_BIN="$ROOT/dwt/loader/build/loader.bin" \
|
||||
LOADER_COFF="$APP/build/termtris.coff" \
|
||||
LOADER_OUT="$EV" \
|
||||
"$M" vt1200 -rompath "$ROOT/harness/mame/roms" \
|
||||
-video none -sound none -nothrottle -skip_gameinfo \
|
||||
-debug -debugger none \
|
||||
-autoboot_script "$DIR/play.lua" -autoboot_delay 0 \
|
||||
>"$LOG" 2>&1 || true
|
||||
grep -E 'PLAY:|LOADER: result|LOADER: snapshot' "$LOG" | tail -6
|
||||
[ -s "$EV/loader_frame3300.png" ] || { echo "play.sh: no snapshot (see $LOG)" >&2; exit 1; }
|
||||
cp "$EV/loader_frame3300.png" "$EV/termtris_frame3300.png"
|
||||
echo "play: $EV/termtris_frame3300.png"
|
||||
@@ -0,0 +1,390 @@
|
||||
-- loader.lua -- debugger-assisted COFF loader acceptance harness.
|
||||
--
|
||||
-- Boots the firmware with VT1200_SKIP_LEVEL1=1 (the documented divergence) to
|
||||
-- the OS, then at frame LOADER_AT pokes:
|
||||
-- * the loader payload at bit 0x01880000 (raw dwt/loader/build/loader.bin)
|
||||
-- * a GSPLNK COFF image at LOADER_BUF (default bit 0x01900000)
|
||||
-- * the loader descriptor at 0x01882C00
|
||||
-- and sets PC = 0x01880000. It does not use the network, the rdebug service
|
||||
-- or any firmware transport: this is the debugger-assisted milestone.
|
||||
--
|
||||
-- The loader itself copies the COFF sections to their linked bit addresses and
|
||||
-- calls create_process(entry, "LOADER", 0x6000, 1, 0). This script only pokes
|
||||
-- the input and reads the result; it never writes the app's code or markers.
|
||||
--
|
||||
-- Env:
|
||||
-- LOADER_BIN loader payload (default dwt/loader/build/loader.bin)
|
||||
-- LOADER_COFF app COFF (default dwt/loader/build/marker.coff)
|
||||
-- LOADER_BUF COFF buffer bit address (default 0x01900000)
|
||||
-- LOADER_AT injection frame (default 2500)
|
||||
-- LOADER_END inspection/exit frame (default 2700)
|
||||
-- LOADER_OUT evidence directory (default dwt/loader/evidence)
|
||||
-- LOADER_SNAP 1 = write a PNG at LOADER_END (default 0)
|
||||
-- LOADER_VERBOSE 1 = print the result block every 25 frames
|
||||
|
||||
local function hex(v, w) return string.format("%0" .. (w or 8) .. "X", v) end
|
||||
|
||||
local machine = manager.machine
|
||||
local PLAY_MODE = os.getenv("PLAY_MODE") or "move"
|
||||
print("PLAYKEYS: mode="..PLAY_MODE)
|
||||
local DL = tonumber(os.getenv("DIAG_LOOPS") or "0")
|
||||
local DE = tonumber(os.getenv("DIAG_EVENTS") or "0")
|
||||
local DT = tonumber(os.getenv("DIAG_LAST") or "0")
|
||||
local DF = tonumber(os.getenv("DIAG_FOCUS") or "0")
|
||||
local DR = tonumber(os.getenv("DIAG_REV") or "0")
|
||||
local DQ = tonumber(os.getenv("DIAG_QUIT") or "0")
|
||||
local DTICK = tonumber(os.getenv("DIAG_TICK") or "0")
|
||||
local ports = machine.ioport.ports
|
||||
local key_a = ports[":lk401:K26"].fields["A"]
|
||||
local key_d = ports[":lk401:K28"].fields["D"]
|
||||
local key_s = ports[":lk401:K27"].fields["S"]
|
||||
local key_w = ports[":lk401:K14"].fields["W"]
|
||||
local key_0 = ports[":lk401:K10"].fields["0 )"]
|
||||
local key_p = ports[":lk401:K22"].fields["P"]
|
||||
local key_r = ports[":lk401:K16"].fields["R"]
|
||||
local key_h = ports[":lk401:K31"].fields["H"]
|
||||
local key_q = ports[":lk401:K13"].fields["Q"]
|
||||
local tabx = ports[":tablet:TABLET_X"].fields["Tablet X"]
|
||||
local taby = ports[":tablet:TABLET_Y"].fields["Tablet Y"]
|
||||
local tabb = ports[":tablet:TABLET_BTN"].fields["P1 Button 1"]
|
||||
local cpu = machine.devices[':maincpu']
|
||||
local dbg = machine.debugger
|
||||
local prog = cpu.spaces['program']
|
||||
|
||||
if not dbg then
|
||||
print("LOADER: debugger not enabled; pass -debug")
|
||||
machine:exit()
|
||||
return
|
||||
end
|
||||
|
||||
local AT = tonumber(os.getenv("LOADER_AT") or "2500")
|
||||
local ENDF = tonumber(os.getenv("LOADER_END") or "2700")
|
||||
local BIN = os.getenv("LOADER_BIN") or "dwt/loader/build/loader.bin"
|
||||
local COFF = os.getenv("LOADER_COFF") or "dwt/loader/build/marker.coff"
|
||||
local BUF = tonumber(os.getenv("LOADER_BUF") or "0x01900000")
|
||||
local OUT = os.getenv("LOADER_OUT") or "dwt/loader/evidence"
|
||||
local SNAP = os.getenv("LOADER_SNAP") == "1"
|
||||
local MODE = tonumber(os.getenv("LOADER_MODE") or "0")
|
||||
local A8DESC = os.getenv("LOADER_A8_DESC") == "1"
|
||||
local VERBOSE = os.getenv("LOADER_VERBOSE") == "1"
|
||||
|
||||
local LOADER_BASE = 0x01880000
|
||||
local DESC = 0x01882C00
|
||||
local RESULT = 0x01882D00
|
||||
local RETURN_STUB = 0x01883100
|
||||
local RETURN_SP = 0x01883110
|
||||
|
||||
local function rd(p) return io.open(p, "rb") end
|
||||
local function slurp(p)
|
||||
local f, err = rd(p)
|
||||
if not f then error("cannot open " .. p .. ": " .. tostring(err)) end
|
||||
local s = f:read("*a")
|
||||
f:close()
|
||||
return s
|
||||
end
|
||||
|
||||
local function u16(s, o)
|
||||
return s:byte(o + 1) + s:byte(o + 2) * 256
|
||||
end
|
||||
|
||||
local function u32(s, o)
|
||||
return s:byte(o + 1) + s:byte(o + 2) * 256
|
||||
+ s:byte(o + 3) * 65536 + s:byte(o + 4) * 16777216
|
||||
end
|
||||
|
||||
local function parse_coff(d)
|
||||
local c = { data = d }
|
||||
c.magic = u16(d, 0)
|
||||
c.nscns = u16(d, 2)
|
||||
c.opthdr = u16(d, 16)
|
||||
c.sections = {}
|
||||
if c.opthdr > 0 then
|
||||
c.optmagic = u16(d, 20)
|
||||
c.entry = u32(d, 20 + 16)
|
||||
c.textstart = u32(d, 20 + 20)
|
||||
c.datastart = u32(d, 20 + 24)
|
||||
end
|
||||
local base = 20 + c.opthdr
|
||||
for i = 0, c.nscns - 1 do
|
||||
local o = base + i * 40
|
||||
local name = d:sub(o + 1, o + 8):gsub("%z", "")
|
||||
c.sections[#c.sections + 1] = {
|
||||
name = name,
|
||||
paddr = u32(d, o + 8),
|
||||
size = u32(d, o + 16), -- bits
|
||||
scnptr = u32(d, o + 20),
|
||||
flags = u32(d, o + 36),
|
||||
}
|
||||
end
|
||||
return c
|
||||
end
|
||||
|
||||
local function poke_bytes(bitaddr, data)
|
||||
local n = math.floor((#data + 1) / 2)
|
||||
for i = 0, n - 1 do
|
||||
local lo = data:byte(i * 2 + 1) or 0
|
||||
local hi = data:byte(i * 2 + 2) or 0
|
||||
prog:write_u16(bitaddr + i * 16, lo + hi * 256)
|
||||
end
|
||||
return n
|
||||
end
|
||||
|
||||
local function verify_bytes(bitaddr, data)
|
||||
local n = math.floor((#data + 1) / 2)
|
||||
local bad = 0
|
||||
for i = 0, n - 1 do
|
||||
local lo = data:byte(i * 2 + 1) or 0
|
||||
local hi = data:byte(i * 2 + 2) or 0
|
||||
if prog:read_u16(bitaddr + i * 16) ~= lo + hi * 256 then
|
||||
bad = bad + 1
|
||||
end
|
||||
end
|
||||
return n, bad
|
||||
end
|
||||
|
||||
local function w32(a, v) prog:write_u32(a, v) end
|
||||
local function r32(a)
|
||||
local ok, v = pcall(function() return prog:read_u32(a) end)
|
||||
if ok then return v end
|
||||
return 0xDEADBEEF
|
||||
end
|
||||
|
||||
local loader = slurp(BIN)
|
||||
local coff = slurp(COFF)
|
||||
local app = parse_coff(coff)
|
||||
|
||||
print(string.format("LOADER: payload %s (%d B) -> bit %s", BIN, #loader, hex(LOADER_BASE)))
|
||||
print(string.format("LOADER: COFF %s (%d B) -> bit %s", COFF, #coff, hex(BUF)))
|
||||
print(string.format("LOADER: file magic=%04X nscns=%d opthdr=%d optmagic=%s entry=%s textstart=%s",
|
||||
app.magic, app.nscns, app.opthdr,
|
||||
app.optmagic and string.format("%04X", app.optmagic) or "none",
|
||||
hex(app.entry or 0), hex(app.textstart or 0)))
|
||||
for _, s in ipairs(app.sections) do
|
||||
print(string.format("LOADER: sec %-8s paddr=%s size=%d bits (%d B) scnptr=%#x flags=%#x",
|
||||
s.name, hex(s.paddr), s.size, math.floor(s.size / 8), s.scnptr, s.flags))
|
||||
end
|
||||
|
||||
local TRACE = os.getenv("LOADER_TRACE") == "1"
|
||||
|
||||
local frames = 0
|
||||
local injected = false
|
||||
local done = false
|
||||
local last_count = -1
|
||||
|
||||
local function status_name(v)
|
||||
local t = {
|
||||
[0] = "OK",
|
||||
[1] = "bad descriptor",
|
||||
[2] = "truncated COFF / section headers",
|
||||
[3] = "bad COFF magic",
|
||||
[4] = "section size not whole words",
|
||||
[5] = "target outside RAM",
|
||||
[6] = "target overlaps loader page or COFF buffer",
|
||||
[7] = "relocation records present (unsupported)",
|
||||
[8] = "target not in a free-list block",
|
||||
[9] = "no entry point",
|
||||
}
|
||||
return t[v] or ("error " .. tostring(v))
|
||||
end
|
||||
|
||||
local function result_dump()
|
||||
local st = r32(RESULT + 0x000)
|
||||
print(string.format(
|
||||
"LOADER: result status=%d (%s) entry=%s text=%s/%d bits data=%s/%d bits bss=%s/%d bits relocs=%d pcb=%s nscns=%d len=%d",
|
||||
st, status_name(st), hex(r32(RESULT + 0x020)),
|
||||
hex(r32(RESULT + 0x040)), r32(RESULT + 0x060),
|
||||
hex(r32(RESULT + 0x080)), r32(RESULT + 0x0A0),
|
||||
hex(r32(RESULT + 0x0C0)), r32(RESULT + 0x0E0),
|
||||
r32(RESULT + 0x100), hex(r32(RESULT + 0x120)),
|
||||
r32(RESULT + 0x140), r32(RESULT + 0x160)))
|
||||
end
|
||||
|
||||
local function inject()
|
||||
-- 1. loader itself
|
||||
local n, bad = 0, 0
|
||||
poke_bytes(LOADER_BASE, loader)
|
||||
n, bad = verify_bytes(LOADER_BASE, loader)
|
||||
print(string.format("LOADER: loader poked %d words, readback %d bad at %s",
|
||||
n, bad, hex(LOADER_BASE)))
|
||||
|
||||
-- 2. COFF image
|
||||
poke_bytes(BUF, coff)
|
||||
n, bad = verify_bytes(BUF, coff)
|
||||
print(string.format("LOADER: COFF poked %d words, readback %d bad at %s",
|
||||
n, bad, hex(BUF)))
|
||||
|
||||
-- 3. pre-fill every .bss section with A5A5, so a loaded zero is meaningful
|
||||
for _, s in ipairs(app.sections) do
|
||||
if s.flags & 0x80 ~= 0 then
|
||||
for i = 0, math.floor(s.size / 16) - 1 do
|
||||
prog:write_u16(s.paddr + i * 16, 0xA5A5)
|
||||
end
|
||||
print(string.format("LOADER: pre-filled .bss %s..%s with A5A5",
|
||||
hex(s.paddr), hex(s.paddr + s.size)))
|
||||
end
|
||||
end
|
||||
|
||||
-- 4. descriptor: magic "LD" "R1", buf, len, mode=0 (spin)
|
||||
w32(DESC + 0x00, 0)
|
||||
prog:write_u16(DESC + 0x00, 0x4C44)
|
||||
prog:write_u16(DESC + 0x10, 0x5231)
|
||||
w32(DESC + 0x20, BUF)
|
||||
w32(DESC + 0x40, #coff)
|
||||
w32(DESC + 0x60, MODE)
|
||||
print(string.format("LOADER: descriptor @%s magic=%04X/%04X buf=%s len=%d mode=%d",
|
||||
hex(DESC), prog:read_u16(DESC + 0x00), prog:read_u16(DESC + 0x10),
|
||||
hex(r32(DESC + 0x20)), r32(DESC + 0x40), r32(DESC + 0x60)))
|
||||
|
||||
-- 5. return stub. mode 0 ignores it (the loader jumps to ORIG_PC
|
||||
-- itself); mode 1 returns here, and the stub resumes the interrupted
|
||||
-- PC so the caller yields and the scheduler can run the child.
|
||||
local orig_pc = cpu.state['PC'].value
|
||||
if MODE == 1 then
|
||||
prog:write_u16(RETURN_STUB, 0xC080) -- jauc <orig_pc>
|
||||
prog:write_u16(RETURN_STUB + 0x10, orig_pc & 0xFFFF)
|
||||
prog:write_u16(RETURN_STUB + 0x20, math.floor(orig_pc / 0x10000) & 0xFFFF)
|
||||
else
|
||||
prog:write_u16(RETURN_STUB, 0xC0FF) -- jruc self
|
||||
end
|
||||
|
||||
-- 6. enter the loader. A8=0 selects the fixed descriptor; SP points at
|
||||
-- the return slot so mode=1 would return to the spin stub.
|
||||
-- Leave SP alone for mode 0: the loader pushes onto the current
|
||||
-- process stack, then restores it and resumes at ORIG_PC. (mode 1
|
||||
-- would use the return stub.)
|
||||
w32(0x01882A20, orig_pc) -- LD_ORIG_PC
|
||||
print(string.format("LOADER: will resume at %s after create_process", hex(orig_pc)))
|
||||
cpu.state['A8'].value = A8DESC and DESC or 0
|
||||
if MODE == 1 then
|
||||
-- Simulate a CALLA from the current process: push the return
|
||||
-- address on the *firmware* stack (a real transport would do this).
|
||||
local sp = cpu.state['SP'].value - 0x20
|
||||
w32(sp, RETURN_STUB)
|
||||
cpu.state['SP'].value = sp
|
||||
end
|
||||
cpu.state['PC'].value = LOADER_BASE
|
||||
injected = true
|
||||
print(string.format("LOADER: frame %d: entered loader at %s (caller SP=%s)",
|
||||
frames, hex(LOADER_BASE), hex(cpu.state['SP'].value)))
|
||||
end
|
||||
|
||||
local function inspect()
|
||||
result_dump()
|
||||
|
||||
-- Verify the placement from RAM: .text/.data must equal the file bytes.
|
||||
for _, s in ipairs(app.sections) do
|
||||
if s.scnptr ~= 0 and s.size > 0 then
|
||||
local nb = math.floor(s.size / 8)
|
||||
local bytes = coff:sub(s.scnptr + 1, s.scnptr + nb)
|
||||
local _, bad = verify_bytes(s.paddr, bytes)
|
||||
print(string.format("LOADER: placed %s at %s: %d bytes, %d differing",
|
||||
s.name, hex(s.paddr), nb, bad))
|
||||
end
|
||||
end
|
||||
|
||||
local base = COFF:match("([^/]+)$") or COFF
|
||||
if base:find("marker") then
|
||||
print(string.format("LOADER: marker app: start=%s data=%s data2=%s bss=%s count=%d",
|
||||
hex(r32(0x018E0000)), hex(r32(0x018E0020)), hex(r32(0x018E0040)),
|
||||
hex(r32(0x018E0060)), r32(0x018E0080)))
|
||||
elseif base:find("xhello") then
|
||||
print(string.format("LOADER: xhello: stage=%d strlen=%d display=%s root=%s window=%s sync=%s",
|
||||
r32(0x01890140), r32(0x01890160), hex(r32(0x01890100)),
|
||||
hex(r32(0x01890110)), hex(r32(0x01890120)), hex(r32(0x01890130))))
|
||||
end
|
||||
if DL ~= 0 then
|
||||
print(string.format("DIAG: loops=%d events=%d last=%d focus=%08X rev=%d quit=%d", r32(DL), r32(DE), r32(DT), DF ~= 0 and r32(DF) or 0, DR ~= 0 and r32(DR) or 0, DQ ~= 0 and r32(DQ) or 0))
|
||||
if DTICK ~= 0 then
|
||||
print(string.format("DIAG: tick=%d", r32(DTICK)))
|
||||
end
|
||||
end
|
||||
print(string.format("LOADER: PC at inspection = %s", hex(cpu.state['PC'].value)))
|
||||
end
|
||||
|
||||
emu.register_frame_done(function()
|
||||
frames = frames + 1
|
||||
if frames == 2900 then tabx:set_value(700); taby:set_value(500); print("PLAYKEYS: move") end
|
||||
if frames == 3000 then tabb:set_value(1); print("PLAYKEYS: click down") end
|
||||
if frames == 3020 then tabb:set_value(0); print("PLAYKEYS: click up") end
|
||||
if PLAY_MODE == "move" then
|
||||
if frames == 3100 then key_a:set_value(1) end
|
||||
if frames == 3110 then key_a:set_value(0) end
|
||||
if frames == 3120 then key_d:set_value(1) end
|
||||
if frames == 3130 then key_d:set_value(0) end
|
||||
if frames == 3140 then key_s:set_value(1) end
|
||||
if frames == 3150 then key_s:set_value(0) end
|
||||
if frames == 3160 then key_w:set_value(1) end
|
||||
if frames == 3170 then key_w:set_value(0) end
|
||||
if frames == 3180 then key_0:set_value(1) end
|
||||
if frames == 3190 then key_0:set_value(0) end
|
||||
elseif PLAY_MODE == "panels" then
|
||||
if frames == 3100 then key_h:set_value(1) end
|
||||
if frames == 3110 then key_h:set_value(0) end
|
||||
if frames == 3120 then key_r:set_value(1) end
|
||||
if frames == 3130 then key_r:set_value(0) end
|
||||
elseif PLAY_MODE == "quit" then
|
||||
if frames == 3095 then print("PLAYKEYS: quit arm") end
|
||||
if frames == 3100 then print("PLAYKEYS: Q down"); key_q:set_value(1) end
|
||||
if frames == 3110 then print("PLAYKEYS: Q up"); key_q:set_value(0) end
|
||||
end
|
||||
if frames == AT and not injected then
|
||||
inject()
|
||||
if TRACE then dbg:command("trace /tmp/opencode/ld1.tr") end
|
||||
end
|
||||
if injected and TRACE and frames == AT + 1 then
|
||||
dbg:command("trace off")
|
||||
print("LOADER: trace written to /tmp/opencode/ld.tr")
|
||||
end
|
||||
if injected and not done then
|
||||
if TRACE and frames <= AT + 40 then
|
||||
print(string.format("LOADER: trace f=%d PC=%s SP=%s A14=%s status=%s entry=%s",
|
||||
frames, hex(cpu.state['PC'].value), hex(cpu.state['SP'].value),
|
||||
hex(cpu.state['A14'].value), hex(r32(RESULT + 0x000)),
|
||||
hex(r32(RESULT + 0x020))))
|
||||
end
|
||||
local st = r32(RESULT + 0x000)
|
||||
-- status 0 means OK; nonzero is final too once parsing has begun
|
||||
if st ~= 0xFFFFFFFF and not done then
|
||||
-- The loader writes 0 on success. A nonzero status is final
|
||||
-- only after it has been written once (it is 0 from power-on,
|
||||
-- so require the loader to have run: PC no longer at entry or
|
||||
-- the app counter moved).
|
||||
end
|
||||
if VERBOSE and frames % 25 == 0 then
|
||||
result_dump()
|
||||
end
|
||||
if base_contains_marker then
|
||||
local c = r32(0x018E0080)
|
||||
if c ~= last_count then
|
||||
if last_count ~= -1 then
|
||||
print(string.format("LOADER: marker counter %d -> %d (child running)",
|
||||
last_count, c))
|
||||
end
|
||||
last_count = c
|
||||
end
|
||||
end
|
||||
end
|
||||
if frames >= ENDF then
|
||||
if injected then
|
||||
done = true
|
||||
inspect()
|
||||
if SNAP then
|
||||
local screen = nil
|
||||
for _, s in pairs(machine.screens) do screen = s break end
|
||||
if screen then
|
||||
local out = OUT .. "/loader_frame" .. frames .. ".png"
|
||||
local ok, err = pcall(function() return screen:snapshot(out) end)
|
||||
print(string.format("LOADER: snapshot %s ok=%s %s",
|
||||
out, tostring(ok), tostring(err or "")))
|
||||
end
|
||||
end
|
||||
end
|
||||
machine:exit()
|
||||
end
|
||||
end, "vt1200-loader")
|
||||
|
||||
-- (Lua has no forward use of a name defined later; set a small flag here.)
|
||||
base_contains_marker = (COFF:match("([^/]+)$") or ""):find("marker") ~= nil
|
||||
|
||||
dbg.visible_cpu = cpu
|
||||
dbg.execution_state = 'run'
|
||||
Executable
+35
@@ -0,0 +1,35 @@
|
||||
#!/bin/sh
|
||||
# play_keys.sh -- run the real game through move, panels and quit paths.
|
||||
set -eu
|
||||
DIR=$(cd "$(dirname "$0")" && pwd)
|
||||
APP=$(cd "$DIR/.." && pwd)
|
||||
ROOT=$(cd "$APP/../.." && pwd)
|
||||
M=${MAME:-/home/arti/code/k-space/mame0289/vt1200}
|
||||
EV="$APP/evidence"
|
||||
mkdir -p "$EV" "$APP/build"
|
||||
for mode in ${MODES:-move panels quit}; do
|
||||
LOG="$APP/build/play_$mode.log"
|
||||
rm -f "$EV/loader_frame3200.png"
|
||||
DL=$(awk '{for(i=2;i<=NF;i++) if($i=="_dbg_loops") print $(i-1)}' "$APP/build/termtris.map" | head -1)
|
||||
DE=$(awk '{for(i=2;i<=NF;i++) if($i=="_dbg_events") print $(i-1)}' "$APP/build/termtris.map" | head -1)
|
||||
DT=$(awk '{for(i=2;i<=NF;i++) if($i=="_dbg_last") print $(i-1)}' "$APP/build/termtris.map" | head -1)
|
||||
DF=$(awk '{for(i=2;i<=NF;i++) if($i=="_dbg_focus") print $(i-1)}' "$APP/build/termtris.map" | head -1)
|
||||
DR=$(awk '{for(i=2;i<=NF;i++) if($i=="_dbg_rev") print $(i-1)}' "$APP/build/termtris.map" | head -1)
|
||||
DQ=$(awk '{for(i=2;i<=NF;i++) if($i=="_quit") print $(i-1)}' "$APP/build/termtris.map" | head -1)
|
||||
DTICK=$(awk '{for(i=2;i<=NF;i++) if($i=="_tick_interval") print $(i-1)}' "$APP/build/termtris.map" | head -1)
|
||||
VT1200_SKIP_LEVEL1=1 LOADER_SNAP=1 PLAY_MODE=$mode DIAG_LOOPS="0x$DL" DIAG_EVENTS="0x$DE" DIAG_LAST="0x$DT" DIAG_FOCUS="0x$DF" DIAG_REV="0x$DR" DIAG_QUIT="0x$DQ" \
|
||||
DIAG_TICK="0x$DTICK" \
|
||||
LOADER_AT=2500 LOADER_END=3200 \
|
||||
LOADER_BIN="$ROOT/dwt/loader/build/loader.bin" \
|
||||
LOADER_COFF="$APP/build/termtris.coff" \
|
||||
LOADER_OUT="$EV" \
|
||||
"$M" vt1200 -rompath "$ROOT/harness/mame/roms" \
|
||||
-video none -sound none -nothrottle -skip_gameinfo \
|
||||
-debug -debugger none \
|
||||
-autoboot_script "$DIR/play_keys.lua" -autoboot_delay 0 \
|
||||
>"$LOG" 2>&1 || true
|
||||
grep -E 'PLAYKEYS:|LOADER: result|LOADER: snapshot' "$LOG" | tail -5
|
||||
[ -s "$EV/loader_frame3200.png" ] || { echo "play_keys.sh: no $mode snapshot" >&2; exit 1; }
|
||||
cp "$EV/loader_frame3200.png" "$EV/termtris_${mode}_3200.png"
|
||||
done
|
||||
echo "play_keys: $EV/termtris_move_3200.png $EV/termtris_panels_3200.png $EV/termtris_quit_3200.png"
|
||||
Reference in new issue
Block a user