From e8bdb4b81e1871f0e74057223991b0f43f228fc4 Mon Sep 17 00:00:00 2001 From: jonnosan Date: Fri, 17 Jul 2009 02:53:46 +0000 Subject: [PATCH] git-svn-id: http://svn.code.sf.net/p/netboot65/code@157 93682198-c243-4bdb-bd91-e943c89aac3b --- CHANGES.txt | 2 + client/cfg/c64_16kcart.cfg | 5 +- client/cfg/c64prg.cfg | 3 +- client/drivers/c64inputs.s | 16 +- client/examples/dumb_telnet.asm | 207 ++++++++++++++++++ client/{test/test_gopher.s => inc/gopher.i} | 221 +++++++++++--------- client/inc/nb65_constants.i | 7 +- client/ip65/Makefile | 13 +- client/ip65/function_dispatcher.s | 101 ++++++++- client/nb65/nb65_c64.s | 43 +++- client/test/Makefile | 2 +- client/test/retro_gopher.txt | 16 -- client/test/rob_gopher.txt | 40 ---- doc/nb65_api_technical_reference.doc | Bin 128000 -> 131072 bytes 14 files changed, 487 insertions(+), 189 deletions(-) create mode 100644 client/examples/dumb_telnet.asm rename client/{test/test_gopher.s => inc/gopher.i} (75%) delete mode 100644 client/test/retro_gopher.txt delete mode 100644 client/test/rob_gopher.txt diff --git a/CHANGES.txt b/CHANGES.txt index 51256ec..3ce69f8 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,3 +1,5 @@ + - bugfix - NB65 API not setting "ip initialized" flag correct, hence extra DHCP inits occuring + 0.9.8 - created stubs for TCP functions - split nb65 cart images into 8k (UDP only - green) & 16k (UDP+TCP - purple) images diff --git a/client/cfg/c64_16kcart.cfg b/client/cfg/c64_16kcart.cfg index 31b11fa..b6b0bd1 100644 --- a/client/cfg/c64_16kcart.cfg +++ b/client/cfg/c64_16kcart.cfg @@ -1,13 +1,13 @@ # CA65 config for a 16KB cart MEMORY { - IP65ZP: start = $A3, size = $0E, type = rw, define = yes; + IP65ZP: start = $A3, size = $11, type = rw, define = yes; HEADER: start = $8000, size = $18, file = %O; DEFAULTS: start = $8018, size = $1E, file = %O; ROM: start = $8036, size = $3FC8, define = yes, file = %O; RAM: start = $C010, size = $0fE0, define = yes; RAM2: start = $0334, size = $CB, define = yes; #extra scratch area - Tape I/O buffer - + RAM3: start = $0800, size = $7800, define = yes; #scratch area for apps embedded in cart to use } SEGMENTS { @@ -17,6 +17,7 @@ SEGMENTS { RODATA: load = ROM, run=ROM, type = ro; DATA: load = ROM, run = RAM, type = rw, define = yes; BSS: load = RAM, type = bss; + APP_SCRATCH: load = RAM3, type = bss; TCP_VARS: load = RAM2, type = bss; IP65ZP: load = IP65ZP, type = zp; } diff --git a/client/cfg/c64prg.cfg b/client/cfg/c64prg.cfg index a9ef89f..9fb881b 100644 --- a/client/cfg/c64prg.cfg +++ b/client/cfg/c64prg.cfg @@ -10,7 +10,8 @@ SEGMENTS { DATA: load = RAM, type = rw,define = yes; RODATA: load = RAM, type = ro,define = yes; IP65_DEFAULTS: load = RAM, type = rw,define = yes; - BSS: load = RAM, type = bss; + BSS: load = RAM, type = bss; + APP_SCRATCH: load = RAM, type = bss; ZEROPAGE: load = ZP, type = zp; IP65ZP: load = IP65ZP, type = zp; EXEHDR: load = DISCARD, type = ro; diff --git a/client/drivers/c64inputs.s b/client/drivers/c64inputs.s index bd3b68a..469ee14 100644 --- a/client/drivers/c64inputs.s +++ b/client/drivers/c64inputs.s @@ -3,6 +3,7 @@ .export filter_text .export filter_ip .export filter_dns +.export filter_number .export check_for_abort_key .export get_key_if_available .importzp copy_src @@ -73,6 +74,11 @@ INPUT_GET: cmp #$0d ;Return beq INPUT_DONE + ;End reached? + lda INPUT_Y + cmp MAXCHARS + beq INPUT_GET + ;Check the allowed list of characters. ldy #$00 CHECKALLOWED: @@ -94,10 +100,6 @@ INPUTOK: inc INPUT_Y ;Next character - ;End reached? - lda INPUT_Y - cmp MAXCHARS - beq INPUT_DONE ;Not yet. jmp INPUT_GET @@ -145,9 +147,11 @@ DELETE_OK: filter_text: .byte ",+!#$%&'()* " filter_dns: -.byte " -ABCDEFGHIJKLMNOPQRSTUVWXYZ" +.byte "-ABCDEFGHIJKLMNOPQRSTUVWXYZ" filter_ip: -.byte "1234567890.",0 +.byte "." +filter_number: +.byte "1234567890",0 ;================================================= .bss diff --git a/client/examples/dumb_telnet.asm b/client/examples/dumb_telnet.asm new file mode 100644 index 0000000..ee67e37 --- /dev/null +++ b/client/examples/dumb_telnet.asm @@ -0,0 +1,207 @@ +;NB65 API example in DASM format (http://www.atari2600.org/DASM/) + processor 6502 + + include "../inc/nb65_constants.i" + + ;useful macros + mac ldax + lda [{1}] + ldx [{1}]+1 + endm + + mac ldaxi + lda #<[{1}] + ldx #>[{1}] + endm + + mac stax + sta [{1}] + stx [{1}]+1 + endm + + mac cout + lda [{1}] + jsr print_a + endm + + mac print_cr + cout #13 + jsr print_a + endm + + mac nb65call + ldy [{1}] + jsr NB65_DISPATCH_VECTOR + endm + + mac print + + ldaxi [{1}] + ldy #NB65_PRINT_ASCIIZ + jsr NB65_DISPATCH_VECTOR + endm + + +;some routines & zero page variables +print_a equ $ffd2 +temp_ptr equ $FB ; scratch space in page zero + + +;start of code +;NO BASIC stub! needs to be direct booted via TFTP + org $1000 + + ldaxi #NB65_CART_SIGNATURE ;where signature should be in cartridge (if cart is banked in) + jsr look_for_signature + bcc found_nb65_signature + + ldaxi #NB65_RAM_STUB_SIGNATURE ;where signature should be in a RAM stub + jsr look_for_signature + bcs nb65_signature_not_found + jsr NB65_RAM_STUB_ACTIVATE ;we need to turn on NB65 cartridge + jmp found_nb65_signature + +nb65_signature_not_found + ldaxi #nb65_api_not_found_message + jsr print_ax + rts + +found_nb65_signature + + lda NB65_API_VERSION + cmp #02 + bpl .version_ok + print incorrect_version + jmp reset_after_keypress +.version_ok + print #initializing + nb65call #NB65_INITIALIZE + bcc .init_ok + print_cr + print #failed + print_cr + jsr print_errorcode + jmp reset_after_keypress +.init_ok + +;if we got here, we have found the NB65 API and initialised the IP stack +;print out the current configuration + nb65call #NB65_PRINT_IP_CONFIG +;prompt for a hostname, then resolve to an IP address +.get_hostname + print #remote_host + nb65call #NB65_INPUT_HOSTNAME + bcc .host_entered + ;if no host entered, then bail. + jmp reset_after_keypress +.host_entered + stax nb65_param_buffer + print_cr + print #resolving + ldax nb65_param_buffer + nb65call #NB65_PRINT_ASCIIZ + print_cr + ldaxi #nb65_param_buffer + nb65call #NB65_DNS_RESOLVE + bcc .resolved_ok + print #failed + print_cr + jsr print_errorcode + jmp .get_hostname +.resolved_ok +.get_port + print #remote_port + nb65call #NB65_INPUT_PORT_NUMBER + bcc .port_entered + ;if no port entered, then assume port 23 + ldaxi #23 +.port_entered + stax nb65_param_buffer+NB65_TCP_PORT + ldaxi #tcp_callback + stax nb65_param_buffer+NB65_TCP_CALLBACK + print #connecting + ldaxi #nb65_param_buffer + nb65call #NB65_TCP_CONNECT + bcc .connect_ok + print_cr + print #failed + jsr print_errorcode + jmp .get_hostname +.connect_ok + print #ok +.loop_forever + jsr NB65_PERIODIC_PROCESSING_VECTOR + jmp .loop_forever + + +;tcp callback - will be executed whenever data arrives on the TCP connection +tcp_callback + .byte $92 + rts + +;look for NB65 signature at location pointed at by AX +look_for_signature subroutine + stax temp_ptr + ldy #3 +.check_one_byte + lda (temp_ptr),y + cmp nb65_signature,y + bne .bad_match + dey + bpl .check_one_byte + clc + rts +.bad_match + sec + rts + +print_ax subroutine + stax temp_ptr + ldy #0 +.next_char + lda (temp_ptr),y + beq .done + jsr print_a + iny + jmp .next_char +.done + rts + +get_key + jsr $ffe4 + cmp #0 + beq get_key + rts + +reset_after_keypress + print #press_a_key_to_continue + jsr get_key + jmp $fce2 ;do a cold start + + +print_errorcode + print #error_code + nb65call #NB65_GET_LAST_ERROR + nb65call #NB65_PRINT_HEX + print_cr + rts + + + +;constants +nb65_api_not_found_message dc.b "ERROR - NB65 API NOT FOUND.",13,0 +incorrect_version dc.b "ERROR - NB65 API MUST BE AT LEAST VERSION 2.",13,0 + +nb65_signature dc.b $4E,$42,$36,$35 ; "NB65" - API signature +initializing dc.b "INITIALIZING ",13,0 +error_code dc.b "ERROR CODE: $",0 +resolving dc.b "RESOLVING ",0 +connecting dc.b "CONNECTING ",0 +remote_host dc.b "REMOTE HOST - BLANK TO QUIT",13,": ",0 +remote_port dc.b "REMOTE PORT - BLANK FOR TELNET DEFAULT",13,": ",0 +press_a_key_to_continue dc.b "PRESS A KEY TO CONTINUE",13,0 +failed dc.b "FAILED ", 0 +ok dc.b "OK ", 0 + +;variables +nb65_param_buffer DS.B $20 diff --git a/client/test/test_gopher.s b/client/inc/gopher.i similarity index 75% rename from client/test/test_gopher.s rename to client/inc/gopher.i index 964af3c..3fd0d8b 100644 --- a/client/test/test_gopher.s +++ b/client/inc/gopher.i @@ -1,24 +1,24 @@ - .include "../inc/common.i" - .include "../inc/commonprint.i" - .include "../inc/net.i" - .include "../inc/char_conv.i" - .include "../inc/c64keycodes.i" +; C64 gopher browser +; july 2009 - jonno @ jamtronix.com +; this contains the key gopher rendering routines +; to use: +; 1) include this file +; 2) include these other files: +; .include "../inc/common.i" +; .include "../inc/commonprint.i" +; .include "../inc/net.i" +; .include "../inc/char_conv.i" +; .include "../inc/c64keycodes.i" +; 3) define a routine called 'exit_gopher' - .import get_key +; .import get_key .import get_key_if_available - .import __CODE_LOAD__ - .import __CODE_SIZE__ - .import __RODATA_SIZE__ - .import __DATA_SIZE__ - .import mul_8_16 .importzp acc16 - .importzp copy_src .importzp copy_dest .import copymem - .import tcp_connect .import tcp_send .import tcp_send_data_len @@ -39,31 +39,7 @@ ; pointer for moving through buffers buffer_ptr: .res 2 ; source pointer - - -.segment "STARTUP" ;this is what gets put at the start of the file on the C64 - - .word basicstub ; load address - - -basicstub: - .word @nextline - .word 2003 - .byte $9e - .byte <(((init / 1000) .mod 10) + $30) - .byte <(((init / 100 ) .mod 10) + $30) - .byte <(((init / 10 ) .mod 10) + $30) - .byte <(((init ) .mod 10) + $30) - .byte 0 -@nextline: - .word 0 - -.segment "EXEHDR" ;this is what gets put an the start of the file on the Apple 2 - .addr __CODE_LOAD__-$11 ; Start address - .word __CODE_SIZE__+__RODATA_SIZE__+__DATA_SIZE__+4 ; Size - jmp init - - + .data get_next_byte: lda $ffff @@ -76,9 +52,9 @@ get_next_byte: current_resource_history_entry: .byte 0 -.bss +.segment "APP_SCRATCH" -DISPLAY_LINES=24 +DISPLAY_LINES=22 page_counter: .res 1 MAX_PAGES = 50 page_pointer_lo: .res MAX_PAGES @@ -104,34 +80,20 @@ resource_hostname: .res RESOURCE_HOSTNAME_MAX_LENGTH resource_port: .res 2 resource_selector: .res 160 resource_selector_length: .res 1 +displayed_resource_type: .res 1 + RESOURCE_HISTORY_ENTRIES=8 resource_history: .res $100*RESOURCE_HISTORY_ENTRIES +input_buffer: + .res 16000 + .code -init: - - lda #14 - jsr print_a ;switch to lower case - - jsr print_cr - init_ip_via_dhcp - jsr print_ip_config - - jsr prompt_for_gopher_resource - bcs @use_default_start_page - rts -@use_default_start_page: - ldax #initial_location - sta resource_pointer_lo - stx resource_pointer_hi - ldx #0 - jsr select_resource - rts +;display whatever is in the buffer either as plain text or gopher text display_resource_in_buffer: - ldax #input_buffer stax get_next_byte+1 @@ -146,7 +108,7 @@ display_resource_in_buffer: ; lda page_counter ; jsr print_hex - jsr print_resource_description +; jsr print_resource_description ldx page_counter lda get_next_byte+1 sta page_pointer_lo,x @@ -154,10 +116,33 @@ display_resource_in_buffer: sta page_pointer_hi,x inc page_counter + + lda displayed_resource_type + cmp #'0' + bne @displayed_resource_is_directory + +;if this is a text file, just convert ascii->petscii and print to screen +@show_one_char: + jsr get_next_byte + tax ;this both sets up X as index into ascii_to_petscii_table, and sets Z flag + bne :+ + lda #1 + sta this_is_last_page + jmp @get_keypress +: + + lda ascii_to_petscii_table,x + jsr print_a + lda $d6 + cmp #DISPLAY_LINES + bmi @show_one_char + jmp @end_of_current_page + +@displayed_resource_is_directory: + lda #0 sta resource_counter - - + @next_line: jsr get_next_byte cmp #'.' @@ -176,6 +161,7 @@ display_resource_in_buffer: ;if we got here, we know not what it is jmp @skip_to_end_of_line @standard_resource: + pha ldx resource_counter sta resource_type,x sec @@ -186,17 +172,27 @@ display_resource_in_buffer: sbc #0 ;in case there was an overflow on the low byte sta resource_pointer_hi,x inc resource_counter - lda $d3 + + lda $d3 ;are we at the start of the current line? beq :+ jsr print_cr : - lda #18 + pla ;get back the resource type +; cmp #'1' +; beq @resource_is_a_dir +; lda #'-' +; jmp @print_resource_indicator +;@resource_is_a_dir: +; lda #'+' +;@print_resource_indicator: +; jsr print_a + lda #18 ;inverse mode on jsr print_a lda resource_counter clc adc #'a'-1 jsr print_a - lda #146 + lda #146 ;inverse mode off jsr print_a lda #' ' jsr print_a @@ -216,6 +212,8 @@ display_resource_in_buffer: jsr get_next_byte cmp #$0A bne @skip_to_end_of_line + + lda $d3 cmp #0 beq :+ @@ -223,7 +221,10 @@ display_resource_in_buffer: : lda $d6 cmp #DISPLAY_LINES - bmi @next_line + bpl @end_of_current_page + jmp @next_line + +@end_of_current_page: lda #0 sta this_is_last_page @done: @@ -246,8 +247,10 @@ display_resource_in_buffer: beq @back_in_history cmp #KEYCODE_F3 beq @back_in_history - cmp #KEYCODE_ABORT - + cmp #KEYCODE_F5 + beq @prompt_for_new_server + + cmp #KEYCODE_ABORT beq @quit ;if fallen through we don't know what the keypress means, go get another one and #$7f ;turn off the high bit @@ -260,7 +263,7 @@ display_resource_in_buffer: @valid_resource: tax dex - jsr select_resource + jsr select_resource_from_current_directory @not_a_resource: jmp @get_keypress @back_in_history: @@ -270,16 +273,21 @@ display_resource_in_buffer: stx current_resource_history_entry txa jsr load_resource_from_history - jmp display_resource_in_buffer + jsr load_resource_into_buffer + jmp display_resource_in_buffer @show_history: - jmp show_history + jsr show_history + jmp display_resource_in_buffer @go_next_page: lda this_is_last_page bne @get_keypress - jmp @do_one_page +@prompt_for_new_server: + jsr prompt_for_gopher_resource ;that routine only returns if no server was entered. + jmp display_resource_in_buffer + @quit: - rts + jmp exit_gopher @go_prev_page: ldx page_counter dex @@ -299,14 +307,16 @@ display_resource_in_buffer: ;get a gopher resource ;X should be the selected resource number ;the resources selected should be loaded into resource_pointer_* - -select_resource: +select_resource_from_current_directory: lda resource_pointer_lo,x sta buffer_ptr lda resource_pointer_hi,x sta buffer_ptr+1 ldy #0 ldx #0 + + lda (buffer_ptr),y + sta displayed_resource_type @skip_to_next_tab: iny beq @done_skipping_over_tab @@ -314,6 +324,7 @@ select_resource: cmp #$09 bne @skip_to_next_tab @done_skipping_over_tab: + ;should now be pointing at the tab just before the selector @copy_selector: iny @@ -429,7 +440,11 @@ show_history: sec sbc #1 bne @show_one_entry - + jsr print_cr + ldax #any_key_to_continue + jsr print + jsr get_key + rts ;load the 'current_resource' into the buffer @@ -444,8 +459,9 @@ load_resource_into_buffer: ldax #resource_hostname jsr dns_set_hostname - bcs @error + bcs :+ jsr dns_resolve +: bcs @error ldx #3 ; save IP address just retrieved @@ -468,6 +484,7 @@ load_resource_into_buffer: jsr print ldax #resource_selector jsr print + jsr print_cr ldx #0 stx download_flag stx dl_loop_counter @@ -507,6 +524,14 @@ gopher_download_callback: bne @not_end_of_file lda #1 sta download_flag + + ;put a zero byte at the end of the file (in case it was a text file) + ldax tcp_buffer_ptr + stax copy_dest + lda #0 + tay + sta (copy_dest),y + rts @not_end_of_file: @@ -561,9 +586,13 @@ print_resource_description: ; jsr print_hex ; jsr print_cr - ldax #gopher + ldax #server jsr print ldax #resource_hostname + + jsr print + jsr print_cr + ldax #selector jsr print ldax #resource_selector jsr print @@ -591,8 +620,9 @@ prompt_for_gopher_resource: sta resource_selector_length+1 lda #1 sta resource_selector_length + lda #'1' + sta displayed_resource_type jsr print_cr - clc jmp add_resource_to_history_and_display @no_server_entered: sec @@ -603,30 +633,21 @@ page_header: port_no: .byte "PORT NO ",0 history: -.byte "gopher history ",13,0 -gopher: -.byte "gopher://",0 +.byte "GOPHER HISTORY ",13,0 cr_lf: .byte $0D,$0A error: -.byte "error - code ",0 -resolving: -.byte "resolving ",0 +.byte "ERROR - CODE ",0 connecting: -.byte "connecting ",0 +.byte "CONNECTING ",0 retrieving: -.byte "retrieving ",0 +.byte "RETRIEVING ",0 gopher_server: -.byte "gopher server:",0 - -initial_location: -.byte "1gopher.floodgap.com",$09,"/",$09,"gopher.floodgap.com",$09,"70",$0D,$0A,0 -.byte "1luddite",$09,"",$09,"retro-net.org",$09,"70",$0D,$0A,0 -;.byte "1luddite",$09,"/luddite/",$09,"retro-net.org",$09,"70",$0D,$0A,0 +.byte "GOPHER " +server: +.byte "SERVER :",0 +selector: +.byte "SELECTOR :",0 +any_key_to_continue: +.byte "PRESS ANY KEY TO CONTINUE",0 -.bss -input_buffer: - .res 8000 -;.incbin "rob_gopher.txt" -;.incbin "retro_gopher.txt" -;.byte 0 diff --git a/client/inc/nb65_constants.i b/client/inc/nb65_constants.i index ffb646b..234fd76 100644 --- a/client/inc/nb65_constants.i +++ b/client/inc/nb65_constants.i @@ -32,9 +32,9 @@ NB65_GET_INPUT_PACKET_INFO EQU $11 ;inputs: AX points to a UDP/TCP packet pa NB65_SEND_UDP_PACKET EQU $12 ;inputs: AX points to a UDP packet parameter structure, outputs: none packet is sent NB65_UDP_REMOVE_LISTENER EQU $13 ;inputs: AX contains UDP port number that listener will be removed from -NB65_TCP_CONNECT EQU $14 ;inputs: AX points to a TCP connect parameter structure, outputs: A = connection # +NB65_TCP_CONNECT EQU $14 ;inputs: AX points to a TCP connect parameter structure, outputs: none NB65_SEND_TCP_PACKET EQU $15 ;inputs: AX points to a TCP send parameter structure, outputs: none packet is sent -NB65_TCP_CLOSE_CONNECTION EQU $16 ;inputs: A = connection # to close, outputs: none +NB65_TCP_CLOSE_CONNECTION EQU $16 ;inputs: none outputs: none NB65_TFTP_SET_SERVER EQU $20 ;inputs: AX points to a TFTP server parameter structure, outputs: none NB65_TFTP_DOWNLOAD EQU $22 ;inputs: AX points to a TFTP transfer parameter structure, outputs: TFTP param structure updated with @@ -53,6 +53,9 @@ NB65_PRINT_DOTTED_QUAD EQU $82 ;inputs: AX=pointer to 4 bytes that will NB65_PRINT_IP_CONFIG EQU $83 ;no inputs, no outputs, prints to screen current IP configuration +NB65_INPUT_HOSTNAME EQU $90 ;no inputs, outputs: AX = pointer to hostname (which may be IP address). +NB65_INPUT_PORT_NUMBER EQU $91 ;no inputs, outputs: AX = port number entered ($0000..$FFFF) + NB65_GET_LAST_ERROR EQU $FF ;no inputs, outputs A EQU error code (from last function that set the global error value, not necessarily the ;last function that was called) diff --git a/client/ip65/Makefile b/client/ip65/Makefile index c270aa3..c7915ce 100644 --- a/client/ip65/Makefile +++ b/client/ip65/Makefile @@ -16,7 +16,6 @@ ETHOBJS= \ cs8900a.o \ eth.o \ arp.o \ - ip.o \ icmp.o \ udp.o \ ip65.o \ @@ -27,22 +26,20 @@ ETHOBJS= \ dottedquad.o \ output_buffer.o\ tftp.o \ - tcp.o \ arithmetic.o\ - function_dispatcher.o \ all: ip65.lib ip65_tcp.lib -ip65.lib: $(ETHOBJS) +ip65.lib: $(ETHOBJS) function_dispatcher.s ip.s $(AS) $(AFLAGS) function_dispatcher.s $(AS) $(AFLAGS) ip.s - ar65 a ip65.lib $^ + ar65 a ip65.lib $(ETHOBJS) function_dispatcher.o ip.o -ip65_tcp.lib: tcp.o $(ETHOBJS) - $(AS) $(AFLAGS) function_dispatcher.s -DTCP +ip65_tcp.lib: tcp.o $(ETHOBJS) function_dispatcher.s ip.s tcp.s + $(AS) $(AFLAGS) function_dispatcher.s -DTCP -DAPI_VERSION=2 $(AS) $(AFLAGS) ip.s -DTCP - ar65 a ip65_tcp.lib $^ + ar65 a ip65_tcp.lib $(ETHOBJS) function_dispatcher.o ip.o tcp.o clean: rm -f *.o diff --git a/client/ip65/function_dispatcher.s b/client/ip65/function_dispatcher.s index 5fe7512..615b113 100644 --- a/client/ip65/function_dispatcher.s +++ b/client/ip65/function_dispatcher.s @@ -37,6 +37,7 @@ .import udp_send_dest .import udp_send_dest_port .import udp_send_len + .import copymem .import cfg_mac .import cfg_tftp_server @@ -45,7 +46,7 @@ ;reuse the copy_src zero page location nb65_params = copy_src - +buffer_ptr= copy_dest .data @@ -123,9 +124,9 @@ nb65_dispatcher: jsr dhcp_init bcc dhcp_ok jsr ip65_init ;if DHCP failed, then reinit the IP stack (which will reset IP address etc that DHCP messed with to cartridge default values) +dhcp_ok: lda #1 sta ip_configured_flag -dhcp_ok: irq_handler_installed: clc init_failed: @@ -135,6 +136,7 @@ ip_configured: lda irq_handler_installed_flag bne irq_handler_installed jsr install_irq_handler + clc rts : @@ -399,6 +401,101 @@ ip_configured: rts : +;these are the API "version 2" functions + +.ifdef API_VERSION +.if (API_VERSION>1) + + .segment "TCP_VARS" + port_number: .res 2 + .code + + cpy #NB65_TCP_CONNECT + bne :+ + .import tcp_connect + .import tcp_callback + .import tcp_connect_ip + ldy #3 +@copy_dest_ip: + lda (nb65_params),y + sta tcp_connect_ip,y + dey + bpl @copy_dest_ip + + ldy #NB65_TCP_CALLBACK + lda (nb65_params),y + sta tcp_callback + iny + lda (nb65_params),y + sta tcp_callback+1 + + ldy #NB65_TCP_PORT+1 + lda (nb65_params),y + tax + dey + lda (nb65_params),y + jmp tcp_connect + +: + + + +.import filter_dns +.import get_filtered_input +.import filter_number + cpy #NB65_INPUT_HOSTNAME + bne :+ + ldy #40 ;max chars + ldax #filter_dns + jmp get_filtered_input +: + +cpy #NB65_INPUT_PORT_NUMBER + bne :+ + .import mul_8_16 + .import acc16 + + ldy #5 ;max chars + ldax #filter_number + jsr get_filtered_input + bcs @no_port_entered + + ;AX now points a string containing port number + + stax buffer_ptr + lda #0 + sta port_number + sta port_number+1 + tay +@parse_port: + lda (buffer_ptr),y + cmp #$1F + bcc @end_of_port ;any control char should be treated as end of port field + ldax port_number + stax acc16 + lda #10 + jsr mul_8_16 + ldax acc16 + stax port_number + lda (buffer_ptr),y + sec + sbc #'0' + clc + adc port_number + sta port_number + bcc @no_rollover + inc port_number+1 +@no_rollover: + iny + bne @parse_port +@end_of_port: + ldax port_number + clc +@no_port_entered: + rts +: +.endif +.endif cpy #NB65_GET_LAST_ERROR bne :+ diff --git a/client/nb65/nb65_c64.s b/client/nb65/nb65_c64.s index 6cdd5a3..4fa1e5e 100644 --- a/client/nb65/nb65_c64.s +++ b/client/nb65/nb65_c64.s @@ -43,7 +43,11 @@ .include "../inc/common.i" .include "../inc/c64keycodes.i" .include "../inc/menu.i" - + +.if (BANKSWITCH_SUPPORT=$03) + .include "../inc/char_conv.i" + .include "../inc/gopher.i" +.endif .import cls .import beep .import exit_to_basic @@ -102,14 +106,13 @@ exit_cart: sta $0001 ;turns off ordinary cartridge by modifying HIRAM/LORAM (this will also bank out BASIC) .endif -get_value_of_axy: ;some more self-modifying code - lda $ffff,y - rts - call_downloaded_prg: jsr $0000 ;overwritten when we load a file jmp init +get_value_of_axy: ;some more self-modifying code + lda $ffff,y + rts .segment "CARTRIDGE_HEADER" @@ -117,7 +120,11 @@ call_downloaded_prg: .word $FE47 ;warm start vector .byte $C3,$C2,$CD,$38,$30 ; "CBM80" .byte $4E,$42,$36,$35 ; "NB65" - API signature -.byte $01 ;NB65_API_VERSION +.if (BANKSWITCH_SUPPORT=$03) +.byte $02 ;NB65_API_VERSION 2 requires 16KB cart +.else +.byte $01 ;NB65_API_VERSION 1 (in an 8KB cart) +.endif .byte BANKSWITCH_SUPPORT ; jmp nb65_dispatcher ; NB65_DISPATCH_VECTOR : entry point for NB65 functions jmp ip65_process ;NB65_PERIODIC_PROCESSING_VECTOR : routine to be periodically called to check for arrival of ethernet packets @@ -213,10 +220,19 @@ main_menu: bne @not_tftp jmp @tftp_boot @not_tftp: -.if !(BANKSWITCH_SUPPORT=$03) - cmp #KEYCODE_F3 - beq @exit_to_basic + + cmp #KEYCODE_F3 + .if (BANKSWITCH_SUPPORT=$03) + bne @not_f3 + jsr cls + lda #14 + jsr print_a ;switch to lower case + jsr prompt_for_gopher_resource ;only returns if no server was entered. + jmp exit_gopher + .else + beq @exit_to_basic .endif +@not_f3: cmp #KEYCODE_F5 bne @not_util_menu jsr print_main_menu @@ -515,7 +531,6 @@ cmp #KEYCODE_F7 exit_cart_via_ax: sta call_downloaded_prg+1 stx call_downloaded_prg+2 - jmp exit_cart print_errorcode: @@ -577,7 +592,13 @@ cfg_get_configuration_ptr: ldax #nb65_param_buffer nb65call #NB65_GET_IP_CONFIG rts - + +.if (BANKSWITCH_SUPPORT=$03) +exit_gopher: + lda #142 + jsr print_a ;switch to upper case + jmp main_menu +.endif .rodata netboot65_msg: diff --git a/client/test/Makefile b/client/test/Makefile index 150558a..b3dde21 100644 --- a/client/test/Makefile +++ b/client/test/Makefile @@ -42,7 +42,7 @@ all: \ test_tcp.prg: test_tcp.o $(IP65TCPLIB) $(C64PROGLIB) $(INCFILES) ../cfg/c64prg.cfg $(LD) -m test_tcp.map -vm -C ../cfg/c64prg.cfg -o test_tcp.prg $(AFLAGS) $< $(IP65TCPLIB) $(C64PROGLIB) -gopher_browser.prg: gopher_browser.o $(IP65TCPLIB) $(C64PROGLIB) $(INCFILES) ../cfg/c64prg.cfg +gopher_browser.prg: gopher_browser.o $(IP65TCPLIB) $(C64PROGLIB) $(INCFILES) ../cfg/c64prg.cfg ../inc/gopher.i $(LD) -m gopher_browser.map -vm -C ../cfg/c64prg.cfg -o gopher_browser.prg $(AFLAGS) $< $(IP65TCPLIB) $(C64PROGLIB) diff --git a/client/test/retro_gopher.txt b/client/test/retro_gopher.txt deleted file mode 100644 index 109117b..0000000 --- a/client/test/retro_gopher.txt +++ /dev/null @@ -1,16 +0,0 @@ -i WELCOME TO THE RETRONET GOPHER HOLE! error.host 1 -i error.host 1 -i _____ _____ _____ _____ _____ error.host 1 -i | __ | __|_ _| __ | | error.host 1 -i ,,, | -| __| | | | -| | | error.host 1 -i o^.^o |__|__|_____| |_| |__|__|_____| error.host 1 -i \"/ _____ _____ _____ _________ error.host 1 -i / ;; | | | __|_ _| / n___n \ error.host 1 -i~( )) | | | | __| | | ==/ (o) \== error.host 1 -i `L L |_|___|_____| |_| |_______| error.host 1 -i error.host 1 -1luddite /luddite/ retro-net.org 70 -1wgoodf /wgoodf/ retro-net.org 70 -1arfink /arfink/ retro-net.org 70 -1rsayers /rsayers/ retro-net.org 70 -. diff --git a/client/test/rob_gopher.txt b/client/test/rob_gopher.txt deleted file mode 100644 index 27ec906..0000000 --- a/client/test/rob_gopher.txt +++ /dev/null @@ -1,40 +0,0 @@ -igopher. error.host 1 -i ___ _ ___ error.host 1 -i| _ \ ___ | |__ / __| __ _ _ _ ___ _ _ ___ error.host 1 -i| / / _ \ | '_ \ \__ \ / _` | | || | / -_) | '_| (_-< error.host 1 -i|_|_\ \___/ |_.__/ |___/ \__,_| \_, | \___| |_| /__/ error.host 1 -i |__/ error.host 1 -i .com error.host 1 -i error.host 1 -iServer Info error.host 1 -0About this server /serverinfo.txt robsayers.com 7070 -7Search This Server (experiemental) /localsearch robsayers.com 70 -0Server Update /uptime.sh robsayers.com 70 -0Tail of server log /log.sh robsayers.com 70 -i error.host 1 -iPersonal error.host 1 -0About Me /aboutme.txt robsayers.com 70 -1Pics /pics robsayers.com 70 -0Latest Twitter Updates /twitter.txt robsayers.com 70 -0My GPG/PGP Public Key /pubkey.asc robsayers.com 70 -i error.host 1 -iGopher error.host 1 -0Why am I running gopher? /whygopher.txt robsayers.com 70 -1RedGopher - Ruby Gopher Client /redgopher robsayers.com 70 -0Search Engine Design Notes /searchengine.txt robsayers.com 70 -gXKCD Comic comic mentioning Gopher /not_enough_work.png robsayers.com 70 -1Links to other Gopher Sites /world gopher.floodgap.com 70 -i error.host 1 -iMisc. Geeky things error.host 1 -1My Tandy Model 100 /model100 robsayers.com 70 -0My Computers /computers.txt robsayers.com 70 -0Why Public Domain /why_public_domain.txt robsayers.com 70 -1Programs I have released /programs robsayers.com 70 -1Collection of Programming PDFs /documents robsayers.com 70 -12009 RetroChallenge /retrochallenge robsayers.com 70 -i error.host 1 -i error.host 1 -i error.host 1 -i error.host 1 -i error.host 1 -. diff --git a/doc/nb65_api_technical_reference.doc b/doc/nb65_api_technical_reference.doc index 5643bd96829b4fedeafb851f24d8cd86c2acee8a..fb757edad4708b5cf7d33fcfda04468bc4cbc208 100644 GIT binary patch delta 9464 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 delta 8098 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