├── .gitignore ├── test ├── test_console.bas ├── plot.bas ├── get.bas ├── sieve.bas ├── fileio.bas ├── color_ansi_all.bas ├── fileeof.bas ├── color_chr.bas ├── cursor_chr.bas ├── test_console_ansi.bas ├── color.bas └── color_ansi.bas ├── Makefile ├── plugin.h ├── stat.h ├── readdir.h ├── Makefile.sun32 ├── console.h ├── ANSI_Extension.md ├── Makefile.win32 ├── glue.h ├── readdir.c ├── README.md ├── cbmbasic.vcproj ├── console.c ├── plugin.c └── runtime.c /.gitignore: -------------------------------------------------------------------------------- 1 | cbmbasic 2 | *.o 3 | -------------------------------------------------------------------------------- /test/test_console.bas: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mist64/cbmbasic/HEAD/test/test_console.bas -------------------------------------------------------------------------------- /test/plot.bas: -------------------------------------------------------------------------------- 1 | 10 PRINT "HELLO";POS(0);"WORLD" 2 | 20 PRINT "HELLO";SPC(5);"WORLD" 3 | 30 PRINT "HELLO";TAB(10);"WORLD" 4 | -------------------------------------------------------------------------------- /test/get.bas: -------------------------------------------------------------------------------- 1 | 10 GET I$ 2 | 20 IF I$<>"" THEN PRINT I$; 3 | 30 IF I$="" THEN PRINT "."; 4 | 40 IF I$="Q" THEN END 5 | 45 FOR I=0 TO 1000: NEXT I 6 | 50 GOTO 10 7 | -------------------------------------------------------------------------------- /test/sieve.bas: -------------------------------------------------------------------------------- 1 | 10 REM SIEVE 2 | 20 FORI=1TO120000 3 | 30 FORJ=2TOSQR(I) 4 | 40 IFI/J=INT(I/J)GOTO70 5 | 50 NEXTJ 6 | 60 PRINTI; 7 | 70 NEXTI 8 | RUN 9 | 10 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | OBJS=cbmbasic.o runtime.o plugin.o console.o 2 | CFLAGS=-Wall -O3 3 | 4 | all: cbmbasic 5 | 6 | cbmbasic: $(OBJS) 7 | $(CC) -o cbmbasic $(OBJS) 8 | 9 | clean: 10 | rm -f $(OBJS) cbmbasic 11 | 12 | -------------------------------------------------------------------------------- /plugin.h: -------------------------------------------------------------------------------- 1 | unsigned short plugin_error(void); 2 | unsigned short plugin_main(void); 3 | unsigned short plugin_crnch(void); 4 | unsigned short plugin_qplop(void); 5 | unsigned short plugin_gone(void); 6 | unsigned short plugin_eval(void); 7 | 8 | -------------------------------------------------------------------------------- /test/fileio.bas: -------------------------------------------------------------------------------- 1 | 10 OPEN 1,1,1,"TEST.DAT" 2 | 20 PRINT#1, 1234 3 | 30 PRINT#1, "Hello" 4 | 40 CLOSE 1 5 | 6 | 50 OPEN 2,1,0,"TEST.DAT" 7 | 60 INPUT#2, A 8 | 70 INPUT#2, I$ 9 | 80 CLOSE 2 10 | 11 | 90 PRINT A 12 | 100 PRINT I$ 13 | -------------------------------------------------------------------------------- /test/color_ansi_all.bas: -------------------------------------------------------------------------------- 1 | 10 SYS 1: REM Turn on extensions 2 | 20 FOR F=30 TO 37 3 | 30 FOR B=40 TO 47 4 | 40 FOR A=0 TO 8 5 | 50 ANSISGM A,F,B:PRINT "TEST STRING";:ANSISGM 0,37,40:PRINT "Attr:";A;"FG:";F;"BG:";B 6 | 60 NEXT A:NEXT B:NEXT F 7 | 70 ANSISGM 0,37,40:PRINT 8 | -------------------------------------------------------------------------------- /stat.h: -------------------------------------------------------------------------------- 1 | #ifndef STAT_H_INCLUDED 2 | #define STAT_H_INCLUDED 3 | 4 | #include 5 | #include 6 | 7 | #ifdef _WIN32 8 | #define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR) 9 | #define stat _stat 10 | #else 11 | #include 12 | #endif 13 | 14 | #endif /* STAT_H_INCLUDED */ 15 | -------------------------------------------------------------------------------- /test/fileeof.bas: -------------------------------------------------------------------------------- 1 | 10 PRINT "SAVING..." 2 | 20 OPEN 1,1,1,"TEST.DAT" 3 | 30 FOR I = 1 TO 10 4 | 40 A = RND(0) 5 | 50 PRINT A 6 | 60 PRINT#1, A 7 | 70 NEXT I 8 | 80 CLOSE 1 9 | 10 | 90 PRINT "LOADING..." 11 | 100 OPEN 1,1,0,"TEST.DAT" 12 | 110 INPUT#1, I 13 | 120 IF ST <> 0 GOTO 150 14 | 130 PRINT I 15 | 140 GOTO 110 16 | 150 CLOSE 1 17 | 18 | -------------------------------------------------------------------------------- /test/color_chr.bas: -------------------------------------------------------------------------------- 1 | 40 PRINT CHR$(5);:PRINT "White" 2 | 50 PRINT CHR$(28);:PRINT "Red" 3 | 60 PRINT CHR$(30);:PRINT"Green" 4 | 70 PRINT CHR$(31);:PRINT"Blue" 5 | 80 PRINT CHR$(144);:PRINT"Black";CHR$(5);"(Black)" 6 | 90 PRINT CHR$(156);:PRINT"Magenta" 7 | 100 PRINT CHR$(158);:PRINT"Yellow" 8 | 110 PRINT CHR$(159);:PRINT"Cyan" 9 | 120 PRINT CHR$(18);"REVERSE SCREEN";CHR$(146) 10 | -------------------------------------------------------------------------------- /test/cursor_chr.bas: -------------------------------------------------------------------------------- 1 | 10 SYS 1: REM Extensions on for LOCATE command 2 | 20 ? CHR$(147);"Screen clear and home"; 3 | 30 ? CHR$(17);CHR$(17);CHR$(17);"DOWN 3 lines"; 4 | 40 ? CHR$(17); CHR$(29); CHR$(29); CHR$(29);"Down 1, right 3"; 5 | 50 ? CHR$(17);CHR$(17);CHR$(17); 6 | 60 ? CHR$(145); CHR$(157); CHR$(157); CHR$(157);"Down 3, up 1, left 3"; 7 | 70 ? CHR$(19); CHR$(17); CHR$(17);"Home, down 2"; 8 | 80 LOCATE 20,1 9 | -------------------------------------------------------------------------------- /readdir.h: -------------------------------------------------------------------------------- 1 | #ifndef READDIR_H_INCLUDED 2 | #define READDIR_H_INCLUDED 3 | 4 | #include 5 | 6 | #ifdef _WIN32 7 | 8 | #ifndef MAX_PATH 9 | #define MAX_PATH 260 10 | #endif 11 | 12 | struct dirent 13 | { 14 | char d_name[MAX_PATH]; 15 | }; 16 | 17 | typedef struct dir_private DIR; 18 | 19 | DIR *opendir(const char *name); 20 | struct dirent *readdir(DIR *dir); 21 | int closedir(DIR *dir); 22 | 23 | #else 24 | #include 25 | #endif 26 | 27 | #endif /* READDIR_H_INCLUDED */ 28 | -------------------------------------------------------------------------------- /test/test_console_ansi.bas: -------------------------------------------------------------------------------- 1 | 5 SYS 1: REM turn extensions on 2 | 10 REM Clear screen 3 | 20 PRINT CHR$(147) 4 | 30 REM Print some stuff at screen locations 5 | 40 FOR L=10 TO 20: LOCATE L,20:PRINT "At ";L;",20":NEXT L 6 | 50 REM Move cursor to location and save location 7 | 60 LOCATE 13,22:ANSISC 0 8 | 70 REM Move cursor to location, erase part of the line 9 | 80 LOCATE 15,22:ANSIEL 0 10 | 90 REM Restore cursor location and print something 11 | 100 ANSIRC 0:PRINT "Cursor restore worked" 12 | 110 LOCATE 24,1 13 | -------------------------------------------------------------------------------- /test/color.bas: -------------------------------------------------------------------------------- 1 | 10 PRINT CHR$(5),"WHITE" 2 | 20 PRINT CHR$(28),"RED" 3 | 30 PRINT CHR$(30),"GREEN" 4 | 40 PRINT CHR$(31),"BLUE" 5 | 50 PRINT CHR$(144),"BLACK";CHR$(5);"(BLACK)" 6 | 60 PRINT CHR$(156),"PURPLE" 7 | 70 PRINT CHR$(158),"YELLOW" 8 | 80 PRINT CHR$(159),"CYAN" 9 | 90 PRINT CHR$(129),"ORANGE" 10 | 100 PRINT CHR$(149),"BROWN" 11 | 110 PRINT CHR$(150),"LT. RED" 12 | 120 PRINT CHR$(151),"DK. GREY" 13 | 130 PRINT CHR$(152),"GREY" 14 | 140 PRINT CHR$(153),"LT. GREEN" 15 | 150 PRINT CHR$(154),"LT. BLUE" 16 | 160 PRINT CHR$(155),"LT. GREY" 17 | -------------------------------------------------------------------------------- /Makefile.sun32: -------------------------------------------------------------------------------- 1 | CC=gcc 2 | CFLAGS=-Wall -Wa,-xarch=v8 -m32 -mcpu=v8 3 | 4 | ifdef nodebug 5 | CDEBUG=-O3 -DNDEBUG 6 | else 7 | CDEBUG=-O0 -DDEBUG 8 | endif 9 | 10 | OUTDIR=./bin/sun32 11 | TARGET=$(OUTDIR)/cbmbasic 12 | 13 | build: $(OUTDIR) $(TARGET) 14 | rebuild: clean build 15 | clean: 16 | -rm -fr $(OUTDIR) 17 | 18 | $(OUTDIR)/%.o:%.c 19 | $(CC) $(CDEBUG) $(CFLAGS) -c $< -o $@ 20 | 21 | $(OUTDIR): 22 | mkdir $(OUTDIR) 23 | 24 | $(TARGET): $(OUTDIR)/cbmbasic.o $(OUTDIR)/runtime.o $(OUTDIR)/plugin.o $(OUTDIR)/console.o 25 | $(CC) $(CDEBUG) $(CFLAGS) -o $(TARGET) $? 26 | -------------------------------------------------------------------------------- /console.h: -------------------------------------------------------------------------------- 1 | #ifndef CONSOLE_H_INCLUDED 2 | #define CONSOLE_H_INCLUDED 3 | 4 | enum { 5 | COLOR_WHITE, 6 | COLOR_RED, 7 | COLOR_GREEN, 8 | COLOR_BLUE, 9 | COLOR_BLACK, 10 | COLOR_PURPLE, 11 | COLOR_YELLOW, 12 | COLOR_CYAN, 13 | COLOR_ORANGE, 14 | COLOR_BROWN, 15 | COLOR_LTRED, 16 | COLOR_GREY1, 17 | COLOR_GREY2, 18 | COLOR_LTGREEN, 19 | COLOR_LTBLUE, 20 | COLOR_GREY3, 21 | }; 22 | 23 | void clear_screen(); 24 | void up_cursor(); 25 | void down_cursor(); 26 | void left_cursor(); 27 | void right_cursor(); 28 | void move_cursor(int x, int y); 29 | void get_cursor(int* x, int* y); 30 | void set_color(int c); 31 | void reverse_screen(int c); 32 | 33 | #endif /* CONSOLE_H_INCLUDED */ 34 | -------------------------------------------------------------------------------- /ANSI_Extension.md: -------------------------------------------------------------------------------- 1 | # ANSI display extensions 2 | 3 | Use "SYS" 1 to turn on the extensions. 4 | 5 | #ANSISGM Attr, Foreground, Background 6 | Set Graphics Mode. See [ANSI escape sequences](http://ascii-table.com/ansi-escape-sequences.php) for the values. 7 | 8 | #ANSIEL 0 9 | Erase line from current position to end of line 10 | 11 | #ANSISC 0 12 | Save current cursor position. 13 | 14 | #ANSIRC 0 15 | Restore cursor position 16 | 17 | Note that these are highly dependant on the terminal program you are using. Testing on PuTTY showed that blink does not work. Instead, you get reverse screen with a dimmer background color. 18 | 19 | # Compatibility 20 | 21 | It has been tested with 22 | 23 | * Raspian on a Raspberry PI 0 W using PuTTY running on Windows 24 | 25 | -------------------------------------------------------------------------------- /Makefile.win32: -------------------------------------------------------------------------------- 1 | APPVER = 5.0 2 | TARGETOS = WINNT 3 | 4 | !include 5 | 6 | !ifndef nounicode 7 | cvarsdll = $(cvarsdll) -DUNICODE -D_UNICODE 8 | !endif 9 | 10 | OUTDIR=.\bin\win32 11 | TARGET=$(OUTDIR)\cbmbasic.exe 12 | 13 | build: "$(OUTDIR)" "$(TARGET)" 14 | rebuild: clean build 15 | clean: 16 | -erase /f /q "$(OUTDIR)\*.*" 17 | -rmdir "$(OUTDIR)" 18 | 19 | .c{$(OUTDIR)}.obj: 20 | $(cc) $(cdebug) $(cflags) $(cvarsdll) -D_CRT_SECURE_NO_DEPRECATE=1 -D_CRT_NONSTDC_NO_DEPRECATE=1 /Fo"$(OUTDIR)/" /Fd"$(OUTDIR)/" $< 21 | 22 | "$(OUTDIR)": 23 | mkdir "$(OUTDIR)" 24 | 25 | "$(TARGET)": "$(OUTDIR)\cbmbasic.obj" "$(OUTDIR)\runtime.obj" "$(OUTDIR)\readdir.obj" "$(OUTDIR)\plugin.obj" "$(OUTDIR)\console.obj" 26 | $(link) $(ldebug) $(conlflags) -out:"$(TARGET)" $** $(conlibsdll) 27 | -------------------------------------------------------------------------------- /glue.h: -------------------------------------------------------------------------------- 1 | extern unsigned char RAM[65536]; 2 | 3 | extern unsigned char A, X, Y, S; 4 | extern unsigned short PC; 5 | extern unsigned char V, B, D, I, C, N, Z; 6 | 7 | #define SETZ(a) Z=a; 8 | #define SETSZ(a) Z = (a)? 0:1; N = ((signed char)(a))<0?1:0 9 | #define SETNC(a) C = (a)&0x100? 0:1 10 | #define SETV(a) /* not needed */ 11 | #define STACK16(i) (RAM[0x0100+i]|(RAM[0x0100+i+1]<<8)) 12 | #define PUSH(b) RAM[0x0100+S--] = (b) 13 | #define PUSH_WORD(b) PUSH((b)>>8); PUSH((b)&0xFF) 14 | 15 | void CHRGET(void); 16 | void CHRGOT(void); 17 | 18 | int main(int, char **); 19 | 20 | #define VEC_ERROR 0x0300 21 | #define VEC_MAIN 0x0302 22 | #define VEC_CRNCH 0x0304 23 | #define VEC_QPLOP 0x0306 24 | #define VEC_GONE 0x0308 25 | #define VEC_EVAL 0x030A 26 | #define MAGIC_ERROR 0xFF00 27 | #define MAGIC_MAIN 0xFF01 28 | #define MAGIC_CRNCH 0xFF02 29 | #define MAGIC_QPLOP 0xFF03 30 | #define MAGIC_GONE 0xFF04 31 | #define MAGIC_EVAL 0xFF05 32 | #define MAGIC_CONTINUATION 0xFFFF 33 | -------------------------------------------------------------------------------- /test/color_ansi.bas: -------------------------------------------------------------------------------- 1 | 10 SYS 1: REM Turn extensions on 2 | 20 ANSISGM 0,37,40:PRINT "Normal white on black" 3 | 30 ANSISGM 1,37,40:PRINT "Bold white on black" 4 | 40 ANSISGM 4,37,40:PRINT "Underscore white on black" 5 | 50 ANSISGM 5,37,40:PRINT "Blink white on black" 6 | 60 ANSISGM 7,37,40:PRINT "Reverse white on black";:ANSISGM 0,37,40:PRINT 7 | 70 ANSISGM 8,37,40:PRINT "Concealed white on black" 8 | 80 ANSISGM 0,30,47:PRINT "Normal black on white";:ANSISGM 0,37,40:PRINT 9 | 90 ANSISGM 0,31,40:PRINT "Normal red on black" 10 | 100 ANSISGM 0,32,40:PRINT "Normal green on black" 11 | 110 ANSISGM 0,33,40:PRINT "Normal yellow on black" 12 | 120 ANSISGM 0,34,40:PRINT "Normal blue on black" 13 | 130 ANSISGM 0,35,40:PRINT "Normal magenta on black" 14 | 140 ANSISGM 0,36,40:PRINT "Normal cyan on black" 15 | 160 ANSISGM 0,30,41:PRINT "Normal black on red";:ANSISGM 0,37,40:PRINT 16 | 170 ANSISGM 0,30,42:PRINT "Normal black on green";:ANSISGM 0,37,40:PRINT 17 | 180 ANSISGM 0,30,43:PRINT "Normal black on yellow";:ANSISGM 0,37,40:PRINT 18 | 190 ANSISGM 0,30,44:PRINT "Normal black on blue";:ANSISGM 0,37,40:PRINT 19 | 200 ANSISGM 0,30,45:PRINT "Normal black on Magenta";:ANSISGM 0,37,40:PRINT 20 | 210 ANSISGM 0,30,46:PRINT "Normal black on Cyan";:ANSISGM 0,37,40:PRINT 21 | 220 ANSISGM 1,36,40:PRINT "Bold cyan on black";:ANSISGM 0,37,40:PRINT 22 | 230 ANSISGM 4,36,40:PRINT "Underscore cyan on black";:ANSISGM 0,37,40:PRINT 23 | 240 ANSISGM 7,36,40:PRINT "Reverse cyan on black";:ANSISGM 0,37,40:PRINT 24 | 250 ANSISGM 8,36,40:PRINT "Concealed cyan on black";:ANSISGM 0,37,40:PRINT 25 | 260 ANSISGM 2,36,40:PRINT "Attr 2 cyan on black";:ANSISGM 0,37,40:PRINT 26 | 270 ANSISGM 3,36,40:PRINT "Attr 3 cyan on black";:ANSISGM 0,37,40:PRINT 27 | 280 ANSISGM 3,36,40:PRINT "Attr 6 cyan on black";:ANSISGM 0,37,40:PRINT 28 | -------------------------------------------------------------------------------- /readdir.c: -------------------------------------------------------------------------------- 1 | #define WIN32_LEAN_AND_MEAN 2 | #include 3 | #include 4 | #include // FindFirstFile, FindNextFile 5 | #include "readdir.h" 6 | 7 | typedef struct dir_private 8 | { 9 | HANDLE h; 10 | int first_read; 11 | struct dirent buf; 12 | } DIR; 13 | 14 | DIR *opendir(const char *name) 15 | { 16 | DIR *ret = NULL; 17 | char name2[MAX_PATH]; 18 | WIN32_FIND_DATAA findData; 19 | 20 | if (strlen(name) >= MAX_PATH - 2 - 1) 21 | { 22 | errno = ENAMETOOLONG; 23 | return ret; 24 | } 25 | strcpy(name2, name); 26 | strcat(name2, "/*"); 27 | 28 | if (!(ret = (DIR *)malloc(sizeof(DIR)))) 29 | { 30 | errno = ENOMEM; 31 | return ret; 32 | } 33 | 34 | ret->h = FindFirstFileA(name2, &findData); 35 | if (ret->h == INVALID_HANDLE_VALUE) 36 | { 37 | DWORD err = GetLastError(); 38 | free(ret); 39 | ret = NULL; 40 | switch (err) 41 | { 42 | case ERROR_FILE_NOT_FOUND: errno = ENOENT; break; 43 | case ERROR_PATH_NOT_FOUND: errno = ENOENT; break; 44 | case ERROR_ACCESS_DENIED: errno = EACCES; break; 45 | case ERROR_DIRECTORY: errno = ENOTDIR; break; 46 | default: errno = EINVAL; 47 | } 48 | return ret; 49 | } 50 | 51 | strcpy(ret->buf.d_name, findData.cFileName); 52 | ret->first_read = 1; 53 | return ret; 54 | } 55 | 56 | struct dirent *readdir(DIR *dir) 57 | { 58 | WIN32_FIND_DATAA findData; 59 | 60 | if (!dir) 61 | { 62 | errno = EBADF; 63 | return NULL; 64 | } 65 | 66 | if (dir->first_read) 67 | { 68 | dir->first_read = 0; 69 | return &dir->buf; 70 | } 71 | 72 | if (!FindNextFileA(dir->h, &findData)) 73 | { 74 | switch (GetLastError()) 75 | { 76 | case ERROR_NO_MORE_FILES: errno = ENOENT; break; 77 | case ERROR_INVALID_HANDLE: errno = EBADF; break; 78 | default: errno = EINVAL; 79 | } 80 | return NULL; 81 | } 82 | 83 | strcpy(dir->buf.d_name, findData.cFileName); 84 | return &dir->buf; 85 | } 86 | 87 | int closedir(DIR *dir) 88 | { 89 | if (!dir) 90 | { 91 | errno = EBADF; 92 | return -1; 93 | } 94 | FindClose(dir->h); 95 | free(dir); 96 | return 0; 97 | } 98 | 99 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # cbmbasic - Commodore BASIC V2 as a scripting language 2 | 3 | "Commodore BASIC" (cbmbasic) is a 100% compatible version of Commodore's version of Microsoft BASIC 6502 as found on the Commodore 64. You can use it in interactive mode or pass a BASIC file as a command line parameter. 4 | 5 | This source does not emulate 6502 code; all code is completely native. On a 1 GHz CPU you get about 1000x speed compared to a 1 MHz 6502. 6 | 7 | ## Compatibility 8 | 9 | It has been tested with 10 | 11 | * Mac OS X 10.4/10.5 i386/x86_64/ppc (GCC 3.3/4.0) 12 | * Debian Linux Etch (GCC 3.3/4.1) 13 | * Windows NT (Visual Studio 2005/2008) 14 | 15 | Other CPUs, operating systems and compilers should work, too. 16 | 17 | Windows users should also install the [Microsoft Visual C++ 2005 SP1 Redistributable Package (x86)]("http://www.microsoft.com/downloads/details.aspx?familyid=200b2fd9-ae1a-4a14-984d-389c36f85647&displaylang=en"). 18 | 19 | ## Usage 20 | You can use cbmbasic in interactive mode by just running the binary without parameters, or you can specify an ASCII-encoded BASIC program on the command line. You can also use cbmbasic as a UNIX scripting language by adding a hashbang line to your BASIC program and making it executable. 21 | 22 | $ ls -l hello.bas 23 | -rwxr-xr-x 1 mist staff 40 7 Apr 21:30 hello.bas 24 | $ cat hello.bas 25 | #!/usr/bin/env cbmbasic 26 | PRINT"HELLO WORLD!" 27 | $ ./hello.bas 28 | HELLO WORLD! 29 | 30 | cbmbasic implements a small plugin system that lets developers add additional statements, functions etc. Right now, you can turn this on with "SYS 1" (turn off with "SYS 0"), and use the new statements LOCATE *y*, *x* (set cursor position), SYSTEM *string* (run command line command) and the extended WAIT *port*, *mask*, which implements the [Bill Gates easter egg](http://www.pagetable.com/?p=43). 31 | 32 | ## Internals 33 | The core of the BASIC interpreter is in the file `cbmbasic.c`, which is platform, endianness and bitness independent. For all I/O, it calls out into `runtime.c`, so it should be possible to adapt this project for any OS by just changing `runtime.c`. 34 | 35 | All function calls that the core interpreter can't handle end up in kernal_dispatch() in `runtime.c`, where a switch statement dispatches these to C functions. For Commodore BASIC, `runtime.c` has to support several Commodore KERNAL library routines. Some of them are very important (CHRIN, CHROUT) and some are only used for certain BASIC statements (LOAD, SAVE, OPEN, SETTIM). `runtime.c` does not implement all functions yet. 36 | 37 | ## License 38 | Feel free to use this project for any purpose, give credit if you like, and send back improvements to the authors, if you like, so that others can benefit from it. See source for license details. 39 | 40 | ## Contact 41 | [Michael Steil](mailto:mist@c64.org), [James Abbatiello](mailto:abbeyj@gmail.com) 42 | -------------------------------------------------------------------------------- /cbmbasic.vcproj: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 14 | 15 | 16 | 17 | 18 | 25 | 28 | 31 | 34 | 37 | 40 | 51 | 54 | 57 | 60 | 67 | 70 | 73 | 76 | 79 | 82 | 85 | 88 | 91 | 92 | 100 | 103 | 106 | 109 | 112 | 115 | 126 | 129 | 132 | 135 | 145 | 148 | 151 | 154 | 157 | 160 | 163 | 166 | 169 | 170 | 171 | 172 | 173 | 174 | 179 | 182 | 183 | 186 | 187 | 190 | 191 | 194 | 195 | 198 | 199 | 200 | 205 | 208 | 209 | 212 | 213 | 216 | 217 | 220 | 221 | 224 | 225 | 226 | 231 | 232 | 233 | 234 | 235 | 236 | -------------------------------------------------------------------------------- /console.c: -------------------------------------------------------------------------------- 1 | #include "console.h" 2 | 3 | #include 4 | #include 5 | 6 | #ifdef _WIN32 7 | #include 8 | 9 | static int get_console_info(HANDLE *h, COORD *pos, DWORD *size) { 10 | CONSOLE_SCREEN_BUFFER_INFO info; 11 | 12 | *h = GetStdHandle(STD_OUTPUT_HANDLE); 13 | if( *h == INVALID_HANDLE_VALUE || GetConsoleScreenBufferInfo(*h, &info) == FALSE ) 14 | return -1; 15 | 16 | *pos = info.dwCursorPosition; 17 | *size = (DWORD)info.dwSize.X * info.dwSize.Y; 18 | return 0; 19 | } 20 | #endif /* _WIN32 */ 21 | 22 | void clear_screen() { 23 | #ifdef _WIN32 24 | static COORD upper_left = {0, 0}; 25 | COORD dummy1; 26 | DWORD dummy2, size; 27 | HANDLE hstd_out; 28 | 29 | if( (get_console_info(&hstd_out, &dummy1, &size)) == -1 ) 30 | return; 31 | 32 | FillConsoleOutputCharacter(hstd_out, ' ', size, upper_left, &dummy2); 33 | SetConsoleCursorPosition(hstd_out, upper_left); 34 | #else /* ANSI */ 35 | fputs("\033[2J\033[;H", stdout); 36 | #endif /* _WIN32 */ 37 | } 38 | 39 | void up_cursor() { 40 | #ifdef _WIN32 41 | COORD pos; 42 | DWORD dummy; 43 | HANDLE hstd_out; 44 | 45 | if( (get_console_info(&hstd_out, &pos, &dummy)) == -1 ) 46 | return; 47 | 48 | --pos.Y; 49 | SetConsoleCursorPosition(hstd_out, pos); 50 | #else /* ANSI */ 51 | fputs("\033[A", stdout); 52 | #endif /* _WIN32 */ 53 | } 54 | 55 | void down_cursor() { 56 | #ifdef _WIN32 57 | COORD pos; 58 | DWORD dummy; 59 | HANDLE hstd_out; 60 | 61 | if( (get_console_info(&hstd_out, &pos, &dummy)) == -1 ) 62 | return; 63 | 64 | ++pos.Y; 65 | SetConsoleCursorPosition(hstd_out, pos); 66 | #else /* ANSI */ 67 | fputs("\033[B", stdout); 68 | #endif /* _WIN32 */ 69 | } 70 | 71 | void left_cursor() { 72 | #ifdef _WIN32 73 | COORD pos; 74 | DWORD dummy; 75 | HANDLE hstd_out; 76 | 77 | if( (get_console_info(&hstd_out, &pos, &dummy)) == -1 ) 78 | return; 79 | 80 | --pos.X; 81 | SetConsoleCursorPosition(hstd_out, pos); 82 | #else /* ANSI */ 83 | fputs("\033[D", stdout); 84 | #endif /* _WIN32 */ 85 | } 86 | 87 | void right_cursor() { 88 | #ifdef _WIN32 89 | COORD pos; 90 | DWORD dummy; 91 | HANDLE hstd_out; 92 | 93 | if( (get_console_info(&hstd_out, &pos, &dummy)) == -1 ) 94 | return; 95 | 96 | ++pos.X; 97 | SetConsoleCursorPosition(hstd_out, pos); 98 | #else /* ANSI */ 99 | fputs("\033[C", stdout); 100 | #endif /* _WIN32 */ 101 | } 102 | 103 | void move_cursor(int x, int y) { 104 | #ifdef _WIN32 105 | COORD dummy1; 106 | DWORD dummy2; 107 | HANDLE hstd_out; 108 | const COORD pos = {x-1, y-1}; 109 | 110 | if( (get_console_info(&hstd_out, &dummy1, &dummy2)) == -1 ) 111 | return; 112 | 113 | SetConsoleCursorPosition(hstd_out, pos); 114 | #else /* ANSI */ 115 | printf("\033[%d;%df", y, x); 116 | #endif /* _WIN32 */ 117 | } 118 | 119 | void get_cursor(int* x, int* y) 120 | { 121 | #ifdef _WIN32 122 | COORD pos; 123 | DWORD dummy; 124 | HANDLE hstd_out; 125 | 126 | if( (get_console_info(&hstd_out, &pos, &dummy)) == -1 ) 127 | { 128 | *x = 0; 129 | *y = 0; 130 | } 131 | else 132 | { 133 | *x = pos.X; 134 | *y = pos.Y; 135 | } 136 | #else /* ANSI */ 137 | /* TODO we always return 0/0 as the cursor position */ 138 | *x = 0; 139 | *y = 0; 140 | #endif /* _WIN32 */ 141 | } 142 | 143 | void reverse_screen(int c) { 144 | #ifdef _WIN32 145 | /* No implementation for Windows yet */ 146 | #else /* ANSI */ 147 | if (c == 1) { 148 | fputs("\033[7m", stdout); 149 | } else { 150 | fputs("\033[0m", stdout); 151 | } 152 | 153 | #endif 154 | } 155 | 156 | void set_color(int c) 157 | { 158 | #ifdef _WIN32 159 | HANDLE h; 160 | CONSOLE_SCREEN_BUFFER_INFO info; 161 | WORD bg; 162 | 163 | h = GetStdHandle(STD_OUTPUT_HANDLE); 164 | if( h == INVALID_HANDLE_VALUE || GetConsoleScreenBufferInfo(h, &info) == FALSE ) 165 | return; 166 | 167 | bg = info.wAttributes & (BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE | BACKGROUND_INTENSITY); 168 | switch (c) 169 | { 170 | case COLOR_WHITE: 171 | info.wAttributes = bg | FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_INTENSITY; 172 | break; 173 | 174 | case COLOR_RED: 175 | info.wAttributes = bg | FOREGROUND_RED; 176 | break; 177 | 178 | case COLOR_GREEN: 179 | info.wAttributes = bg | FOREGROUND_GREEN; 180 | break; 181 | 182 | case COLOR_BLUE: 183 | info.wAttributes = bg | FOREGROUND_BLUE; 184 | break; 185 | 186 | case COLOR_BLACK: 187 | info.wAttributes = bg; 188 | break; 189 | 190 | case COLOR_PURPLE: 191 | info.wAttributes = bg | FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_INTENSITY; 192 | break; 193 | 194 | case COLOR_YELLOW: 195 | info.wAttributes = bg | FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_INTENSITY; 196 | break; 197 | 198 | case COLOR_CYAN: 199 | info.wAttributes = bg | FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_INTENSITY; 200 | break; 201 | 202 | case COLOR_ORANGE: 203 | info.wAttributes = bg | FOREGROUND_GREEN | FOREGROUND_BLUE; 204 | break; 205 | 206 | case COLOR_BROWN: 207 | info.wAttributes = bg | FOREGROUND_RED | FOREGROUND_GREEN; 208 | break; 209 | 210 | case COLOR_LTRED: 211 | info.wAttributes = bg | FOREGROUND_RED | FOREGROUND_INTENSITY; 212 | break; 213 | 214 | case COLOR_GREY1: 215 | info.wAttributes = bg | FOREGROUND_RED | FOREGROUND_BLUE; 216 | break; 217 | 218 | case COLOR_GREY2: 219 | info.wAttributes = bg | FOREGROUND_INTENSITY; 220 | break; 221 | 222 | case COLOR_LTGREEN: 223 | info.wAttributes = bg | FOREGROUND_GREEN | FOREGROUND_INTENSITY; 224 | break; 225 | 226 | case COLOR_LTBLUE: 227 | info.wAttributes = bg | FOREGROUND_BLUE | FOREGROUND_INTENSITY; 228 | break; 229 | 230 | case COLOR_GREY3: 231 | info.wAttributes = bg | FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_GREEN; 232 | break; 233 | } 234 | 235 | SetConsoleTextAttribute(h, info.wAttributes); 236 | 237 | #else /* ANSI */ 238 | int fg,attr; 239 | fg=37; 240 | attr=0; 241 | switch (c) 242 | { 243 | case COLOR_WHITE: /* Bold white */ 244 | fg=37; 245 | attr=1; 246 | break; 247 | 248 | case COLOR_GREY1: /* Bold black is a dark grey */ 249 | case COLOR_BROWN: 250 | fg=30; 251 | attr=1; 252 | break; 253 | 254 | case COLOR_GREY2: 255 | case COLOR_GREY3: 256 | fg=37; 257 | attr=0; 258 | break; 259 | 260 | case COLOR_RED: 261 | fg=31; 262 | attr=0; 263 | break; 264 | 265 | case COLOR_ORANGE: 266 | case COLOR_LTRED: 267 | attr=1; 268 | fg=31; 269 | break; 270 | 271 | case COLOR_GREEN: 272 | fg=32; 273 | attr=1; 274 | break; 275 | 276 | case COLOR_LTGREEN: 277 | fg=32; 278 | attr=0; 279 | break; 280 | 281 | case COLOR_BLUE: 282 | attr=1; 283 | fg=34; 284 | break; 285 | 286 | case COLOR_BLACK: 287 | fg=30; 288 | attr=0; 289 | break; 290 | 291 | case COLOR_PURPLE: 292 | attr=1; 293 | fg=35; /* Magenta */ 294 | break; 295 | 296 | case COLOR_YELLOW: 297 | attr=1; 298 | fg=33; 299 | break; 300 | 301 | case COLOR_CYAN: 302 | attr=1; 303 | fg=36; 304 | break; 305 | 306 | case COLOR_LTBLUE: 307 | attr=0; 308 | fg=36; 309 | break; 310 | } 311 | printf("\033[%d;%d;40m", attr,fg); 312 | 313 | #endif /* _WIN32 */ 314 | } 315 | -------------------------------------------------------------------------------- /plugin.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2009 Michael Steil 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions 7 | * are met: 8 | * 1. Redistributions of source code must retain the above copyright 9 | * notice, this list of conditions and the following disclaimer. 10 | * 2. Redistributions in binary form must reproduce the above copyright 11 | * notice, this list of conditions and the following disclaimer in the 12 | * documentation and/or other materials provided with the distribution. 13 | * 14 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 15 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 18 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 20 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 21 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 22 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 23 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 24 | * SUCH DAMAGE. 25 | */ 26 | 27 | /* 28 | * This plugin interface makes use of the standard plugin facility built into 29 | * Commodore BASIC that is used by BASIC extensions such as Simons' BASIC. 30 | * There are several vectors at 0x0300 in RAM for functions like error printing, 31 | * tokenizing, de-tokenizing and the interpreter loop. We hook this from C. 32 | * Since this adds code to the interpreter loop, it is disabled by default, 33 | * and can be enabled like this: 34 | * 35 | * SYS 1 36 | * 37 | * It can be disabled with: 38 | * 39 | * SYS 0 40 | * 41 | * Please note that the current implementation does not tokenize new keywords, 42 | * but stores them verbatim and compares strings when during execution, which 43 | * is very bad for performance. Also, there is currently no demo code for 44 | * added functions. 45 | */ 46 | 47 | #include 48 | #include 49 | #ifdef _WIN32 50 | #include 51 | #undef ERROR_FILE_NOT_FOUND /* avoid conflict with CBM value below */ 52 | #endif 53 | #include "plugin.h" 54 | #include "glue.h" 55 | #include "console.h" 56 | 57 | static unsigned short 58 | get_chrptr() { 59 | return RAM[0x7A] | RAM[0x7B]<<8; 60 | } 61 | 62 | static void 63 | set_chrptr(unsigned short a) { 64 | RAM[0x7A] = a & 0xFF; 65 | RAM[0x7B] = a >> 8; 66 | } 67 | 68 | int 69 | compare(const char *s1) { 70 | const unsigned char *s = (const unsigned char *)s1; 71 | unsigned short chrptr = get_chrptr(); 72 | 73 | while (*s) { 74 | CHRGET(); 75 | if (A != *s++) { 76 | set_chrptr(chrptr); 77 | return 0; 78 | } 79 | } 80 | CHRGET(); 81 | return 1; 82 | } 83 | 84 | /* 85 | * Continuation 86 | * 87 | * This will put a magic value onto the stack and run the main 88 | * function again with another PC value as a start address. 89 | * When the code returns, it will find the magic value, and 90 | * the main function will quit, so we end up here again. 91 | */ 92 | static void 93 | call(unsigned short pc) { 94 | PC = pc; 95 | PUSH_WORD(MAGIC_CONTINUATION-1); 96 | main(0,0); 97 | } 98 | 99 | static void 100 | check_comma() { 101 | call(0xAEFD); 102 | } 103 | 104 | static unsigned short 105 | get_word() { 106 | call(0xAD8A); 107 | call(0xB7F7); 108 | return RAM[0x14] | (RAM[0x15]<<8); 109 | } 110 | 111 | static unsigned char 112 | get_byte() { 113 | call(0xB79E); 114 | return X; 115 | } 116 | 117 | static void 118 | get_string(char *s) { 119 | int i; 120 | 121 | call(0xAD9E); 122 | call(0xB6A3); 123 | for (i = 0; i < A; i++) 124 | s[i] = RAM[(X|(Y<<8))+i]; 125 | s[A] = 0; 126 | } 127 | 128 | #define ERROR_TOO_MANY_FILES 0x01 129 | #define ERROR_FILE_OPEN 0x02 130 | #define ERROR_FILE_NOT_OPEN 0x03 131 | #define ERROR_FILE_NOT_FOUND 0x04 132 | #define ERROR_DEVICE_NOT_PRESENT 0x05 133 | #define ERROR_NOT_INPUT_FILE 0x06 134 | #define ERROR_NOT_OUTPUT_FILE 0x07 135 | #define ERROR_MISSING_FILE_NAME 0x08 136 | #define ERROR_ILLEGAL_DEVICE_NUMBER 0x09 137 | #define ERROR_NEXT_WITHOUT_FOR 0x0A 138 | #define ERROR_SYNTAX 0x0B 139 | #define ERROR_RETURN_WITHOUT_GOSUB 0x0C 140 | #define ERROR_OUT_OF_DATA 0x0D 141 | #define ERROR_ILLEGAL_QUANTITY 0x0E 142 | #define ERROR_OVERFLOW 0x0F 143 | #define ERROR_OUT_OF_MEMORY 0x10 144 | #define ERROR_UNDEFD_STATMENT 0x11 145 | #define ERROR_BAD_SUBSCRIPT 0x12 146 | #define ERROR_REDIMD_ARRAY 0x13 147 | #define ERROR_DEVISION_BY_ZERO 0x14 148 | #define ERROR_ILLEGAL_DIRECT 0x15 149 | #define ERROR_TYPE_MISMATCH 0x16 150 | #define ERROR_STRING_TOO_LONG 0x17 151 | #define ERROR_FILE_DATA 0x18 152 | #define ERROR_FORMULA_TOO_COMPLEX 0x19 153 | #define ERROR_CANT_CONTINUE 0x1A 154 | #define ERROR_UNDEFD_FUNCTION 0x1B 155 | #define ERROR_VERIFY 0x1C 156 | #define ERROR_LOAD 0x1D 157 | #define ERROR_BREAK 0x1E 158 | 159 | static unsigned short 160 | error(unsigned char index) { 161 | X = index; 162 | return 0xA437; /* error handler */ 163 | } 164 | 165 | /* 166 | * Print BASIC Error Message 167 | * 168 | * We could add handling of extra error codes here, or 169 | * print friendlier strings, or implement "ON ERROR GOTO". 170 | */ 171 | unsigned short 172 | plugin_error() { 173 | return 0; 174 | } 175 | 176 | /* 177 | * BASIC Warm Start 178 | * 179 | * This gets called whenever we are in direct mode. 180 | */ 181 | unsigned short 182 | plugin_main() { 183 | return 0; 184 | } 185 | 186 | /* 187 | * Tokenize BASIC Text 188 | */ 189 | unsigned short 190 | plugin_crnch() { 191 | return 0; 192 | } 193 | 194 | /* 195 | * BASIC Text LIST 196 | */ 197 | unsigned short 198 | plugin_qplop() { 199 | return 0; 200 | } 201 | 202 | /* 203 | * BASIC Char. Dispatch 204 | * 205 | * This is used for interpreting statements. 206 | */ 207 | unsigned short 208 | plugin_gone() { 209 | set_chrptr(get_chrptr()+1); 210 | for (;;) { 211 | unsigned short chrptr; 212 | set_chrptr(get_chrptr()-1); 213 | chrptr = get_chrptr(); 214 | /* 215 | * this example shows: 216 | * - how to get a 16 bit integer 217 | * - how to get an 8 bit integer 218 | * - how to check for a comma delimiter 219 | * - how to do error handling 220 | */ 221 | if (compare("LOCATE")) { 222 | unsigned char x,y; 223 | y = get_byte(); /* 'line' first */ 224 | check_comma(); 225 | x = get_byte(); /* then 'column' */ 226 | /* XXX ignores terminal size */ 227 | if (x>80 || y>25 || x==0 || y==0) 228 | return error(ERROR_ILLEGAL_QUANTITY); 229 | move_cursor(x, y); 230 | 231 | continue; 232 | } 233 | 234 | /* Implements the ANSI Set Graphics Mode 235 | as appears on http://ascii-table.com/ansi-escape-sequences.php 236 | Note that some attributes will be implemented differently depending on what 237 | terminal program you are using. 238 | */ 239 | if (compare("ANSISGM")) { 240 | char attr, fg, bg; 241 | attr = get_byte(); /* Attribute */ 242 | check_comma(); 243 | fg = get_byte(); /* Foreground color */ 244 | check_comma(); 245 | bg = get_byte(); /* Background color */ 246 | 247 | if (attr < 0 || attr > 8) return error(ERROR_ILLEGAL_QUANTITY); 248 | if (fg < 30 || fg > 37) return error(ERROR_ILLEGAL_QUANTITY); 249 | if (bg < 40 || bg > 47) return error(ERROR_ILLEGAL_QUANTITY); 250 | 251 | printf("\033[%d;%d;%dm",attr,bg,fg); 252 | continue; 253 | } 254 | 255 | /* ANSI erase line */ 256 | if (compare("ANSIEL")) { 257 | printf("\033[K"); 258 | } 259 | 260 | /* ANSI save cursor pos */ 261 | if (compare("ANSISC")) { 262 | printf("\033[s"); 263 | } 264 | 265 | /* ANSI restore cursor pos */ 266 | if (compare("ANSIRC")) { 267 | printf("\033[u"); 268 | } 269 | 270 | /* 271 | * this example shows: 272 | * - how to override existing keywords 273 | * - how to hand the instruction to the 274 | * original interpreter if we don't want 275 | * to handle it 276 | */ 277 | if (compare("\222")) { /* 0x92 - WAIT */ 278 | unsigned short a; 279 | unsigned char b; 280 | a = get_word(); 281 | check_comma(); 282 | b = get_byte(); 283 | if (a==6502) { 284 | printf("MICROSOFT!"); 285 | continue; 286 | } else { 287 | set_chrptr(chrptr); 288 | return 0; 289 | } 290 | } 291 | 292 | /* 293 | * this example shows: 294 | * - how to deal with new keywords that contain 295 | * existing keywords 296 | * - how to parse a string 297 | */ 298 | if (compare("\236TEM")) { 299 | char s[256]; 300 | 301 | get_string(s); 302 | system(s); 303 | 304 | continue; 305 | } 306 | 307 | if (compare("QUIT")) { 308 | exit(0); 309 | } 310 | break; 311 | } 312 | return 0; 313 | } 314 | 315 | /* 316 | * BASIC Token Evaluation 317 | * 318 | * This is used for expression evaluation. 319 | * New functions and operators go here. 320 | */ 321 | unsigned short 322 | plugin_eval() { 323 | return 0; 324 | } 325 | -------------------------------------------------------------------------------- /runtime.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2009 Michael Steil, James Abbatiello 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions 7 | * are met: 8 | * 1. Redistributions of source code must retain the above copyright 9 | * notice, this list of conditions and the following disclaimer. 10 | * 2. Redistributions in binary form must reproduce the above copyright 11 | * notice, this list of conditions and the following disclaimer in the 12 | * documentation and/or other materials provided with the distribution. 13 | * 14 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 15 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 18 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 20 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 21 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 22 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 23 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 24 | * SUCH DAMAGE. 25 | */ 26 | 27 | //#define NO_CLRHOME 28 | 29 | #include 30 | #include 31 | #include 32 | #include 33 | #ifdef _WIN32 34 | #include // getcwd, chdir 35 | #include // GetLocalTime, SetLocalTime 36 | #include // _kbhit, _getch 37 | #else 38 | #include 39 | #include 40 | #endif 41 | #include "stat.h" 42 | #include "readdir.h" 43 | #include "plugin.h" 44 | #include "glue.h" 45 | #include "console.h" 46 | 47 | int 48 | stack4(unsigned short a, unsigned short b, unsigned short c, unsigned short d) { 49 | // printf("stack4: %x,%x,%x,%x\n", a, b, c, d); 50 | if (STACK16(S+1) + 1 != a) return 0; 51 | if (STACK16(S+3) + 1 != b) return 0; 52 | if (STACK16(S+5) + 1 != c) return 0; 53 | if (STACK16(S+7) + 1 != d) return 0; 54 | return 1; 55 | } 56 | 57 | /* 58 | * CHRGET/CHRGOT 59 | * CBMBASIC implements CHRGET/CHRGOT as self-modifying 60 | * code in the zero page. This cannot be done with 61 | * static recompilation, so here is a reimplementation 62 | * of these functions in C. 63 | 0073 E6 7A INC $7A 64 | 0075 D0 02 BNE $0079 65 | 0077 E6 7B INC $7B 66 | 0079 AD XX XX LDA $XXXX 67 | 007C C9 3A CMP #$3A ; colon 68 | 007E B0 0A BCS $008A 69 | 0080 C9 20 CMP #$20 ; space 70 | 0082 F0 EF BEQ $0073 71 | 0084 38 SEC 72 | 0085 E9 30 SBC #$30 ; 0 73 | 0087 38 SEC 74 | 0088 E9 D0 SBC #$D0 75 | 008A 60 RTS 76 | */ 77 | static void 78 | CHRGET_common(int inc) { 79 | unsigned short temp16; 80 | if (!inc) goto CHRGOT_start; 81 | CHRGET_start: 82 | RAM[0x7A]++; SETSZ(RAM[0x7A]); 83 | if (!Z) goto CHRGOT_start; 84 | RAM[0x7B]++; SETSZ(RAM[0x7B]); 85 | CHRGOT_start: 86 | A = RAM[RAM[0x7A] | RAM[0x7B]<<8]; SETSZ(A); 87 | temp16 = ((unsigned short)A) - ((unsigned short)0x3A); SETNC(temp16); SETSZ(temp16&0xFF); 88 | if (C) return; 89 | temp16 = ((unsigned short)A) - ((unsigned short)0x20); SETNC(temp16); SETSZ(temp16&0xFF); 90 | if (Z) goto CHRGET_start; 91 | C = 1; 92 | temp16 = (unsigned short)A-(unsigned short)0x30-(unsigned short)(1-C); SETV(((A ^ temp16) & 0x80) && ((A ^ 0x30) & 0x80)); A = (unsigned char)temp16; SETSZ(A); SETNC(temp16); 93 | C = 1; 94 | temp16 = (unsigned short)A-(unsigned short)0xD0-(unsigned short)(1-C); SETV(((A ^ temp16) & 0x80) && ((A ^ 0xD0) & 0x80)); A = (unsigned char)temp16; SETSZ(A); SETNC(temp16); 95 | } 96 | 97 | void 98 | CHRGET() { 99 | CHRGET_common(1); 100 | } 101 | void 102 | CHRGOT() { 103 | CHRGET_common(0); 104 | } 105 | 106 | 107 | /************************************************************/ 108 | /* KERNAL interface implementation */ 109 | /* http://members.tripod.com/~Frank_Kontros/kernal/addr.htm */ 110 | /************************************************************/ 111 | 112 | /* KERNAL constants */ 113 | #if 0 114 | #define RAM_BOT 0x0400 /* we could just as well start at 0x0400, as there is no screen RAM */ 115 | #else 116 | #define RAM_BOT 0x0800 117 | #endif 118 | #define RAM_TOP 0xA000 119 | #define KERN_ERR_NONE 0 120 | #define KERN_ERR_FILE_OPEN 2 121 | #define KERN_ERR_FILE_NOT_OPEN 3 122 | #define KERN_ERR_FILE_NOT_FOUND 4 123 | #define KERN_ERR_DEVICE_NOT_PRESENT 5 124 | #define KERN_ERR_NOT_INPUT_FILE 6 125 | #define KERN_ERR_NOT_OUTPUT_FILE 7 126 | #define KERN_ERR_MISSING_FILE_NAME 8 127 | #define KERN_ERR_ILLEGAL_DEVICE_NUMBER 9 128 | 129 | #define KERN_ST_TIME_OUT_READ 0x02 130 | #define KERN_ST_EOF 0x40 131 | 132 | /* KERNAL internal state */ 133 | unsigned char kernal_msgflag, kernal_status = 0; 134 | unsigned short kernal_filename; 135 | unsigned char kernal_filename_len; 136 | unsigned char kernal_lfn, kernal_dev, kernal_sec; 137 | int kernal_quote = 0; 138 | unsigned char kernal_output = 0, kernal_input = 0; 139 | FILE* kernal_files[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; 140 | int kernal_files_next[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; 141 | 142 | /* shell script hack */ 143 | int readycount = 0; 144 | int interactive; 145 | FILE *input_file; 146 | 147 | int 148 | init_os(int argc, char **argv) { 149 | // printf("init_os %d\n", argc); 150 | if (!argc) /* continuation */ 151 | return PC; 152 | 153 | if (argc>1) { 154 | interactive = 0; 155 | input_file = fopen(argv[1], "r"); 156 | if (!input_file) { 157 | printf("Error opening: %s\n", argv[1]); 158 | exit(1); 159 | } 160 | if (fgetc(input_file)=='#') { 161 | char c; 162 | do { 163 | c = fgetc(input_file); 164 | } while((c!=13)&&(c!=10)); 165 | } else { 166 | fseek(input_file, 0, SEEK_SET); 167 | } 168 | } else { 169 | interactive = 1; 170 | input_file = NULL; 171 | } 172 | srand((unsigned int)time(NULL)); 173 | 174 | return 0xE394; /* main entry point of BASIC */ 175 | } 176 | 177 | unsigned short orig_error, orig_main, orig_crnch, orig_qplop, orig_gone, orig_eval; 178 | 179 | int plugin = 0; 180 | 181 | void 182 | replace_vector(unsigned short address, unsigned short new, unsigned short *old) { 183 | *old = RAM[address] | (RAM[address+1]<<8); 184 | RAM[address] = (new)&0xFF; 185 | RAM[address+1] = (new)>>8; 186 | } 187 | 188 | void 189 | plugin_on() { 190 | if (plugin) 191 | return; 192 | 193 | replace_vector(VEC_ERROR, MAGIC_ERROR, &orig_error); 194 | replace_vector(VEC_MAIN, MAGIC_MAIN, &orig_main); 195 | replace_vector(VEC_CRNCH, MAGIC_CRNCH, &orig_crnch); 196 | replace_vector(VEC_QPLOP, MAGIC_QPLOP, &orig_qplop); 197 | replace_vector(VEC_GONE, MAGIC_GONE, &orig_gone); 198 | replace_vector(VEC_EVAL, MAGIC_EVAL, &orig_eval); 199 | 200 | plugin = 1; 201 | } 202 | 203 | static void 204 | plugin_off() { 205 | unsigned short dummy; 206 | 207 | if (!plugin) 208 | return; 209 | 210 | replace_vector(VEC_ERROR, orig_error, &dummy); 211 | replace_vector(VEC_MAIN, orig_main, &dummy); 212 | replace_vector(VEC_CRNCH, orig_crnch, &dummy); 213 | replace_vector(VEC_QPLOP, orig_qplop, &dummy); 214 | replace_vector(VEC_GONE, orig_gone, &dummy); 215 | replace_vector(VEC_EVAL, orig_eval, &dummy); 216 | 217 | plugin = 0; 218 | } 219 | 220 | static void 221 | SETMSG() { 222 | kernal_msgflag = A; 223 | A = kernal_status; 224 | } 225 | 226 | static void 227 | MEMTOP() { 228 | #if DEBUG /* CBMBASIC doesn't do this */ 229 | if (!C) { 230 | printf("UNIMPL: set top of RAM"); 231 | exit(1); 232 | } 233 | #endif 234 | X = RAM_TOP&0xFF; 235 | Y = RAM_TOP>>8; 236 | 237 | /* 238 | * if we want to turn on the plugin 239 | * automatically at start, we can do it here. 240 | */ 241 | //plugin_on(); 242 | } 243 | 244 | /* MEMBOT */ 245 | static void 246 | MEMBOT() { 247 | #if DEBUG /* CBMBASIC doesn't do this */ 248 | if (!C) { 249 | printf("UNIMPL: set bot of RAM"); 250 | exit(1); 251 | } 252 | #endif 253 | X = RAM_BOT&0xFF; 254 | Y = RAM_BOT>>8; 255 | } 256 | 257 | /* READST */ 258 | static void 259 | READST() { 260 | A = kernal_status; 261 | } 262 | 263 | /* SETLFS */ 264 | static void 265 | SETLFS() { 266 | kernal_lfn = A; 267 | kernal_dev = X; 268 | kernal_sec = Y; 269 | } 270 | 271 | /* SETNAM */ 272 | static void 273 | SETNAM() { 274 | kernal_filename = X | Y<<8; 275 | kernal_filename_len = A; 276 | } 277 | 278 | /* OPEN */ 279 | static void 280 | OPEN() { 281 | kernal_status = 0; 282 | if (kernal_files[kernal_lfn]) { 283 | C = 1; 284 | A = KERN_ERR_FILE_OPEN; 285 | } else if (kernal_filename_len == 0) { 286 | C = 1; 287 | A = KERN_ERR_MISSING_FILE_NAME; 288 | } else { 289 | unsigned char savedbyte = RAM[kernal_filename+kernal_filename_len]; 290 | const char* mode = kernal_sec == 0 ? "r" : "w"; 291 | RAM[kernal_filename+kernal_filename_len] = 0; 292 | kernal_files[kernal_lfn] = fopen(RAM+kernal_filename, mode); 293 | RAM[kernal_filename+kernal_filename_len] = savedbyte; 294 | if (kernal_files[kernal_lfn]) { 295 | kernal_files_next[kernal_lfn] = EOF; 296 | C = 0; 297 | } else { 298 | C = 1; 299 | A = KERN_ERR_FILE_NOT_FOUND; 300 | } 301 | } 302 | } 303 | 304 | /* CLOSE */ 305 | static void 306 | CLOSE() { 307 | if (!kernal_files[kernal_lfn]) { 308 | C = 1; 309 | A = KERN_ERR_FILE_NOT_OPEN; 310 | } else { 311 | fclose(kernal_files[kernal_lfn]); 312 | kernal_files[kernal_lfn] = 0; 313 | C = 0; 314 | } 315 | } 316 | 317 | /* CHKIN */ 318 | static void 319 | CHKIN() { 320 | kernal_status = 0; 321 | if (!kernal_files[X]) { 322 | C = 1; 323 | A = KERN_ERR_FILE_NOT_OPEN; 324 | } else { 325 | // TODO Check read/write mode 326 | kernal_input = X; 327 | C = 0; 328 | } 329 | } 330 | 331 | /* CHKOUT */ 332 | static void 333 | CHKOUT() { 334 | kernal_status = 0; 335 | if (!kernal_files[X]) { 336 | C = 1; 337 | A = KERN_ERR_FILE_NOT_OPEN; 338 | } else { 339 | // TODO Check read/write mode 340 | kernal_output = X; 341 | C = 0; 342 | } 343 | } 344 | 345 | /* CLRCHN */ 346 | static void 347 | CLRCHN() { 348 | kernal_input = 0; 349 | kernal_output = 0; 350 | } 351 | 352 | static const char run[] = { 'R', 'U', 'N', 13 }; 353 | 354 | int fakerun = 0; 355 | int fakerun_index = 0; 356 | 357 | /* CHRIN */ 358 | static void 359 | CHRIN() { 360 | if ((!interactive) && (readycount==2)) { 361 | exit(0); 362 | } 363 | if (kernal_input != 0) { 364 | if (feof(kernal_files[kernal_input])) { 365 | kernal_status |= KERN_ST_EOF; 366 | kernal_status |= KERN_ST_TIME_OUT_READ; 367 | A = 13; 368 | } else { 369 | if (kernal_files_next[kernal_input] == EOF) 370 | kernal_files_next[kernal_input] = fgetc(kernal_files[kernal_input]); 371 | A = kernal_files_next[kernal_input]; 372 | kernal_files_next[kernal_input] = fgetc(kernal_files[kernal_input]); 373 | if (kernal_files_next[kernal_input] == EOF) 374 | kernal_status |= KERN_ST_EOF; 375 | } 376 | } else if (!input_file) { 377 | A = getchar(); /* stdin */ 378 | if (A=='\n') A = '\r'; 379 | } else { 380 | if (fakerun) { 381 | A = run[fakerun_index++]; 382 | if (fakerun_index==sizeof(run)) 383 | input_file = 0; /* switch to stdin */ 384 | } else { 385 | A = fgetc(input_file); 386 | if ((A==255)&&(readycount==1)) { 387 | fakerun = 1; 388 | fakerun_index = 0; 389 | A = run[fakerun_index++]; 390 | } 391 | if (A=='\n') A = '\r'; 392 | } 393 | } 394 | C = 0; 395 | } 396 | 397 | /* CHROUT */ 398 | static void 399 | CHROUT() { 400 | #if 0 401 | int a = *(unsigned short*)(&RAM[0x0100+S+1]) + 1; 402 | int b = *(unsigned short*)(&RAM[0x0100+S+3]) + 1; 403 | int c = *(unsigned short*)(&RAM[0x0100+S+5]) + 1; 404 | int d = *(unsigned short*)(&RAM[0x0100+S+7]) + 1; 405 | printf("CHROUT: %d @ %x,%x,%x,%x\n", A, a, b, c, d); 406 | #endif 407 | if (!interactive) { 408 | if (stack4(0xe10f,0xab4a,0xab30,0xe430)) { 409 | /* COMMODORE 64 BASIC V2 */ 410 | C = 0; 411 | return; 412 | } 413 | if (stack4(0xe10f,0xab4a,0xab30,0xe43d)) { 414 | /* 38911 */ 415 | C = 0; 416 | return; 417 | } 418 | if (stack4(0xe10f,0xab4a,0xab30,0xe444)) { 419 | /* BASIC BYTES FREE */ 420 | C = 0; 421 | return; 422 | } 423 | } 424 | if (stack4(0xe10f,0xab4a,0xab30,0xa47b)) { 425 | /* READY */ 426 | if (A=='R') readycount++; 427 | if (!interactive) { 428 | C = 0; 429 | return; 430 | } 431 | } 432 | if (stack4(0xe10f,0xab4a,0xaadc,0xa486)) { 433 | /* 434 | * CR after each entered numbered program line: 435 | * The CBM screen editor returns CR when the user 436 | * hits return, but does not print the character, 437 | * therefore CBMBASIC does. On UNIX, the terminal 438 | * prints all input characters, so we have to avoid 439 | * printing it again 440 | */ 441 | C = 0; 442 | return; 443 | } 444 | 445 | #if 0 446 | printf("CHROUT: %c (%d)\n", A, A); 447 | #else 448 | if (kernal_output) { 449 | if (fputc(A, kernal_files[kernal_output]) == EOF) { 450 | C = 1; 451 | A = KERN_ERR_NOT_OUTPUT_FILE; 452 | } else 453 | C = 0; 454 | } else { 455 | if (kernal_quote) { 456 | if (A == '"' || A == '\n' || A == '\r') kernal_quote = 0; 457 | putchar(A); 458 | } else { 459 | switch (A) { 460 | case 5: 461 | set_color(COLOR_WHITE); 462 | break; 463 | case 10: 464 | break; 465 | case 13: 466 | putchar(13); 467 | putchar(10); 468 | break; 469 | case 17: /* CSR DOWN */ 470 | down_cursor(); 471 | break; 472 | case 18: /* REV ON */ 473 | reverse_screen(1); 474 | break; 475 | case 19: /* CSR HOME */ 476 | move_cursor(0, 0); 477 | break; 478 | case 28: 479 | set_color(COLOR_RED); 480 | break; 481 | case 29: /* CSR RIGHT */ 482 | right_cursor(); 483 | break; 484 | case 30: 485 | set_color(COLOR_GREEN); 486 | break; 487 | case 31: 488 | set_color(COLOR_BLUE); 489 | break; 490 | case 129: 491 | set_color(COLOR_ORANGE); 492 | break; 493 | case 144: 494 | set_color(COLOR_BLACK); 495 | break; 496 | case 145: /* CSR UP */ 497 | up_cursor(); 498 | break; 499 | case 146: /* REV OFF */ 500 | reverse_screen(0); 501 | break; 502 | case 147: /* clear screen */ 503 | #ifndef NO_CLRHOME 504 | clear_screen(); 505 | #endif 506 | break; 507 | case 149: 508 | set_color(COLOR_BROWN); 509 | break; 510 | case 150: 511 | set_color(COLOR_LTRED); 512 | break; 513 | case 151: 514 | set_color(COLOR_GREY1); 515 | break; 516 | case 152: 517 | set_color(COLOR_GREY2); 518 | break; 519 | case 153: 520 | set_color(COLOR_LTGREEN); 521 | break; 522 | case 154: 523 | set_color(COLOR_LTBLUE); 524 | break; 525 | case 155: 526 | set_color(COLOR_GREY3); 527 | break; 528 | case 156: 529 | set_color(COLOR_PURPLE); 530 | break; 531 | case 158: 532 | set_color(COLOR_YELLOW); 533 | break; 534 | case 159: 535 | set_color(COLOR_CYAN); 536 | break; 537 | case 157: /* CSR LEFT */ 538 | left_cursor(); 539 | break; 540 | case '"': 541 | kernal_quote = 1; 542 | // fallthrough 543 | default: 544 | putchar(A); 545 | } 546 | } 547 | #endif 548 | fflush(stdout); 549 | C = 0; 550 | } 551 | } 552 | 553 | /* LOAD */ 554 | static void 555 | LOAD() { 556 | FILE *f; 557 | struct stat st; 558 | unsigned short start; 559 | unsigned short end; 560 | unsigned char savedbyte; 561 | 562 | if (A) { 563 | printf("UNIMPL: VERIFY\n"); 564 | exit(1); 565 | } 566 | if (!kernal_filename_len) 567 | goto missing_file_name; 568 | 569 | /* on special filename $ read directory entries and load they in the basic area memory */ 570 | if( RAM[kernal_filename]=='$' ) { 571 | DIR *dirp; 572 | struct dirent *dp; 573 | int i, file_size; 574 | unsigned short old_memp, memp = 0x0801; // TODO hack! 575 | 576 | old_memp = memp; 577 | memp += 2; 578 | RAM[memp++] = 0; 579 | RAM[memp++] = 0; 580 | RAM[memp++] = 0x12; /* REVERS ON */ 581 | RAM[memp++] = '"'; 582 | for(i=0; i<16; i++) 583 | RAM[memp+i] = ' '; 584 | if( (getcwd((char*)&RAM[memp], 256)) == NULL ) 585 | goto device_not_present; 586 | memp += strlen((char*)&RAM[memp]); /* only 16 on COMMODORE DOS */ 587 | RAM[memp++] = '"'; 588 | RAM[memp++] = ' '; 589 | RAM[memp++] = '0'; 590 | RAM[memp++] = '0'; 591 | RAM[memp++] = ' '; 592 | RAM[memp++] = '2'; 593 | RAM[memp++] = 'A'; 594 | RAM[memp++] = 0; 595 | 596 | RAM[old_memp] = (memp) & 0xFF; 597 | RAM[old_memp+1] = (memp) >> 8; 598 | 599 | if ( !(dirp = opendir(".")) ) 600 | goto device_not_present; 601 | while ((dp = readdir(dirp))) { 602 | size_t namlen = strlen(dp->d_name); 603 | stat(dp->d_name, &st); 604 | file_size = (st.st_size + 253)/254; /* convert file size from num of bytes to num of blocks(254 bytes) */ 605 | if (file_size>0xFFFF) 606 | file_size = 0xFFFF; 607 | old_memp = memp; 608 | memp += 2; 609 | RAM[memp++] = file_size & 0xFF; 610 | RAM[memp++] = file_size >> 8; 611 | if (file_size<1000) { 612 | RAM[memp++] = ' '; 613 | if (file_size<100) { 614 | RAM[memp++] = ' '; 615 | if (file_size<10) { 616 | RAM[memp++] = ' '; 617 | } 618 | } 619 | } 620 | RAM[memp++] = '"'; 621 | if (namlen>16) 622 | namlen=16; /* TODO hack */ 623 | memcpy(&RAM[memp], dp->d_name, namlen); 624 | memp += namlen; 625 | RAM[memp++] = '"'; 626 | for (i=namlen; i<16; i++) 627 | RAM[memp++] = ' '; 628 | RAM[memp++] = ' '; 629 | RAM[memp++] = 'P'; 630 | RAM[memp++] = 'R'; 631 | RAM[memp++] = 'G'; 632 | RAM[memp++] = ' '; 633 | RAM[memp++] = ' '; 634 | RAM[memp++] = 0; 635 | 636 | RAM[old_memp] = (memp) & 0xFF; 637 | RAM[old_memp+1] = (memp) >> 8; 638 | } 639 | RAM[memp] = 0; 640 | RAM[memp+1] = 0; 641 | (void)closedir(dirp); 642 | end = memp + 2; 643 | /* 644 | for (i=0; i<255; i++) { 645 | if (!(i&15)) 646 | printf("\n %04X ", 0x0800+i); 647 | printf("%02X ", RAM[0x0800+i]); 648 | } 649 | */ 650 | goto load_noerr; 651 | } /* end if( RAM[kernal_filename]=='$' ) */ 652 | 653 | savedbyte = RAM[kernal_filename+kernal_filename_len]; /* TODO possible overflow */ 654 | RAM[kernal_filename+kernal_filename_len] = 0; 655 | 656 | /* on directory filename chdir on it */ 657 | if( (stat((char*)&RAM[kernal_filename], &st)) == -1 ) 658 | goto file_not_found; 659 | if(S_ISDIR(st.st_mode)) { 660 | if( (chdir((char*)&RAM[kernal_filename])) == -1 ) 661 | goto device_not_present; 662 | 663 | RAM[0x0801] = RAM[0x0802] = 0; 664 | end = 0x0803; 665 | goto load_noerr; 666 | } 667 | 668 | /* on file load it read it and load in the basic area memory */ 669 | f = fopen((char*)&RAM[kernal_filename], "rb"); 670 | if (!f) 671 | goto file_not_found; 672 | start = ((unsigned char)fgetc(f)) | ((unsigned char)fgetc(f))<<8; 673 | if (!kernal_sec) 674 | start = X | Y<<8; 675 | end = start + fread(&RAM[start], 1, 65536-start, f); /* TODO may overwrite ROM */ 676 | printf("LOADING FROM $%04X to $%04X\n", start, end); 677 | fclose(f); 678 | 679 | load_noerr: 680 | X = end & 0xFF; 681 | Y = end >> 8; 682 | C = 0; 683 | A = KERN_ERR_NONE; 684 | return; 685 | file_not_found: 686 | C = 1; 687 | A = KERN_ERR_FILE_NOT_FOUND; 688 | return; 689 | device_not_present: 690 | C = 1; 691 | A = KERN_ERR_DEVICE_NOT_PRESENT; 692 | return; 693 | missing_file_name: 694 | C = 1; 695 | A = KERN_ERR_MISSING_FILE_NAME; 696 | return; 697 | } 698 | 699 | /* SAVE */ 700 | static void 701 | SAVE() { 702 | FILE *f; 703 | unsigned char savedbyte; 704 | unsigned short start; 705 | unsigned short end; 706 | 707 | start = RAM[A] | RAM[A+1]<<8; 708 | end = X | Y << 8; 709 | if (end> 8, f); 729 | fwrite(&RAM[start], end-start, 1, f); 730 | fclose(f); 731 | C = 0; 732 | A = KERN_ERR_NONE; 733 | } 734 | 735 | /* SETTIM */ 736 | static void 737 | SETTIM() { 738 | unsigned long jiffies = Y*65536 + X*256 + A; 739 | unsigned long seconds = jiffies/60; 740 | #ifdef _WIN32 741 | SYSTEMTIME st; 742 | 743 | GetLocalTime(&st); 744 | st.wHour = (WORD)(seconds/3600); 745 | st.wMinute = (WORD)(seconds/60); 746 | st.wSecond = (WORD)(seconds%60); 747 | st.wMilliseconds = (WORD)((jiffies % 60) * 1000 / 60); 748 | SetLocalTime(&st); 749 | #else 750 | time_t now = time(0); 751 | struct tm bd; 752 | struct timeval tv; 753 | 754 | localtime_r(&now, &bd); 755 | 756 | bd.tm_sec = seconds%60; 757 | bd.tm_min = seconds/60; 758 | bd.tm_hour = seconds/3600; 759 | 760 | tv.tv_sec = mktime(&bd); 761 | tv.tv_usec = (jiffies % 60) * (1000000/60); 762 | 763 | settimeofday(&tv, 0); 764 | #endif 765 | } 766 | 767 | /* RDTIM */ 768 | static void 769 | RDTIM() { 770 | unsigned long jiffies; 771 | #ifdef _WIN32 772 | SYSTEMTIME st; 773 | 774 | GetLocalTime(&st); 775 | jiffies = ((st.wHour*60 + st.wMinute)*60 + st.wSecond)*60 + st.wMilliseconds * 60 / 1000; 776 | #else 777 | time_t now = time(0); 778 | struct tm bd; 779 | struct timeval tv; 780 | 781 | localtime_r(&now, &bd); 782 | gettimeofday(&tv, 0); 783 | 784 | jiffies = ((bd.tm_hour*60 + bd.tm_min)*60 + bd.tm_sec)*60 + tv.tv_usec / (1000000/60); 785 | #endif 786 | Y = (unsigned char)(jiffies/65536); 787 | X = (unsigned char)((jiffies%65536)/256); 788 | A = (unsigned char)(jiffies%256); 789 | 790 | } 791 | 792 | /* STOP */ 793 | static void 794 | STOP() { 795 | SETZ(0); /* TODO we don't support the STOP key */ 796 | } 797 | 798 | /* GETIN */ 799 | static void 800 | GETIN() { 801 | if (kernal_input != 0) { 802 | if (feof(kernal_files[kernal_input])) { 803 | kernal_status |= KERN_ST_EOF; 804 | kernal_status |= KERN_ST_TIME_OUT_READ; 805 | A = 199; 806 | } else { 807 | if (kernal_files_next[kernal_input] == EOF) 808 | kernal_files_next[kernal_input] = fgetc(kernal_files[kernal_input]); 809 | A = kernal_files_next[kernal_input]; 810 | kernal_files_next[kernal_input] = fgetc(kernal_files[kernal_input]); 811 | if (kernal_files_next[kernal_input] == EOF) 812 | kernal_status |= KERN_ST_EOF; 813 | } 814 | C = 0; 815 | } else { 816 | #ifdef _WIN32 817 | if (_kbhit()) 818 | A = _getch(); 819 | else 820 | A = 0; 821 | #else 822 | A = getchar(); 823 | #endif 824 | if (A=='\n') A = '\r'; 825 | C = 0; 826 | } 827 | } 828 | 829 | /* CLALL */ 830 | static void 831 | CLALL() { 832 | int i; 833 | for (i = 0; i < sizeof(kernal_files)/sizeof(kernal_files[0]); ++i) { 834 | if (kernal_files[i]) { 835 | fclose(kernal_files[i]); 836 | kernal_files[i] = 0; 837 | } 838 | } 839 | } 840 | 841 | /* PLOT */ 842 | static void 843 | PLOT() { 844 | if (C) { 845 | int CX, CY; 846 | get_cursor(&CX, &CY); 847 | Y = CX; 848 | X = CY; 849 | } else { 850 | printf("UNIMPL: set cursor %d %d\n", Y, X); 851 | exit(1); 852 | } 853 | } 854 | 855 | 856 | /* IOBASE */ 857 | static void 858 | IOBASE() { 859 | #define CIA 0xDC00 /* we could put this anywhere... */ 860 | /* 861 | * IOBASE is just used inside RND to get a timer value. 862 | * So, let's fake this here, too. 863 | * Commodore BASIC reads offsets 4/5 and 6/7 to get the 864 | * two timers of the CIA. 865 | */ 866 | int pseudo_timer; 867 | pseudo_timer = rand(); 868 | RAM[CIA+4] = pseudo_timer&0xff; 869 | RAM[CIA+5] = pseudo_timer>>8; 870 | pseudo_timer = rand(); /* more entropy! */ 871 | RAM[CIA+8] = pseudo_timer&0xff; 872 | RAM[CIA+9] = pseudo_timer>>8; 873 | X = CIA & 0xFF; 874 | Y = CIA >> 8; 875 | } 876 | 877 | int 878 | kernal_dispatch() { 879 | //{ printf("kernal_dispatch $%04X; ", PC); int i; printf("stack (%02X): ", S); for (i=S+1; i<0x100; i++) { printf("%02X ", RAM[0x0100+i]); } printf("\n"); } 880 | 881 | unsigned int new_pc; 882 | switch(PC) { 883 | case 0x0073: CHRGET(); break; 884 | case 0x0079: CHRGOT(); break; 885 | case 0xFF90: SETMSG(); break; 886 | case 0xFF99: MEMTOP(); break; 887 | case 0xFF9C: MEMBOT(); break; 888 | case 0xFFB7: READST(); break; 889 | case 0xFFBA: SETLFS(); break; 890 | case 0xFFBD: SETNAM(); break; 891 | case 0xFFC0: OPEN(); break; 892 | case 0xFFC3: CLOSE(); break; 893 | case 0xFFC6: CHKIN(); break; 894 | case 0xFFC9: CHKOUT(); break; 895 | case 0xFFCC: CLRCHN(); break; 896 | case 0xFFCF: CHRIN(); break; 897 | case 0xFFD2: CHROUT(); break; 898 | case 0xFFD5: LOAD(); break; 899 | case 0xFFD8: SAVE(); break; 900 | case 0xFFDB: SETTIM(); break; 901 | case 0xFFDE: RDTIM(); break; 902 | case 0xFFE1: STOP(); break; 903 | case 0xFFE4: GETIN(); break; 904 | case 0xFFE7: CLALL(); break; 905 | case 0xFFF0: PLOT(); break; 906 | case 0xFFF3: IOBASE(); break; 907 | 908 | case 0: plugin_off(); S+=2; break; 909 | case 1: plugin_on(); S+=2; break; 910 | 911 | case MAGIC_ERROR: new_pc = plugin_error(); PUSH_WORD(new_pc? new_pc-1:orig_error-1); break; 912 | case MAGIC_MAIN: new_pc = plugin_main(); PUSH_WORD(new_pc? new_pc-1:orig_main-1); break; 913 | case MAGIC_CRNCH: new_pc = plugin_crnch(); PUSH_WORD(new_pc? new_pc-1:orig_crnch-1); break; 914 | case MAGIC_QPLOP: new_pc = plugin_qplop(); PUSH_WORD(new_pc? new_pc-1:orig_qplop-1); break; 915 | case MAGIC_GONE: new_pc = plugin_gone(); PUSH_WORD(new_pc? new_pc-1:orig_gone-1); break; 916 | case MAGIC_EVAL: new_pc = plugin_eval(); PUSH_WORD(new_pc? new_pc-1:orig_eval-1); break; 917 | 918 | case MAGIC_CONTINUATION: /*printf("--CONTINUATION--\n");*/ return 0; 919 | 920 | default: printf("unknown PC=$%04X S=$%02X\n", PC, S); exit(1); 921 | } 922 | return 1; 923 | } 924 | 925 | --------------------------------------------------------------------------------