aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/atari.cfg52
-rw-r--r--src/cio.h5
-rw-r--r--src/cio.s8
-rw-r--r--src/cmd.c19
-rw-r--r--src/conio.c37
-rw-r--r--src/conio.h14
-rw-r--r--src/err.c37
-rw-r--r--src/err.h22
-rw-r--r--src/intr.s7
-rw-r--r--src/irc.c306
-rw-r--r--src/irc.h59
-rw-r--r--src/main.c311
-rw-r--r--src/nio.c183
-rw-r--r--src/nio.h79
-rw-r--r--src/sio.h7
-rw-r--r--src/sio.s17
-rw-r--r--src/ui.c73
17 files changed, 1236 insertions, 0 deletions
diff --git a/src/atari.cfg b/src/atari.cfg
new file mode 100644
index 0000000..ea190f7
--- /dev/null
+++ b/src/atari.cfg
@@ -0,0 +1,52 @@
+FEATURES {
+ STARTADDRESS: default = $2000;
+}
+SYMBOLS {
+ __EXEHDR__: type = import;
+ __AUTOSTART__: type = import; # force inclusion of autostart "trailer"
+ __STACKSIZE__: type = weak, value = $0800; # 2k stack
+ __STARTADDRESS__: type = export, value = %S;
+ __RESERVED_MEMORY__: type = weak, value = $0000;
+}
+MEMORY {
+ ZP: file = "", define = yes, start = $0082, size = $007E;
+
+# file header, just $FFFF
+ HEADER: file = %O, start = $0000, size = $0002;
+
+# "main program" load chunk
+ MAINHDR: file = %O, start = $0000, size = $0004;
+ MAIN: file = %O, define = yes, start = %S, size = $BC20 - __STACKSIZE__ - __RESERVED_MEMORY__ - %S;
+ TRAILER: file = %O, start = $0000, size = $0006;
+}
+SEGMENTS {
+ ZEROPAGE: load = ZP, type = zp;
+ EXTZP: load = ZP, type = zp, optional = yes;
+ EXEHDR: load = HEADER, type = ro;
+ MAINHDR: load = MAINHDR, type = ro;
+ STARTUP: load = MAIN, type = ro, define = yes;
+ LOWBSS: load = MAIN, type = rw, optional = yes; # not zero initialized
+ LOWCODE: load = MAIN, type = ro, define = yes, optional = yes;
+ ONCE: load = MAIN, type = ro, optional = yes;
+ CODE: load = MAIN, type = ro, define = yes;
+ RODATA: load = MAIN, type = ro;
+ DATA: load = MAIN, type = rw;
+ INIT: load = MAIN, type = rw, optional = yes;
+ BSS: load = MAIN, type = bss, define = yes;
+ AUTOSTRT: load = TRAILER, type = ro;
+}
+FEATURES {
+ CONDES: type = constructor,
+ label = __CONSTRUCTOR_TABLE__,
+ count = __CONSTRUCTOR_COUNT__,
+ segment = ONCE;
+ CONDES: type = destructor,
+ label = __DESTRUCTOR_TABLE__,
+ count = __DESTRUCTOR_COUNT__,
+ segment = RODATA;
+ CONDES: type = interruptor,
+ label = __INTERRUPTOR_TABLE__,
+ count = __INTERRUPTOR_COUNT__,
+ segment = RODATA,
+ import = __CALLIRQ__;
+}
diff --git a/src/cio.h b/src/cio.h
new file mode 100644
index 0000000..d07c875
--- /dev/null
+++ b/src/cio.h
@@ -0,0 +1,5 @@
+/**
+ * Function to call cio
+ */
+
+void ciov();
diff --git a/src/cio.s b/src/cio.s
new file mode 100644
index 0000000..69a789a
--- /dev/null
+++ b/src/cio.s
@@ -0,0 +1,8 @@
+ ;; Call CIO
+
+ .export _ciov
+
+_ciov: LDX #$00
+ JSR $E456
+ RTS
+
diff --git a/src/cmd.c b/src/cmd.c
new file mode 100644
index 0000000..3c7f482
--- /dev/null
+++ b/src/cmd.c
@@ -0,0 +1,19 @@
+#include <atari.h>
+#include <stdio.h>
+#include "irc.h"
+
+void cmd_chan_text(const char *cmd) {
+ txbuf_set_str("PRIVMSG ");
+ txbuf_append_str(channel);
+ txbuf_append_str(" :");
+ txbuf_append_str(cmd);
+ txbuf_send();
+}
+
+void cmd_command(const char *cmd) {
+ if(*cmd == '/')
+ txbuf_send_str(cmd + 1);
+ else if(channel[0])
+ cmd_chan_text(cmd);
+ else ui_print("*** You are not on a channel\n");
+}
diff --git a/src/conio.c b/src/conio.c
new file mode 100644
index 0000000..2742ab6
--- /dev/null
+++ b/src/conio.c
@@ -0,0 +1,37 @@
+/**
+ * Simple conio for E:
+ */
+
+#include <atari.h>
+#include <string.h>
+#include "cio.h"
+
+void printl(const char* c, int l)
+{
+ OS.iocb[0].buffer=c;
+ OS.iocb[0].buflen=l;
+ OS.iocb[0].command=IOCB_PUTCHR;
+ ciov();
+}
+
+void printc(char* c)
+{
+ OS.iocb[0].buffer=c;
+ OS.iocb[0].buflen=1;
+ OS.iocb[0].command=IOCB_PUTCHR;
+ ciov();
+}
+
+void print(const char* c)
+{
+ int l=strlen(c);
+ printl(c,l);
+}
+
+void get_line(char* buf, unsigned char len)
+{
+ OS.iocb[0].buffer=buf;
+ OS.iocb[0].buflen=len;
+ OS.iocb[0].command=IOCB_GETREC;
+ ciov();
+}
diff --git a/src/conio.h b/src/conio.h
new file mode 100644
index 0000000..25ca6c2
--- /dev/null
+++ b/src/conio.h
@@ -0,0 +1,14 @@
+/**
+ * conio
+ */
+
+#ifndef CONIO_H
+#define CONIO_H
+
+void print(const char* c);
+void printc(char* c);
+void printl(const char* c, unsigned short l);
+void get_line(char* buf, unsigned char len);
+char get_char(void);
+
+#endif /* CONIO_H */
diff --git a/src/err.c b/src/err.c
new file mode 100644
index 0000000..8d58670
--- /dev/null
+++ b/src/err.c
@@ -0,0 +1,37 @@
+/**
+ * FujiNet Tools for CLI
+ *
+ * Error output
+ *
+ * Author: Thomas Cherryhomes
+ * <thom.cherryhomes@gmail.com>
+ *
+ * Released under GPL, see COPYING
+ * for details
+ */
+
+#include <atari.h>
+#include "conio.h"
+
+const char error_138[]="FUJINET NOT RESPONDING\x9B";
+const char error_139[]="FUJINET NAK\x9b";
+const char error[]="SIO ERROR\x9b";
+
+/**
+ * Show error
+ */
+void err_sio(void)
+{
+ switch (OS.dcb.dstats)
+ {
+ case 138:
+ print(error_138);
+ break;
+ case 139:
+ print(error_139);
+ break;
+ default:
+ print(error);
+ break;
+ }
+}
diff --git a/src/err.h b/src/err.h
new file mode 100644
index 0000000..d3bf3d7
--- /dev/null
+++ b/src/err.h
@@ -0,0 +1,22 @@
+/**
+ * FujiNet Tools for CLI
+ *
+ * Error output
+ *
+ * Author: Thomas Cherryhomes
+ * <thom.cherryhomes@gmail.com>
+ *
+ * Released under GPL, see COPYING
+ * for details
+ */
+
+
+#ifndef ERR_H
+#define ERR_H
+
+/**
+ * Show error
+ */
+void err_sio(void);
+
+#endif /* ERR_H */
diff --git a/src/intr.s b/src/intr.s
new file mode 100644
index 0000000..00de34f
--- /dev/null
+++ b/src/intr.s
@@ -0,0 +1,7 @@
+ .export _ih
+ .import _trip
+
+_ih: LDA #$01
+ STA _trip
+ PLA
+ RTI
diff --git a/src/irc.c b/src/irc.c
new file mode 100644
index 0000000..735ca2f
--- /dev/null
+++ b/src/irc.c
@@ -0,0 +1,306 @@
+#include <stdbool.h>
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+
+#include "irc.h"
+
+#ifdef __ATARI__
+#include <atari.h>
+#include <conio.h>
+#include "conio.h"
+#include "nio.h"
+#else
+#define CH_EOL '|'
+unsigned char rx_buf[MAX_IRC_MSG_LEN]; // RX buffer.
+unsigned short bw=0; // # of bytes waiting.
+#endif
+
+#define MAX_MSG 512
+
+char *msg_src, *msg_cmd, *msg_dest, *msg_text;
+char *msg_args[MAX_MSG_ARGS];
+int msg_argcount;
+
+static char msgbuf[MAX_MSG] = { 0 };
+static char *msg; /* with source removed */
+static int msgbuf_len = 0, msg_len = 0;
+
+static int joined = 0;
+
+#ifdef __ATARI__
+static void join_channel(void) {
+ ui_print("Joining channel...\n");
+ txbuf_set_str("JOIN ");
+ txbuf_append_str(channel);
+ txbuf_append_str("\n");
+ txbuf_send();
+ joined = 1;
+}
+
+static void do_pong(void) {
+ ui_putchar(CH_EOL);
+ ui_print("PING/PONG\n"); /* make hiding this a preference, or just ditch it */
+ txbuf_set_str("PONG ");
+ txbuf_append_str(msg_args[0]);
+ txbuf_send();
+}
+
+static void do_privmsg(void) {
+ static char chan;
+
+ chan = (*msg_dest == '#');
+
+ if(chan) {
+ ui_putchar('<');
+ } else {
+ ui_putchar('*');
+ }
+
+ ui_print(msg_src);
+
+ if(chan) {
+ ui_putchar('>');
+ } else {
+ ui_putchar('*');
+ }
+
+ ui_putchar(' ');
+ ui_print(msg_text);
+}
+
+static void do_catchall(void) {
+ int i;
+ if(msg_src) {
+ ui_print(msg_src);
+ ui_putchar(' ');
+ }
+ ui_print(msg_cmd);
+ for(i = 0; i < msg_argcount; i++) {
+ ui_putchar(' ');
+ ui_print(msg_args[i]);
+ }
+ if(msg_text) {
+ ui_putchar(' ');
+ ui_print(msg_text);
+ }
+}
+
+static void do_numeric(void) {
+ do_catchall();
+
+ /* RPL_ENDOFMOTD or RPL_NOMOTD */
+ if(!joined && (streq(msg_cmd, "372") || streq(msg_cmd, "422"))) {
+ join_channel();
+ }
+}
+
+static void invalid_msg(char type) {
+ ui_print("??? unknown, type ");
+ ui_putchar(type);
+ ui_putchar('\n');
+}
+#else
+static void do_pong(void) { }
+static void invalid_msg(char type) {
+ printf("??? unknown, type %c\n", type);
+}
+#endif
+
+/* msgbuf contains a complete message from the server, whose
+ length is msgbuf_len. the last character *must* be CH_EOL,
+ and the last argument ends with CH_EOL. */
+static void parse_msg(void) {
+ char *p;
+
+#ifndef __ATARI__
+ printf("\ngot message:\n");
+ for(msg = msgbuf; *msg != CH_EOL; msg++)
+ putchar(*msg);
+ putchar('\n');
+ putchar('\n');
+#endif
+
+ msg_cmd = msg_text = msg_src = msg_dest = 0;
+ msg = msgbuf;
+
+ /* ignore empty message */
+ if(*msg == CH_EOL) return;
+
+ /* if there's a final multiword arg... */
+ /* FIXME: channel names can have colons, which breaks this... */
+ p = strstr(msg + 1, " :"); /* +1 to skip leading colon in msg source */
+ if(p) {
+ msg_text = p + 2;
+ *p = 0;
+ }
+
+ /* first token is either the source (with a :) or a command (without) */
+ p = strtok(msg, " ");
+ if(!p) {
+ invalid_msg('1');
+ return;
+ }
+
+ if(*p == ':') {
+ msg_src = p; /* generally :irc.example.com or :nick!user@host */
+ msg_cmd = strtok(0, " ");
+ } else {
+ msg_src = 0; /* no source supplied */
+ msg_cmd = p;
+ }
+
+ if(!msg_cmd) {
+ invalid_msg('2');
+ return;
+ }
+
+ /* special case for ping, treat as 1 arg, even if it has space and no : */
+ if(streq_i(msg_cmd, "PING")) {
+ msg_argcount = 1;
+ msg_args[0] = msg_cmd + 6;
+ do_pong();
+ return;
+ } else {
+ for(msg_argcount = 0; msg_argcount < MAX_MSG_ARGS; msg_argcount++) {
+ p = strtok(0, " ");
+ if(p) {
+ msg_args[msg_argcount] = p;
+ } else {
+ break;
+ }
+ }
+ }
+ if(msg_argcount) msg_dest = msg_args[0];
+
+ if(msg_src) {
+ if((p = strstr(msg_src, "!"))) {
+ msg_src++;
+ *p = '\0';
+ } else {
+ msg_src = 0;
+ }
+ }
+
+#ifdef __ATARI__
+ OS.crsinh = 1;
+ ui_start_msg();
+ if(streq_i(msg_cmd, "PRIVMSG")) {
+ do_privmsg();
+ } else if(isdigit(msg_cmd[0])) {
+ do_numeric();
+ } else {
+ do_catchall();
+ }
+ ui_end_msg();
+#else
+ {
+ int i;
+ printf("src: %s\n", msg_src ? msg_src : "<none>");
+ printf("cmd: %s\n", msg_cmd ? msg_cmd : "<none>");
+ printf("args: %d\n", msg_argcount);
+ for(i = 0; i < msg_argcount; i++)
+ printf(" %d: %s\n", i, msg_args[i]);
+ printf("text: %s\n", msg_text ? msg_text : "<none>");
+ }
+#endif
+}
+
+static void irc_parse(void) {
+ int i;
+ char *p = rx_buf;
+
+#ifndef __ATARI__
+ printf("irc_parse() called, bw == %d\n", bw);
+#endif
+
+ for(i = 0; i < bw; i++) {
+ msgbuf[msgbuf_len] = *p;
+ if(*p == CH_EOL) {
+ msgbuf[msgbuf_len + 1] = '\0';
+ parse_msg();
+ msgbuf_len = 0;
+ } else {
+ msgbuf_len++;
+ }
+ p++;
+ }
+}
+
+#ifdef __ATARI__
+bool irc_read(void) {
+ if(!trip) return 1;
+
+ err = nstatus(url);
+
+ if(err == 136) {
+ ui_print("Disconnected, press any key...\n");
+ cgetc();
+ return 0;
+ } else if(err != 1) {
+ print_error(err);
+ return 0;
+ }
+
+ // Get # of bytes waiting, no more than size of rx_buf
+ bw = OS.dvstat[1] * 256 + OS.dvstat[0];
+
+ if(bw > sizeof(rx_buf))
+ bw = sizeof(rx_buf);
+
+ if(bw > 0) {
+ err = nread(url, rx_buf, bw);
+ if(err != 1) {
+ ui_print("READ ERROR: ");
+ print_error(err);
+ return 0;
+ }
+
+ trip = 0;
+ PIA.pactl |= 1; // Flag interrupt as serviced, ready for next one.
+
+ irc_parse();
+ }
+
+ return 1;
+}
+
+/* modern.ircdocs.horse say to do this IMMEDIATELY upon TCP
+ connection, without waiting for anything from the server. */
+void irc_register(void) {
+ txbuf_init();
+ txbuf_append_str("USER ");
+ txbuf_append_str(usernick); /* local (UNIX) username, just use the nick */
+ txbuf_append_str(" 0 * :FujiNetChat User\n"); /* "real" name (make it a pref?) */
+ txbuf_send();
+
+ txbuf_init();
+ txbuf_append_str("NICK ");
+ txbuf_append_str(usernick);
+ txbuf_append_str("\n");
+ txbuf_send();
+}
+
+/* only exits on error (e.g. connection closed, which might be via /QUIT). */
+void irc_loop(void) {
+ while(1) {
+ if(!irc_read()) return;
+
+ if(kbhit())
+ if(joined)
+ ui_keystroke();
+ else join_channel();
+ }
+}
+
+#else // !defined(__ATARI__)
+/* parsetest */
+int main(int argc, char **argv) {
+ strcpy((char *)rx_buf, argv[1]);
+ bw = strlen(rx_buf);
+ irc_parse();
+ /*
+ */
+ return 0;
+}
+#endif
diff --git a/src/irc.h b/src/irc.h
new file mode 100644
index 0000000..77510d1
--- /dev/null
+++ b/src/irc.h
@@ -0,0 +1,59 @@
+#define FNET_TRANSLATION 3
+#define MAX_IRC_MSG_LEN 512
+
+#define streq(x,y) !strcmp(x,y)
+#define streq_i(x,y) !strcasecmp(x,y)
+
+/**** main.c */
+extern char url[256];
+extern char usernick[32];
+extern char channel[32];
+extern unsigned char rx_buf[MAX_IRC_MSG_LEN];
+extern unsigned short bw;
+extern unsigned char err;
+extern unsigned char trip;
+
+extern unsigned int txbuflen;
+extern char tx_buf[MAX_IRC_MSG_LEN];
+
+/* clears the transmit buffer. */
+void txbuf_init(void);
+
+/* appends a string to the transmit buffer, updates txbuflen. */
+void txbuf_append_str(const char *str);
+
+/* clears the transmit buffer, then appends a string to it. */
+void txbuf_set_str(const char *str);
+
+/* sends whatever's in the transmit buffer, then clears it. if nothing was
+ in the buffer, nothing gets sent. */
+void txbuf_send(void);
+
+/* sends a string. clears transmit buffer first, then clears it again on exit. */
+void txbuf_send_str(const char *str);
+
+void print_error(unsigned char err);
+
+/**** irc.c */
+#define MAX_MSG_ARGS 8
+extern char *msg_src, *msg_cmd, *msg_dest, *msg_text;
+extern char *msg_args[MAX_MSG_ARGS];
+extern int msg_argcount;
+
+/* call this once, right after TCP connection is established. */
+void irc_register(void);
+
+/* does all the work. doesn't return until we get disconnected from
+ the IRC server (via /quit or error). */
+void irc_loop(void);
+
+/**** ui.c */
+void ui_init(void);
+void ui_start_msg(void);
+void ui_end_msg(void);
+void ui_keystroke(void);
+void ui_print(const char *str);
+void ui_putchar(char c);
+
+/**** cmd.c */
+void cmd_command(const char *cmd);
diff --git a/src/main.c b/src/main.c
new file mode 100644
index 0000000..81f598e
--- /dev/null
+++ b/src/main.c
@@ -0,0 +1,311 @@
+/* FujiNetChat, an IRC client. Based on NetCat and the old FujiChat. */
+
+#define SELF "FujiNetChat"
+#define VERSION "0.0"
+#define BANNER SELF " v" VERSION " (B. Watson)\n"
+
+#define DEF_URL "N:TCP://irc.libera.chat:6667"
+#define DEF_NICK "FNChatTest"
+#define DEF_CHANNEL "##atari"
+
+#include <atari.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+#include <conio.h> // for kbhit() and cgetc()
+#include "conio.h" // our local one.
+#include "nio.h"
+#include "irc.h"
+
+char url[256] = DEF_URL; // URL
+char usernick[32] = DEF_NICK;
+char tmp[8]; // temporary # to string
+unsigned char err; // error code of last operation.
+unsigned char trip=0; // if trip=1, fujinet is asking us for attention.
+bool old_enabled=false; // were interrupts enabled for old vector
+void* old_vprced; // old PROCEED vector, restored on exit.
+unsigned short bw=0; // # of bytes waiting.
+unsigned char rx_buf[MAX_IRC_MSG_LEN]; // RX buffer.
+unsigned char tx_buf[MAX_IRC_MSG_LEN]; // TX buffer.
+unsigned int txbuflen; // TX buffer length
+char channel[32] = DEF_CHANNEL;
+
+/* TODO: user modes (default +iw), fg/bg color... */
+
+extern void ih(); // defined in intr.s
+
+static void strcpy_to_eol(char *dst, const char *src) {
+ while(*src && (*src != CH_EOL)) {
+ *dst++ = *src++;
+ }
+ *dst = '\0';
+}
+
+/**
+ * Get URL from user.
+ */
+void get_config(void) {
+ OS.crsinh = 0;
+
+ putchar(CH_CLR);
+ print(BANNER);
+
+ while(1) {
+ print("\nURL [");
+ print(url);
+ print("]?\n");
+ get_line(tx_buf, sizeof(url) - 1);
+ if(tx_buf[0] != CH_EOL) strcpy_to_eol(url, tx_buf);
+
+ print("Nick [");
+ print(usernick);
+ print("]? ");
+ get_line(tx_buf, sizeof(usernick) - 1);
+ if(tx_buf[0] != CH_EOL) strcpy_to_eol(usernick, tx_buf);
+
+ print("Channel [");
+ print(channel);
+ print("]? ");
+ get_line(tx_buf, sizeof(channel) - 1);
+ if(tx_buf[0] != CH_EOL) strcpy_to_eol(channel, tx_buf);
+
+ /*
+ print("\n\nURL: ");
+ print(url);
+ print("\nNick: ");
+ print(usernick);
+ print("\nChannel: ");
+ print(channel);
+ */
+
+ print("\n\nAre these settings OK [Y/n]? ");
+ if(tolower(cgetc()) != 'n') break;
+ }
+
+ // print("Press Return to connect\n");
+ // cgetc();
+}
+
+/**
+ * Print error
+ */
+void print_error(unsigned char err) {
+ itoa(err, tmp, 10);
+ print(tmp);
+ print("\n");
+}
+
+void txbuf_init(void) {
+ txbuflen = tx_buf[0] = 0;
+}
+
+void txbuf_append_str(const char *str) {
+ while(*str) {
+ tx_buf[txbuflen++] = *str++;
+ }
+}
+
+void txbuf_set_str(const char *str) {
+ txbuf_init();
+ txbuf_append_str(str);
+}
+
+void txbuf_send(void) {
+ if(!txbuflen) return;
+ nwrite(url, tx_buf, txbuflen);
+ txbuf_init();
+}
+
+void txbuf_send_str(const char *str) {
+ txbuf_init();
+ txbuf_append_str(str);
+ txbuf_send();
+}
+
+int fn_connect(void) {
+ print("\n" "Connecting to: ");
+ print(url);
+ print("\n");
+
+ err = nopen(url, FNET_TRANSLATION);
+
+ if(err != SUCCESS) {
+ print("Connection failed: ");
+ print_error(err);
+ return 0;
+ }
+
+ // Open successful, set up interrupt
+ old_vprced = OS.vprced; // save the old interrupt vector
+ old_enabled = PIA.pactl & 1; // keep track of old interrupt state
+ PIA.pactl &= (~1); // Turn off interrupts before changing vector
+ OS.vprced = ih; // Set PROCEED interrupt vector to our interrupt handler.
+ PIA.pactl |= 1; // Indicate to PIA we are ready for PROCEED interrupt.
+
+ return 1;
+}
+
+void fn_disconnect(void) {
+ // Restore old PROCEED interrupt.
+ PIA.pactl &= ~1; // disable interrupts
+ OS.vprced=old_vprced;
+ PIA.pactl |= old_enabled;
+}
+
+int main(void) {
+ OS.lmargn = 0; // Set left margin to 0
+ OS.shflok = 0; // turn off shift-lock.
+ OS.soundr = 0; // Turn off SIO beeping sound
+ cursor(1); // Keep cursor on
+
+ while(1) {
+ get_config();
+ if(fn_connect()) {
+ irc_register();
+ irc_loop();
+ fn_disconnect();
+ }
+ }
+
+ OS.soundr = 3; // Restore SIO beeping sound
+ return 0;
+}
+
+/* cruft from netcat: */
+/**
+ * Main entrypoint
+ */
+#if 0
+int main(int argc, char* argv[])
+{
+ OS.soundr=0; // Turn off SIO beeping sound
+ cursor(1); // Keep cursor on
+
+ while (running==true)
+ {
+ if (get_url(argc, argv))
+ nc();
+ else
+ running=false;
+ }
+
+ OS.soundr=3; // Restore SIO beeping sound
+ return 0;
+}
+#endif
+
+#if 0
+/**
+ * NetCat
+ */
+void nc()
+{
+ OS.lmargn=0; // Set left margin to 0
+ OS.shflok=0; // turn off shift-lock.
+
+ // Attempt open.
+ print("\x9bOpening:\x9b");
+ print(url);
+ print("\x9b");
+
+ err=nopen(url,trans);
+
+ if (err != SUCCESS)
+ {
+ print("OPEN ERROR: ");
+ print_error(err);
+ return;
+ }
+
+ // Open successful, set up interrupt
+ old_vprced = OS.vprced; // save the old interrupt vector
+ old_enabled = PIA.pactl & 1; // keep track of old interrupt state
+ PIA.pactl &= (~1); // Turn off interrupts before changing vector
+ OS.vprced = ih; // Set PROCEED interrupt vector to our interrupt handler.
+ PIA.pactl |= 1; // Indicate to PIA we are ready for PROCEED interrupt.
+
+ // MAIN LOOP ///////////////////////////////////////////////////////////
+
+ while (running==true)
+ {
+ // If key pressed, send it.
+ while (kbhit())
+ {
+ tx_buf[txbuflen++]=cgetc();
+ }
+
+ if (txbuflen>0)
+ {
+ if (echo==true)
+ for (i=0;i<txbuflen;i++)
+ printc(&tx_buf[i]);
+
+ err=nwrite(url,tx_buf,txbuflen); // Send character.
+
+ if (err!=1)
+ {
+ print("WRITE ERROR: ");
+ print_error(err);
+ running=false;
+ continue;
+ }
+ txbuflen=0;
+ }
+
+ if (trip==0) // is nothing waiting for us?
+ continue;
+
+ // Something waiting for us, get status and bytes waiting.
+ err=nstatus(url);
+
+ if (err==136)
+ {
+ print("DISCONNECTED.\x9b");
+ running=false;
+ continue;
+ }
+ else if (err!=1)
+ {
+ print("STATUS ERROR: ");
+ print_error(err);
+ running=false;
+ continue;
+ }
+
+ // Get # of bytes waiting, no more than size of rx_buf
+ bw=OS.dvstat[1]*256+OS.dvstat[0];
+
+ if (bw>sizeof(rx_buf))
+ bw=sizeof(rx_buf);
+
+ if (bw>0)
+ {
+ err=nread(url,rx_buf,bw);
+
+ if (err!=1)
+ {
+ print("READ ERROR: ");
+ print_error(err);
+ running=false;
+ continue;
+ }
+
+ // Print the buffer to screen.
+ printl(rx_buf,bw);
+
+ trip=0;
+ PIA.pactl |= 1; // Flag interrupt as serviced, ready for next one.
+ } // if bw > 0
+ } // while running
+
+ // END MAIN LOOP ///////////////////////////////////////////////////////
+
+ // Restore old PROCEED interrupt.
+ PIA.pactl &= ~1; // disable interrupts
+ OS.vprced=old_vprced;
+ PIA.pactl |= old_enabled;
+
+}
+#endif
diff --git a/src/nio.c b/src/nio.c
new file mode 100644
index 0000000..1a5b14c
--- /dev/null
+++ b/src/nio.c
@@ -0,0 +1,183 @@
+/**
+ * N: I/O
+ */
+
+#include "nio.h"
+#include "sio.h"
+#include <atari.h>
+#include <stddef.h>
+
+#define TIMEOUT 0x1f /* approx 30 seconds */
+
+unsigned char nunit(char* devicespec)
+{
+ unsigned char unit=1;
+
+ // Set unit to 1 unless explicitly specified.
+ if (devicespec[1]==':')
+ unit=1;
+ else if (devicespec[2]==':')
+ unit=devicespec[1]-0x30; // convert from alpha to integer.
+ else
+ unit=1;
+
+ return unit;
+}
+
+unsigned char nopen(char* devicespec, unsigned char trans)
+{
+ unsigned char unit=nunit(devicespec);
+
+ OS.dcb.ddevic = DFUJI; // Fuji Device Identifier
+ OS.dcb.dunit = unit; // Unit number integer 1 through 4
+ OS.dcb.dcomnd = 'O'; // Open
+ OS.dcb.dstats = DWRITE; // sending to to SIO device
+ OS.dcb.dbuf = devicespec; // eg: N:TCP//
+ OS.dcb.dtimlo = TIMEOUT; // approximately 30 second timeout
+ OS.dcb.dbyt = 256; // max size of our device spec
+ OS.dcb.daux1 = OUPDATE; // Read and write
+ OS.dcb.daux2 = trans; // CR/LF translation
+ siov();
+
+ if (OS.dcb.dstats!=SUCCESS)
+ {
+ // something went wrong
+ // do we need to return extended status?
+ if (OS.dcb.dstats==DERROR)
+ {
+ nstatus(devicespec);
+ return OS.dvstat[DVSTAT_EXTENDED_ERROR]; // return extended error.
+ }
+ }
+ return OS.dcb.dstats; // Return SIO error or success
+}
+
+unsigned char nclose(char* devicespec)
+{
+ unsigned char unit=nunit(devicespec);
+
+ OS.dcb.ddevic = DFUJI;
+ OS.dcb.dunit = unit;
+ OS.dcb.dcomnd = 'C'; // Close
+ OS.dcb.dstats = 0x00;
+ OS.dcb.dbuf = NULL;
+ OS.dcb.dtimlo = TIMEOUT;
+ OS.dcb.dbyt = 0;
+ OS.dcb.daux = 0;
+ siov();
+
+ if (OS.dcb.dstats!=SUCCESS)
+ {
+ // something went wrong
+ // do we need to return extended status?
+ if (OS.dcb.dstats==DERROR)
+ {
+ nstatus(devicespec);
+ return OS.dvstat[DVSTAT_EXTENDED_ERROR]; // return extended error.
+ }
+ }
+ return OS.dcb.dstats; // Return SIO error or success.
+}
+
+unsigned char nstatus(char* devicespec)
+{
+ unsigned char unit=nunit(devicespec);
+
+ OS.dcb.ddevic = DFUJI;
+ OS.dcb.dunit = unit;
+ OS.dcb.dcomnd = 'S'; // status
+ OS.dcb.dstats = DREAD;
+ OS.dcb.dbuf = OS.dvstat;
+ OS.dcb.dtimlo = TIMEOUT;
+ OS.dcb.dbyt = sizeof(OS.dvstat);
+ OS.dcb.daux = 0;
+ siov();
+
+ return OS.dvstat[DVSTAT_EXTENDED_ERROR]; // return extended status
+}
+
+unsigned char nread(char* devicespec, unsigned char* buf, unsigned short len)
+{
+ unsigned char unit=nunit(devicespec);
+
+ OS.dcb.ddevic = DFUJI;
+ OS.dcb.dunit = unit;
+ OS.dcb.dcomnd = 'R'; // read
+ OS.dcb.dstats = DREAD;
+ OS.dcb.dbuf = buf;
+ OS.dcb.dtimlo = TIMEOUT;
+ OS.dcb.dbyt = OS.dcb.daux = len; // Set the buffer size AND daux with length
+ siov();
+
+ if (OS.dcb.dstats!=SUCCESS)
+ {
+ // something went wrong
+ // do we need to return extended status?
+ if (OS.dcb.dstats==DERROR)
+ {
+ nstatus(devicespec);
+ return OS.dvstat[DVSTAT_EXTENDED_ERROR]; // return extended error.
+ }
+ }
+ return OS.dcb.dstats; // Return SIO error or success.
+}
+
+unsigned char nwrite(char* devicespec, unsigned char* buf, unsigned short len)
+{
+ unsigned char unit=nunit(devicespec);
+
+ OS.dcb.ddevic = DFUJI;
+ OS.dcb.dunit = unit;
+ OS.dcb.dcomnd = 'W'; // write
+ OS.dcb.dstats = DWRITE;
+ OS.dcb.dbuf = buf;
+ OS.dcb.dtimlo = TIMEOUT;
+ OS.dcb.dbyt = OS.dcb.daux = len;
+ siov();
+
+ if (OS.dcb.dstats!=SUCCESS)
+ {
+ // something went wrong
+ // do we need to return extended status?
+ if (OS.dcb.dstats==DERROR)
+ {
+ nstatus(devicespec);
+ return OS.dvstat[DVSTAT_EXTENDED_ERROR]; // return extended error.
+ }
+ }
+ return OS.dcb.dstats; // Return SIO error or success.
+}
+
+unsigned char nlogin(char* devicespec, char *login, char *password)
+{
+ unsigned char unit=nunit(devicespec);
+
+ OS.dcb.ddevic=0x71;
+ OS.dcb.dunit=unit;
+ OS.dcb.dcomnd=0xFD;
+ OS.dcb.dstats=0x80;
+ OS.dcb.dbuf=login;
+ OS.dcb.dtimlo=0x1f;
+ OS.dcb.dbyt=256;
+ OS.dcb.daux=0;
+ siov();
+
+ if (OS.dcb.dstats!=1)
+ {
+ nstatus(devicespec);
+ return OS.dvstat[DVSTAT_EXTENDED_ERROR]; // return ext err
+ }
+
+ OS.dcb.dcomnd=0xFE;
+ OS.dcb.dstats=0x80;
+ OS.dcb.dbuf=password;
+ siov();
+
+ if (OS.dcb.dstats!=1)
+ {
+ nstatus(devicespec);
+ return OS.dvstat[DVSTAT_EXTENDED_ERROR]; // return ext err
+ }
+
+ return OS.dcb.dstats;
+}
diff --git a/src/nio.h b/src/nio.h
new file mode 100644
index 0000000..df992a2
--- /dev/null
+++ b/src/nio.h
@@ -0,0 +1,79 @@
+/**
+ * N: I/O
+ */
+
+#ifndef NIO_H
+#define NIO_H
+
+#define DFUJI 0x71
+#define DREAD 0x40
+#define DWRITE 0x80
+#define DUPDATE 0xC0
+
+#define OREAD 0x04
+#define OWRITE 0x08
+#define OUPDATE 0x0C
+
+#define SUCCESS 1
+#define DERROR 144
+
+#define DVSTAT_BYTES_WATING_LO 0
+#define DVSTAT_BYTES_WATING_HI 1
+#define DVSTAT_PROTOCOL 2
+#define DVSTAT_EXTENDED_ERROR 3
+
+/**
+ * Open N: device with devicespec
+ * @param devicespec - an N: device spec, e.g. N:TCP://FOO.COM:1234/
+ * @param translation mode, 0=none, 1=cr, 2=lf, 3=cr/lf
+ * @return error code, or 1 if successful.
+ */
+unsigned char nopen(char* devicespec, unsigned char trans);
+
+/**
+ * Close N: device with devicespec
+ * @param devicespec - an N: device spec to close (the unit number is extracted)
+ * @return error code, or 1 if successful.
+ */
+unsigned char nclose(char* devicespec);
+
+/**
+ * Get status of specific N: device
+ * @param devicespec - an N: device spec to status (the unit number is extracted)
+ * @return error code, or 1 if successful, DVSTAT is also filled with status info.
+ *
+ * Format of DVSTAT:
+ * OS.dcb.dvstat[0] = # of bytes waiting LO
+ * OS.dcb.dvstat[1] = # of bytes waiting HI
+ * OS.dcb.dvstat[2] = reserved
+ * OS.dcb.dvstat[3] = Error code of last I/O operation. !1 = error.
+ */
+unsigned char nstatus(char* devicespec);
+
+/**
+ * Read # of bytes from specific N: device.
+ * @param devicespec - an N: device spec to read bytes from.
+ * @param buf - The buffer to read into, must be at least as big as len.
+ * @param len - The # of bytes to read (up to 65535)
+ * @return error code, or 1 if successful, buf is filled with data.
+ */
+unsigned char nread(char* devicespec, unsigned char* buf, unsigned short len);
+
+/**
+ * Write # of bytes to specific N: device.
+ * @param devicespec - an N: device spec to write to.
+ * @param buf - The buffer to write to device, should be at least as big as len.
+ * @param len - The # of bytes to write (up to 65535)
+ * @return error code, or 1 if successful, buf is filled with data.
+ */
+unsigned char nwrite(char* devicespec, unsigned char* buf, unsigned short len);
+
+/**
+ * Send username and password credentials
+ * @param devicespec - The devicespec.
+ * @param login - The username to send
+ * @param password - The password to send
+ */
+unsigned char nlogin(char* devicespec, char* login, char* password);
+
+#endif /* NIO_H */
diff --git a/src/sio.h b/src/sio.h
new file mode 100644
index 0000000..87b787e
--- /dev/null
+++ b/src/sio.h
@@ -0,0 +1,7 @@
+/**
+ * Function to call sio
+ */
+
+void siov();
+void rtclr();
+void cold_start();
diff --git a/src/sio.s b/src/sio.s
new file mode 100644
index 0000000..3dd9191
--- /dev/null
+++ b/src/sio.s
@@ -0,0 +1,17 @@
+ ;; Call SIO
+
+ .export _siov
+ .export _rtclr
+ .export _cold_start
+
+_siov: JSR $E459
+ RTS
+
+_rtclr: LDA #$00
+ STA $12
+ STA $13
+ STA $14
+ RTS
+
+_cold_start:
+ JMP $E477
diff --git a/src/ui.c b/src/ui.c
new file mode 100644
index 0000000..68f32a0
--- /dev/null
+++ b/src/ui.c
@@ -0,0 +1,73 @@
+#include <atari.h>
+#include <stdio.h>
+#include "irc.h"
+#include <conio.h>
+#include "conio.h"
+
+#define MAX_INPUT_LEN 119
+
+static char input_buffer[MAX_INPUT_LEN + 1];
+static short inbuf_len = 0;
+
+void ui_init(void) {
+ OS.escflg = 0;
+ inbuf_len = input_buffer[0] = 0;
+}
+
+void ui_start_msg() {
+ /* TODO: use msg_src and msg_dest to decide which window to
+ print to (when we have multi-window support) */
+ OS.crsinh = 1;
+ putchar(CH_DELLINE);
+}
+
+void ui_end_msg(void) {
+ OS.crsinh = 0;
+ // putchar(CH_EOL); // NO!
+ if(inbuf_len) print(input_buffer);
+}
+
+void ui_print(const char *str) {
+ OS.escflg = 0x80;
+ print(str);
+ OS.escflg = 0;
+}
+
+void ui_putchar(char c) {
+ putchar(c);
+}
+
+void ui_keystroke(void) {
+ char c;
+
+ OS.escflg = 0x80;
+
+ /* pressing ctrl-3 (aka EOF) crashes cc65-compiled binaries *hard*,
+ so don't allow it. */
+ if(OS.ch == (KEY_3 | KEY_CTRL)) {
+ OS.ch = KEY_NONE;
+ return;
+ }
+
+ c = cgetc();
+
+ if(c == CH_EOL && !inbuf_len)
+ return; /* ignore empty message */
+
+ if(c == CH_DEL && inbuf_len) {
+ OS.escflg = 0;
+ putchar(c);
+ OS.escflg = 0x80;
+ input_buffer[inbuf_len--] = 0;
+ } else if(inbuf_len == MAX_INPUT_LEN) {
+ return; /* ignore */
+ } else {
+ putchar(c);
+ input_buffer[inbuf_len++] = c;
+ input_buffer[inbuf_len] = 0;
+ if(c == CH_EOL) {
+ cmd_command(input_buffer);
+ ui_init();
+ }
+ }
+}