Recent posts

#11
Herdware / C128D's going in for repair!
Last post by RobertB - August 16, 2026, 09:26 AM
Last year I had repair tech Ray Carlsen install a North American power supply, convert to 64K video RAM, and fix a disk drive problem on my plastic, European Commodore 128D.  Since that time, I've exhibited it at various shows, and it has worked perfectly with a Dell U2410 multiscan monitor.

This November I bring more for Ray to repair!  The plastic C128D returns to him for the C128 upgrade ROMs and the 1571 upgrade ROM.  Also I will bring in my club's flat C128 for him to upgrade its ROMs and to bring it up to 64K video RAM.  Not only that but I bring him three, metal C128DCR's that were given to me by Al Jackson, former president of the defunct Clark County Commodore Computer Club (of Las Vegas).  The cases and keyboards were covered in desert dust, and so, I'll have Ray check them out.  For sure, a disk drive head clean and drive rail lubrication.  One keyboard has two, non-working keys.  And some of the cases have holes drilled in them for various, extra switches; those will have to be investigated and checked for functionality.

What else can I bring Ray to repair?  Another power supply upgrade for a 1541 drive?  Maybe I can dig out some C64's for him to check out (like earlier this year when he diagnosed bad RAM in a C64C short-board model.  He said that was the first time he had seen bad RAM in a short board, because those didn't go bad.)  Maybe my personal, metal C128DCR for a power supply upgrade?

Truly,
Robert Bernardo
Fresno Commodore User Group - http://www.dickestel.com/fcug.htm
Southern California Commodore & Amiga Network - http://www.portcommodore.com/sccan
#12
RBBS-128 1.0 is the first functional proof release containing:

  • TCPSRV-based incoming call handling and disconnect detection
  • C128 SwiftLink/ACIA serial I/O
  • user registration and password login
  • persistent REL user records
  • caller counters/history
  • SYSOP flag/privileges
  • multiple message areas
  • dedicated Private Mail area
  • public/private message entry
  • message retrieval
  • summary scanning
  • new-message reading
  • per-user/per-area last-read tracking
  • replies
  • message deletion with ownership/SYSOP rules
  • Australian date handling

The current command loop already represents the core messaging release nicely:

a - change message area
e - enter a message
p - reply to a message
r - retrieve a message
s - scan message summaries
n - read new messages
k - kill a message
g - goodbye
? - help
#13
Interim code (15/8/2026) (WIP)

10 rem rbbs-128 1.0 main program
20 da=56832:sa=56833:ca=56834:ta=56835
30 sd=8:dd=9:fd=10
40 fast
45 dim m(200,2),ms$(10),ya$(16),ys$(16),ym$(16),yt$(16)
50 graphic 5
60 poke ta,30:poke ca,11
62 c1$=chr$(35)+chr$(67)+chr$(79)+chr$(78)+chr$(78)+chr$(69)+chr$(67)+chr$(84)+chr$(58)
64 c3$=chr$(35)+chr$(68)+chr$(73)+chr$(83)+chr$(67)+chr$(79)+chr$(78)+chr$(78)+chr$(69)+chr$(67)+chr$(84)
70 scnclr
80 print "rbbs-128 1.0"
90 print
100 print "commodore 128 remote bulletin board v1.0"
110 print
120 gosub 8300:gosub 10200:goto 7000

1000 rem caller login
1010 ur=0:hm=0:mf$="":pw$="":ci$=""
1020 a1$="enter your first name: ":n=1:gosub 5040
1030 c=1:ml=20:gosub 5170
1040 if cd then goto 7900
1050 n$=b$
1060 if n$="" then 1020
1070 if n$<"a" or len(n$)=1 then 1020
1080 ck=0
1090 for ix=1 to len(n$)
1100 if mid$(n$,ix,1)=" " then ck=1
1110 next ix
1120 if ck=0 then 1150
1130 a$="please do not use spaces in your name":gosub 5040
1140 gosub 5040:goto 1020
1150 a1$="enter your last name:  ":n=1:gosub 5040
1160 c=1:ml=20:gosub 5170
1170 if cd then goto 7900
1180 o$=b$
1190 if o$="" then 1020
1200 if o$<"a" or len(o$)=1 then 1020
1210 ck=0
1220 for ix=1 to len(o$)
1230 if mid$(o$,ix,1)=" " then ck=1
1240 next ix
1250 if ck=0 then 1280
1260 a$="please do not use spaces in your name":gosub 5040
1270 gosub 5040:goto 1150
1280 a$="checking user file...":gosub 5040
1290 gosub 2000
1300 if ok=0 then 1400
1310 a1$="enter your password: ":n=1:gosub 5040
1320 c=2:ml=20:gosub 5170
1330 if cd then goto 7900
1340 if b$=pw$ then 1370
1350 a$="incorrect password":gosub 5040
1360 goto 1310
1370 a$="login accepted":gosub 5040
1380 a$="welcome "+n$+" "+o$:gosub 5040
1390 goto 1800

