--- title: "Willie’s BBS" id: 55084 type: "computer_media" slug: "willies-bbs" url: "http://localhost/computer_media/willies-bbs/" markdown_url: "http://localhost/computer_media/willies-bbs.md" published_at: "2024-06-14T10:23:41+00:00" modified_at: "2026-03-31T23:06:03+00:00" author: "David Anderson" featured_image: url: "http://localhost/wp-content/uploads/2022/04/20230809-041332.jpg" excerpt: "A full dial-up bulletin board system with user registration, private messaging, sysop chat, and machine code serial I/O loaded from a companion tape." category: - name: "Archived Media" slug: "archived-media" taxonomy: "category" url: "http://localhost/category/archived-media/" post_tag: - name: "Downloadable" slug: "downloadable" taxonomy: "post_tag" url: "http://localhost/tag/downloadable/" - name: "TS 2068" slug: "ts2068" taxonomy: "post_tag" url: "http://localhost/tag/ts2068/" model: - name: "Timex/Sinclair 2068" slug: "ts-2068" taxonomy: "model" url: "http://localhost/model/ts-2068/" indiv: - name: "Willie Jones" slug: "willie-jones" taxonomy: "indiv" url: "http://localhost/indiv/willie-jones/" genre: - name: "BBS" slug: "bbs" taxonomy: "genre" url: "http://localhost/type/bbs/" media_type: "Program" programmers: - name: "Willie Jones" slug: "willie-jones" taxonomy: "indiv" url: "http://localhost/indiv/willie-jones/" download_url: "https://archive.org/download/timex-sinclair-software-archive/Willies%20BBS%20%28198x%29%28Jones%2C%20Willie%29%28TS2068%29%28US%29%28Program%29.zip" mediadate: "198x" media_type_tags: "BBS" --- # Willie’s BBS Willie’s BBS is a bulletin board system that enables a home computer to act as a dial-up BBS, communicating with callers over a phone line using port 119 for modem control and port 115 for serial character output. The system supports up to 50 registered users with first/last name login, password protection, private messaging, a chat mode between the caller and the sysop, and a utilities menu for managing the user file. Machine code routines loaded from a separate cassette tape (“bbsmc”) handle low-level serial I/O, referenced via named labels such as `output`, `input`, and `statchk`. A software connect-time timer is implemented by reading the system’s three-byte frame counter at addresses 23672–23674 and converting it to hours and minutes. The program saves itself back to tape with `SAVE "tsbbs" LINE 10` to allow the sysop to update the stored copy after making changes. *** ## Program Analysis ### Program Structure The program is organized into clearly labeled functional blocks using `REM` comments. Execution flows through a series of subroutines and `GO TO` labels rather than a tight main loop. The primary sections are: 1. **Lines 10–50:** Initialization — loads machine code tape, gets the date, waits for a keypress to go online. 2. **Lines 1000–1090:** Phone answer loop — polls port 119 for an incoming ring, with an option to save the program back to tape. 3. **Lines 2000–2070:** Warm start and main menu — sets online timer, calls logon/name subroutines, presents the main menu. 4. **Lines 3000–3110:** Read messages — scans the message array for messages addressed to the current user. 5. **Lines 4000–4145:** Leave message — finds a free slot, collects recipient and message text, stores it. 6. **Lines 5000–5130:** Quit/hang up — sends goodbye text, resets modem port, returns to auto-answer loop. 7. **Lines 6000–6340:** Logon bulletin and utilities sub-menu (change user file, list users). 8. **Lines 7000–7230:** Chat mode — bidirectional character-at-a-time terminal chat between sysop and caller. 9. **Lines 7235–7265:**`getkey` input subroutine — collects a string from the caller via serial port. 10. **Lines 7400:**`cls` subroutine — clears screen and sends control codes to remote terminal. 11. **Lines 8000–8440:** User identification — new user registration or existing user login with password. 12. **Lines 9990–9993:** Timer subroutine — computes elapsed online time from system frame counters. ### Machine Code Usage The program loads a companion machine code file named `bbsmc` from cassette at line 30 using `LOAD "bbsmc" CODE`. Three entry points are referenced by label throughout the program: - `output` — called via `RANDOMIZE USR output` to prepare or clock out a character; typically followed immediately by `OUT 115, CODE p$(d)` to send the byte serially. - `input` — called via `LET c=USR input` to receive a single byte from the remote terminal. - `statchk` — called via `LET a=USR statchk` in the chat loop (line 7100) to check serial status: the return value is interpreted as a bitmask where 1 = transmit ready, 2 = receive ready, 3 = both. Port 119 is used for modem control (off-hook, answer, hangup commands), and port 115 is used for direct byte output to the serial line. These port assignments are consistent with an RS-232 or modem interface card for this platform. ### Key BASIC Idioms Several characteristic BASIC idioms appear throughout: - **Named line targets:** Labels such as `inputcom`, `quit`, `read`, `leave`, `chat`, `autoans`, `logonbull`, `inname`, `cls`, `nl`, `sm`, `getkey`, and `timer` are used in `GO TO` and `GO SUB` statements. These resolve to actual line numbers at runtime and serve as readable structured-programming anchors. - **DATA/RESTORE for menus:** Lines 2007–2020 and 6105–6115 use `DATA` statements to store menu text strings, then `RESTORE` to the specific line and `READ` in a loop. This avoids long chains of individual `PRINT` statements. - **Boolean arithmetic:** Line 9992 uses the expression `"0" AND hr<10` to conditionally prepend a leading zero to hours and minutes — a standard Sinclair BASIC trick exploiting the fact that a true condition evaluates to 1 (selecting the string) and false to 0 (yielding an empty string). - **ON ERR:** Used at lines 2036, 5001, and 7001 to trap errors and redirect to `quit` or to execute `RESET`, providing a safety net for unexpected runtime errors during a live call. ### Data Structures User and message data are maintained in string and numeric arrays (declared implicitly or by `DIM` calls assumed to be in the machine code bootstrap or an earlier initialization block not shown). The key structures are: | Variable | Purpose | | --- | --- | | `f$(x)` | First name of user `x` | | `l$(x)` | Last name of user `x` | | `w$(x)` | Password of user `x` | | `z$(x)` | Date of last call for user `x` | | `t(x)` | Call count for user `x` | | `w(x)` | Waiting message count for user `x` | | `m$(u)` | Message slot `u` — a fixed-width string: bytes 1–32 = To, 33–64 = From, 65+ = body | | `m(u)` | Flag: 1 = message in use, 0 = free | | `un` | Current user index | | `tu` | Total registered users | | `u$` | Current user’s full name | Messages are stored as fixed-width slices of a string array: `m$(y, 1 TO 32)` holds “To:”, `m$(y, 33 TO 64)` holds “From:”, and `m$(y, 65 TO )` holds the body, terminated by `CHR$ 3` (Ctrl-C / ETX). ### Chat Mode Implementation The chat mode at lines 7000–7163 implements a full-duplex character terminal. The status-check machine code routine is polled in a tight loop at line 7100, and receive-ready and transmit-ready flags are extracted from the return value using bitwise AND idioms. Received characters are printed locally and echoed; a received `CHR$ 3` (Ctrl-C) causes an exit back to the main menu. On the transmit side, `INKEY$` is polled and any local keypress is both printed and sent via `OUT 115`. The escape from chat for the sysop is detected at line 7161 by checking for `CODE i$ = 195`, which corresponds to the “NOT” or Symbol Shift key combination. ### Connect-Time Timer Lines 2003 and 9990–9993 implement a software timer. At logon, the three frame-counter bytes at addresses 23672, 23673, and 23674 are zeroed via `POKE`. The timer subroutine reads them back, reconstructs a 24-bit value, divides by 60 (frames per second) to get seconds, then computes hours and minutes. The result is read back from the screen character positions using `SCREEN$` into the string array `x$` so it can be transmitted to the caller as the connect-time string `x$`. ### Notable Techniques - The sysop-page sequence at line 7006 uses a counted loop of 128 iterations, printing a dot and beeping each time the caller’s key is not pressed, providing a visible “paging” progress indication while audibly alerting the sysop. - Line 1025 allows the sysop to press “S” while scanning to immediately re-save the program to tape as `tsbbs`, providing a convenient in-situ backup mechanism. - The `getkey` subroutine (lines 7237–7265) handles backspace (`CHR$ 8`) by shortening `r$` using the idiom `r$( TO (LEN r$-(LEN r$>0)))`, which safely prevents underflow to a negative length. - The new-user cap check at line 8127–8128 has a logic issue: when `tu` exceeds 50, the code decrements it but then falls through to `GO SUB sm` and `GO TO quit` on the same logical block, which works correctly only if those statements are on the same line as the decrement — as written, line 8128 unconditionally calls `GO SUB sm` and `PAUSE 300: GO TO quit` regardless of whether the user limit was hit, meaning every new user registration triggers those lines. The conditional logic is incomplete because the `IF` on line 8127 does not extend to line 8128. - Password comparison at line 8410 uses `w$(un, 1 TO LEN r$)` rather than a full equality check, meaning a caller who enters only the first few characters of their password will be authenticated — a security weakness inherent in the substring comparison approach. ## Source Code ``` 10 CLS 20 PRINT AT 5,0;"Load BBSMC tape and press ""PLAY""" 30 LOAD "bbsmc"CODE 35 PRINT "Loaded...Enter Date" 37 INPUT "DATE: DD/MM ";d$ 40 PRINT ,,"PRESS ANY KEY TO GO ONLINE..." 50 PAUSE 0 1000 REM ANSWER PHONE 1002 OUT 119,34 1004 OUT 119,0 1006 PRINT AT 5,4;"SCANNING PHONE LINE..." 1010 PRINT AT 10,6;"PRESS ""S"" to update cassette" 1012 PRINT TAB 6;"PRESS ""C"" ANYTIME TO FORCE CALLER INTO CHAT MODE" 1020 LET a=IN 119 1025 IF INKEY$="S" OR INKEY$="s" THEN SAVE "tsbbs" LINE 10 1030 IF a=5 THEN GO TO 1020 1080 REM RING IN 1090 OUT 119,2: OUT 119,34 2000 REM WARM START 2001 OUT 119,64: OUT 119,123: OUT 119,55 2002 REM SET ONLINE TIMER 2003 POKE 23672,0: POKE 23673,0: POKE 23674,0 2004 GO SUB logonbull: GO SUB inname 2005 REM MAIN MENU 2006 GO SUB cls 2007 DATA " Willie's TS-BBS"," ","ead your messages","eave a message","hat with SYSOP","tilities Menu","ime on since logon","ang Up"," ","Choice?:" 2010 RESTORE 2007 2020 FOR p=1 TO 10: READ p$: GO SUB nl: GO SUB sm: NEXT p 2036 ON ERR GO TO quit: GO SUB getkey: LET c$=r$ 2037 IF c$="U" OR c$="u" THEN GO TO 6100 2040 IF c$="r" OR c$="R" THEN FOR x=1 TO 2: GO SUB nl: NEXT x: GO TO read 2041 IF c$="t" OR c$="T" THEN GO SUB timer: LET p$="Connect time HH/MM: ": LET p$=p$+x$: GO SUB nl: GO SUB sm: PAUSE 100: GO TO inputcom 2045 IF c$="c" OR c$="C" THEN GO TO chat 2047 IF c$="H" OR c$="h" THEN GO TO quit 2050 IF c$="l" OR c$="L" THEN GO TO leave 2070 GO TO inputcom 3000 REM read messages 3007 LET w(un)=0 3010 LET p$="Scanning message base.....": GO SUB sm 3020 FOR u=1 TO 50 3025 LET r$=m$(u,5 TO (LEN u$+4)): IF r$=u$ THEN GO TO 3070 3030 NEXT u 3040 REM DONE SCANNING MESSAGES 3045 GO SUB nl: GO SUB nl 3050 LET p$="message scan completed...": GO SUB sm: PAUSE 100: GO TO inputcom 3060 REM MESSAGE #X FOUND 3070 GO SUB cls: LET p$="Message #"+STR$ u: GO SUB sm: GO SUB nl 3080 LET p$=m$(u): GO SUB sm 3085 GO SUB nl: GO SUB nl: LET p$="N=Next One D=Delete Q=Quit: ": GO SUB sm 3090 GO SUB getkey: LET c$=r$ 3095 IF c$="N" OR c$="n" THEN GO SUB cls: GO TO 3030 3100 IF c$="D" OR c$="d" THEN FOR x=1 TO 400: LET m$(u,x)="": NEXT x: GO SUB nl: LET p$="*DELETED*": LET m(u)=0: GO SUB sm: PAUSE 100: GO SUB cls: RETURN 3105 IF c$="Q" OR c$="q" THEN GO TO inputcom 3110 GO TO 3090 4000 REM leave message 4010 LET y=1 4015 IF m(y)=0 THEN GO TO 4025 4020 LET y=y+1: GO TO 4015 4025 GO SUB cls 4030 LET p$="Message #"+STR$ y: GO SUB sm 4032 GO SUB nl: GO SUB nl 4035 LET p$="Who gets message?: ": GO SUB sm 4040 GO SUB getkey: LET a$=r$ 4042 LET i$="": LET k$="" 4045 FOR x=1 TO LEN a$ 4050 IF a$(x)<>" " THEN LET i$=i$+a$(x) 4055 IF a$(x)=" " THEN GO TO 4065 4060 NEXT x 4065 LET k$=a$(x+1 TO ) 4066 FOR x=1 TO tu: IF f$(x,1 TO LEN i$)=i$ AND l$(x,1 TO LEN k$)=k$ THEN GO TO 4070 4067 NEXT x 4070 LET w(x)=w(x)+1 4072 LET m$(y,1 TO 32)="To: "+a$ 4075 LET p$="From: "+u$: GO SUB sm: LET m$(y,33 TO 64)=p$ 4080 GO SUB nl 4085 LET p$="Input message (400 chars max)... to save...": GO SUB sm: GO SUB nl 4090 LET wm=1: GO SUB getkey: LET wm=0: LET p$="*DONE*": GO SUB sm: LET m$(y,65 TO )=r$ 4095 LET m$(y,LEN r$+65)=CHR$ 3 4110 GO SUB nl: GO SUB nl 4120 LET p$="R=Redo S=Save A=Abort: (R,S,A)": GO SUB sm 4125 GO SUB getkey: LET c$=r$ 4130 IF c$="R" OR c$="r" THEN GO TO 4025 4135 IF c$="S" OR c$="s" THEN LET m(y)=1: LET p$="*STORED*": GO SUB sm: PAUSE 100: GO TO inputcom 4140 IF c$="A" OR c$="a" THEN GO TO inputcom 4145 GO TO 4125 5000 REM quit message base 5001 ON ERR RESET 5100 GO SUB nl 5110 LET p$="Thank you for calling!" 5115 GO SUB cls 5120 GO SUB sm 5125 OUT 119,64: OUT 119,0: OUT 119,0 5130 PAUSE 100: GO TO autoans 6000 REM LOGON MESSAGE 6001 PAUSE 30: FOR x=1 TO 30: RANDOMIZE USR output: OUT 115,0: NEXT x 6002 IF IN 119<128 THEN GO TO quit 6008 RANDOMIZE USR output: OUT 115,28 6010 GO SUB cls 6020 LET P$=" Willie's TS2068 BBS c/o Indiana Sinclair-Timex User Group": GO SUB sm 6030 FOR x=1 TO 5: GO SUB nl: NEXT x 6040 RETURN 6100 REM Utilities Sub-Menu 6102 GO SUB cls 6105 DATA " Utility Sub-Menu","hange Personal UserFile","ist of users","eturn to Main Menu" 6110 RESTORE 6105 6115 FOR p=1 TO 4: READ p$: GO SUB sm: GO SUB nl: NEXT p 6117 GO SUB nl: GO SUB nl: LET p$="Choice? (C,L, or R): ": GO SUB sm 6120 GO SUB getkey 6125 IF r$="C" OR r$="c" THEN GO TO 6200 6130 IF r$="L" OR r$="l" THEN GO TO 6300 6135 IF r$="R" OR r$="r" THEN GO TO inputcom 6140 GO TO 6120 6199 REM CHANGE user file 6200 GO SUB cls 6205 LET p$="First Name: "+f$(un): GO SUB sm 6207 IF LEN f$(un)<20 THEN GO SUB nl 6210 LET p$="Last Name : "+l$(un): GO SUB sm 6213 IF LEN l$(un)<20 THEN GO SUB nl 6215 LET p$="PASSWORD : "+w$(un): GO SUB sm 6220 GO SUB nl 6225 LET p$="Change line? (1-3, 0=Exit): ": GO SUB sm: GO SUB getkey 6227 LET c$=r$ 6230 IF c$="0" THEN GO TO 6100 6235 IF c$="3" THEN LET p$="New Password: ": GO SUB sm: GO SUB getkey: IF LEN r$>8 THEN LET p$="Must be 1-8 characters!": GO SUB sm: GO TO 6235 6237 IF c$="3" THEN LET w$(un)=r$ 6240 IF c$="2" THEN LET p$="New Last Name: ": GO SUB sm: GO SUB getkey: LET l$(un)=r$ 6245 IF c$="1" THEN LET p$="New First Name: ": GO SUB sm: GO SUB getkey: LET f$(un)=r$ 6250 GO TO 6200 6300 REM transmit userfile list 6305 GO SUB cls 6310 LET p$="Press ENTER when ready:": GO SUB sm 6315 LET c=USR input 6316 IF c<>13 THEN GO TO 6315 6317 GO SUB nl 6320 FOR x=1 TO tu 6322 LET p$="User File #"+STR$ x 6323 GO SUB sm: GO SUB nl 6325 LET p$="Last name: "+l$(x): GO SUB sm 6327 IF LEN l$(x)<20 THEN GO SUB nl 6330 LET p$="First name: "+f$(x): GO SUB sm 6335 GO SUB nl: NEXT x 6340 GO SUB nl: LET p$="Press any key to go to main menu": GO SUB sm: LET c=USR input: GO TO inputcom 7000 REM chat 7001 ON ERR GO TO quit 7005 GO SUB nl: LET p$="Paging sysop...................": FOR x=1 TO 10: BEEP .1,1: NEXT x: GO SUB sm: GO SUB nl 7006 FOR x=1 TO 128: IF INKEY$="" THEN LET p$=".": GO SUB sm: BEEP .1,10: NEXT x: LET p$="The sysop is not available.....": GO TO inputcom 7008 GO SUB nl: LET p$="You are now in chat mode:": GO SUB sm: GO SUB nl 7009 CLS : PRINT "You are in chat mode with ";u$: PRINT : PRINT "SYMBL SHIFT/NOT to escape..." 7015 POKE 23692,255 7020 LET RxRDY=0: LET TxRDY=0 7100 LET a=USR statchk: LET TxRDY=1 AND (a=1 OR a=3): LET RxRDY=1 AND (a=2 OR a=3) 7130 IF RxRDY THEN LET k=USR input: PRINT CHR$ k;: IF k=13 THEN PRINT ">"; 7135 IF RxRDY THEN IF k=3 THEN GO TO inputcom 7140 IF TxRDY AND INKEY$<>"" THEN GO SUB 7160 7150 GO TO 7010 7160 IF TxRDY THEN LET i$=INKEY$: PRINT i$;: IF CODE i$<>12 THEN OUT 115,CODE i$: IF CODE i$=13 THEN PRINT ">"; 7161 IF CODE i$=195 THEN GO TO inputcom 7162 IF INKEY$<>"" THEN GO TO 7162 7163 RETURN 7200 FOR d=1 TO LEN p$: RANDOMIZE USR output: OUT 115,CODE p$(d): PRINT p$(d); 7202 IF CODE p$(d)=3 THEN GO TO 7210 7205 NEXT d 7210 RETURN 7220 REM Send a CHR$ 13 7230 POKE 23692,255: PRINT : RANDOMIZE USR output: OUT 115,13: RETURN 7235 REM input subroutine 7237 LET r$="" 7240 LET c=USR input 7241 IF c=3 AND wm=1 THEN RETURN 7242 IF c=13 AND wm=1 THEN LET r$=r$+CHR$ 13: GO TO 7240 7245 IF c=13 THEN LET wm=0: RETURN 7250 IF c=8 THEN LET r$=r$( TO (LEN r$-(LEN r$>0))): GO TO 7240 7255 IF c<>13 AND c<>8 AND (c<32 OR c>122) THEN GO TO 7240 7260 LET r$=r$+CHR$ c: PRINT CHR$ c; 7265 GO TO 7240 7400 CLS : RANDOMIZE USR output: OUT 115,28: RANDOMIZE USR output: OUT 115,31: RETURN 8000 REM user input subroutine 8010 LET p$="First Name: ": GO SUB sm 8020 GO SUB getkey: LET a$=r$ 8050 LET p$="Last Name : ": GO SUB sm 8060 GO SUB getkey: LET b$=r$ 8065 GO SUB nl: LET p$="Above correct? (Y/N): ": GO SUB sm: GO SUB getkey: IF r$="n" OR r$="N" THEN GO TO 8000 8075 LET u$=a$+" "+b$ 8080 FOR x=1 TO tu 8085 IF l$(x,1 TO LEN b$)=b$ AND f$(x,1 TO LEN a$)=a$ THEN GO TO 8300 8090 NEXT x 8120 LET p$="You are a new user....": GO SUB sm 8125 REM new user 8127 LET tu=tu+1: IF tu>50 THEN LET tu=tu-1: LET p$="Sorry! Total number of users allowed access to this system has been reached. Please call back in a few days, by then a new spot should be open: THANKS!" 8128 GO SUB sm: PAUSE 300: GO TO quit 8130 GO SUB cls 8132 LET un=x 8135 LET p$=" WELCOME!": GO SUB sm: GO SUB nl: GO SUB nl 8140 GO SUB nl: GO SUB nl 8145 LET p$="I do hope you enjoy your visit with my BBS and please feel freeto leave any comments you wish addressed to SYSOP": GO SUB sm 8150 GO SUB nl: GO SUB nl 8155 LET p$="Please enter a 1-8 character password for further protection of your private mail-": GO SUB sm 8160 GO SUB nl 8165 GO SUB nl: LET p$="PASSWORD: ": GO SUB sm 8170 GO SUB getkey: LET c$=r$: IF LEN c$>8 THEN LET p$="Must be 1-8 characters ONLY!": GO SUB sm: GO TO 8165 8175 LET p$="Your password is "+c$: GO SUB sm: LET w$(un)=c$ 8180 GO SUB nl: LET p$="Change password? (Y/N):": GO SUB sm 8185 GO SUB getkey 8190 IF r$="Y" OR r$="y" THEN GO TO 8160 8195 LET l$(un)=b$: LET f$(un)=a$ 8200 LET z$(un)=d$ 8205 LET t(un)=1 8250 PAUSE 150: GO TO inputcom 8300 REM old user 8305 GO SUB nl 8310 LET un=x 8315 LET p$="PASSWORD : ": GO SUB sm 8320 GO SUB getkey 8407 GO SUB nl 8410 IF r$<>w$(un,1 TO LEN r$) THEN LET p$="Incorrect password -- Try again": GO SUB sm: GO TO 8300 8415 LET p$="You have called this system "+STR$ t(un): GO SUB sm: GO SUB nl: LET p$="times before.": GO SUB sm 8417 GO SUB nl 8420 LET p$="Last time called was on "+z$(un): GO SUB sm 8425 IF w(un)>0 THEN LET p$="You have "+STR$ w(un)+" messages waiting.": GO SUB sm 8430 LET z$(un)=d$ 8435 LET t(un)=t(un)+1 8440 PAUSE 200: GO TO inputcom 9990 REM timer 9992 DIM x$(5): LET time=INT ((PEEK 23672+256*PEEK 23673+256*256*PEEK 23674)/60): LET hr=INT (time/60/60): LET min=INT ((time-(hr*60*60))/60): PRINT AT 20,0;"0" AND hr<10;hr;":";"0" AND min<10;min: FOR x=1 TO 5: LET x$(x)=SCREEN$ (20,x-1): NEXT x 9993 RETURN ```