To help get started, we've provided a simple netcat 'nc' like program that echoes received data, and sends keypresses to the specified host.
Atari BASIC
The BASIC version uses the N: handler, and thus is very compact and easy to understand. It is described in detail in this video: https://www.youtube.com/watch?v=LxT6w9JHfKk
100 OPEN #1,12,3,"N1:TCP://BBS.FOZZTEXX.NET/":OPEN #2,4,0,"K:"
101 TRAP 140
110 IF PEEK(764)<>255 THEN GET #2,K:PUT #1,K:XIO 15,#1,12,3,"N:"
120 STATUS #1,A:BW=PEEK(747)*256+PEEK(746):IF BW=0 THEN 110
130 FOR M=1 TO BW:GET #1,C:PUT #16,C:NEXT M:GOTO 110
140 CLOSE #1:? "DISCONNECTED.":END
You can load a copy of it by loading "D:DUMBTERM.BAS" from the atari-apps.irata.online/Networking/n-handler/n-handler.atr via TNFS.
Modifying:
- Change the hostname on line 100.
- You can set the translation on line 100, 3 = CR/LF, 2 = LF, 1 = CR, 0 = NONE. Also change it on line 110.
FastBasic
The latest version of FastBasic supports FujiNet. Learn about the the N commands in the FastBasic Manual. The code below demonstrates opening a connection, getting and putting data, and closing the connection when done. Press [Escape] to end the program.
DIM result(255) BYTE:POKE 65,0
NOPEN 1, 12, 3, "N:TCP://BBS.FOZZTEXX.COM:23"
REPEAT
NSTATUS 1: bw = peek(746) ' Check if data is available
IF bw: NGET 1, &result, bw: BPUT #0, &result, bw : ENDIF ' Get and write data to screen
IF KEY() : GET k: NPUT 1, &k, 1: ENDIF ' Send keypress
UNTIL k = 27
NCLOSE 1:? "DISCONNECTED.":GET k
C
The C version can be found here: https://github.com/FujiNetWIFI/fujinet-apps/tree/master/netcat/atari/src
It can be compiled with CC65: http://cc65.github.io/
It shows:
- How to use SIO directly to talk to FujiNet https://github.com/FujiNetWIFI/fujinet-apps/blob/master/netcat/atari/src/nio.c
- How to set the interrupt so that polls only occur when needed. https://github.com/FujiNetWIFI/fujinet-apps/tree/master/netcat/atari/src/main.c#L129
Assembler
An assembler version that compiles with the MAD Assembler (aka MADS), can be found here: https://github.com/FujiNetWIFI/fujinet-apps/tree/master/netcat-asm
Assembler source code
;
; NETCAT - THE Assembly / SIO VERSION - for FujiNet
; by Norman Davie
;
; Assembled using WUSDN / MADS
;
;
ORG $6000
; CIO
ICHID = $0340 ; Set by OS. Handler Identifier. If not
; in use, the value is 255 ($FF), which
; is also the initialization value.
ICDNO = ICHID+1 ; Set by OS. Device number (eg: D1: D2:).
ICCOM = ICHID+2 ; Set by User. Command
ICSTA = ICHID+3 ; Set by OS. May or may not be the same
; value as CMD_STATUS returned
ICBAL = ICHID+4 ; Set by User. Buffer address (low byte)
ICBAH = ICHID+5 ; Set by User. buffer address (high byte)
ICPTL = ICHID+6 ; Used by BASIC. Address of put byte routine.
ICPTH = ICHID+7 ; Used by BASIC. Address of put byte routine.
ICBLL = ICHID+8 ; buffer length (low byte) in put/get operations
ICBLH = ICHID+9 ; buffer length (high byte)
ICAX1 = ICHID+10 ; auxiliary information. Used by Open cmd
; for READ/WRITE/UPDATE
; Bit 7 6 5 4 3 2 1 0
; Use ....unused.... W R D A
; W equals write, R equals read,
; D equals directory, A equals append.
ICAX2 = ICHID+11 ; Auxiliary byte two
ICAX3 = ICHID+12 ; Auxiliary bytes three
ICAX4 = ICHID+13 ; Auxiliary bytes four
ICAX5 = ICHID+14 ; Auxiliary bytes five
ICAX6 = ICHID+15 ; Auxiliary bytes six
;
; BASIC CIO assignments
; Channel 0 Permanently assigned to the screen editor
; 6 Used for graphics commands
; 7 Used for the Cassette, disk and printer
;
; Only use these channels if you have BASIC running,
; otherwise they're all available
;IOCB1 = $10 ; Channel 1
;IOCB2 = $20 ; Channel 2
;IOCB3 = $30 ; Channel 3
;IOCB4 = $40 ; Channel 4
;IOCB5 = $50 ; Channel 5
;
CIOV = $E456 ; Y will contain status code after call
; SIO
SIOV = $E459 ; SIO VECTOR
EOL = $9B ; EOL
DDEVIC = $300 ; DEVICE SERIAL BUS ID
DUNIT = $301 ; SERIAL UNIT NUMBER
DCOMND = $302 ; SIO COMMAND
DSTATS = $303 ; SIO DATA DIRECTION
DBUFLO = $304 ; BUFFER LOW ADDRESS
DBUFHI = $305 ; BUFFER HIGH ADDRESS
DTIMLO = $306 ; SIO TIMEOUT
DTIMHI = $307
DBYTLO = $308 ; BUFFER LENGTH
DBYTHI = $309
DAUX1 = $30A ; AUXILARY BYTE---PRINTER MODE
DAUX2 = $30B ; AUXILARY BYTE---NOT USED
PACTL = $D302 ;
IRQ_ENABLE = $01 ; PACTL IRQ Enable: If set, enables interrupts from peripheral A.
VPRCED = $0202 ; Serial (peripheral) proceed line vector,
; initialize to 59314 ($E7B2), which is merely a PLA, RTI instruction sequence.
; It is used when an IRQ interrupt occurs due to the serial I/O bus proceed
; line which is available for peripheral use.
CH = $2FC ; keyboard status buffer
LMARGIN = $52
SOUNDR = $41
BRKKEY = $11 ; 0 = break key pressed
;
FUJI_DEVICE_ID = $71
FUJI_TRANSLATE_CR_LF = $03
FUJI_TRANSLATE_LF = $02
FUJI_TRANSLATE_CR = $01
FUJI_TRANSLATE_NONE = $00
;
FUJI_CLIENT = $00
FUJI_SERVER = $01
FUJI_TIMEOUT = $001F ; appoximately 30 seconds
;
; OPTIONS FOR ICCOM / XIO
;
; "record" of data, that is, data terminated by an ATASCII EOL ($9B) such as text.
CMD_OPEN = $03
CMD_GET_REC = $05 ; reads data from the device
; until a carriage-return
CMD_GET_CHARS = $07 ; get bytes
CMD_PUT_REC = $09 ; print until EOL
CMD_PUT_CHARS = $0B ; put bytes
CMD_CLOSE = $0C
CMD_STATUS = $0D
; STATUS CODES
; OS STATUS CODES
SUCCES = $01 ; SUCCESSFUL OPERATION
BRKABT = $80 ; (128) BREAK KEY ABORT
PRVOPN = $81 ; (129) IOCB ALREADY OPEN
NONDEV = $82 ; (130) NON-EX DEVICE
WRONLY = $83 ; (131) IOCB OPENED FOR WRITE ONLY
NVALID = $84 ; (132) INVALID COMMAND
NOTOPN = $85 ; (133) DEVICE OR FILE NOT OPEN
BADIOC = $86 ; (134) INVALID IOCB NUMBER
RDONLY = $87 ; (135) IOCB OPENED FOR READ ONLY
EOFERR = $88 ; (136) END OF FILE
TRNRCD = $89 ; (137) TRUNCATED RECORD
TIMOUT = $8A ; (138) DEVICE TIMEOUT
DNACK = $8B ; (139) DEVICE DOES NOT ACK COMMAND
FRMERR = $8C ; (140) SERIAL BUS FRAMING ERROR
CRSROR = $8D ; (141) CURSOR OUT OF RANGE
OVRRUN = $8E ; (142) SERIAL BUS DATA OVERRUN
CHKERR = $8F ; (143) SERIAL BUS CHECKSUM ERROR
DERROR = $90 ; (144) DEVICE ERROR (OPERATION INCOMPLETE)
BADMOD = $91 ; (145) BAD SCREEN MODE NUMBER
FNCNOT = $92 ; (146) FUNCTION NOT IN HANDLER
SCRMEM = $93 ; (147) INSUFFICIENT MEMORY FOR SCREEN MODE
;
; OPTIONS FOR OPEN ICAX1
OREAD = $04 ; Read Only
OWRITE = $08 ; Write Only
OUPDATE = $0C ; Read and write
ODIR = $02 ; Directory
ORDIR = $06 ; Read Directory
OAPPEND = $01 ; Append
OWAPPEND = $09 ; Write Append
SCLEAR_GT = $10 ; Clear Graphics and Text
SKEEP_GT = $20 ; Keep Graphics and Text
SCLEAR_T = $30 ; Clear Text
DWRITE = $80 ; sending data to SIO device
DREAD = $40 ; receiving data from the SIO device
DVSTAT = $02EA
DVSTAT_BYTES_WAITING_LO = $02EA
DVSTAT_BYTES_WAITING_HI = $02EB
DVSTAT_PROTOCOL = $02EC ; depends on the protocol
DVSTAT_EXTENDED_ERROR = $02ED
;
; largest GET/POST length is 2048, but we'll
; keep it to BUFSIZE characters
BUFSIZE = 256 ; NOTE: Code assumes a multiple of 256 byte buffer size
;
; ACCEPTABLE COMBINATIONS
;
; SCREEN (E:)
; OWRITE - Screen Output
; OUPDATE - Keyboard Input / Screen Write
; OUPDATE + OAPPEND - Keyboard Input / Screen Read and Write
; NOTE: E: BIT 0 equals one is a forced read (GET command).
;
; KEYBOARD (K:)
; OREAD
;
; PRINTER (P:)
; OWRITE
;
; RS-232 (R:)
; OWRITE - Block write
; OWRITE + OAPPEND - Concurrent write
; OUPDATE + OAPPEND - Concurrent read and write
;
; SCREEN (S:)
; Clear Text Read
; Screen Window Oper-
; after GR. also ation
;
; OWRITE yes no no
; OUPDATE yes no yes
; SCLEAR_GT + OWRITE yes yes no
; SCLEAR_GR + OUPDATE yes yes yes
; SKEEP_GT + OWRITE no no no
; SKEEP_GT + OUPDATE no no yes
; SCLEAR_T + OWRITE no yes no
; SCLEAR_T + OUPDATE no yes yes
;
;
; Set up screen and keyboard access
;
START:
LDA #$00 ; make the left margin column 0
STA LMARGIN
JSR GET_CHANNEL ; find an available IOCB channel
CPX #$80
BNE GOT_SCREEN_CHANNEL
JMP ERROR_HANDLER ; If X == $80, no CIO available
;
GOT_SCREEN_CHANNEL:
; Remember the channel we're using (Currently in X)
STX SCREEN_KEYBOARD_CHANNEL
; Open Available Channel to the Screen
LDA #CMD_OPEN ; open the device
STA ICCOM,X
LDA #<SCREEN_KEYBOARD_DEV ; address of the string of the device (E:)
STA ICBAL,X
LDA #>SCREEN_KEYBOARD_DEV
STA ICBAH,X
LDA #OUPDATE ; put chars to screen get chars from keyboard
STA ICAX1,X
LDA #$00 ; not used, but good practice to store zero here
STA ICAX2,X
STA ICBLH,X ; 0/0 End of string is indicated by $9B
STA ICBLL,X
JSR CIOV
BPL DISPLAY_VERSION ; Opened device
JMP ERROR_HANDLER ; Geesh, we couldn't even display an error message
; Display program name
DISPLAY_VERSION:
LDA #<PROG_NAME
LDY #>PROG_NAME
JSR PRINT_STRING
; Ask for the URL
ASK_QUESTION:
LDX SCREEN_KEYBOARD_CHANNEL
LDA #CMD_PUT_REC ; Prepare to send to screen
STA ICCOM,X
LDA #<ASK_SITE ; Get our string
STA ICBAL,X
LDA #>ASK_SITE
STA ICBAH,X
LDA #$00 ; EOL ends our string
STA ICBLL,X
LDA #$FF
STA ICBLH,X
JSR CIOV
BPL READ_URL ; start processig the user input
CMP #$80 ; break key was pressed?
BNE NOT_BREAK1b
JMP DISCONNECT
NOT_BREAK1b:
LDA #<KEYBOARD_FAILURE ; something very strange happened
LDY #>KEYBOARD_FAILURE
JSR PRINT_STRING
JMP ERROR_HANDLER
;
READ_URL:
; Get the users response or if he just presses RETURN use the default
LDA #CMD_GET_REC ; Get input from keyboard
STA ICCOM,X
LDA #<URL_FUJI ; Where to store the result
STA ICBAL,X
LDA #>URL_FUJI
STA ICBAH,X
LDA #<BUFSIZE ; Max buffer size
STA ICBLL,X
LDA #>BUFSIZE
STA ICBLH,X
JSR CIOV
BPL PROCESS_URL ; all is well
CMP #$80 ; is the break key held down?
BNE NOT_BREAK2
JMP DISCONNECT
NOT_BREAK2:
JMP ERROR_HANDLER ; An error occurred
;
PROCESS_URL:
CLC
LDA ICBLH,X ; returned length of string
ADC ICBLL,X
CMP #$01 ; Equals 1 if just pressed return (or unlikely 256 characters)
BNE ASK_TRANSLATION
; Copy the default URL
LDY #$FF
COPY_DEFAULT: ; if return was pressed, then copy the default url
INY ; which is a zero terminated string
LDA URL_DEFAULT,Y
STA URL_FUJI,Y
BNE COPY_DEFAULT
; TRANSLATION
ASK_TRANSLATION:
; this string has multiple EOL, so we can't use our print string routine
LDX SCREEN_KEYBOARD_CHANNEL
LDA #<TRANSLATION
STA ICBAL,X
LDA #>TRANSLATION
STA ICBAH,X
LDA #CMD_PUT_CHARS ; Prepare to send to screen
STA ICCOM,X
LDA #$00
STA ICBLH,X
LDA #<(END_TRANS-TRANSLATION) ; the number of characters to output
STA ICBLL,X
JSR CIOV
BPL READ_TRANSLATION ; All is well
CMP #$80
BNE NOT_BREAK1
JMP DISCONNECT
NOT_BREAK1:
LDA #<KEYBOARD_FAILURE
LDY #>KEYBOARD_FAILURE
JSR PRINT_STRING
JMP ERROR_HANDLER ; Oh oh
;
READ_TRANSLATION:
; read the keyboard
LDA #CMD_GET_REC ; Get input from keyboard
STA ICCOM,X
LDA #<TEMP_BUFFER ; Where to store the result
STA ICBAL,X
LDA #>TEMP_BUFFER
STA ICBAH,X
LDA #<BUFSIZE ; Max buffer size
STA ICBLL,X
LDA #>BUFSIZE
STA ICBLH,X
JSR CIOV
BPL PROCESS_TRANS ; all is well
CMP #$80 ; is the break key held down?
BNE NOT_BREAK2b
JMP DISCONNECT
NOT_BREAK2b:
JMP ERROR_HANDLER ; An error occurred
;
PROCESS_TRANS:
LDA TEMP_BUFFER
CMP #$9B ; Equals 1 if just pressed return (or unlikely 256 characters)
BNE USER_TRANSLATION
; Copy the default URL
LDA #FUJI_TRANSLATE_CR_LF
BNE STORE_TRANS
USER_TRANSLATION:
SEC
SBC '0'
STORE_TRANS:
STA TRANS
ASK_ECHO:
LDA #<ECHO_INPUT
LDY #>ECHO_INPUT
JSR PRINT_STRING
READ_ECHO:
; read the keyboard
LDA #CMD_GET_REC ; Get input from keyboard
STA ICCOM,X
LDA #<TEMP_BUFFER ; Where to store the result
STA ICBAL,X
LDA #>TEMP_BUFFER
STA ICBAH,X
LDA #<BUFSIZE ; Max buffer size
STA ICBLL,X
LDA #>BUFSIZE
STA ICBLH,X
JSR CIOV
BPL PROCESS_ECHO ; all is well
CMP #$80 ; is the break key held down?
BNE NOT_BREAK2c
JMP DISCONNECT
NOT_BREAK2c:
JMP ERROR_HANDLER ; An error occurred
;
PROCESS_ECHO:
LDA TEMP_BUFFER
CMP #$9B ; Did the user just press RETURN?
BEQ NO_ECHO ; use default
CMP #'Y' ; How about Yes
BNE NO_ECHO
LDA #$01 ; Equals 1 if just pressed return (or unlikely 256 characters)
BNE STORE_ECHO
NO_ECHO:
LDA #$00 ; Store No echo
STORE_ECHO:
STA ECHO
;
;
; Set up screen and keyboard access
;
OPEN_KEYBOARD:
JSR GET_CHANNEL ; we get a keyboard channel so we don't get echo
CPX #$80
BNE FOUND_KEYBOARD_CHANNEL
JMP ERROR_HANDLER ; If X == $80, no CIO available
;
FOUND_KEYBOARD_CHANNEL:
; Remember the channel we're using (Stored in X)
STX KEYBOARD_CHANNEL
; Open Available Channel to the Keyboard
LDA #CMD_OPEN ; open the device
STA ICCOM,X
LDA #<KEYBOARD_DEV ; address of the string of the device (E)
STA ICBAL,X
LDA #>KEYBOARD_DEV
STA ICBAH,X
LDA #OREAD ; get chars from keyboard
STA ICAX1,X
LDA #$00
STA ICAX2,X
STA ICBLH,X ; Not needed for open
STA ICBLL,X
JSR CIOV
BPL OPEN_KEYBOARD_OK ; Opened device successfully
TYA
PHA
LDA #<KEYBOARD_FAILURE
LDY #>KEYBOARD_FAILURE
JSR PRINT_STRING
PLA
JMP ERROR_HANDLER
OPEN_KEYBOARD_OK:
GET_UNIT_NUMBER:
LDY #1 ; Unit number is the 2nd or 3rd character
LDA URL_FUJI,Y
CMP #':' ; if we found a : then it must be the 3rd
BNE GET_NUM
LDA #$01
BNE UNIT_NUM
GET_NUM:
SEC
SBC #'0' ; we should do some sanity check here, but nah
UNIT_NUM:
STA UNIT ; store it as a raw number
SET_INTERRUPTS:
; insure interrupts are off before changing vector
LDA PACTL ; IRQ Enable: If set, enables interrupts from peripheral A.
STA OLD_INTERRUPT_STATE
AND #~IRQ_ENABLE ; disable the interrupt
STA PACTL
LDA VPRCED ; save the old interrupt vector, but jump to it after
STA OLD_INTERRUPT_HANDLER ; we process it
LDA VPRCED+1
STA OLD_INTERRUPT_HANDLER+1
LDA #<INTERRUPT_HANDLER ; put our interrupt handler in place
STA VPRCED
LDA #>INTERRUPT_HANDLER
STA VPRCED+1
LDA #$00
STA FUJI_DATA_WAITING ; before we start interrupts, let's tell it we have no data
LDA PACTL ; IRQ Enable
ORA #IRQ_ENABLE
STA PACTL
;---------------------------------------------------------------
;---------------------------------------------------------------
; OPEN
;---------------------------------------------------------------
;---------------------------------------------------------------
OPEN_FUJI:
LDA #<CONNECTING ; show the user where we're connecting to
LDY #>CONNECTING
JSR PRINT_STRING
LDA #<URL_FUJI
LDY #>URL_FUJI
JSR PRINT_STRING
LDA #FUJI_DEVICE_ID ; Device
STA DDEVIC
LDA UNIT
STA DUNIT
LDA #'O' ; Open
STA DCOMND
LDA #DWRITE ; sending filespec to SIO
STA DSTATS
LDA #<URL_FUJI ; filespec
STA DBUFLO
LDA #>URL_FUJI
STA DBUFHI
LDA #<FUJI_TIMEOUT ; Approximate the number of seconds to wait
STA DTIMLO
LDA #>FUJI_TIMEOUT
STA DTIMHI
LDA #<BUFSIZE ; the size of the buffer containing the URL
STA DBYTLO
LDA #>BUFSIZE
STA DBYTHI
LDA #OUPDATE ; Read and Write
STA DAUX1
LDA #FUJI_TRANSLATE_CR_LF ; translate cr and lf
STA DAUX2
JSR SIOV
LDA DSTATS ; find out if we were successful
CMP #SUCCES
BEQ OPEN_FUJI_OK ; Opened device successfully
; OPEN FAILURE
TYA
PHA
LDA #<FUJI_ERROR
LDY #>FUJI_ERROR
JSR PRINT_STRING
PLA
PHA
CMP #TIMOUT
BNE NOT_TIMEOUT4
LDA #<DEVICE_TIMEOUT_FAILURE ; we timed out
LDY #>DEVICE_TIMEOUT_FAILURE
JSR PRINT_STRING
NOT_TIMEOUT4:
PLA
JSR PRINT_HEX ; print the error code
JMP ERROR_HANDLER
OPEN_FUJI_OK:
LDA #$00 ; at this point, no more sound
STA SOUNDR ; SILENCE
; MAIN EVENT!
;
CHECK_KEYBOARD:
LDA BRKKEY ; Doing it this way works
BNE LOOK_KEYS ; on all versions of roms
JMP DISCONNECT
LOOK_KEYS:
LDA CH ; did the user press a key?
CMP #$FF
BNE GET_KEY
JMP FUJI_STATUS
GET_KEY:
; Get the key but don't display it
LDX KEYBOARD_CHANNEL
LDA #CMD_GET_CHARS ; Get character from keyboard
STA ICCOM,X
LDA #<TEMP_BUFFER ; Where to store the character
STA ICBAL,X
LDA #>TEMP_BUFFER
STA ICBAH,X
LDA #$01 ; Just one character
STA ICBLL,X
LDA #$00
STA ICBLH,X
JSR CIOV
BPL SEND_KEY ; all is well
CMP #$80
BNE NOT_BREAK3
JMP DISCONNECT
NOT_BREAK3:
LDA #<KEYBOARD_FAILURE ; what the???
LDY #>KEYBOARD_FAILURE
JSR PRINT_STRING
JMP DISCONNECT ; assume disconnected
LDA ECHO
BEQ SEND_KEY
ECHO_KEYS:
; Print the data
LDX SCREEN_KEYBOARD_CHANNEL
LDA #CMD_PUT_CHARS ; Prepare to send to screen
STA ICCOM,X
LDA #<TEMP_BUFFER ; Where to store the result
STA ICBAL,X
LDA #>TEMP_BUFFER
STA ICBAH,X
LDA #$01 ; How many characters to send
STA ICBLL,X
LDA #$00
STA ICBLH,X
JSR CIOV
BPL KEY_PRINTED ; All is well
CMP #$80
BNE NOT_BREAK4
JMP DISCONNECT
NOT_BREAK4:
TYA
PHA
LDA #<SCREEN_PUT_FAILURE
LDY #>SCREEN_PUT_FAILURE
JSR PRINT_STRING
PLA
JMP DISCONNECT ; Assume disconnect
;
KEY_PRINTED:
JMP SEND_KEY
;
;
;---------------------------------------------------------------
;---------------------------------------------------------------
; WRITE
;---------------------------------------------------------------
;---------------------------------------------------------------
SEND_KEY:
; Send the key to the outside world
LDA #FUJI_DEVICE_ID ; Device
STA DDEVIC
LDA UNIT
STA DUNIT
LDA #'W'
STA DCOMND
LDA #DWRITE
STA DSTATS
LDA #<TEMP_BUFFER
STA DBUFLO
LDA #>TEMP_BUFFER
STA DBUFHI
LDA #<FUJI_TIMEOUT
STA DTIMLO
LDA #>FUJI_TIMEOUT
STA DTIMHI
LDA #$01 ; one character to send
STA DBYTLO ; need to send both buffer size and the daux
STA DAUX1
LDA #$00
STA DBYTHI
STA DAUX2
JSR SIOV
LDA DSTATS
CMP #SUCCES
BEQ FUJI_STATUS ; all is well
TYA
PHA
LDA #<PUT_FUJI_FAILURE
LDY #>PUT_FUJI_FAILURE
JSR PRINT_STRING
PLA
PHA
CMP #TIMOUT
BNE NOT_TIMEOUT3
LDA #<DEVICE_TIMEOUT_FAILURE
LDY #>DEVICE_TIMEOUT_FAILURE
JSR PRINT_STRING
NOT_TIMEOUT3:
PLA
JSR PRINT_HEX
JMP DISCONNECT ; assume disconnect
;---------------------------------------------------------------
;---------------------------------------------------------------
; STATUS
;---------------------------------------------------------------
;---------------------------------------------------------------
FUJI_STATUS:
LDA #FUJI_DEVICE_ID ; Device
STA DDEVIC
LDA UNIT
STA DUNIT
LDA #'S'
STA DCOMND
LDA #DREAD ; SIO is going to send us data
STA DSTATS
LDA #<DVSTAT ; fuji status will return length of buffer here
STA DBUFLO
LDA #>DVSTAT
STA DBUFHI
LDA #<FUJI_TIMEOUT
STA DTIMLO
LDA #>FUJI_TIMEOUT
STA DTIMHI
LDA #$04 ; four bytes
STA DBYTLO
LDA #$00
STA DBYTHI
STA DAUX1
STA DAUX2
JSR SIOV
CHECK_FOR_FUJI_DATA:
LDA FUJI_DATA_WAITING ; set by interrupt handler
BNE GET_FUJI_BYTES_WAITING ; something is coming in!
JMP CHECK_KEYBOARD ; if zero, no data
GET_FUJI_BYTES_WAITING:
;
; data has shown up!
;
CLC ; check and see if we have data
LDA DVSTAT_BYTES_WAITING_LO
ADC DVSTAT_BYTES_WAITING_HI
BNE FUJI_DATA_INCOMING ; yes
JMP CHECK_KEYBOARD ; nope
FUJI_DATA_INCOMING:
LDA DVSTAT_BYTES_WAITING_LO ; find out how much data we have
STA DATA_SIZE_LO
LDA DVSTAT_BYTES_WAITING_HI
STA DATA_SIZE_HI
CMP #>BUFSIZE ; is it more data than our buffer?
BMI BUFFER_SIZE_OK
; if the amount of data coming is
; greater than our buffer, clamp it
LDA #<BUFSIZE
STA DATA_SIZE_LO
LDA #>BUFSIZE
STA DATA_SIZE_HI
BUFFER_SIZE_OK:
;---------------------------------------------------------------
;---------------------------------------------------------------
; READ
;---------------------------------------------------------------
;---------------------------------------------------------------
GET_FUJI_DATA:
LDA #FUJI_DEVICE_ID ; Device
STA DDEVIC
LDA UNIT
STA DUNIT
LDA #'R'
STA DCOMND
LDA #DREAD
STA DSTATS
LDA #<TEMP_BUFFER
STA DBUFLO
LDA #>TEMP_BUFFER
STA DBUFHI
LDA #<FUJI_TIMEOUT
STA DTIMLO
LDA #>FUJI_TIMEOUT
STA DTIMHI
LDA DATA_SIZE_HI
STA DBYTHI
STA DAUX2
LDA DATA_SIZE_LO
STA DBYTLO
STA DAUX1
JSR SIOV
LDA DSTATS
CMP #SUCCES
BEQ DATA_HANDLED ; all is well
TYA
PHA
LDA #<FUJI_DATA_FAILURE
LDY #>FUJI_DATA_FAILURE
JSR PRINT_STRING
PLA
PHA
CMP #TIMOUT
BNE NOT_TIMEOUT2
LDA #<DEVICE_TIMEOUT_FAILURE
LDY #>DEVICE_TIMEOUT_FAILURE
JSR PRINT_STRING
NOT_TIMEOUT2:
PLA
JMP DISCONNECT
DATA_HANDLED:
LDA #$00
STA FUJI_DATA_WAITING
; re-enable interrupts
LDA PACTL ; IRQ Enable: If set, enables interrupts from peripheral A.
ORA #IRQ_ENABLE ; If clear, interrupts are disabled.
STA PACTL
;
PRINT_INCOMING:
; Print the data
LDX SCREEN_KEYBOARD_CHANNEL
LDA #CMD_PUT_CHARS ; Prepare to send to screen
STA ICCOM,X
LDA #<TEMP_BUFFER ; Where to store the result
STA ICBAL,X
LDA #>TEMP_BUFFER
STA ICBAH,X
LDA DATA_SIZE_LO ; How many characters to send
STA ICBLL,X
LDA DATA_SIZE_HI
STA ICBLH,X
JSR CIOV
BPL DATA_PRINTED ; All is well
CMP #$80
BNE NOT_BREAK5
JMP DISCONNECT
NOT_BREAK5:
TYA
PHA
LDA #<SCREEN_PUT_FAILURE
LDY #>SCREEN_PUT_FAILURE
JSR PRINT_STRING
PLA
JMP DISCONNECT ; Assume disconnect
;
DATA_PRINTED:
JMP CHECK_KEYBOARD
;
DISCONNECT:
LDA #<DISCONNECTED
LDY #>DISCONNECTED
JSR PRINT_STRING
;
ERROR_HANDLER:
; Wait for RETURN key from screen *IF* we have the screen and keyboard channel
JSR WAIT_FOR_RETURN
;
CLOSE_ALL:
; Close all the channels, but
; don't close channels that were never opened
LDX SCREEN_KEYBOARD_CHANNEL
JSR CLOSE_CHANNEL
LDX KEYBOARD_CHANNEL
JSR CLOSE_CHANNEL
;
;---------------------------------------------------------------
;---------------------------------------------------------------
; restore the old interrupt state
;---------------------------------------------------------------
;---------------------------------------------------------------
LDA PACTL ; IRQ Enable: If set, enables interrupts from peripheral A.
AND #~IRQ_ENABLE ; If clear, interrupts are disabled.
STA PACTL
LDA OLD_INTERRUPT_HANDLER+1 ; Put back the old vector
STA VPRCED+1
LDA OLD_INTERRUPT_HANDLER
STA VPRCED
LDA OLD_INTERRUPT_STATE ; if interrupts were enabled before we started
AND #IRQ_ENABLE ; restore interrupts
ORA PACTL
STA PACTL
;---------------------------------------------------------------
;---------------------------------------------------------------
; CLOSE
;---------------------------------------------------------------
;---------------------------------------------------------------
;
LDA #FUJI_DEVICE_ID ; Device
STA DDEVIC
LDA UNIT
STA DUNIT
LDA #'C'
STA DCOMND
LDA #$00
STA DSTATS
STA DBUFLO
STA DBUFHI
LDA #<FUJI_TIMEOUT
STA DTIMLO
LDA #>FUJI_TIMEOUT
STA DTIMHI
LDA #$00
STA DBYTHI
LDA #$01
STA DBYTLO
LDA #$00
STA DAUX1
STA DAUX2
JSR SIOV
LDA #$01 ; Let the noise return
STA SOUNDR ; NOISE
RTS
;====================================
; WAIT_FOR_RETURN
; If screen_keyboard_channel is open
; then waits for enter key
; RETURNS
; Nothing
; OTHER REGISTERS AFFECTED
; None
;====================================
WAIT_FOR_RETURN:
; Wait for enter key from screen *IF* we have the screen and keyboard channel
PHA ; SAVE REGISTERS
TXA
PHA
TYA
PHA
LDX SCREEN_KEYBOARD_CHANNEL
CPX #$7F
BPL WFE_DONE
LDA #<WAITING
LDY #>WAITING
JSR PRINT_STRING
; wait for a key
LDX SCREEN_KEYBOARD_CHANNEL
LDA #CMD_GET_REC ; Get input from keyboard
STA ICCOM,X
LDA #<WAIT_BUFFER ; Where to store the result
STA ICBAL,X
LDA #>WAIT_BUFFER
STA ICBAH,X
LDA #<BUFSIZE ; Max buffer size
STA ICBLL,X
LDA #>BUFSIZE
STA ICBLH,X
JSR CIOV
PLA ; RESTORE REGISTERS
TAY
PLA
TAX
PLA
WFE_DONE:
RTS
DUMP_SIO:
LDY #00
DUMP:
LDA DDEVIC,Y
JSR PRINT_HEX_NO_EOL
INY
CPY #17
BNE DUMP
LDA #<EOL_ENDING
LDY #>EOL_ENDING
JSR PRINT_STRING
RTS
;====================================
; CLOSE_CHANNEL
; Close channel if open
; X = channel to close
; will be $80 if never opened
; RETURNS
; Nothing
; OTHER REGISTERS AFFECTED
; ALL
;====================================
CLOSE_CHANNEL:
CPX #$7F
BPL ALREADY_CLOSED
; Close the channel
LDA #CMD_CLOSE
STA ICCOM,X
JSR CIOV
ALREADY_CLOSED:
RTS
;
;====================================
; GET_CHANNEL
; Look for an unused channel
; RETURNS
; X - Available channel * 16
; if X == $80, no channel found
; OTHER REGISTERS AFFECTED
; ALL
;====================================
GET_CHANNEL:
LDY #$00
CHECK_CHANNEL:
TYA ; Transfer Y to the Accumulator
CLC
ROL ; Multiply it by 16
ROL
ROL
ROL
TAX ; Transfer A to the X register
LDA ICHID,X
CMP #$FF ; if the Channel ID is FF it's available
BEQ AVAILABLE
INY
CMP #$07
BNE CHECK_CHANNEL
LDX #$80 ; $80 indicates no channel available
AVAILABLE:
RTS
;
;====================================
; PRINT_STRING
; Displays message on screen
; A = low byte of address
; Y = high byte of address
; assumes succefull open of screen
; RETURNS
; NOTHING
; OTHER REGISTERS AFFECTED
; NONE
;====================================
PRINT_STRING:
LDX SCREEN_KEYBOARD_CHANNEL
STA ICBAL,X
TYA
STA ICBAH,X
LDA #CMD_PUT_REC ; Prepare to send to screen
STA ICCOM,X
LDA #$00
STA ICBLL,X
LDA #$FF
STA ICBLH,X
JSR CIOV
RTS
;
;====================================
; STRING_LENGTH
; Calculate string length ended by $9B
; RETURNS
; Y = Length
; OTHER REGISTERS AFFECTED
; A
;====================================
STRING_LENGTH:
LDY #$FF
TEST:
INY
LDA URL_FUJI,Y
CMP #$9B
BNE TEST
INY ; length is not zero based
RTS
;
;====================================
; PRINT_HEX_NO_EOL
; Displays the A register on screen
; assumes succefull open of screen
; RETURNS
; NOTHING
; REGISTERS AFFECTED
; NONE
;====================================
PRINT_HEX_NO_EOL:
PHA ; Save A once
PHA ; Save A again
PHA ; Third times the charm
LDA #$20 ; SPACE
STA HEX_ENDING
PLA
LSR
LSR
LSR
LSR
CLC
CMP #$0A
BPL A_F3
ADC #'0'
JMP STORE_TOP_NIBBLE1
A_F3:
ADC #'A'-$0B
STORE_TOP_NIBBLE1:
STA HEX_BUFFER
;
PLA ; Get back A
AND #$0F
CLC
CMP #$0A
BPL A_F4
ADC #'0'
JMP STORE_BOT_NIBBLE1
A_F4:
ADC #'A'-$0B
STORE_BOT_NIBBLE1:
STA HEX_BUFFER+1
; $9B is already in HEX_BUFFER+2
TXA
PHA ; Save X
TYA ; Save Y
PHA
LDX SCREEN_KEYBOARD_CHANNEL
LDA #<HEX_BUFFER
STA ICBAL,X
LDA #>HEX_BUFFER
STA ICBAH,X
LDA #CMD_PUT_CHARS ; Prepare to send to screen
STA ICCOM,X
LDA #$00
STA ICBLH,X
LDA #$03
STA ICBLL,X
JSR CIOV
PLA
TAY ; Restore Y
PLA
TAX ; Restore X
PLA ; Restore A
RTS
;====================================
; PRINT_HEX
; Displays the A register on screen
; assumes succefull open of screen
; RETURNS
; NOTHING
; REGISTERS AFFECTED
; NONE
;====================================
PRINT_HEX:
PHA ; Save A once
PHA ; Save A again
PHA ; Third times the charm
LDA #$9B ; EOL
STA HEX_ENDING
PLA
LSR
LSR
LSR
LSR
CLC
CMP #$0A
BPL A_F1
ADC #'0'
JMP STORE_TOP_NIBBLE
A_F1:
ADC #'A'-$0B
STORE_TOP_NIBBLE:
STA HEX_BUFFER
;
PLA ; Get back A
AND #$0F
CLC
CMP #$0A
BPL A_F2
ADC #'0'
JMP STORE_BOT_NIBBLE
A_F2:
ADC #'A'-$0B
STORE_BOT_NIBBLE:
STA HEX_BUFFER+1
; $9B is already in HEX_BUFFER+2
TXA
PHA ; Save X
TYA
PHA ; Save Y
LDA #<HEX_BUFFER
LDY #>HEX_BUFFER
JSR PRINT_STRING
PLA
TAY ; Restore Y
PLA
TAX ; Restore X
PLA ; Restore A
RTS
;------------------------
;------------------------
;------------------------
;------------------------
INTERRUPT_HANDLER:
LDA #$01
STA FUJI_DATA_WAITING
.BYTE $4C ; JMP
OLD_INTERRUPT_HANDLER: ; to old vector
.WORD 0
;=================DATA===============
;
ASK_SITE: .byte 'Enter URL (ENTER for default): ',$9B
TRANSLATION: .byte 'TRANSLATION',$9B
.byte ' 0 = NONE, 1=CR, 2=LF, *3=CR/LF?',$9B
.byte ' *=Default',$9B
END_TRANS:
ECHO_INPUT: .byte 'LOCAL ECHO? Y=YES, *NO? *Default', $9B
CONNECTING: .byte 'Connecting to: ',$9B
DISCONNECTED: .byte 'Disconnected.',$9B
WAITING: .byte 'Press RETURN.',$9B
FUJI_OPENED: .byte 'Fuji successfully opened.',$9B
NO_CHANNEL_ERROR: .byte 'No CIO channel available',$9B
FUJI_ERROR: .byte 'Could not open FujiNet Device.',$9B
STATUS_FAILURE: .byte 'Status failure.', $9B
KEYBOARD_FAILURE .byte 'Keyboard failure', $9B
FUJI_DATA_FAILURE: .byte 'Fuji data failure.',$9B
SCREEN_PUT_FAILURE: .byte 'Screen Output failure.',$9B
PUT_FUJI_FAILURE: .byte 'Put fuji failure.',$9B
DEVICE_TIMEOUT_FAILURE: .byte 'Device Timeout.',$9B
KEYBOARD_DEV: .byte 'K:',$9B
SCREEN_KEYBOARD_DEV: .byte 'E:',$9B
FUJI_DEV: .byte 'N:',$9B
KEYBOARD_CHANNEL: .BYTE $80
SCREEN_KEYBOARD_CHANNEL: .BYTE $80
ECHO .BYTE $01
TRANS .BYTE $03
FUJI_DATA_WAITING .BYTE $00
OLD_INTERRUPT_STATE .BYTE $00
HEX_BUFFER: .BYTE $00,$00
HEX_ENDING: .BYTE $9B
EOL_ENDING .BYTE $9B
PROG_NAME: .byte 'NETCAT SIO/ASM Version 2020/11/13 5:00',$9B
URL_DEFAULT: .byte 'N1:TCP://BBS.FOZZTEXX.NET/',$9B,00
DATA_SIZE_LO: .BYTE 0
DATA_SIZE_HI: .BYTE 0
UNIT .BYTE 0
URL_FUJI: .DS BUFSIZE
TEMP_BUFFER: .DS BUFSIZE
WAIT_BUFFER: .DS BUFSIZE
run START
ACTION!
https://github.com/FujiNetWIFI/fujinet-apps/tree/master/netcat-action
NIO.ACT - Example Network I/O bindings
;
; #FujiNet Network I/O Library
; For ACTION!
;
; Author: Thomas Cherryhomes
; <thom.cherryhomes@gmail.com>
;
; These routines call the #Fujinet
; directly from SIO, and thus do
; not need the N: handler (NDEV)
;
MODULE
;
; PROCEED interrupt vars
;
CARD VPRCEDSAVE ; Save vector for vprced
CARD VPRCED = $0202 ; Proceed vector
BYTE PACTL = $D302 ; PIA Control for Proceed
BYTE trip ; Trip FLAG.
;
; DVSTAT (Status)
;
BYTE DVSTAT = $02EA ; PTR TO DVSTAT
BYTE EXTERR = $02ED ; DVSTAT+3
;
; DEVICE CONTROL BLOCK (DCB)
;
BYTE DDEVIC = $0300 ; Device #
BYTE DUNIT = $0301 ; Unit #
BYTE DCOMND = $0302 ; Command
BYTE DSTATS = $0303 ; <-> and error
CARD DBUF = $0304 ; buffer
BYTE DTIMLO = $0306 ; timeout secs
BYTE DUNUSE = $0307 ; reserved
CARD DBYT = $0308 ; pyld byte len
CARD DAUX = $030A ; daux1/daux2
BYTE DAUX1 = $030A ; daux1
BYTE DAUX2 = $030B ; daux2
;
; Interrupt handler
;
; A9 01 LDA #$01
; 8D XX XX STA trip
; 68 PLA
; 40 RTI
;
PROC ninterrupt_handler=*()
[$A9$01$8D trip $68$40]
;
; Enable Interrupt handler.
;
PROC nenableproc()
trip=0
VPRCEDSAVE=VPRCED
VPRCED=ninterrupt_handler
PACTL = PACTL % 1
RETURN
;
; Disable Interrupt handler.
;
PROC ndisableproc()
trip=0
VPRCED=VPRCEDSAVE
RETURN
;
; PROC to call SIO Vector (SIOV)
;
PROC siov=$E459()
;
; Get the unit number from devicespec
;
BYTE FUNC ngetunit(BYTE ARRAY ds)
BYTE unit=1
IF ds(2)=': THEN
unit=1
ELSEIF ds(3)=': THEN
unit=ds(2)-$30
ELSE
unit=1
FI
RETURN (unit)
;
; Get status of last NIO operation,
; Return in DVSTAT
;
; @param devicespec N: devicespec
;
PROC nstatus(BYTE ARRAY ds)
DDEVIC = $71
DUNIT = ngetunit(ds)
DCOMND = 'S ; STATUS
DSTATS = $40 ; Payload to Atari
DBUF = $02EA ; status buffer
DTIMLO = $1F ; 32 second timeout
DBYT = 4 ; 4 byte payload
DAUX1 = 0 ; R/W
DAUX2 = 0 ; translation
siov()
RETURN
;
; Return error of last NIO operation.
; If SIO error = 144, then a status
; is done, and the extended err is
; returned.
;
; @param devicespec N: devicespec
; @return error 1=successful
;
BYTE FUNC geterror(BYTE ARRAY ds)
BYTE errno
IF DSTATS=144 THEN
nstatus(ds)
errno=EXTERR
ELSE
errno=DSTATS
FI
RETURN (errno)
;
; Open the N: device pointed to by
; devicespec.
;
; @param devicespec N: devicespec
; @param trans - translation mode
; 0=NONE, 1=CR, 2=LF, 3=CR/LF
; @return error, 1=successful.
;
BYTE FUNC nopen(BYTE ARRAY ds, BYTE t)
DDEVIC = $71
DUNIT = ngetunit(ds)
DCOMND = 'O ; OPEN
DSTATS = $80 ; Write to fujinet
DBUF = ds ; send devicespec
DTIMLO = $1F ; 32 second timeout
DBYT = 256 ; 256 byte payload
DAUX1 = 12 ; R/W
DAUX2 = t ; translation
siov();
IF DSTATS=1 THEN
nenableproc()
FI
RETURN (geterror(ds))
;
; Close the N: device pointed to by
; devicespec.
;
; @param devicespec N: devicespec
; @return error, 1=successful.
;
BYTE FUNC nclose(BYTE ARRAY ds)
DDEVIC = $71
DUNIT = ngetunit(ds)
DCOMND = 'C ; CLOSE
DSTATS = $00 ; No Payload
DBUF = 0 ; send devicespec
DTIMLO = $1F ; 32 second timeout
DBYT = 0 ; No payload
DAUX1 = 0
DAUX2 = 0
siov();
ndisableproc()
RETURN (geterror(ds))
;
; Read len bytes from N: device
; pointed to by devicespec.
;
; @param devicespec N: devicespec
; @param buf The dest buffer
; @param len # of bytes to read
; @return error 1=successful
;
BYTE FUNC nread(BYTE ARRAY ds, BYTE ARRAY buf, CARD len)
DDEVIC = $71
DUNIT = ngetunit(ds)
DCOMND = 'R ; READ
DSTATS = $40 ; Atari<-Payload
DBUF = buf ; send devicespec
DTIMLO = $1F ; 32 second timeout
DBYT = len ; No payload
DAUX = len
siov();
RETURN (geterror(ds))
;
; Write len bytes to N: device
; pointed to by devicespec.
;
; @param devicespec N: devicespec
; @param buf The src buffer
; @param len # of bytes to read
; @return error 1=successful
;
BYTE FUNC nwrite(BYTE ARRAY ds, BYTE ARRAY buf, CARD len)
DDEVIC = $71
DUNIT = ngetunit(ds)
DCOMND = 'W ; WRITE
DSTATS = $80 ; Payload->FujiNet
DBUF = buf ; send devicespec
DTIMLO = $1F ; 32 second timeout
DBYT = len ; No payload
DAUX = len
siov();
RETURN (geterror(ds))
; Get FujiNet Status
;
; Author: Michael Goroll
; <atari@goroll.net>
;
PROC fstatus(BYTE ARRAY buf)
DDEVIC = $70
DUNIT = $01
DCOMND = $E8
DSTATS = $40 ; Payload to Atari
DBUF = buf ; status buffer
DTIMLO = $0F ; 8 second timeout
DBYT = $88 ; 139 byte payload
DAUX1 = 0 ; R/W
DAUX2 = 0 ; translation
siov()
RETURN
NC.ACT - Main Program
;
; A simple netcat program
; to show how to do basic network
; input and output.
;
; Author: Thomas Cherryhomes
; <thom.cherryhomes@gmail.com>
;
INCLUDE "D2:SYS.ACT" ; ACTION! Runtime!
INCLUDE "D2:NIO.ACT" ; NETWORK I/O
MODULE
CARD BYTESWAITING=$02EA ; # of bytes waiting
BYTE KP=$02FC ; Key pressed?
BYTE ARRAY devicespec(256) ; DeviceSpec
BYTE trans ; translation mode
BYTE localEcho ; local echo off/on?
BYTE running ; is program running?
BYTE ARRAY rxbuf(8192) ; receive buffer
DEFINE KEYBOARD_IOCB="2"
DEFINE TRUE="1"
DEFINE FALSE="0"
;
; Prompt for URL
;
PROC getURL()
PrintE("NETCAT--ENTER DEVICE SPEC?")
InputS(devicespec)
PutE()
PrintE("TRANS--0=NONE, 1=CR, 2=LF, 3=CR/LF?")
trans=InputB()
PutE()
PrintE("LOCAL ECHO--0=NO, 1=YES?")
localEcho=InputB()
PutE()
RETURN
;
; Handle nc output
;
PROC ncoutput()
BYTE ARRAY ch(1)
BYTE err
IF KP=$FF THEN
RETURN
FI
ch(0)=GetD(KEYBOARD_IOCB)
IF localEcho=1 THEN
Put(ch(0))
FI
err=nwrite(devicespec,ch,1)
IF err<>1 THEN
Print("Write Error: ")
PrintB(err)
running=FALSE
FI
RETURN
;
; Handle nc input
;
PROC ncinput()
BYTE err
CARD I
IF trip=0 THEN
RETURN
FI
nstatus()
IF EXTERR=136 THEN
PrintE("Disconnected.")
running=FALSE
RETURN
FI
IF BYTESWAITING=0 THEN
RETURN
FI
IF BYTESWAITING>8192 THEN
BYTESWAITING=8192
FI
; Do the network read.
err=nread(devicespec,rxbuf,BYTESWAITING)
IF err<>1 THEN
Print("Read Error: ")
PrintB(err)
running=FALSE
RETURN
FI
; Drain/display rx buffer
FOR I=0 TO BYTESWAITING-1
DO
Put(rxbuf(I))
OD
; Done, reset interrupt
trip=0
PACTL=PACTL%1
RETURN
;
; The main Netcat function
;
PROC nc()
BYTE err
err=nopen(deviceSpec,trans)
IF err<>1 THEN
Print("Open Error: ")
PrintB(err)
RETURN
FI
; flag program as running.
running=1
; Open keyboard
Open(KEYBOARD_IOCB,"K:",4,0)
WHILE running = TRUE
DO
ncoutput()
ncinput()
OD
PrintE("Bye.")
; Clean up
Close(KEYBOARD_IOCB) ; close kybd
nclose(deviceSpec)
RETURN
;
; Main entrypoint
;
PROC main()
getURL()
nc()
RETURN
Coleco Adam CP/M (MBASIC)
5 GOSUB 64000:REM SETUP ADAMNET FUNCS
10 PRINT "URL: ";:LINE INPUT URL$
20 NET$="O"+CHR$(12)+CHR$(0)+URL$:REM OPEN COMMAND
30 CMD=WRI:GOSUB 64200
100 NET$=STRING$(255,0)
101 K$=INKEY$:IF K$="" THEN GOTO 110
102 NET$="W"+K$:CMD=WRI:GOSUB 64200:GOTO 100
110 CMD=REA:GOSUB 64200
120 IF FNDCBSTATUS(NET)=&H8C THEN GOTO 100
130 POKE VARPTR(NET$),PEEK(FNDCBLENL(NET))
140 PRINT NET$;
150 GOTO 100
63999 END
64000 REM FUJINET ROUTINES
64010 REM ----------------
64011 REM DCB FUNCS
64012 REM ----------------
64020 DCBLEN=21:DCBEGIN=&HFEC4:DCBNUM=&HFEC3
64021 NET$=STRING$(255,0)
64022 STAT=1:WRI=3:REA=4
64030 DEF FN DCB(X)=X*DCBLEN+DCBEGIN
64040 DEF FN DCBSTATUS(X)=PEEK(FNDCB(X))
64050 DEF FN DCBCMD(X)=FNDCB(X)
64060 DEF FN DCBBUFL(X)=FNDCB(X)+1
64061 DEF FN DCBBUFH(X)=FNDCB(X)+2
64070 DEF FN DCBDEVSTATUS(X)=PEEK(FNDCB(X)+20)
64080 DEF FN DCBDEV(X)=PEEK(FNDCB(X)+16)
64081 DEF FN DCBLENL(X)=FNDCB(X)+3
64082 DEF FN DCBLENH(X)=FNDCB(X)+4
64090 DEF FN DCBNUM(X)=PEEK(DCBNUM)
64092 RETURN
64093 REM --------
64100 REM FIND DCB
64101 REM --------
64110 FOR X=0 TO FNDCBNUM(0)-1
64120 IF FNDCBDEV(X)=9 THEN NET=X:RETURN
64130 NEXT X
64140 RETURN
64150 REM --------------------
64200 REM DO FUJINET OPERATION
64210 REM --------------------
64220 GOSUB 64100:REM FIND DCB
64230 POKE FNDCBBUFL(NET),PEEK(VARPTR(NET$)+1)
64240 POKE FNDCBBUFH(NET),PEEK(VARPTR(NET$)+2)
64250 POKE FNDCBLENL(NET),PEEK(VARPTR(NET$))
64260 POKE FNDCBLENH(NET),0
64270 POKE FNDCBCMD(NET),CMD
64280 IF FNDCBSTATUS(NET)<&H80 THEN GOTO 64280
64290 RETURN
Example devicespecs:
N:TCP://BBS.FOZZTEXX.NET/ (trans 3, local echo N)
N:TCP://CAVEMUSH.COM:6116/ (trans 3 local echo Y)
N:TCP://SOUTHERNAMIS.COM/ (trans 0, local echo N)
Coleco ADAM CP/M (Z80 Assembler)
This example is split into two files, the FujiNet I/O functions, which are based on routines in the EOS ROM, and the Netcat program itself.
FUJINET.Z80
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;
;;; Global Variables
;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
DEVICE_ID: DS 1
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;
;;; IS DEVICE DONE WITH
;;; PREVIOUS COMMAND?
;;;
;;; ENTRY:
;;; IY = DCB ADDRESS
;;;
;;; EXIT:
;;; ZF: SET IF DEVICE DONE
;;; A: 3 IF NOT DONE
;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
_DEVICE_DONE:
LD A,(IY+0) ; Read Status Byte
OR A ; Is it 0?
JR NZ,_DDON_DONE ; No, so all done.
LD A,3 ; Yes, A=3 NOT DONE
OR A ; Clear ZF
RET
_DDON_DONE:
XOR A ; A=0; ZF Set
RET ; Bye...
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;
;;; IS DEVICE READY FOR
;;; ANOTHER COMMAND?
;;;
;;; ENTRY:
;;; A = ADAMNET ID
;;; IY = DCB ADDRESS
;;;
;;; EXIT:
;;; ZF: SET IF DEVICE READY
;;; A: 2 IF NOT READY
;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
_DEVICE_READY:
PUSH BC
LD C,A ; Save Dev in C
LD A,(IY+0) ; Get status byte
CP 0 ; Is it zero?
JR Z,_DRDY_SUCCESS ; Yes, Exit OK
BIT 7,(IY+0) ; Is bit 7 set?
JR Z,_DRDY_NOT ; No, exit not ready.
_DRDY_SUCCESS:
XOR A ; Zero out A
LD A,C ; Restore Dev #
JR Z,_DRDY_DONE ; ...and done.
_DRDY_NOT:
INC A ; Clear ZF
LD A,2 ; A=2 Device not ready
_DRDY_DONE:
POP BC ; Restore BC
RET ; Bye...
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;
;;; FIND DCB
;;;
;;; ENTRY:
;;; A = ADAMNET ID
;;;
;;; EXIT:
;;; ZF: SET IF DEVICE EXISTS
;;; IY: DCB ADDRESS
;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
CURRENT_PCB EQU 0FEC0H ; Address of Processor Control Block (PCB)
DCB_LENGTH EQU 21 ; DCBs are 21 bytes long
_FIND_DCB:
PUSH BC ; Preserve regs
PUSH DE
LD C,A ; Save Dev # in C
LD IY,CURRENT_PCB
LD B,(IY+3) ; B=# of valid DCBs
XOR A ; Zero out A
CP B ; Any valid DCBs?
JR z,_FDCB_DONE ; Nope, we're done.
LD DE,4 ; Yep, Skip over PCB...
ADD IY,DE ; Point to first DCB
LD DE,DCB_LENGTH ; Length of each DCB
LD A,C ; Put back dev #
AND 0FH ; We don't care about upper nibble
_FDCB_LOOP:
CP (IY+16) ; Does this match A?
JR Z,_FDCB_SUCCESS ; Yup, exit and set ZF
ADD IY,DE ; No, go to next one.
DJNZ _FDCB_LOOP ; Keep looking until done
JR _FDCB_ERR ; Couldn't find, so error.
_FDCB_SUCCESS:
LD A,C ; Restore A
JR _FDCB_DONE ; And we're done
_FDCB_ERR:
LD C,9
LD DE,FDCBMSG
CALL 0005H
LD A,1 ; 1=error
OR A ; Clear ZF
_FDCB_DONE:
POP DE ; Put regs back
POP BC
RET ; Bye...
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;
;;; REQUEST DEVICE STATUS
;;;
;;; ENTRY:
;;; A = ADAMNET ID
;;;
;;; EXIT:
;;; ZF: SET IF DEVICE ACKS
;;; A: RESULT OF REQUEST
;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
CMD_STATUS EQU 1
_REQUEST_STATUS:
CALL _FIND_DCB ; Get DCB address in IY
JR NZ,_REQS_DONE ; bail if doesn't exist
LD (IY+0),CMD_STATUS ; Ask device for status
_REQS_WAIT:
BIT 7,(IY+0) ; Bit 7 = Completed
JR Z,_REQS_WAIT ; Nope? wait some more.
LD A,(IY+0) ; Yes, return result
CP 80H ; Is it OK? Set ZF if yes.
_REQS_DONE:
RET
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;
;;; GET DEVICE STATUS BYTE
;;; (BYTE 20 of DCB)
;;;
;;; THIS IS INTENDED TO BE
;;; CALLED AFTER
;;; REQUEST STATUS HAS
;;; COMPLETED.
;;;
;;; ENTRY:
;;; A = ADAMNET ID
;;;
;;; EXIT:
;;; ZF: SET IF DEVICE EXISTS
;;; A: DEVICE STATUS BYTE
;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
_GET_STATUS:
PUSH IY
CALL _FIND_DCB ; Get DCB address in IY
JR NZ,_GETS_DONE ; bail if doesn't exist
XOR A ; Device exists, so set ZF
LD A,(IY+20) ; Get Device status byte in A
_GETS_DONE:
POP IY
RET ; Bye
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;
;;; START WRITE CHAR DEVICE
;;;
;;; ENTRY:
;;; A = ADAMNET ID
;;; HL = BUFFER ADDRESS
;;; BC = BUFFER LENGTH
;;;
;;; EXIT:
;;; ZF: SET IF START OK
;;; A: ERROR CODE IF ZF=0
;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
CMD_WRITE EQU 3
_START_WRITE_CHAR_DEV:
PUSH IY
CALL _FIND_DCB ; Find DCB in IY
JR NZ,_SWCHD_DONE ; Not found, bye.
CALL _DEVICE_READY ; Is device ready?
JR NZ,_SWCHD_DONE ; Nope, bye.
LD (IY+3),C ; Set up DCB
LD (IY+4),B
LD (IY+1),L
LD (IY+2),H
LD (IY+0),CMD_WRITE ; Command is last!
_SWCHD_DONE:
POP IY
RET
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;
;;; END WRITE CHAR DEVICE
;;;
;;; ENTRY:
;;; A = ADAMNET ID
;;;
;;; EXIT:
;;; CF: SET IF I/O DONE
;;; ZF: SET IF END OK
;;; A: ERROR CODE IF ZF=0
;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
_END_WRITE_CHAR_DEV:
PUSH IY
CALL _FIND_DCB ; Find DCB in IY
SCF ; Set CF
JR NZ,_EWCHD_DONE ; Done if Dev not exist
CALL _DEVICE_DONE ; Check if device done
JR NZ,_EWCHD_DONE ; Device not done, exit.
OR A ; Clear CF
BIT 7,(IY+0) ; Bit 7 set?
JR Z,_EWCHD_DONE ; No, so exit.
LD A,(IY+0) ; Get Status Byte
CP 80H ; Successful?
SCF ; Set CF
_EWCHD_DONE:
POP IY
RET
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;
;;; WRITE CHAR DEVICE
;;;
;;; ENTRY:
;;; A = ADAMNET ID
;;; HL = BUFFER ADDRESS
;;; BC = BUFFER LENGTH
;;;
;;; EXIT:
;;; ZF: SET IF OK
;;; A: ERROR CODE IF ZF=0
;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
_WRITE_CHAR_DEV:
LD (DEVICE_ID),A
CALL _START_WRITE_CHAR_DEV
RET NZ
_WCHDEV_WAIT:
LD A,(DEVICE_ID)
CALL _END_WRITE_CHAR_DEV
JR NC,_WCHDEV_WAIT
RET
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;
;;; START READ CHAR DEVICE
;;;
;;; ENTRY:
;;; A = ADAMNET ID
;;; DE = BUFFER ADDRESS
;;; BC = BUFFER LENGTH
;;;
;;; EXIT:
;;; ZF: SET IF START OK
;;; A: ERROR CODE IF ZF=0
;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
CMD_READ EQU 4
_START_READ_CHAR_DEV:
PUSH IY
CALL _FIND_DCB ; Find DCB in IY
JR NZ,_SRCHD_DONE ; Not found, bye.
CALL _DEVICE_READY ; Is device ready?
JR NZ,_SRCHD_DONE ; Nope, bye.
LD (IY+3),C ; Set up DCB
LD (IY+4),B
LD (IY+1),E
LD (IY+2),D
LD (IY+0),CMD_READ ; Command is last!
_SRCHD_DONE:
POP IY
RET
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;
;;; END READ CHAR DEVICE
;;;
;;; ENTRY:
;;; A = ADAMNET ID
;;;
;;; EXIT:
;;; CF: SET IF I/O DONE
;;; ZF: SET IF END OK
;;; A: ERROR CODE IF ZF=0
;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
_END_READ_CHAR_DEV:
PUSH IY
CALL _FIND_DCB ; Find DCB in IY
SCF ; Set CF
JR NZ,_ERCHD_DONE ; Done if Dev not exist
CALL _DEVICE_DONE ; Check if device done
JR NZ,_ERCHD_DONE ; Device not done, exit.
OR A ; Clear CF
BIT 7,(IY+0) ; Bit 7 set?
JR Z,_ERCHD_DONE ; No, so exit.
LD A,(IY+0) ; Get Status Byte
CP 80H ; Successful?
SCF ; Set CF
_ERCHD_DONE:
POP IY
RET
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;
;;; READ CHAR DEVICE
;;;
;;; ENTRY:
;;; A = ADAMNET ID
;;; HL = BUFFER ADDRESS
;;; BC = BUFFER LENGTH
;;;
;;; EXIT:
;;; ZF: SET IF OK
;;; A: ERROR CODE IF ZF=0
;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
_READ_CHAR_DEV:
LD (DEVICE_ID),A
CALL _START_READ_CHAR_DEV
RET NZ
_RCHDEV_WAIT:
LD A,(DEVICE_ID)
CALL _END_READ_CHAR_DEV
JR NC,_RCHDEV_WAIT
RET
;;
;; Debug msgs
;;
FDCBMSG: DB 'COULD NOT FIND DCB.',0DH,0AH,24H
netcat.z80
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;
;;; NETCAT
;;;
;;; Simple Terminal
;;;
;;; @author Thom Cherryhomes
;;; @email thom dot cherryhomes at gmail dot com
;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;
;;; Constants
;;;
NETDEV EQU 9 ; Adamnet device ID
RESP_SIZE EQU 1024 ; 1K resp buff
;;;
;;; BDOS functions
;;;
C_WRITE EQU 2
C_RAWIO EQU 6
WRITESTR EQU 9
READSTR EQU 10
C_STAT EQU 11
;;;
;;; N: Mode values
;;;
READWRITE EQU 12
NOTRANS EQU 0
;;;
;;; Addresses
;;;
WARMST EQU 0000H
BDOS EQU 0005H
TPA EQU 0100H
ORG TPA
JP START
INCLUDE FUJINET.Z80
;;;
;;; Display banner and URL prompt.
;;;
START:
LD C,WRITESTR
LD DE,BANNER
CALL BDOS
;;;
;;; Input URL from user
;;;
GETURL:
LD C,READSTR
LD DE,COPEN+2
CALL BDOS
;;;
;;; Attempt connection
;;; See if this can be done
;;; in a less awkward manner.
;;;
CONNECT:
LD IY,COPEN
LD C,(IY+3) ; Grab len be4 we obliterate
LD B,0 ; not more than 256b
INC BC
INC BC
INC BC
LD (IY+1),'O' ; Move open command up.
LD (IY+2),0CH
LD (IY+3),00H
LD HL,COPEN+1 ; Finally, set buffer to open cmd
LD A,NETDEV ; Network device
CALL _WRITE_CHAR_DEV ; Do it.
;;;
;;; Wait for connection,
;;; first request status.
;;;
CONNECT_WAIT:
LD A,NETDEV
CALL _REQUEST_STATUS
JR Z,CWCHECK
CP 09BH ; Timeout?
JR Z,CONNECT_WAIT
;;;
;;; Then see if node status has
;;; bit 2 set.
CWCHECK:
LD A,NETDEV
CALL _GET_STATUS
BIT 1,A
JR NZ,CONNECTED
;;;
;;; Connected bit not set yet,
;;; So print a dot and go back.
;;;
DODOT: LD C,9
LD DE,DOT
CALL BDOS
JP CONNECT_WAIT
;;;
;;; Print connected msg!
;;;
CONNECTED:
LD C,9
LD DE,CNMSG
CALL BDOS
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;
;;; Netcat main loop BEG
;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
MAIN: LD A,8
LD HL,SCTR ; Reset status counter.
LD (HL),A
STAT: LD A,(SCTR) ; Check status counter.
CP 0
JR NZ,SEND ; Go to send if not time.
STATRC: LD A,8
LD HL,SCTR ; otherwise reset counter
LD (HL),A ;
STATR: LD A,NETDEV ; set net device.
CALL _REQUEST_STATUS ; send status request
CP 80H ; Status OK?
JR NZ,STATR ; try again.
LD A,NETDEV ; Otherwise...
CALL _GET_STATUS ; Get the network status byte
BIT 1,A ; Get connected bit status
JR Z,DISCONNECTED ; Disconnected?
SEND: LD C,6 ; No. Set up console input
LD E,0FFH ; Get char without echo.
CALL BDOS ; and do it...
CP 0 ; No char waiting?
JR Z,RECV ; Correct, go to recv.
LD (CWRITC),A ; otherwise put char in cmd
LD A,NETDEV ; To Net device
LD HL,CWRIT ; Buffer in CWRIT
LD BC,2 ; 'W' + One char
CALL _WRITE_CHAR_DEV ; Do it, ignore errs
RECV: LD A,NETDEV ; From net device
LD DE,RESP ; Point to resp buff
LD BC,RESP_SIZE ; buff len
CALL _READ_CHAR_DEV ; Do it.
RECV_OK:
LD A,NETDEV
CALL _FIND_DCB ; Get DCB in IY
LD A,(IY+0) ; Check status
CP 128 ; is it ACK?
JR NZ,STAT ; Nope, go back.
RECV_LEN:
LD E,(IY+3) ; Load pointers from DCB
LD D,(IY+4)
LD L,(IY+1)
LD H,(IY+2)
RECV_PRINT:
PUSH DE
PUSH HL
LD C,C_WRITE ; Console write
LD E,(HL) ; Next char in buf
CALL BDOS ; Do it.
POP HL
POP DE
INC HL ; Increment buff ptr.
DEC DE ; Decrement len
ld a,d
or e
JR NZ,RECV_PRINT ; Not done yet.
UPDATE_SCTR:
LD HL,SCTR ; Decrement status counter
LD A,(HL)
DEC A
LD (HL),A
JP STAT ; Back to stat.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;
;;; Netcat main loop END
;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;
;;; Host disconnected.
;;; Print msg, close connection.
;;; and head back to CPM.
DISCONNECTED:
LD C,WRITESTR ; We disconnected.
LD DE,DISCMSG ; Show the disconnected string
CALL BDOS ; Do it.
LD A,NETDEV ; To Net device
LD HL,CCLOS ; Buffer ptr to CLOSE command
LD BC,1 ; only one char.
CALL _WRITE_CHAR_DEV ; Do it
; ... fall thru to BYE.
;;;
;;; Exit to CP/M
;;;
BYE: RST 0
;;;
;;; Fujinet Commands
;;;
COPEN: DB 'O' ; FujiNet N: OPEN
DB 0CH ; reused as str len. changed to 0C
DB 50H ; 80 chars
DS 80
CWRIT: DB 'W' ; FujiNet N: WRITE
CWRITC: DS 1 ; Char to send
CCLOS: DB 'C' ; Fujinet N: CLOSE
;;;
;;; WRITESTR strings.
;;;
BANNER:
DB 'NETCAT!',0DH,0AH,0D,0AH
DB 'ENTER URL:',0DH,0AH,24H
TOMSG:
DB 'TIMEOUT WAITING FOR STATUS.',0DH,0AH,24H
DOT:
DB '.',24H
CNMSG: DB 0DH,0AH,'CONNECTED!',0DH,0AH,24H
DISCMSG:
DB 0DH,0AH,'DISCONNECTED.',0DH,0AH,24H
;;;
;;; 1024 BYTE RESPONSE BUFFER
;;; ALL FUJINET PROGS NEED THIS
;;; AND RECEIVE SIZE IS ALWAYS
;;; 1024 BYTES.
;;;
RESP: DS 1024
;;; A STATUS COUNTER, COMPARED AND
;;; CHECKED EVERY TIME ZF IS SET
SCTR: DS 1
Any not here?
Do you have an equivalent in other languages to put here, add it!
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
- Home
- What is FujiNet?
- The Definition of Done
- Board bring up for FujiNet Platform.IO code
- The Complete Linux CLI Guide
- The Complete macOS CLI Guide
- Development Env for Apps
- FujiNet-Development-Guidelines
- System Quickstarts
- FujiNet Flasher
- Setting up a TNFS Server
- FujiNet Configuration File: fnconfig.ini
- AppKey Registry - AppKey-Registry-Page
- CP-M Support
- BBS
- Official Hardware Versions
- Prototype Board Revisions
- FujiNet Development Guidelines
- The Network Protocol Handbook
- Atari Programming
- Apple Programming
- C64 Programming
- ADAM Programming
- Intellivision Programming
- Testing Plan
- Hacker List
- FujiNet VirtualMachine
Copyright 2026 Contributors to the FujiNetWIFI project.
Join us on Discord: https://discord.gg/7MfFTvD