1400 rem new caller
1410 a$="new user registration":gosub 5040
1420 gosub 2200
1430 if cd then goto 7900
1440 if ok=0 then 1020
1450 a$="registration complete":gosub 5040
1460 a$="welcome "+n$+" "+o$:gosub 5040

1800 rem login complete
1810 lg=-1
1820 gosub 8700
1830 gosub 5040
1840 a$="you are caller number:"+str$(tc):gosub 5040
1850 a$="rbbs-128 login complete.":gosub 5040
1860 a$="date/time: "+dd$+"/"+mo$+"/"+yr$+" "+tm$:gosub 5040
1870 gosub 9000
1880 goto 4000

2000 rem check rel users file
2010 ok=0:pw$="":ci$="":hm=0:ur=0:mf$=""
2020 open 15,dd,15
2030 open 2,dd,2,"users"
2040 rn=1:gosub 8000:gosub 8100
2050 nu=val(r$)
2060 if nu<1 then 2160
2070 for ur=2 to nu+1
2080 rn=ur:gosub 8000:gosub 8100
2090 u$=mid$(r$,3)
2100 gosub 2700
2110 if f1$=n$ and f2$=o$ then 2140
2120 next ur
2130 goto 2160
2140 mf$=left$(r$,1)
2150 pw$=f4$:ci$=f3$:hm=val(f5$):ok=-1
2160 close 2
2170 close 15
2180 return

2200 rem new user registration
2210 ok=0
2220 a1$="city/state: ":n=1:gosub 5040
2230 c=1:ml=30:gosub 5170
2240 if cd then return
2250 ci$=b$
2260 if len(ci$)<2 then 2220
2270 a1$="choose a password: ":n=1:gosub 5040
2280 c=2:ml=20:gosub 5170
2290 if cd then return
2300 p1$=b$
2310 if len(p1$)<3 then 2270
2320 a1$="enter password again: ":n=1:gosub 5040
2330 c=2:ml=20:gosub 5170
2340 if cd then return
2350 if b$=p1$ then 2390
2360 a$="passwords do not match":gosub 5040
2370 gosub 5040
2380 goto 2270
2390 gosub 5040
2400 a$="name: "+n$+" "+o$:gosub 5040
2410 a$="city/state: "+ci$:gosub 5040
2420 a1$="is this correct (y/n)? ":n=1:gosub 5040
2430 c=1:ml=1:gosub 5170
2440 if cd then return
2450 if b$="" then 2420
2460 if asc(b$)=89 then 2500
2470 if asc(b$)=78 then 2220
2480 goto 2420

2500 rem append new rel user
2510 ok=0
2520 open 15,dd,15
2530 open 2,dd,2,"users"
2540 rn=1:gosub 8000:gosub 8100
2550 nu=val(r$)
2560 rn=nu+2:ur=rn
2570 gosub 8000
2580 r$="  "+n$+";"+o$+";"+ci$+";"+p1$+";0"
2590 gosub 8200
2600 rn=1:gosub 8000
2610 r$=str$(nu+1)
2620 gosub 8200
2630 close 2
2640 input#15,en,em$,et,es
2650 close 15
2660 if en<>0 then 2690
2670 pw$=p1$:hm=0:mf$=" ":ok=-1
2680 return
2690 a$="error writing users file":gosub 5040
2695 return

2700 rem split user record
2710 f1$="":f2$="":f3$="":f4$="":f5$=""
2720 fi=1
2730 for ix=1 to len(u$)
2740 q$=mid$(u$,ix,1)
2750 if q$=";" then fi=fi+1:goto 2810
2760 if fi=1 then f1$=f1$+q$
2770 if fi=2 then f2$=f2$+q$
2780 if fi=3 then f3$=f3$+q$
2790 if fi=4 then f4$=f4$+q$
2800 if fi=5 then f5$=f5$+q$
2810 next ix
2820 return

2900 rem update current user record
2910 if ur=0 then return
2920 if mf$="" then mf$=" "
2930 open 15,dd,15
2940 open 2,dd,2,"users"
2950 rn=ur:gosub 8000
2960 r$=mf$+" "+n$+";"+o$+";"+ci$+";"+pw$+";"+str$(hm)
2970 gosub 8200
2980 close 2
2990 close 15
2995 return

3000 rem sysop functions - reserved
3010 return

4000 rem temporary command loop
4010 gosub 5040
4020 a1$=n$+"? command (a,g,?): ":n=1:gosub 5040
4030 c=1:ml=1:gosub 5170
4040 if cd then goto 7900
4050 if b$="" then 4000
4060 if b$="a" then gosub 9000:goto 4000
4070 if b$="g" then 4200
4080 if b$="?" then 4100
4090 a$="i don't understand '"+b$+"'":gosub 5040
4095 goto 4000

4100 rem temporary help
4110 a$="a - change message area":gosub 5040
4120 a$="g - goodbye":gosub 5040
4130 a$="? - this help":gosub 5040
4140 goto 4000

4200 rem temporary goodbye
4210 gosub 5040
4220 a$="thanks for calling, "+n$+".":gosub 5040
4230 a$="please call again!":gosub 5040
4240 if ur>0 then gosub 2900
4250 lg=0
4260 a$="please disconnect.":gosub 5040
4270 goto 7800

