diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..a78e0fb --- /dev/null +++ b/LICENSE @@ -0,0 +1,19 @@ +/* + * Valence 64 - The input-only VNC client for the Commodore 64. + * + * Copyright 2012 David Simmons + * http://cafbit.com/ + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..dea723d --- /dev/null +++ b/Makefile @@ -0,0 +1,35 @@ + +all: valence64.prg setup.prg hello.prg vncclient/vncclient.c64 combined.prg disk + +clean: + $(MAKE) TARGET=c64 -C vncclient sterile + rm -f valence64.prg + rm -f combined.prg + rm -f setup.prg + rm -f valence64.d64 + rm -f hello.o hello.prg + +vncclient/vncclient.c64: + $(MAKE) TARGET=c64 -C vncclient build + +combined.prg: valence64.prg vncclient/vncclient.c64 + ./combine.pl valence64.prg vncclient/vncclient.c64 vncclient/contiki-c64.map > combined.prg + +%.o: %.asm + ca65 -o $@ $< + +%.prg: %.o %.cfg + ld65 -C $(lastword $^) -o $@ $< + +%.prg: %.bas + petcat -c -w2 -o $@ -- $< + +disk: + c1541 -format valence64,00 d64 valence64.d64 + c1541 -attach valence64.d64 -write combined.prg valence64,p + c1541 -attach valence64.d64 -write setup.prg setup,p + c1541 -attach valence64.d64 -write assets/contiki.cfg contiki.cfg,u + c1541 -attach valence64.d64 -write assets/valence.cfg valence.cfg,u + c1541 -attach valence64.d64 -write vncclient/cs8900a.eth cs8900a.eth,u + c1541 -attach valence64.d64 -write vncclient/lan91c96.eth lan91c96.eth,u + #./randomizesrcport.pl diff --git a/README b/README new file mode 100644 index 0000000..ee9573e --- /dev/null +++ b/README @@ -0,0 +1,97 @@ + +Valence64: The input-only VNC client for the Commodore 64 +====================================================================== + +(c) 2012 David Simmons +Licensed under the Apache License, Version 2.0 + +http://cafbit.com/entry/valence64 + +Valence64 is a simple proof-of-concept application for controlling a +remote computer using a networked Commodore 64. It was developed as +a way for me to learn the Contiki framework and the uIP TCP/IP stack, +both of which are used in actual modern-day embedded systems. + + +Using Valence64 +---------------------------------------- + +The easiest way to use Valence64 is to obtain a prebuilt version of the +valence.d64 disk image from the above web site. Attach the disk image +to your C64 emulator, or write the image to a 1541 5.25" disk for use on +an actual C64. + +Run the SETUP program to configure your TCP/IP settings, select an +ethernet driver, and indicate the VNC server address of the host you +want to control: + +LOAD "SETUP",8 +RUN + +Then, load and run Valence64: + +LOAD "VALENCE64",8 +RUN + +Press F1 to connect to the VNC server. By default, the program is in +"mouse mode" -- CRSR keys will be used to move the mouse, and RETURN +will send a mouse click. Press F7 to toggle "keyboard mode" for typing. +A joystick in control port 2 can also be used to operate the mouse +pointer. + + +Building Valence64 +---------------------------------------- + +Requirements: + Linux development machine. + (It could be made to work with other platforms.) + Contiki 2.5 + The cc65 compiler/assembler (cc65-c64-2.13.2-1) + +You must have a copy of the Contiki 2.5 source code located in the +../contiki-2.5 directory relative to the root of the Valence64 source. +Patch the Contiki source with the supplied contiki-2.5-autostart.patch +file: + +$ cd ../contiki-2.5 +$ patch -p1 < ../valence64/contiki-2.5-autostart.patch + +If you want to use my randomizesrcport.pl script to randomize the +initial source port number (which is useful during development), then +also apply this patch (and uncomment ./randomizesrcport.pl from the disk +target of the Makefile): + +$ patch -p1 < ../valence64/contiki-2.5-randomsrcport.patch + +You must also have the cc65 compiler/assembler package installed in +/usr/local/bin. (Or edit the CC65_HOME value in vncclient/Makefile to +point to the location.) + +Type "make". + + +Build concept +---------------------------------------- + +Valence64 uses a somewhat convoluted process to produce the final +product, due to it being a BASIC/ML hybrid program. In the final +output, the binary 6502 executable code ("ML code") is appended to the +end of the BASIC program, and launched from BASIC via a SYS call. This +poses several challenges: + +1. The ML link step needs knowledge of the final memory location of the +program for proper linking. This location will change every time the +BASIC program is modified, since the starting address will be at the end +of the BASIC code. +2. The BASIC program doesn't know the memory locations to SYS until the +ML code is compiled/assembled/linked. + +To work around this, I use scripts for post-processing. This allows the +BASIC program to SYS with placeholder addresses, which are filled in +based on the symbol table used by the linker. During linking, which +happens after the BASIC program has been rendered into a tight PRG file, +a script inspects the PRG file and determines the ML starting address. +A linker configuration is dynamically built from this information using +a template. + diff --git a/assets/contiki.cfg b/assets/contiki.cfg new file mode 100644 index 0000000..c7886cb Binary files /dev/null and b/assets/contiki.cfg differ diff --git a/assets/valence.cfg b/assets/valence.cfg new file mode 100644 index 0000000..2b7f7c8 --- /dev/null +++ b/assets/valence.cfg @@ -0,0 +1 @@ +À¨å \ No newline at end of file diff --git a/combine.pl b/combine.pl new file mode 100755 index 0000000..38f2c90 --- /dev/null +++ b/combine.pl @@ -0,0 +1,106 @@ +#!/usr/bin/perl +###################################################################### +# +# combine +# +# Merge a BASIC program and a 6502 machine language program into one +# file for convenience and fast loading. The ML code should be linked +# for a starting address just after the end of the BASIC program. +# +# (c) 2012 David Simmons +# +# usage: combine.pl basicprogram.prg mlprog.prg > combinedprog.prg +# +###################################################################### + +use strict; +use warnings; +use bytes; + +if (@ARGV < 2) { + die "usage: combine.pl basicprogram.prg mlprog.prg [symbol.map]\n"; +} + +my ($bas, $ml); + +# read BASIC file +open(BAS, $ARGV[0]) || die "cannot open basic file: $!\n"; +{ + local $/; + $bas = ; +} +close(BAS); + +# read ML file +open(ML, $ARGV[1]) || die "cannot open ml file: $!\n"; +{ + local $/; + $ml = ; +} +close(ML); + +# read symbol table, if available +my $exports; +if (@ARGV >= 3) { + my $in = 0; + open(MAP, $ARGV[2]) || die "cannot open symbol map: $!\n"; + while () { + chomp; + if (/^Exports list:$/) { + $in = 1; next; + } + if ($in) { + if (/^$/) { + $in = 0; next; + } + if (/^\s*(\S+)\s+(\S+)\s+\S+\s+(\S+)\s+(\S+)\s+\S+\s*$/) { + $exports->{$1} = hex($2); + $exports->{$3} = hex($4); + } elsif (/^\s*(\S+)\s+(\S+)\s+\S+\s*$/) { + $exports->{$1} = hex($2); + } + } + + } + close(MAP); +} + +my $jumpaddr = sprintf("%05d", 0x7FF+length($bas)); + +# write the actual jump addresses into the basic program, +# replacing the placeholder 'SYS "symbol"' calls. +sub translate { + my $symbol = shift; + my $address; + if ($symbol eq 'JUMPSTART') { + $address = $jumpaddr; + } else { + $address = $exports->{lc $symbol}; + } + printf(STDERR "TRANSLATE $symbol -> $address\n"); + return $address; +} +my $l1 = bytes::length($bas); +$bas =~ s/\x9e "(\S+)"(.*?)\x00/sprintf("\x9e %d%s\x00",&translate($1),$2)/ge; +my $l2 = length($bas); +if ($l1 < $l2) { + die "symbol name substitution made the program larger\n"; +} elsif ($l1 > $l2) { + # apply padding + #printf STDERR "PADDING: ".($l1-$l2)."\n"; + $bas .= ("\xff" x ($l1-$l2)); +} + +# trim the first 14 bytes from the ML file. This contains +# the 2-byte starting address, and 12 bytes of needless +# BASIC bootstrap code. +$ml = substr($ml,14); + +# while we're here, hack out the cc65 runtime's annoying +# shift to the upper/lowercase character set +$ml =~ s/\xa9\x0e\x20\xd2\xff/\xa9\x8e\x20\xd2\xff/g; + +# combine and deliver to stdout +print $bas; +print $ml; + diff --git a/contiki-2.5-autostart.patch b/contiki-2.5-autostart.patch new file mode 100644 index 0000000..99d7fb1 --- /dev/null +++ b/contiki-2.5-autostart.patch @@ -0,0 +1,38 @@ +diff -Nru contiki-2.5.orig/platform/c64/contiki-main.c contiki-2.5/platform/c64/contiki-main.c +--- contiki-2.5.orig/platform/c64/contiki-main.c 2011-09-06 15:43:39.000000000 -0600 ++++ contiki-2.5/platform/c64/contiki-main.c 2012-02-13 00:40:44.171722661 -0700 +@@ -63,6 +63,7 @@ + main(void) + { + struct ethernet_config *ethernet_config; ++ struct process *vncclient_process = NULL; + + process_init(); + +@@ -120,6 +121,26 @@ + process_run(); + + etimer_request_poll(); ++ ++ if (! vncclient_process) { ++ // look for vncclient ++ struct process *q; ++ for (q = PROCESS_LIST(); q != NULL; q = q->next) { ++ if (strcmp(q->name, "vncclient")==0) { ++ vncclient_process = q; ++ break; ++ } ++ } ++ } else { ++ struct process *q; ++ for (q = PROCESS_LIST(); q != vncclient_process && q != NULL; q = q->next); ++ if (q == NULL) { ++ // time to leave ++ break; ++ } ++ } ++ ++ + } + } + /*-----------------------------------------------------------------------------------*/ diff --git a/contiki-2.5-randomsrcport.patch b/contiki-2.5-randomsrcport.patch new file mode 100644 index 0000000..3fdb6e4 --- /dev/null +++ b/contiki-2.5-randomsrcport.patch @@ -0,0 +1,16 @@ +--- contiki-2.5.orig/core/net/uip.c 2011-09-06 15:43:39.000000000 -0600 ++++ contiki-2.5/core/net/uip.c 2012-02-15 23:15:18.413803065 -0700 +@@ -377,7 +377,12 @@ + uip_conns[c].tcpstateflags = UIP_CLOSED; + } + #if UIP_ACTIVE_OPEN || UIP_UDP +- lastport = 1024; ++ ++{ ++ // simmons-hack: marker for initial source-port ++ u16_t x[5] = {0xdead, 0xbeef, 0x0000, 0xdead, 0xbeef}; ++ lastport = 1024 + x[2]; ++} + #endif /* UIP_ACTIVE_OPEN || UIP_UDP */ + + #if UIP_UDP diff --git a/hello.asm b/hello.asm new file mode 100644 index 0000000..79d38a9 --- /dev/null +++ b/hello.asm @@ -0,0 +1,29 @@ + +;set up some helpful labels +CLEAR = $E544 +CHROUT = $FFD2 + +.segment "BASICSTUB" + +.byte $00 +.byte $C0 + +.code +main: + jsr CLEAR + ldx #0 +loop: + lda greeting,x + cmp #0 + beq finish + jsr CHROUT + inx + jmp loop +finish: + rts + +.data +greeting: + .byte "HELLO, WORLD!" + .byte $00 + diff --git a/hello.cfg b/hello.cfg new file mode 100644 index 0000000..20dd777 --- /dev/null +++ b/hello.cfg @@ -0,0 +1,8 @@ +MEMORY { + RAM: start = $BFFE, size = $1002, file = %O, define = yes; +} +SEGMENTS { + BASICSTUB: load = RAM, type = ro; + CODE: load = RAM, type = ro; + DATA: load = RAM, type = rw; +} diff --git a/randomizesrcport.pl b/randomizesrcport.pl new file mode 100755 index 0000000..f6a84d8 --- /dev/null +++ b/randomizesrcport.pl @@ -0,0 +1,68 @@ +#!/usr/bin/perl +###################################################################### +# +# randomizesrcport.pl +# +# One annoyance of developing network applications with Contiki and +# uIP for the C64 is that the initial source port is always reset +# with every fresh invocation. This results in the remote host +# rejecting/ignoring connections from subsequent runs while it thinks +# the TCP connection state is FIN_WAIT. +# +# This script is a hack to randomize the initial source port at +# build-time, to ease development and testing. This hack requires +# that the Contiki source be patched to add binary markers. The patch +# is located at the bottom of this script. +# +# March 2012 +# David Simmons +# +###################################################################### + +use strict; +use warnings; + +our $FILENAME = "valence64.d64"; +our $MARKER = pack('vv', 0xDEAD, 0xBEEF); + +# read disk image +my $size = (stat($FILENAME))[7] || die; +my $image; +open(INFILE, $FILENAME) || die; +my $nbytes = sysread(INFILE, $image, $size); +die "underread\n" if ($nbytes != $size); +close(INFILE); + +# randomize the port number +my $initial_srcport = 1024+int(rand(4096)); +my $initial_srcport_packed = pack('v', $initial_srcport); +printf("using initial source port of %d (%04X).\n", $initial_srcport, $initial_srcport); +$image =~ s/$MARKER\x00\x00$MARKER/$MARKER$initial_srcport_packed$MARKER/g; + +# write disk image +open(OUTFILE, "> ".$FILENAME.".new") || die; +$nbytes = syswrite(OUTFILE, $image, $size); +die "underwrite\n" if ($nbytes != $size); +close(OUTFILE); + +unlink($FILENAME); +rename($FILENAME.".new", $FILENAME); + +__END__ + +--- contiki-2.5.orig/core/net/uip.c 2011-09-06 15:43:39.000000000 -0600 ++++ contiki-2.5/core/net/uip.c 2012-02-15 23:15:18.413803065 -0700 +@@ -377,7 +377,12 @@ + uip_conns[c].tcpstateflags = UIP_CLOSED; + } + #if UIP_ACTIVE_OPEN || UIP_UDP +- lastport = 1024; ++ ++{ ++ // simmons-hack: marker for initial source-port ++ u16_t x[5] = {0xdead, 0xbeef, 0x0000, 0xdead, 0xbeef}; ++ lastport = 1024 + x[2]; ++} + #endif /* UIP_ACTIVE_OPEN || UIP_UDP */ + + #if UIP_UDP diff --git a/reloadonsave.sh b/reloadonsave.sh new file mode 100755 index 0000000..1a20218 --- /dev/null +++ b/reloadonsave.sh @@ -0,0 +1,61 @@ +#!/bin/sh +###################################################################### +# reloadonsave +# +# This is a hacky script to restart VICE with valence64.d64 +# whenever that disk image changes (i.e. after a make). +# +###################################################################### + +TARGET=./valence64.d64 +export DISPLAY=:0 + +getts() { + lastts=$ts + unset ts + ts=`stat --format='%Y' $TARGET 2>/dev/null` + if [ "$?" != "0" ]; then + ts=0 + fi +} + +changed() { + getts + if [ "$ts" != 0 -a "$ts" != "$lastts" ]; then + return 0 # true + else + return 1 # false + fi +} + +runvice() { + killall -15 gnome-screensaver >/dev/null 2>&1 + xset -dpms + killall x64 >/dev/null 2>&1 + while ps aux|grep x64|grep -v grep>/dev/null; do + echo waiting... + sleep 0.1 + done + #make && x64 +VICIIfull -basicload valence64.prg & + #x64 -VICIIfull valence64.d64 & + ~/valence64/x64 -VICIIfull -remotemonitor valence64.d64 & + #~/valence64/x64 -VICIIfull -remotemonitor -moncommands vncclient/vncclient.lbl valence64.d64 & + sleep 1 +} + +# prime ts and lastts +getts +getts + +# loop forever +while true; do + inotifywait -e MODIFY . + #echo `date`: wake up + sleep 1 + if changed; then + #echo `date`: run + runvice + fi + #echo `date`: sleep +done + diff --git a/setup.bas b/setup.bas new file mode 100644 index 0000000..4829b8e --- /dev/null +++ b/setup.bas @@ -0,0 +1,195 @@ +; useful command to look for bad line numbering: +; cat setup.bas | awk '{print $1}' |grep -v \; |egrep '[0-9]' | awk 'BEGIN {A=0} {if ($1<=A) print $1; A=$1}' + +10 rem ****************************** +20 rem ** valence64 setup ** +30 rem ** (c) 2012 david simmons ** +40 rem ** apache 2.0 license ** +50 rem ****************************** + + +; "poke 19,65" turns off the question mark prompt in input statements +100 poke 53280,0:poke 53281,0:dim ip(4) + +;--- main --- +110 print "{clr}{lblu}valence{lred}64{gry2} network setup{down}" +120 gosub 2000 +125 gosub 2100 +130 print "current settings:":gosub 3000 +140 print +150 p$=" ip address: ":n=49152:gosub 1000 +160 p$=" netmask: ":n=49156:gosub 1000 +170 p$=" gateway: ":n=49160:gosub 1000 +180 p$="dns nameserver: ":n=49164:gosub 1000 +190 p$=" vnc server: ":n=49216:gosub 1000 +200 print "eth64/tfe: use de00; rr+rr-net: de08" +210 p$=" eth address: ":n=49168:gosub 1500 +220 print "select an ethernet chipset:" +230 print "1) cs8900a (rr+rr-net and tfe)" +240 print "2) lan91c96 (eth64)" +250 poke 19,65 +260 input "select: ";a +270 if a<1 or a>2 goto 250 +280 if a=1 then a$="cs8900a.eth":n=49170:gosub 7000 +290 if a=2 then a$="lan91c96.eth":n=49170:gosub 7000 +292 print:print + +300 print "proposed settings:":gosub 3000 +310 input "write settings to disk (y/n)? ";a$ +311 print +320 if a$<>"y" and a$<>"n" goto 310 +330 if a$="n" then print:print "aborting setup.":end +340 gosub 2200 +350 gosub 2300 +999 end + +;--- ip address prompt --- +1000 ip(0)=0:ip(1)=0:ip(2)=0:ip(3)=0:o=3:p=0:poke 19,65 +1010 print "{gry2}";p$; +1020 input "{wht}";ip$:print +1030 for x = len(ip$) to 1 step -1 +1040 c$ = mid$(ip$,x,1) +1050 if c$<"0" or c$>"9" goto 1100 +1060 ip(o)=ip(o)+(asc(c$)-48)*(10^p):p=p+1 +1070 if (ip(o)>255) goto 1300 +1080 goto 1200 +1100 if c$<>"." goto 1300 +1110 if p=0 goto 1300 +1120 p=0:o=o-1 +1130 if o<0 goto 1300 +1200 next x +1210 if o<>0 goto 1300 +1220 print "{gry2}"; +1230 if n=0 then goto 1250 +1240 for x=0 to 3: poke n+x,ip(x): next x +1250 return +1300 print "{red}invalid ip address. try again.":goto 1000 + +;--- uint16_t prompt --- +1500 b(0)=0:b(1)=0:y=0:z=0:poke 19,65 +1501 print "{gry2}";p$; +1502 input "{wht}";a$:print +1510 if len(a$)<>4 goto 1599 +1520 for x = 4 to 1 step -1 +1530 c$ = mid$(a$,x,1) +1540 v = -1 +1550 if c$>="0" and c$<="9" then v=asc(c$)-48 +1551 if c$>="a" and c$<="f" then v=asc(c$)-55 +1552 if c$>="A" and c$<="F" then v=asc(c$)-183 +1553 if v = -1 goto 1599 +1560 if z>0 then v=v*16: b(y)=b(y) or v: goto 1581 +1570 b(y) = v +1581 if z=0 then z=1: goto 1590 +1582 if z=1 then y=y+1:z=0 +1590 next x +1592 poke n, b(0):poke n+1, b(1) +1593 print "{gry2}":return +1599 print "{red}invalid address. try again.":goto 1500 + +;--- read contiki.cfg --- +2000 print "reading contiki settings..." +2010 for x = 0 to 63: poke 49152+x, 0: next x +2020 open 1,8,8,"contiki.cfg,usr,r" +2030 if (status and 2) goto 2095 +2040 for x = 0 to 63 +2050 get#1,a$ +2060 poke 49152+x, asc(a$+chr$(0)) +2070 if (status and 64) goto 2090 +2080 next x +2090 close 1 +2091 print "{up} ":print "{up}"; +2092 return +;--- handle disk error --- +2095 open 2,8,15:input#2,e,e$,t,s:print e;e$;t;s:close 2 +2096 close 1: return + +;--- read valence.cfg --- +2100 print "reading valence settings..." +2110 for x = 0 to 63: poke 49216+x, 0: next x +2120 open 1,8,8,"valence.cfg,usr,r" +2130 if (status and 2) goto 2095 +2140 for x = 0 to 63 +2150 get#1,a$ +2160 poke 49216+x, asc(a$+chr$(0)) +2170 if (status and 64) goto 2190 +2180 next x +2190 close 1 +2191 print "{up} ":print "{up}"; +2192 return + +;--- write contiki.cfg --- +2200 print "writing contiki settings..." +2205 open 2,8,15,"s:contiki.cfg":input#2,e,e$,t,s:close 2 +2210 open 1,8,8,"contiki.cfg,usr,w" +2220 if (status and 2) goto 2095 +2230 n = 49152 +2240 v = peek(n) +2250 print#1,chr$(v); +2260 if n<49170 or v<>0 then n=n+1:goto 2240 +2270 close 1 +2280 return + +;--- write valence.cfg --- +2300 print "writing valence settings..." +2305 open 2,8,15,"s:valence.cfg":input#2,e,e$,t,s:close 2 +2310 open 1,8,8,"valence.cfg,usr,w" +2320 if (status and 2) goto 2095 +2330 print#1,chr$(peek(49216)); +2331 print#1,chr$(peek(49217)); +2332 print#1,chr$(peek(49218)); +2333 print#1,chr$(peek(49219)); +2340 close 1 +2350 return + +;--- show settings --- +3000 print " ip: ";:n=49152:gosub 4000 +3010 print "netmask: ";:n=49156:gosub 4000 +3020 print "gateway: ";:n=49160:gosub 4000 +3030 print " dns: ";:n=49164:gosub 4000 +3040 print " vnc: ";:n=49216:gosub 4000 +3050 print "ethaddr: ";:n=49168:gosub 5000 +3060 print "ethname: ";:n=49170:gosub 6000 +3099 return + +;--- convert ip to string --- +4000 print "{grn}"; +4010 for x = 0 to 3 +4020 v = peek(n+x) +4030 if v >= 100 then print chr$(48+int(v/100));:v=v-int(v/100)*100 +4031 if v >= 10 then print chr$(48+int(v/10));:v=v-int(v/10)*10 +4032 print chr$(48+v); +4044 if (x < 3) then print "."; +4090 next x +4098 print "{gry2}" +4099 return + +;--- convert uint16_t to hexstring --- +5000 print "{grn}"; +5010 for x = 1 to 0 step -1 +5020 v=peek(n+x) +5030 for y = 0 to 1 +5040 if y then d=v and 15: goto 5060 +5050 d=v/16 +5060 if (d<10) then print chr$(48+d);: goto 5080 +5070 print chr$(65+d-10); +5080 next y: next x: print "{gry2}":return + +;--- print string --- +6000 print "{grn}"; +6010 for x = 0 to 64 +6020 v = peek(n+x) +6030 if v = 0 then goto 6060 +6040 print chr$(v); +6050 next x +6060 print "{gry2}" +6070 return + +;--- set string --- +7000 for x = 1 to len(a$) +7010 poke n+x-1, asc(mid$(a$, x, 1)) +7020 next x +7030 for x = len(a$) to len(a$)+8 +7040 poke n+x,0 +7050 next x +7060 return + diff --git a/valence-label.odg b/valence-label.odg new file mode 100644 index 0000000..d8ad73b Binary files /dev/null and b/valence-label.odg differ diff --git a/valence-label.pdf b/valence-label.pdf new file mode 100644 index 0000000..f16468e Binary files /dev/null and b/valence-label.pdf differ diff --git a/valence64.bas b/valence64.bas new file mode 100644 index 0000000..8e39648 --- /dev/null +++ b/valence64.bas @@ -0,0 +1,86 @@ +; Valence 64 - The input-only VNC client for the Commodore 64. +; +; Copyright 2012 David Simmons +; http://cafbit.com/ +; +; Licensed under the Apache License, Version 2.0 (the "License"); +; you may not use this file except in compliance with the License. +; You may obtain a copy of the License at +; +; http://www.apache.org/licenses/LICENSE-2.0 +; +; Unless required by applicable law or agreed to in writing, software +; distributed under the License is distributed on an "AS IS" BASIS, +; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +; See the License for the specific language governing permissions and +; limitations under the License. +; +10 rem ****************************** +20 rem ** valence64 ** +30 rem ** (c) 2012 david simmons ** +40 rem ** apache 2.0 license ** +50 rem ****************************** +;60 rem ------------------------------ +;61 rem -- lower basic ceiling from -- +;62 rem -- $a000 to $2000 to make -- +;63 rem -- room for more ml code -- +;64 rem ------------------------------ +;65 rem poke 56, 84 // $5400 +;66 rem poke 56, 32 +100 rem ------------------------------ +101 rem -- splash screen -- +102 rem ------------------------------ +110 let sm = 1024 +111 let cm = 55296 +112 print "{clr}" +113 poke 53280,0 : poke 53281, 0 +120 rem ---- top line +121 for x = 0 to 19 +122 let c = (x and 7) + 1 +123 poke cm+20+x, c +124 poke cm+19-x, c+1 +125 poke sm+20+x, 224 +126 poke sm+19-x, 224 +127 next x +128 sm = sm + 40 +129 cm = cm + 40 +140 rem ---- side lines +141 for x = 0 to 22 +142 let c = (x and 7) + 1 +143 poke cm, c +144 poke cm+39, c+1 +145 poke sm, 224 +146 poke sm+39, 224 +147 sm = sm + 40 +148 cm = cm + 40 +149 next x +160 rem ---- bottom line +161 for x = 19 to 0 step -1 +162 let c = (x and 7) + 1 +163 poke cm+20+x, c +164 poke cm+19-x, c+1 +165 poke sm+20+x, 224 +166 poke sm+19-x, 224 +167 next x +199 rem goto 240 + +200 sys "splash" + +240 print "{home}{11 down}" +242 print "{down}{9 right}f1 - connect" +244 print "{9 right}f7 - toggle mouse/keyboard" +246 print "{3 right}crsr/joy - move mouse" +248 print "{5 right}return - click mouse" + +300 rem ------------------------------ +301 rem -- input loop -- +302 rem ------------------------------ +310 get a$ +320 if a$ = "{f1}" then gosub 1000 +330 goto 310 + +997 rem ------------------------------ +998 rem -- event handlers -- +999 rem ------------------------------ +1000 sys "jumpstart" + diff --git a/vncclient/Makefile b/vncclient/Makefile new file mode 100644 index 0000000..72a856f --- /dev/null +++ b/vncclient/Makefile @@ -0,0 +1,34 @@ + +CC65_HOME=/usr/local/lib/cc65 + +PROJECT_SOURCEFILES=splash.S + +# don't rm the .co file.. +.SECONDARY: + +CONTIKI_PROJECT = vncclient +all: $(CONTIKI_PROJECT) + +CFLAGS = -DWITH_UIP=1 -DUIP_CONF_TCP=1 -DWITH_CLIENT=1 + +CONTIKI = ../../contiki-2.5 + +include $(CONTIKI)/Makefile.include + +LDFLAGS = -Ln vncclient.lbl -C vncclient.cfg -D __STACKSIZE__=0x200 -u _main -m contiki-$(TARGET).map + +linkerconfig: + start=`wc -c < ../valence64.prg`; \ + start=`expr 2047 + $$start`; \ + ./mkcfg.pl $$start < vncclient.cfg.template > vncclient.cfg + +sterile: + $(MAKE) -C . clean + rm -f vncclient.c64 + rm -f vncclient.lbl + rm -f vncclient.cfg + rm -f *.eth + +build: linkerconfig + $(MAKE) -C . all + diff --git a/vncclient/Makefile.target b/vncclient/Makefile.target new file mode 100644 index 0000000..a4d378e --- /dev/null +++ b/vncclient/Makefile.target @@ -0,0 +1 @@ +TARGET = c64 diff --git a/vncclient/mkcfg.pl b/vncclient/mkcfg.pl new file mode 100755 index 0000000..b293e1f --- /dev/null +++ b/vncclient/mkcfg.pl @@ -0,0 +1,41 @@ +#!/usr/bin/perl +###################################################################### +# +# mkcfg +# +# Generate a customized ld65 configuration, using the specified +# address as the memory start location. The goal is to link the +# code such that it starts at the end of the BASIC code to which +# it will be attached. +# +# usage: mkcfg.pl 0x2000 < gc.cfg.template > gc.cfg +# +###################################################################### + +if (@ARGV < 1) { + die "usage: $0 memory-start\n"; +} +my $start = $ARGV[0]; +if ($start =~ /^0x([0-9a-fA-F]+)$/) { + $start = hex($1); +} elsif ($start !~ /^\d+$/) { + die "usage: $0 memory-start\n"; +} + +# allow memory usage up to the BASIC ceiling +my $size = 0xA000 - $start; + +# subtract 14 bytes to compensate for the 2-byte load address +# and the 12 bytes of BASIC bootstrap that we'll be getting +# rid of in a later stage. +$start = $start - 14; + +$start = sprintf('$'."%04X", $start); +$size = sprintf('$'."%04X", $size); + +while() { + s/\$\{START\}/$start/g; + s/\$\{SIZE\}/$size/g; + print; +} + diff --git a/vncclient/splash.S b/vncclient/splash.S new file mode 100644 index 0000000..8d7217e --- /dev/null +++ b/vncclient/splash.S @@ -0,0 +1,294 @@ +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; +; splash screen drawing +; +; Valence 64 - The input-only VNC client for the Commodore 64. +; +; Copyright 2012 David Simmons +; http://cafbit.com/ +; +; Licensed under the Apache License, Version 2.0 (the "License"); +; you may not use this file except in compliance with the License. +; You may obtain a copy of the License at +; +; http://www.apache.org/licenses/LICENSE-2.0 +; +; Unless required by applicable law or agreed to in writing, software +; distributed under the License is distributed on an "AS IS" BASIS, +; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +; See the License for the specific language governing permissions and +; limitations under the License. +; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +.macpack cbm + +CHROUT = $FFD2 +SCREEN = $0400 +COLOR = $D800 +WIDTH = 36 + +; According to the C64 Programmer's Reference Guide, these addresses are +; "Free 0-Page Space for User Programs" and shouldn't conflict with +; BASIC or KERNAL. +ZP1 = $FB ; BASZPT +ZP1L = $FB +ZP1H = $FC +ZP2 = $FD +ZP2L = $FD +ZP2H = $FE +; cassette 96-97, 9B-9C, A3-B6(and beyond?) +ZP3 = $96 +ZP3L = $96 +ZP3H = $97 +ZP4 = $9B +ZP4L = $9B +ZP4H = $9C +ZP5 = $A3 +ZP5L = $A3 +ZP5H = $A4 +ZP6 = $A5 +ZP6L = $A5 +ZP6H = $A6 +ZP7 = $A7 +ZP7L = $A7 +ZP7H = $A8 +ZP8 = $A9 +ZP8L = $A9 +ZP8H = $AA +ZP9 = $AB +ZP9L = $AB +ZP9H = $AC + +; splash routine's use of ZP: +screenstart = ZP1 +textstart = ZP2 +textcolorstart = ZP3 +screenblit = ZP4 +colorblit = ZP5 +textblit = ZP6 +textcolorblit = ZP7 +; ZP9L: loop-control variable for animation of a single line +iter = ZP9L + +splash: + + ; initialize + lda #textdata + sta textstart+1 + +row_render_loop: + + ldy #0 + lda (textstart),y + cmp #$FF + bne render_another_row + rts + +render_another_row: + tay + ; load screenstart with the beginning of our screen area + lda #<(SCREEN+2) + sta screenstart + lda #>(SCREEN+2) + sta screenstart+1 + cpy #0 + beq row_adder_finished +row_adder: + ;; add 40 columns for each row + lda screenstart + clc + adc #<40 + sta screenstart + lda screenstart+1 + adc #>40 + sta screenstart+1 + dey + bne row_adder +row_adder_finished: + inc textstart + bne do_row + inc textstart+1 + +do_row: + ; loop over the line-animation sequence + lda #(WIDTH/2-1) + sta iter +animation_loop: + + ; LEFT BLIT + + ; set the blit starts for this blit + ; screenblit = screenstart + (iter-1) + lda screenstart + clc + adc iter + sta screenblit + lda screenstart+1 + adc #00 + sta screenblit+1 + ; colorblit = screenblit + (COLOR-SCREEN) + lda screenblit + clc + adc #<(COLOR-SCREEN) + sta colorblit + lda screenblit+1 + adc #>(COLOR-SCREEN) + sta colorblit+1 + ; textblit = textstart + lda textstart + sta textblit + lda textstart+1 + sta textblit+1 + ; textcolorblit = textblit + width + lda textblit + clc + adc #WIDTH + sta textcolorblit+1 + ; length = (width/2)-iter + lda #(WIDTH/2) + sec + sbc iter + tay + + ; blit +leftblit: + lda (textblit),Y + sta (screenblit),Y + lda (textcolorblit),Y + sta (colorblit),Y + dey + bpl leftblit + + ; RIGHT BLIT + + ; set the blit starts for this blit + ; screenblit = screenstart + width/2 + lda screenstart + clc + adc #<(WIDTH/2) + sta screenblit + lda screenstart+1 + adc #>(WIDTH/2) + sta screenblit+1 + ; colorblit = screenblit + (COLOR-SCREEN) + lda screenblit + clc + adc #<(COLOR-SCREEN) + sta colorblit + lda screenblit+1 + adc #>(COLOR-SCREEN) + sta colorblit+1 + ; textblit = textstart + width/2 + iter + lda textstart + clc + adc #<(WIDTH/2) + sta textblit + lda textstart+1 + adc #>(WIDTH/2) + sta textblit+1 + lda textblit + clc + adc iter + sta textblit + lda textblit+1 + adc #$00 + sta textblit+1 + ; textcolorblit = textblit + width + lda textblit + clc + adc #WIDTH + sta textcolorblit+1 + ; length = (width/2)-iter + lda #(WIDTH/2) + sec + sbc iter + tay + dey + + ; blit +rightblit: + lda (textblit),Y + sta (screenblit),Y + lda (textcolorblit),Y + sta (colorblit),Y + dey + bpl rightblit + + + ; delay + ldx #20 + ldy #00 +delayloop: + dey + bne delayloop + dex + bne delayloop + + dec iter + bmi animation_loop_finish + jmp animation_loop +animation_loop_finish: + + ; advance to the next textdata line + lda textstart + clc + adc #<(WIDTH*2) + sta textstart + lda textstart+1 + adc #>(WIDTH*2) + sta textstart+1 + jmp row_render_loop + + rts + +textdata: + + .byte 2 ; row + .byte $20, $df, $20, $20, $e9, $20, $20, $20, $20, $20, $e9, $20, $20, $20, $20, $20, $20, $20, $20, $20, $20, $20, $20, $20, $20, $20, $20, $20, $e9, $a0, $df, $20, $e9, $20, $e9, $20 + .byte 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6 + .byte 6, 6, 6, 6, 6, 6, 6, 6, 6, 2, 2, 2, 2, 2, 2, 2, 2, 2 + .byte 3 ; row + .byte $20, $a0, $20, $20, $a0, $20, $20, $20, $20, $20, $a0, $20, $20, $20, $20, $20, $20, $20, $20, $20, $20, $20, $20, $20, $20, $20, $20, $20, $a0, $20, $20, $20, $a0, $20, $a0, $20 + .byte 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6 + .byte 6, 6, 6, 6, 6, 6, 6, 6, 6, 2, 2, 2, 2, 2, 2, 2, 2, 2 + .byte 4 ; row + .byte $20, $a0, $20, $20, $a0, $20, $e9, $a0, $a0, $20, $a0, $20, $a0, $e2, $a0, $20, $a0, $a0, $df, $20, $e9, $a0, $df, $20, $a0, $e2, $a0, $20, $a0, $a0, $df, $20, $a0, $a0, $a0, $74 + .byte 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6 + .byte 6, 6, 6, 6, 6, 6, 6, 6, 6, 2, 2, 2, 2, 2, 2, 2, 2, 2 + .byte 5 ; row + .byte $20, $5f, $df, $e9, $69, $20, $a0, $20, $a0, $20, $a0, $20, $a0, $e2, $e2, $20, $a0, $20, $a0, $20, $a0, $20, $20, $20, $a0, $e2, $e2, $20, $a0, $20, $a0, $20, $20, $20, $a0, $20 + .byte 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6 + .byte 6, 6, 6, 6, 6, 6, 6, 6, 6, 2, 2, 2, 2, 2, 2, 2, 2, 2 + .byte 6 ; row + .byte $20, $20, $5f, $69, $20, $20, $5f, $a0, $a0, $20, $a0, $20, $5f, $62, $62, $20, $a0, $20, $a0, $20, $5f, $a0, $69, $20, $5f, $62, $62, $20, $5f, $a0, $69, $20, $20, $20, $a0, $20 + .byte 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6 + .byte 6, 6, 6, 6, 6, 6, 6, 6, 6, 2, 2, 2, 2, 2, 2, 2, 2, 2 + + .byte 9 ; row + scrcode " the input-only vnc client for the " + .byte 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + .byte 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + + .byte 10 ; row + scrcode " commodore 64 " + .byte 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + .byte 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + + .byte 23 ; row + scrcode " (c) 2012 david simmons " + .byte 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + .byte 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + + .byte $FF; end-of-data + +.condes splash, 6 + diff --git a/vncclient/vncclient.c b/vncclient/vncclient.c new file mode 100644 index 0000000..23c734f --- /dev/null +++ b/vncclient/vncclient.c @@ -0,0 +1,635 @@ +/* + * Valence 64 - The input-only VNC client for the Commodore 64. + * + * Copyright 2012 David Simmons + * http://cafbit.com/ + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "contiki.h" +#include "contiki-net.h" +#include "dev/leds.h" +#include +#include +#include + +// defines +#define VNC_PORT 5900 +#define VNC_SECURITY_TYPE_NONE 0x01 +#define TIMEOUT 200 +#define BUFFER_SIZE 640 +// relevant c64 memory locations +#define C64_NDX 0x00C6 +#define C64_KEYD 0x0277 +#define C64_VICSCN 0x0400 +#define C64_COLOR 0xD800 +#define C64_GETIN 0xFFE4 + +// this is only used in the oddball case that there's no +// configuration file +const uint8_t default_vnc_server[] = { 192, 168, 1, 229 }; + +// general-purpose buffer +static uint8_t buffer[BUFFER_SIZE]; + +// states +typedef enum { + VNC_STATE_HANDSHAKE = 0, + VNC_STATE_INIT = 1, + VNC_STATE_OPERATION = 2 +} vnc_state_t; + +// our context structure +typedef struct { + uip_ipaddr_t addr; + uint8_t connection_succeeded; + unsigned char idle; + struct psock psock; + struct psock inbound; + struct psock outbound; + vnc_state_t state; + uint8_t num_sec_types; + uint16_t n; + uint16_t width; + uint16_t height; + uint16_t x, y; + uint16_t server_name_length; + uint8_t mouse_mode; +} vnc_context_t; +static vnc_context_t vnc_context_storage; + +// contiki process setup +PROCESS(vncclient_process, "vncclient"); +AUTOSTART_PROCESSES(&vncclient_process); + +// PETSCII to screen-code translator +//#define S(c) ((c>=0x40 && c<0x60)?(c-0x40):((c<0x20 || c>=0x60)?0x00:c)) + +// screen layout +// (corresponds to the screen drawn by the BASIC code) +static const uint8_t status_row = 21; +static const uint8_t mode_row = 14; +static const uint8_t mode_mouse_col = 21; +static const uint8_t mode_mouse_len = 5; +static const uint8_t mode_keyboard_col = 27; +static const uint8_t mode_keyboard_len = 8; + +//====================================================================== +// utility functions +//====================================================================== + +// read three ASCII digits as an integer value +static int c3_to_int(const uint8_t *s) { + if (((*s < 0x30) || (*s > 0x39)) || + (*(s+1) < 0x30) || (*(s+1) > 0x39) || + (*(s+2) < 0x30) || (*(s+2) > 0x39) ) { + return 0; + } + return ((*s-0x30)*100) + (*(s+1)-0x30)*10 + (*(s+2)-0x30); +} + +// update the status line +static void status(const char *msg) { + const char *m; + char *s = (char *)(C64_VICSCN + (status_row*40)+1); + memset(s, 0x20, 38); + memset((char *)(C64_COLOR + (status_row*40)+1), 0x07, 38); + s += 19-strlen(msg)/2; + for (m=msg; *m!='\0'; m++) { + char c = *m; + if (c>=0x40 && c<0x60) { + c -= 0x40; + } else if (c<0x20 || c>=0x60) { + c = 0x20; + } + *(s++) = c; + } +} + +// update the mouse/keyboard indicator +static void show_mode(uint8_t mouse_mode) { + static char *p; + int i; + p = (char *)(C64_VICSCN + mode_row*40 + mode_mouse_col); + for (i=0; ipsock); + PSOCK_INIT(&vnc->psock, buffer, 12); + + // read server version + PSOCK_READBUF(&vnc->psock); + // parse server version + if ( + (buffer[0] != 0x52) || + (buffer[1] != 0x46) || + (buffer[2] != 0x42) || + (buffer[3] != 0x20) || + (buffer[7] != 0x2E) || + (buffer[11] != 0x0A) + ) { + status("bad version string"); + PSOCK_CLOSE(&vnc->outbound); + PSOCK_EXIT(&vnc->outbound); + } + v1 = c3_to_int(buffer+4); + v2 = c3_to_int(buffer+8); + if ((v1<3)||(v2<8)) { + //printf("unsupported server version: %d.%d\n",v1,v2); + status("unsupported server version"); + PSOCK_CLOSE(&vnc->outbound); + PSOCK_EXIT(&vnc->outbound); + } + //printf("version detected: %d.%d\n", v1,v2); + + // send our version (3.8) + PSOCK_SEND(&vnc->psock, client_version, sizeof(client_version)); + + //------------------------------------------------------------ + // security handshake + //------------------------------------------------------------ + + // receive the list of supported authentication methods + // from the server. + PSOCK_INIT(&vnc->psock, buffer, 1); + PSOCK_READBUF(&vnc->psock); + vnc->num_sec_types = buffer[0]; + if (vnc->num_sec_types == 0) { + status("vnc handshake failure"); + PSOCK_CLOSE(&vnc->outbound); + PSOCK_EXIT(&vnc->outbound); + } + PSOCK_INIT(&vnc->psock, buffer, vnc->num_sec_types); + PSOCK_READBUF(&vnc->psock); + found = 0; + for (i=0; inum_sec_types; i++) { + if (buffer[i] == VNC_SECURITY_TYPE_NONE) { + found = 1; + break; + } + } + if (! found) { + status("server does not support security-none"); + PSOCK_CLOSE(&vnc->outbound); + PSOCK_EXIT(&vnc->outbound); + } + + // tell the server we will use VNC_SECURITY_TYPE_NONE. + buffer[0] = VNC_SECURITY_TYPE_NONE; + PSOCK_SEND(&vnc->psock, buffer, 1); + + // read the security-result + PSOCK_INIT(&vnc->psock, buffer, 4); + PSOCK_READBUF(&vnc->psock); + if (*((uint32_t*)buffer) != 0x00) { + status("security failure"); + PSOCK_CLOSE(&vnc->outbound); + PSOCK_EXIT(&vnc->outbound); + } + + // transition to normal operation + vnc->state = VNC_STATE_INIT; + + PSOCK_END(&vnc->psock); +} + +// perform VNC/RFB initialization +static int do_init(vnc_context_t *vnc) { + PSOCK_BEGIN(&vnc->psock); + PSOCK_INIT(&vnc->psock, buffer, 24); + //------------------------------------------------------------ + // client/server init + //------------------------------------------------------------ + + // client init + // send shared-flag = 1 (allow other clients to connect) + buffer[0] = 0x01; + PSOCK_SEND(&vnc->psock, buffer, 1); + + // server init + PSOCK_READBUF(&vnc->psock); + vnc->width = buffer[0]<<8 | buffer[1]; + vnc->height = buffer[2]<<8 | buffer[3]; + vnc->server_name_length = buffer[22]<<8 | buffer[23]; + if (buffer[20] || buffer[21] || vnc->server_name_length > (BUFFER_SIZE-1)) { + status("unsupported servername length"); + PSOCK_CLOSE(&vnc->outbound); + PSOCK_EXIT(&vnc->outbound); + } + + vnc->x = vnc->width/2; + vnc->y = vnc->height/2; + + //PSOCK_INIT(&vnc->psock, buffer, vnc->server_name_length); + PSOCK_INIT(&vnc->psock, buffer, 14); + PSOCK_READBUF(&vnc->psock); + buffer[vnc->server_name_length] = 0; + //printf("server name: \"%s\"\n", buffer); + + // transition to normal operation + vnc->state = VNC_STATE_OPERATION; + + PSOCK_END(&vnc->psock); +} + +// add a keypress event to the outgoing VNC/RFB stream +static void add_key_event(vnc_context_t *vnc, uint8_t **buf_ptr, uint8_t c) { + uint16_t keysym = 0; + uint8_t *buf; + + // translate PETSCII to keysyms + switch (c) { + case 0x0D: // return + keysym = 0x0D; + break; + case 0x14: // map delete to backspace + keysym = 0xFF08; + break; + case 0x91: // up + keysym = 0xFF52; + break; + case 0x9D: // left + keysym = 0xFF51; + break; + case 0x11: // down + keysym = 0xFF54; + break; + case 0x1D: // right + keysym = 0xFF53; + break; + default: + if (c>=0x20 && c<= 0x60) { + if (c>=0x41 && c<=0x5A) { + keysym = c + 0x20; + } else { + keysym = c; + } + } + } + if (keysym == 0) { + return; + } + + buf = *buf_ptr; + buf[0] = 4; + buf[1] = 1; + buf[2] = 0; + buf[3] = 0; + buf[4] = 0; //(keysym >> 24); + buf[5] = 0; //(keysym >> 16) & 0xFF; + buf[6] = (keysym >> 8) & 0xFF; + buf[7] = keysym & 0xFF; + *buf_ptr = buf+8; + buf = *buf_ptr; + buf[0] = 4; + buf[1] = 0; + buf[2] = 0; + buf[3] = 0; + buf[4] = 0; //(keysym >> 24); + buf[5] = 0; //(keysym >> 16) & 0xFF; + buf[6] = (keysym >> 8) & 0xFF; + buf[7] = keysym & 0xFF; + *buf_ptr = buf+8; +} + +// add a mouse movement event to the outgoing VNC/RFB stream +static void add_mouse_event(vnc_context_t *vnc, uint8_t **buf_ptr) { + uint8_t *buf = *buf_ptr; + buf[0] = 5; + buf[1] = 0; + buf[2] = vnc->x >> 8; + buf[3] = vnc->x & 0xFF; + buf[4] = vnc->y >> 8; + buf[5] = vnc->y & 0xFF; + *buf_ptr = buf+6; +} + +// add a mouse button event to the outgoing VNC/RFB stream +static void add_mouse_button_event(vnc_context_t *vnc, uint8_t **buf_ptr, int button_mask) { + uint8_t *buf = *buf_ptr; + buf[0] = 5; + buf[1] = button_mask; + buf[2] = vnc->x >> 8; + buf[3] = vnc->x & 0xFF; + buf[4] = vnc->y >> 8; + buf[5] = vnc->y & 0xFF; + buf[6] = 5; + buf[7] = 0x00; + buf[8] = vnc->x >> 8; + buf[9] = vnc->x & 0xFF; + buf[10] = vnc->y >> 8; + buf[11] = vnc->y & 0xFF; + *buf_ptr = buf+12; +} + +#define UNIT_MOVE 8 + +// process input events and generate the appropriate +// VNC/RFB events. +static int do_operation(vnc_context_t *vnc) { + uint8_t *buf = uip_buf; + uint8_t *end = uip_buf + UIP_APPDATA_SIZE; + char key = 0; + uint8_t joy; + uint8_t joy_move_flag = 0; + static uint8_t joy_fire_flag = 0; // track fire state + + // process joystick + joy = PEEK(0xDC00); + if (joy & 0x1F) { + if (!(joy & (0x01<<0))) { // up + vnc->y-=UNIT_MOVE; if (vnc->y <= 0) { vnc->y = 0; } + joy_move_flag = 1; + } + if (!(joy & (0x01<<1))) { // down + vnc->y+=UNIT_MOVE; + if (vnc->y >= vnc->height) { vnc->y = vnc->height-1; } + joy_move_flag = 1; + } + if (!(joy & (0x01<<2))) { // left + vnc->x-=UNIT_MOVE; + if (vnc->x <= 0) { vnc->x = 0; } + joy_move_flag = 1; + } + if (!(joy & (0x01<<3))) { // right + vnc->x+=UNIT_MOVE; + if (vnc->x >= vnc->width) { vnc->x = vnc->width-1; } + joy_move_flag = 1; + } + if (!(joy & (0x01<<4))) { // fire + // if the joystick fire was previously noticed, + // don't fire again until the fire switch is + // seen open. this is to prevent a "rapid fire" + // effect which, while fun in games, is annoying + // when navigating GUIs. + if (! joy_fire_flag) { + add_mouse_button_event(vnc, &buf, 0x01); + joy_fire_flag = 1; + } + } else { + // clear fire flag if the fire button has been released + joy_fire_flag = 0; + } + if (joy_move_flag) { + add_mouse_event(vnc, &buf); + } + } + + // process keys + while (PEEK(C64_NDX)) { + key = PEEK(C64_KEYD); + __asm__ ("jsr %w", C64_GETIN); + //printf("key pressed: %02x\n", key); + + if (key == 0x88 /* F7 */) { + vnc->mouse_mode = ! vnc->mouse_mode; + show_mode(vnc->mouse_mode); + } else if (vnc->mouse_mode) { + switch (key) { + case 0x91: // crsr up + vnc->y-=UNIT_MOVE; if (vnc->y <= 0) { vnc->y = 0; } + add_mouse_event(vnc, &buf); + break; + case 0x11: // crsr down + vnc->y+=UNIT_MOVE; + if (vnc->y >= vnc->height) { vnc->y = vnc->height-1; } + add_mouse_event(vnc, &buf); + break; + case 0x9D: // crsr left + vnc->x-=UNIT_MOVE; + if (vnc->x <= 0) { vnc->x = 0; } + add_mouse_event(vnc, &buf); + break; + case 0x1D: // crsr right + vnc->x+=UNIT_MOVE; + if (vnc->x >= vnc->width) { vnc->x = vnc->width-1; } + add_mouse_event(vnc, &buf); + break; + case 0x0D: // return + case 0x85: // F1 + add_mouse_button_event(vnc, &buf, 0x01); + break; + case 0x86: // F3 + add_mouse_button_event(vnc, &buf, 0x02); + break; + case 0x87: // F5 + add_mouse_button_event(vnc, &buf, 0x04); + break; + } + } else { + // key mode + add_key_event(vnc, &buf, key); + } + if (end-buf < 12) { + break; + } + } + if (buf > uip_buf) { + //uint8_t *s; + //for (s=uip_buf; soutbound); + PSOCK_CLOSE(&vnc->outbound); + PSOCK_EXIT(&vnc->outbound); + PSOCK_END(&vnc->outbound); +} + +//---------------------------------------------------------------------- +// inbound protothread +//---------------------------------------------------------------------- + +static int handle_inbound(vnc_context_t *vnc) { + PSOCK_BEGIN(&vnc->inbound); + PSOCK_END(&vnc->inbound); +} + +//---------------------------------------------------------------------- +// connection dispatch routine +//---------------------------------------------------------------------- + +static void handle_connection(vnc_context_t *vnc) { + switch (vnc->state) { + case VNC_STATE_HANDSHAKE: + do_handshake(vnc); + break; + case VNC_STATE_INIT: + do_init(vnc); + break; + case VNC_STATE_OPERATION: + do_operation(vnc); + break; + default: + handle_inbound(vnc); + handle_outbound(vnc); + break; + } +} + +//---------------------------------------------------------------------- +// process +//---------------------------------------------------------------------- + +PROCESS_THREAD(vncclient_process, ev, data) +{ + vnc_context_t *vnc = &vnc_context_storage; + struct uip_conn *conn; + int loaded; + + // begun, this 8-bit process has. + PROCESS_BEGIN(); + + // initialize + status("loading configuration..."); + memset(vnc, 0x00, sizeof(vnc_context_t)); + vnc->state = VNC_STATE_HANDSHAKE; + vnc->mouse_mode = 1; + show_mode(vnc->mouse_mode); + + // load the destination IP address from the configuration file + loaded = 0; + { + uint8_t configured_vnc_server[4]; + int file = cfs_open("valence.cfg", CFS_READ); + if (file>=0) { + if (cfs_read(file, configured_vnc_server, sizeof(configured_vnc_server)) == sizeof(configured_vnc_server)) { + uip_ipaddr(&vnc->addr, + configured_vnc_server[0], + configured_vnc_server[1], + configured_vnc_server[2], + configured_vnc_server[3] + ); + loaded = 1; + } + } + } + if (loaded == 0) { + uip_ipaddr(&vnc->addr, + default_vnc_server[0], + default_vnc_server[1], + default_vnc_server[2], + default_vnc_server[3] + ); + } + status("connecting..."); + + // connect + vnc->connection_succeeded = 0; + conn = tcp_connect(&vnc->addr, UIP_HTONS(VNC_PORT), vnc); + if (! conn) { + status("error launching connect"); + PROCESS_EXIT(); + } + + // loop over TCP/IP events and dispatch them + // to the protothreads. + while (1) { + PROCESS_WAIT_EVENT(); + + if (ev == tcpip_event) { + vnc_context_t *vnc = &vnc_context_storage; + + if (uip_closed()) { + status("connection closed"); + PROCESS_EXIT(); + } else if (uip_aborted()) { + if (! vnc->connection_succeeded) { + // retry + status("retrying..."); + conn = tcp_connect(&vnc->addr, UIP_HTONS(VNC_PORT), vnc); + if (! conn) { + status("error launching reconnect"); + PROCESS_EXIT(); + } + } else { + status("connection aborted\n"); + PROCESS_EXIT(); + } + } else if (uip_timedout()) { + status("connection timed out\n"); + PROCESS_EXIT(); + } else if (uip_connected()) { + status("connected"); + vnc->connection_succeeded = 1; + handle_connection(vnc); + vnc->idle = 0; + } else if (vnc != NULL) { + // is a poll needed? + if (uip_poll()) { + vnc->idle++; + // don't timeout (for now...) + /* + if (vnc->idle > TIMEOUT) { + printf("application layer timeout\n"); + uip_abort(); + PROCESS_EXIT(); + } + */ + } else { + vnc->idle = 0; + } + handle_connection(vnc); + } else { + status("major damage.\n"); + uip_abort(); + PROCESS_EXIT(); + } + } + } + + status("finished.\n"); + PROCESS_END(); +} + diff --git a/vncclient/vncclient.cfg.template b/vncclient/vncclient.cfg.template new file mode 100644 index 0000000..f9ad6b3 --- /dev/null +++ b/vncclient/vncclient.cfg.template @@ -0,0 +1,35 @@ +MEMORY { + ZP: start = $0002, size = $001A, type = rw, define = yes; + RAM: start = ${START}, size = ${SIZE}, file = %O, define = yes; +} +SEGMENTS { + STARTUP: load = RAM, type = ro; + LOWCODE: load = RAM, type = ro, optional = yes; + INIT: load = RAM, type = ro, define = yes, optional = yes; + CODE: load = RAM, type = ro; + RODATA: load = RAM, type = ro; + DATA: load = RAM, type = rw; + ZPSAVE: load = RAM, type = bss; + BSS: load = RAM, type = bss, define = yes; + HEAP: load = RAM, type = bss, optional = yes; # must sit just below stack + ZEROPAGE: load = ZP, type = zp; +} +FEATURES { + CONDES: segment = INIT, + type = constructor, + label = __CONSTRUCTOR_TABLE__, + count = __CONSTRUCTOR_COUNT__; + CONDES: segment = RODATA, + type = destructor, + label = __DESTRUCTOR_TABLE__, + count = __DESTRUCTOR_COUNT__; + CONDES: segment = RODATA, + type = interruptor, + label = __INTERRUPTOR_TABLE__, + count = __INTERRUPTOR_COUNT__; +} +SYMBOLS { + __STACKSIZE__: value = $0800, weak = yes; # 2k stack +} + +