5040 rem rbbs-128 output service
5050 if a1$="" then 5080
5060 a$=a1$
5070 a1$=""
5080 if len(a$)=0 then 5140
5090 for ix=1 to len(a$)
5100 ch=asc(mid$(a$,ix,1))
5110 gosub 5800
5120 gosub 5700
5130 next ix
5135 pp$=a$
5140 if n=1 then 5160
5150 ch=13:gosub 5800:ch=10:gosub 5800:print
5160 a$="":n=0:return

5170 rem rbbs-128 line input service
5180 b$=""
5190 gosub 6210
5200 return

5600 rem initialize swiftlink acia
5610 poke ta,30
5620 poke ca,11
5630 return

5700 rem display ascii byte on c128
5710 x=ch
5720 if x>=97 and x<=122 then x=x-32
5730 print chr$(x);
5740 return

5800 rem send ascii byte to tcpsrv
5810 if (peek(sa) and 16)=0 then 5810
5820 poke da,ch
5830 return

6000 rem get serial byte
6010 if (peek(sa) and 8)=0 then 6010
6020 ch=peek(da)
6030 return

6100 rem get raw tcpsrv control line
6110 ct$=""
6120 gosub 6000
6130 if ch=10 then 6120
6140 if ch=13 then 6180
6150 if ch<32 or ch>126 then 6120
6160 ct$=ct$+chr$(ch)
6170 goto 6120
6180 return

6210 rem rbbs-128 character line editor
6220 b$=""
6230 gosub 6000
6240 if ch=10 then 6230
6250 if ch=13 then 6520
6260 if ch=8 then 6400
6270 if ch=127 then 6400
6280 if ch=18 then 6450
6290 if ch=21 then 6480
6300 if ch=24 then 6480
6310 if ch=12 then 6450
6320 if ch<32 then 6230
6330 if ch>126 then 6230
6340 if len(b$)>=ml then 6230
6350 x=ch
6360 if x>=97 and x<=122 then x=x-32
6370 b$=b$+chr$(x)
6380 if c=1 then gosub 6680
6390 goto 6230

6400 rem delete last character
6410 if len(b$)=0 then 6230
6420 b$=left$(b$,len(b$)-1)
6430 if c=2 then 6230
6440 ch=8:gosub 6680
6445 goto 6230

6450 rem ctrl-r or ctrl-l - retype line
6455 if c=2 then 6230
6460 print
6465 if len(b$)=0 then 6230
6470 for ix=1 to len(b$)
6472 ch=asc(mid$(b$,ix,1))
6474 gosub 6680
6476 next ix
6478 goto 6230

6480 rem ctrl-u or ctrl-x - erase line
6485 if len(b$)=0 then 6230
6490 if c=2 then b$="":goto 6230
6495 for ix=1 to len(b$)
6500 ch=8:gosub 6680
6505 next ix
6510 b$=""
6515 goto 6230

6520 rem finish input line
6525 if right$(b$,len(c3$))=c3$ then cd=-1:return
6530 if c=1 then ch=13:gosub 6600
6535 if c=2 then print
6540 ch=13:gosub 5800
6545 ch=10:gosub 5800
6550 return

6600 rem echo ascii byte locally
6610 if ch=13 then print:return
6620 if ch=10 then return
6630 if ch=8 then print chr$(20);:return
6640 x=ch
6650 if x>=97 and x<=122 then x=x-32
6660 print chr$(x);
6670 return

6680 rem echo ascii byte local and remote
6685 gosub 5800
6690 gosub 6600
6695 return

6700 rem display ascii string locally
6710 for ix=1 to len(a$)
6720 x=asc(mid$(a$,ix,1))
6730 if x>=97 and x<=122 then x=x-32
6740 print chr$(x);
6750 next ix
6760 print
6770 return

7000 rem waiting for call
7010 cd=0:lg=0:ur=0
7020 print
7030 print "waiting for call..."
7040 gosub 6100
7050 p=instr(ct$,c1$)
7060 if p>0 then 7200
7070 goto 7040

7200 rem process connect and host date/time
7210 dt$=mid$(ct$,p+len(c1$))
7220 if len(dt$)<19 then 7300
7230 dd$=left$(dt$,2)
7240 mo$=mid$(dt$,4,2)
7250 yr$=mid$(dt$,7,4)
7260 tm$=mid$(dt$,12,8)
7270 ti$=mid$(dt$,12,2)+mid$(dt$,15,2)+mid$(dt$,18,2)
7280 print
7285 print "caller connected"
7290 print "date: ";dd$;"/";mo$;"/";yr$
7292 print "time: ";tm$
7295 goto 7400

7300 rem invalid time packet
7310 print "invalid time packet"
7320 goto 7800

7400 rem start rbbs caller session
7410 cd=0
7420 scnclr
7430 a$="rbbs-128 1.0":gosub 5040
7440 gosub 5040
7450 a$="commodore 128 remote bulletin board":gosub 5040
7460 gosub 5040
7470 goto 1000

7800 rem wait for caller disconnect
7810 gosub 6100
7820 if instr(ct$,c3$)>0 then 7900
7830 goto 7810

7900 rem caller disconnected cleanup
7910 if lg and ur>0 then gosub 2900
7920 cd=0:lg=0:ur=0
7930 n$="":o$="":pw$="":ci$="":mf$=""
7940 scnclr
7950 print "rbbs-128 1.0"
7960 print
7970 print "caller disconnected"
7980 goto 7000

8000 rem position 62 byte user record
8010 record#2,(rn),1
8020 return

8100 rem read 62 byte rel record
8110 r$=""
8120 for ri=1 to 62
8130 get#2,q$
8140 if q$="" then q$=chr$(0)
8150 r$=r$+q$
8160 next ri
8170 return

8200 rem write 62 byte rel record
8210 if len(r$)>=62 then 8240
8220 r$=r$+" "
8230 goto 8210
8240 if len(r$)>62 then r$=left$(r$,62)
8250 print#2,r$;
8260 return

8300 rem read system counters
8310 tc=0:mg=0:ls=0:rc=0
8320 dopen#4,"counters",d0,u9
8330 record#4,1,1
8340 r$=""
8350 for ri=1 to 32
8360 get#4,q$
8370 if q$="" then q$=" "
8380 r$=r$+q$
8390 next ri
8400 dclose#4
8410 z1$="":z2$="":z3$="":z4$=""
8420 zi=1
8430 for ix=1 to len(r$)
8440 q$=mid$(r$,ix,1)
8450 if q$=";" then zi=zi+1:goto 8500
8460 if zi=1 then z1$=z1$+q$
8470 if zi=2 then z2$=z2$+q$
8480 if zi=3 then z3$=z3$+q$
8490 if zi=4 then z4$=z4$+q$
8500 next ix
8510 tc=val(z1$):mg=val(z2$):ls=val(z3$):rc=val(z4$)
8520 return

8600 rem save system counters
8610 r$=str$(tc)+";"+str$(mg)+";"+str$(ls)+";"+str$(rc)
8620 if len(r$)>=32 then 8650
8630 r$=r$+" "
8640 goto 8620
8650 if len(r$)>32 then r$=left$(r$,32)
8660 dopen#4,"counters",d0,u9
8670 record#4,1,1
8680 print#4,r$;
8690 dclose#4
8695 return

8700 rem record successful caller
8710 tc=tc+1
8720 rc=rc+1
8730 r$=dd$+"/"+mo$+"/"+yr$+" "+tm$+";"+n$+";"+o$+";"+ci$
8740 if len(r$)>=96 then 8770
8750 r$=r$+" "
8760 goto 8740
8770 if len(r$)>96 then r$=left$(r$,96)
8780 dopen#5,"callers",d0,u9
8790 record#5,rc+1,1
8800 print#5,r$;
8810 record#5,1,1
8820 r$=str$(rc)
8830 if len(r$)>=96 then 8860
8840 r$=r$+" "
8850 goto 8830
8860 if len(r$)>96 then r$=left$(r$,96)
8870 print#5,r$;
8880 dclose#5
8890 gosub 8600
8895 return

9000 rem message area selector
9010 gosub 5040
9020 a$="message areas":gosub 5040
9030 gosub 5040
9040 if na<1 then 9290
9050 for ai=1 to na
9060 a$=str$(ai)+" - "+ya$(ai):gosub 5040
9070 next ai
9080 gosub 5040
9090 a1$="select area (1-"+mid$(str$(na),2)+"): ":n=1:gosub 5040
9092 c=1:ml=3:gosub 5170
9094 if cd then return
9096 if b$="" then 9090
9098 ma=val(b$)
9100 if ma<1 or ma>na then 9090
9110 an$=ya$(ma)
9120 sn$=ys$(ma)
9130 mn$=ym$(ma)
9140 at$=yt$(ma)
9150 gosub 9900
9160 gosub 5040
9170 a$="current area: "+an$:gosub 5040
9180 a$="messages:"+str$(mc):gosub 5040
9190 return
9290 a$="no message areas configured.":gosub 5040
9295 return

9600 rem read 64 byte area record
9610 r$=""
9620 for ri=1 to 64
9630 get#6,q$
9640 if q$="" then q$=" "
9650 r$=r$+q$
9660 next ri
9670 return

9700 rem split message area definition
9710 an$="":sn$="":mn$="":at$=""
9720 fi=1
9730 for ix=1 to len(r$)
9740 q$=mid$(r$,ix,1)
9750 if q$=";" then fi=fi+1:goto 9800
9760 if fi=1 then an$=an$+q$
9770 if fi=2 then sn$=sn$+q$
9780 if fi=3 then mn$=mn$+q$
9790 if fi=4 then at$=at$+q$
9800 next ix
9810 gosub 9820
9815 return

9820 rem trim area fields
9830 if len(an$)>0 and right$(an$,1)=" " then an$=left$(an$,len(an$)-1):goto 9830
9840 if len(sn$)>0 and right$(sn$,1)=" " then sn$=left$(sn$,len(sn$)-1):goto 9840
9850 if len(mn$)>0 and right$(mn$,1)=" " then mn$=left$(mn$,len(mn$)-1):goto 9850
9860 if len(at$)>0 and right$(at$,1)=" " then at$=left$(at$,len(at$)-1):goto 9860
9870 return

9900 rem load active area summary index
9910 mz=0:mx=0:iu=0:lm=0:ac=0
9920 dopen#7,(sn$),d0,u9
9930 re=1
9940 record#7,re,1
9950 gosub 10100
9960 g=val(r$)
9970 if g>9998 then 10060
9980 if g>lm then lm=g
9990 mz=mz+1
10000 if g<>0 then ac=ac+1
10010 if mz<=200 then m(mz,1)=g
10020 if g<>0 and iu=0 then iu=g
10030 re=re+5
10040 record#7,re,1
10050 gosub 10100
10052 if mz<=200 then m(mz,2)=val(r$)
10054 mx=mx+val(r$)+6
10056 re=re+1
10058 goto 9940
10060 dclose#7
10070 mc=ac
10080 return

10100 rem read 30 byte summary record
10110 r$=""
10120 for ri=1 to 30
10130 get#7,q$
10140 if q$="" then q$=" "
10150 r$=r$+q$
10160 next ri
10170 return

10200 rem load message areas at startup
10210 na=0
10220 dopen#6,"msgareas",d0,u9
10230 record#6,1,1
10240 gosub 9600
10250 na=val(r$)
10260 if na>16 then na=16
10270 if na<1 then 10380
10280 for ai=1 to na
10290 record#6,ai+1,1
10300 gosub 9600
10310 gosub 9700
10320 ya$(ai)=an$
10330 ys$(ai)=sn$
10340 ym$(ai)=mn$
10350 yt$(ai)=at$
10360 next ai
10380 dclose#6
10390 return

10400 rem per user area last-read pointers - next stage
10410 return

11000 rem common message engine - future
11010 return

14000 rem caller log and statistics - future
14010 return

15000 rem bulletins news and help - future
15010 return

16000 rem file area system - future
16010 return

17000 rem punter file transfers - future
17010 return

18000 rem doors and external programs - future
18010 return

19000 rem sysop chat and status window - future
19010 return

20000 rem ansi terminal support - future
20010 return

21000 rem reserved expansion
21010 return
#14
Diffs to the original 1981 (v3.7) CP/M version so far :

16 message areas (the original had 1), private messages removed to their own message base rather than having one large, monolithic message base.
#15
Interim code for tcpsrv.c (compiles under Microsoft Visual Studio 2026):

/*
 * tcpsrv.c
 *
 * RBBS-128 TCP/VICE communications bridge.
 *
 * Port 23232:
 *     VICE X128 / SwiftLink connection
 *
 * Port 6400:
 *     Incoming BBS caller connection
 *
 * Data flow:
 *
 *     Caller <----> TCPSRV <----> X128
 *
 * Internal control messages sent only to X128:
 *
 *     #CONNECT
 *     #TIME:DD/MM/YYYY HH:MM:SS
 *     #DISCONNECT
 *
 * Internal control messages are deliberately sent more
 * slowly than normal caller traffic because BASIC is
 * polling the emulated 6551 ACIA one character at a time.
 *
 * Usage:
 *
 *     tcpsrv.exe
 */

#define WIN32_LEAN_AND_MEAN

#include <windows.h>
#include <winsock2.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <conio.h>

#pragma comment(lib, "Ws2_32.lib")

#define X128_PORT       23232
#define CALLER_PORT      6400
#define BUFFER_SIZE      4096

#define INVALID_CLIENT INVALID_SOCKET


/*
 * Send an entire buffer without pacing.
 *
 * Used for normal TCP destinations such as the caller.
 */
static int send_all(SOCKET socket,
                    const char *buffer,
                    int length)
{
    int total;
    int sent;

    total = 0;

    while (total < length)
    {
        sent = send(socket,
                    buffer + total,
                    length - total,
                    0);

        if (sent == SOCKET_ERROR)
        {
            return SOCKET_ERROR;
        }

        if (sent == 0)
        {
            return 0;
        }

        total += sent;
    }

    return total;
}


/*
 * Send a zero-terminated string without pacing.
 */
static int send_text(SOCKET socket,
                     const char *text)
{
    return send_all(socket,
                    text,
                    (int)strlen(text));
}


/*
 * Send ordinary caller/console data to the C128.
 *
 * 10 ms pacing is used so a pasted line is not dumped
 * into the emulated ACIA in one instantaneous TCP burst.
 */
static int send_to_x128(SOCKET socket,
                        const char *buffer,
                        int length)
{
    int i;
    int result;

    for (i = 0; i < length; i++)
    {
        result = send(socket,
                      buffer + i,
                      1,
                      0);

        if (result == SOCKET_ERROR)
        {
            return SOCKET_ERROR;
        }

        if (result == 0)
        {
            return 0;
        }

        Sleep(50);
    }

    return length;
}


/*
 * Send internal TCPSRV control messages to the C128.
 *
 * These are deliberately much slower than caller data
 * while we prove reliable reception in BASIC.
 */
static int send_control_to_x128(SOCKET socket,
                                const char *buffer,
                                int length)
{
    int i;
    int result;

    for (i = 0; i < length; i++)
    {
        result = send(socket,
                      buffer + i,
                      1,
                      0);

        if (result == SOCKET_ERROR)
        {
            return SOCKET_ERROR;
        }

        if (result == 0)
        {
            return 0;
        }

        /*
         * Conservative test value.
         * Reduce later once the real RBBS line reader
         * has been proven reliable.
         */
        Sleep(100);
    }

    return length;
}


/*
 * Generate host local date/time.
 *
 * Australian display order:
 *
 *     DD/MM/YYYY HH:MM:SS
 */
static void make_time_message(char *buffer,
                              size_t size)
{
    SYSTEMTIME st;

    GetLocalTime(&st);

    snprintf(buffer,
             size,
             "#TIME:%02u/%02u/%04u %02u:%02u:%02u\r\n",
             (unsigned int)st.wDay,
             (unsigned int)st.wMonth,
             (unsigned int)st.wYear,
             (unsigned int)st.wHour,
             (unsigned int)st.wMinute,
             (unsigned int)st.wSecond);
}


/*
 * Create a listening TCP socket.
 *
 * Both listeners currently bind to localhost only.
 */
static SOCKET create_listener(int port)
{
    SOCKET s;
    struct sockaddr_in address;
    int reuse;
    int result;

    s = socket(AF_INET,
               SOCK_STREAM,
               IPPROTO_TCP);

    if (s == INVALID_SOCKET)
    {
        return INVALID_SOCKET;
    }

    reuse = 1;

    setsockopt(s,
               SOL_SOCKET,
               SO_REUSEADDR,
               (const char *)&reuse,
               sizeof(reuse));

    ZeroMemory(&address,
               sizeof(address));

    address.sin_family = AF_INET;

    address.sin_addr.s_addr =
        htonl(INADDR_LOOPBACK);

    address.sin_port =
        htons((unsigned short)port);

    result = bind(s,
                  (struct sockaddr *)&address,
                  sizeof(address));

    if (result == SOCKET_ERROR)
    {
        closesocket(s);
        return INVALID_SOCKET;
    }

    result = listen(s,
                    4);

    if (result == SOCKET_ERROR)
    {
        closesocket(s);
        return INVALID_SOCKET;
    }

    return s;
}


/*
 * Tell the C128 that a caller has connected.
 *
 * Also send the current Windows host date/time.
 */
static int notify_connect(SOCKET x128Socket)
{
    SYSTEMTIME st;
    char message[128];

    GetLocalTime(&st);

    snprintf(message,
             sizeof(message),
             "#CONNECT:%02u/%02u/%04u %02u:%02u:%02u\r\n",
             (unsigned int)st.wDay,
             (unsigned int)st.wMonth,
             (unsigned int)st.wYear,
             (unsigned int)st.wHour,
             (unsigned int)st.wMinute,
             (unsigned int)st.wSecond);

    if (send_control_to_x128(x128Socket,
                             message,
                             (int)strlen(message))
        == SOCKET_ERROR)
    {
        return SOCKET_ERROR;
    }

    return 0;
}

/*
 * Tell the C128 that the caller disconnected.
 */
static void notify_disconnect(SOCKET x128Socket)
{
    const char *message =
        "#DISCONNECT\r\n";

    send_control_to_x128(x128Socket,
                         message,
                         (int)strlen(message));
}


/*
 * Reject an additional caller while one is online.
 */
static void reject_busy_caller(SOCKET socket)
{
    const char *message =
        "\r\n"
        "RBBS-128 is currently in use.\r\n"
        "Please try again later.\r\n";

    send_text(socket,
              message);

    shutdown(socket,
             SD_BOTH);

    closesocket(socket);
}


int main(void)
{
    WSADATA wsaData;

    SOCKET x128Listen;
    SOCKET callerListen;

    SOCKET x128Socket;
    SOCKET callerSocket;
    SOCKET newSocket;

    fd_set readSet;

    struct timeval timeout;

    unsigned char buffer[BUFFER_SIZE];

    int received;
    int result;

    unsigned char ch;


    /*
     * Initialize Winsock.
     */
    result = WSAStartup(MAKEWORD(2, 2),
                        &wsaData);

    if (result != 0)
    {
        fprintf(stderr,
                "tcpsrv: WSAStartup failed: %d\n",
                result);

        return 1;
    }


    /*
     * Create X128 listener.
     */
    x128Listen =
        create_listener(X128_PORT);

    if (x128Listen == INVALID_SOCKET)
    {
        fprintf(stderr,
                "tcpsrv: unable to listen on port %d: %d\n",
                X128_PORT,
                WSAGetLastError());

        WSACleanup();

        return 1;
    }


    /*
     * Create caller listener.
     */
    callerListen =
        create_listener(CALLER_PORT);

    if (callerListen == INVALID_SOCKET)
    {
        fprintf(stderr,
                "tcpsrv: unable to listen on port %d: %d\n",
                CALLER_PORT,
                WSAGetLastError());

        closesocket(x128Listen);
        WSACleanup();

        return 1;
    }


    fprintf(stderr,
            "tcpsrv: X128 listening on TCP port %d\n",
            X128_PORT);

    fprintf(stderr,
            "tcpsrv: callers listening on TCP port %d\n",
            CALLER_PORT);


    /*
     * Persistent server loop.
     *
     * If X128 disconnects, TCPSRV stays alive and waits
     * for X128 to reconnect.
     */
    for (;;)
    {
        /*
         * Wait for VICE/X128.
         */
        x128Socket =
            accept(x128Listen,
                   NULL,
                   NULL);

        if (x128Socket == INVALID_SOCKET)
        {
            fprintf(stderr,
                    "tcpsrv: X128 accept failed: %d\n",
                    WSAGetLastError());

            break;
        }

        fprintf(stderr,
                "tcpsrv: X128 connected\n");

        callerSocket = INVALID_CLIENT;


        /*
         * Service X128 and caller connections.
         */
        for (;;)
        {
            FD_ZERO(&readSet);

            FD_SET(x128Socket,
                   &readSet);

            FD_SET(callerListen,
                   &readSet);

            if (callerSocket != INVALID_CLIENT)
            {
                FD_SET(callerSocket,
                       &readSet);
            }


            /*
             * Short timeout allows Windows console
             * keyboard polling.
             */
            timeout.tv_sec = 0;
            timeout.tv_usec = 50000;


            result =
                select(0,
                       &readSet,
                       NULL,
                       NULL,
                       &timeout);

            if (result == SOCKET_ERROR)
            {
                fprintf(stderr,
                        "tcpsrv: select failed: %d\n",
                        WSAGetLastError());

                break;
            }


            /*
             * Incoming BBS caller.
             */
            if (FD_ISSET(callerListen,
                         &readSet))
            {
                newSocket =
                    accept(callerListen,
                           NULL,
                           NULL);

                if (newSocket != INVALID_SOCKET)
                {
                    if (callerSocket != INVALID_CLIENT)
                    {
                        reject_busy_caller(newSocket);
                    }
                    else
                    {
                        callerSocket = newSocket;

                        fprintf(stderr,
                                "tcpsrv: caller connected\n");


                        /*
                         * Signal carrier and provide
                         * date/time to RBBS-128.
                         */
                        if (notify_connect(x128Socket)
                            == SOCKET_ERROR)
                        {
                            fprintf(stderr,
                                    "tcpsrv: lost X128 while notifying connection\n");

                            break;
                        }
                    }
                }
            }


            /*
             * X128 -> local console and caller.
             */
            if (FD_ISSET(x128Socket,
                         &readSet))
            {
                received =
                    recv(x128Socket,
                         (char *)buffer,
                         sizeof(buffer),
                         0);

                if (received == 0)
                {
                    fprintf(stderr,
                            "tcpsrv: X128 disconnected\n");

                    break;
                }

                if (received == SOCKET_ERROR)
                {
                    fprintf(stderr,
                            "tcpsrv: X128 recv failed: %d\n",
                            WSAGetLastError());

                    break;
                }


                /*
                 * Always show C128 output locally.
                 */
                fwrite(buffer,
                       1,
                       received,
                       stdout);

                fflush(stdout);


                /*
                 * Forward C128 output to the caller.
                 */
                if (callerSocket != INVALID_CLIENT)
                {
                    if (send_all(callerSocket,
                                 (const char *)buffer,
                                 received)
                        == SOCKET_ERROR)
                    {
                        fprintf(stderr,
                                "tcpsrv: caller send failed\n");

                        shutdown(callerSocket,
                                 SD_BOTH);

                        closesocket(callerSocket);

                        callerSocket =
                            INVALID_CLIENT;

                        notify_disconnect(x128Socket);

                        fprintf(stderr,
                                "tcpsrv: caller disconnected\n");
                    }
                }
            }


            /*
             * Caller -> X128.
             */
            if (callerSocket != INVALID_CLIENT &&
                FD_ISSET(callerSocket,
                         &readSet))
            {
                received =
                    recv(callerSocket,
                         (char *)buffer,
                         sizeof(buffer),
                         0);

                if (received == 0)
                {
                    /*
                     * Normal caller disconnect.
                     */
                    shutdown(callerSocket,
                             SD_BOTH);

                    closesocket(callerSocket);

                    callerSocket =
                        INVALID_CLIENT;

                    notify_disconnect(x128Socket);

                    fprintf(stderr,
                            "tcpsrv: caller disconnected\n");
                }
                else if (received == SOCKET_ERROR)
                {
                    /*
                     * Abnormal caller disconnect.
                     */
                    shutdown(callerSocket,
                             SD_BOTH);

                    closesocket(callerSocket);

                    callerSocket =
                        INVALID_CLIENT;

                    notify_disconnect(x128Socket);

                    fprintf(stderr,
                            "tcpsrv: caller connection lost\n");
                }
                else
                {
                    /*
                     * Ordinary caller data uses the
                     * faster 10 ms pacing.
                     */
                    if (send_to_x128(x128Socket,
                                     (const char *)buffer,
                                     received)
                        == SOCKET_ERROR)
                    {
                        fprintf(stderr,
                                "tcpsrv: X128 send failed\n");

                        break;
                    }
                }
            }


            /*
             * Windows console keyboard -> X128.
             *
             * Retained for development and future
             * SYSOP use.
             */
            while (_kbhit())
            {
                ch =
                    (unsigned char)_getch();


                /*
                 * Ignore Windows extended keys.
                 */
                if (ch == 0 || ch == 224)
                {
                    if (_kbhit())
                    {
                        (void)_getch();
                    }

                    continue;
                }


                /*
                 * Console input uses normal 10 ms
                 * C128 pacing.
                 */
                if (send_to_x128(x128Socket,
                                 (const char *)&ch,
                                 1)
                    == SOCKET_ERROR)
                {
                    fprintf(stderr,
                            "tcpsrv: X128 keyboard send failed\n");

                    break;
                }
            }
        }


        /*
         * Clean up caller if still connected.
         */
        if (callerSocket != INVALID_CLIENT)
        {
            shutdown(callerSocket,
                     SD_BOTH);

            closesocket(callerSocket);

            callerSocket =
                INVALID_CLIENT;
        }


        /*
         * Clean up X128 connection.
         */
        shutdown(x128Socket,
                 SD_BOTH);

        closesocket(x128Socket);


        /*
         * Stay alive and wait for X128 again.
         */
        fprintf(stderr,
                "tcpsrv: waiting for X128\n");
    }


    /*
     * Final shutdown.
     */
    closesocket(callerListen);
    closesocket(x128Listen);

    WSACleanup();

    return 0;
}
#16
Shareware, public domain and freeware / RBBS port to the C128
Last post by Blacklord - August 14, 2026, 08:09 AM
One of the things I've always wanted to do is write a BBS for theC128 (several years ago we had a thread about this). So - I've started porting a CP/M version of RBBs (3.7 from 1981) over.

It's going to be heavily customised :

Although it will run on a real 128, the software is written to run under VICE (on Windows)

It has a custom tcp server (TCPSRV.EXE), utilises Swiftlink emulation, CMD hard disk emulation, JiffyDOS ROMS.

As I complete a task I'll update here.

#17
General chat / C128 core for the Mega65
Last post by RobertB - July 24, 2026, 09:37 AM
Dan Sanderson reports that there is a C128 core for the Mega65 computer.  Here is what he wrote:

Commodore 128 core for the MEGA65, by Stefan Eilers.

Meanwhile, Stefan Eilers has a Commodore 128 core in progress, with an early release for people to try. So far only 40-column mode and IEC disk drives are supported. It does not yet support cartridges, 80-column mode, or virtual disk images off the SD card. But there's meaningful progress, and you can play with it!

You will need C128 ROMs for this one as well. The 72 KB system ROM bundle is to be installed as: /c128/boot0.rom The 512 KB drive ROM is: /c128/boot1.rom Check out the Github repo for the project for more information.

https://github.com/eilers/c128Mega65

Truly,
Robert Bernardo
Fresno Commodore User Group - http://www.dickestel.com/fcug.htm
Southern California Commodore & Amiga Network - http://www.portcommodore.com/sccan
#18
General chat / Re: Photos/video from Pacific ...
Last post by RobertB - July 09, 2026, 02:14 PM
      More videos from the PaCommEx 2026! They are

Bernardo and Amiga Demos - PaCommEx 2026

https://youtu.be/FsKHnit3MKQ

Bernardo and C64 Demos - PaCommEx 2026

https://youtu.be/R-C_8qZtYTo

          Truly,
          Robert Bernardo
          organizer - Pacific Commodore Expo NW
          http://www.portcommodore.com/pacommex
#19
General chat / Re: Photos/video from Pacific ...
Last post by RobertB - July 08, 2026, 11:00 AM
    The first videos from PaCommEx 2026 have been posted!  See

Guidi and Commodore 16/Plus4 enhancements - PaCommEx 2026

https://youtu.be/I17nOqCnroY

Barlow and his various Amigas - PaCommEx 2026

https://youtu.be/XlgcvJQVg30

Sanderson and Mega65 hints - PaCommEx 2026

https://youtu.be/0zKwHOAxd3E

          Truly,
          Robert Bernardo
          organizer - Pacific Commodore Expo NW
          http://www.portcommodore.com/pacommex
#20
General chat / Photos/video from Pacific Comm...
Last post by RobertB - June 23, 2026, 12:39 PM
    PaCommEx 2026 was a success!  Thanks to all the Commodorians and Amigans who attended and/or exhibited!  Here are my photos of the weekend.

http://blog.retro-link.com/2026/06/couple-of-photos-before-pacific.html

http://blog.retro-link.com/2026/06/day-one-at-pacific-commodore-expo-nw.html

http://blog.retro-link.com/2026/06/day-two-at-pacific-commodore-expo-nw.html


          Videos to come later,
          Robert Bernardo
          organizer - Pacific Commodore Expo NW
          http://www.portcommodore.com/pacommex