host filesystem access

christo

New Member
Sep 16, 2019
13
4
3
Reading the emulator README.md here: https://github.com/commanderx16/x16-emulator suggests but does not explicitly state that the host filesystem is not really fully mapped to device 1. It says "the LOAD ($FFD5) and SAVE ($FFD8) KERNAL calls are intercepted by the emulator if the device is 1 (which is the default)" but it does not say the OPEN, READ or WRITE functions are intercepted nor the various KERNAL calls (CHKIN CKOUT etc.) I've virtually confirmed this is the case looking at the relevant code here: https://github.com/commanderx16/x16-emulator/blob/4c645a844c911cf3e0629e6eb1aa4d719d758093/main.c#L553

So it seems like there's no way to programatically read the host filesystem? If I want to develop software that reads or writes files I guess I have to create an sd card image and set that up in my build somehow?

Can anyone confirm/deny this or suggest a different approach?
 
Sep 9, 2019
30
9
8
Reading the emulator README.md here: https://github.com/commanderx16/x16-emulator suggests but does not explicitly state that the host filesystem is not really fully mapped to device 1. It says "the LOAD ($FFD5) and SAVE ($FFD8) KERNAL calls are intercepted by the emulator if the device is 1 (which is the default)" but it does not say the OPEN, READ or WRITE functions are intercepted nor the various KERNAL calls (CHKIN CKOUT etc.) I've virtually confirmed this is the case looking at the relevant code here: https://github.com/commanderx16/x16-emulator/blob/4c645a844c911cf3e0629e6eb1aa4d719d758093/main.c#L553

So it seems like there's no way to programatically read the host filesystem? If I want to develop software that reads or writes files I guess I have to create an sd card image and set that up in my build somehow?

Can anyone confirm/deny this or suggest a different approach?
I'm also looking at this too. I think with the SPI / SDcard interface will be the more "correct" approach currently, as the emulator as you highlighted traps those calls to the kernal.
 
  • Like
Reactions: christo
May 22, 2019
486
250
43
You'll probably have to wait on that. The plan is to develop a CBM compatible DOS on the hardware, and I assume the emulator will either trap the other calls or even emulate an SD card at the hardware level (ie: by mounting a VHD on a virtual drive controller.)

I'm also playing around with some program ideas that will need sequential file access, and so I'm also waiting to see what the guys have planned. The work on the ROM is largely being done by one guy (Michael Steil), and Mike has done most of the commits to the emulator as well. So we have one person who's writing the firmware and the emulator that runs it... and who probably has a day job. That's a lot on one person's plate.

On the bright side, now that the emulator is open source, we'll probably see more people adding features. There's no reason you couldn't add traps for OPEN, GET#, INPUT#, PRINT#, and CLOSE. (or the KERNAL functions that these call.)
 
  • Like
Reactions: tomseago

christo

New Member
Sep 16, 2019
13
4
3
OK an update, the emulator already emulates an sd card. So I have created an sd card image, put my files into it and launched the emulator with that image attached (using the command line option -sdcard sd.dmg which appears to succeed. However, the directory listing does not show the files and while the OPEN command succeeds, the INPUT# command fails. This test program is in basic btw.

I'll have to debug this further. If anyone has successfully used I/O operations from either basic or machine code on the emulator, please let me know.
 
  • Like
Reactions: MonstersGoBoom

Wertyloo

Member
Sep 15, 2019
72
7
8
how fast you can load/save from the sd-card? in kbyte/s
where can i find similar datasheets for the x16?
register,speeds,clockcycles,transfer-bit-resolutions,bandwidths,etc. special stats/datas
 
May 22, 2019
486
250
43
Some of those things are in the Programmer's Reference Manual and the VERA manual. Other data doesn't exist yet.

I'll post a sticky with where to find code and documentation, since so many people are asking the same questions.
 
  • Like
Reactions: DennisJ

rje

New Member
Nov 6, 2019
7
0
1
I'm also playing around with some program ideas that will need sequential file access, and so I'm also waiting to see what the guys have planned. The work on the ROM is largely being done by one guy (Michael Steil), and Mike has done most of the commits to the emulator as well. So we have one person who's writing the firmware and the emulator that runs it... and who probably has a day job. That's a lot on one person's plate.

On the bright side, now that the emulator is open source, we'll probably see more people adding features. There's no reason you couldn't add traps for OPEN, GET#, INPUT#, PRINT#, and CLOSE. (or the KERNAL functions that these call.)
I've got a lot of respect for Mr. Steil -- the X16 emulator is excellent.

But I also have vanilla BASIC 2.0 code that has (a lot of) data stored in SEQ files, and I'd like that code to work on the X16.

I might try my hand at traps. I'm a programmer with C experience... but I haven't done this sort of hacking before.
 
May 22, 2019
486
250
43
I was just pondering this earlier today... I am going to take a look at using the new LOAD syntax to read data directly into high memory and work on it from there.

One way is to cheat and use the MONitor to load and save data. Here's an example of how to do that....

Code:
100 POKE $9F61,0
110 FOR I=0 TO 64
120 POKE $A000+I,64+I
130 NEXT
140 PRINT "\X93\X11\X11MON"
150 PRINT "\X11\X11\X11\X11\X1DS ";CHR$(34);"MYDATA";CHR$(34);",01,A000,A100"
160 PRINT " X"
170 PRINT "\X11GOTO 1000"
180 PRINT "\X13";
190 END
\X11 is just the down arrow, and \X93 is the Clear command. So PRINT "\X93\X11\X11" clears the screen and moves the cursor down 2 lines.

So this writes a sequence of commands, placing them in the right place on the screen for a simple press of the RETURN key to act on them.
MON
S "MYDATA",01,A000,A100
X
GOTO 1000

This enters the MONitor, saves the data from $A000 to $A100, then eXits back to BASIC.
Finally, the GOTO 1000 picks up the program after the save (not shown here.)


Just for fun, I wrote a little program to read 64 bytes into high memory, using the machine monitor to do the reads:

Code:
10 POKE $9F61,0
20 PRINT "\X93\X11\X11MON"
30 PRINT "\X11\X11\X11\X11\X1DL ";CHR$(34);"MYDATA";CHR$(34);",01,A000"
40 PRINT " X"
50 PRINT "\X11GOTO 2000"
60 PRINT "\X13";
70 FOR I=623 TO 632:POKE I,13:NEXT:POKE 670,4
80 END
1000 REM CREATE TEST DATA
1010 POKE $9F61,0
1020 FOR I=0 TO 64
1030 POKE $A000+I,64+I
1040 NEXT
1050 PRINT "\X93\X11\X11MON"
1060 PRINT "\X11\X11\X11\X11\X1DS ";CHR$(34);"MYDATA";CHR$(34);",01,A000,A100"
1070 PRINT " X"
1080 PRINT "\X11GOTO 1000"
1090 PRINT "\X13";
1100 FOR I=623 TO 632:POKE I,13:NEXT:POKE 670,4
1110 END
2000 PRINT"\X93";
2010 FOR I=0 TO 64
2020 PRINT PEEK($A000+I),
2030 NEXT

You can create 64 bytes of test data with GOTO 1000
Then you can RUN the program to see it work. Try removing line 70 to see what the load sequence looks like.

What's actually going on in 70 is this: the keyboard buffer is 10 bytes long, and I'm filling all 10 bytes with the return character (13). Location 670 is the number of bytes outstanding in the buffer; I poke 4 in there to tell the computer that there are 4 keypresses waiting.



So I can add more commands later and just change the POKE 670,4 to another number to add up to 10 RETURNs.
 
Last edited:

rje

New Member
Nov 6, 2019
7
0
1
But I also have vanilla BASIC 2.0 code that has (a lot of) data stored in SEQ files, and I'd like that code to work on the X16.

I might try my hand at traps. I'm a programmer with C experience... but I haven't done this sort of hacking before.
So I browsed the code yesterday, and I found where LOAD and SAVE are being intercepted and handled.

I then asked myself in general terms, how would I handle OPEN, INPUT#, PRINT#, GET#, and CLOSE?

My first guess is to intercept those commands in the same manner LOAD and SAVE are. Since OPEN requires a number from 0 to 15, with 15 being the command channel, then I think that means there would be an array of 15 file pointers that the four commands would operate on. Something like:

typedef struct {
FILE* fp;
char name[MAX_FILENAME_LENGTH]; // e.g. 16
char filetype;
char mode;
} LogicalFile;

LogicalFile file[15];


...then these commands work on files.

; Ignore channel, for now.
OPEN 4,1,6, "0:MYFILE,S,R"
; extract name, extension, mode.
; fclose(file[4]->fp);
; file[4]->fp = fopen(file[4]->name, file[4]->mode);

CLOSE 5
; fclose(file[5]->fp);

INPUT#6, MV$
; fscanf(file[6]->fp, "%s", buffer)
; put buffer in MV$

PRINT#7, MV$
; fprintf(file[7]->fp, whatever's in MV$);


...something like that.

With guards, of course, to prevent the program from dying with a stack trace.

The filename, extension, and mode could be extracted from the string using sscanf() perhaps:


char* token;
char filename[16];
char filetype[16]; // but, only the 1st char is significant
char mode[16]; // but, only the 1st char is significant
char device[16]; // again, only the 1st char matters

// "filespec" is the string argument to OPEN, e.g. "0:MYFILE,S,R"
sscanf(filespec, "%[^:]:%[^,],%[^,],%s", device, filename, filetype, mode);



And I'd need guards and error checking to some reasonable extent.

I note that the string command could be extended with additional arguments, if needed.
 
Last edited:
May 22, 2019
486
250
43
Speaking for myself, if you can get the emulator to trap sequential file access, I'd be super excited. I've got a few projects in mind, and they all need access to the file system.
 

rje

New Member
Nov 6, 2019
7
0
1
Speaking for myself, if you can get the emulator to trap sequential file access, I'd be super excited. I've got a few projects in mind, and they all need access to the file system.
Me too, hence my noodling with the code. At this point, though, I don't know *where* these traps would go. I mean, it seems to me that LOAD and SAVE are OS commands, whereas OPEN, CLOSE, INPUT#, PRINT#, GET# are (typically) expected to show up in a BASIC program itself. Therefore, the trap might not be in "the same place".

Maybe. I don't know.

I mean, for instance: LOAD is KERNAL routine $ffd5, and SAVE is $ffd8, right? Well are there KERNAL routines for OPEN, INPUT#, PRINT#, GET#, and CLOSE?

I guess I should go check!!

Ah, here we are. Here's what those BASIC commands map to... in other words, here's what I'd have to catch-n-patch.


These three are used together:
$FFBA SETLFS (set logical file) A = Logical number; X = Device number; Y = Secondary address.
$FFBD SETNM (set logical filename) A = File name length; X/Y = Pointer to file name.
$FFC0 OPEN

This one looks like a general way to close any file by its logical number:
$FFC3 CLOSE file (A = Logical file number)

Here's how you set the mode of a logical file.
$FFC6 CHKIN. Define file as default input. (X = logical file number)
$FFC9 CHKOUT. Define file as default output. (X = logical file number)

And here's how you move data to and from files.
$FFCF CHRIN. Read byte from default input.
$FFD2 CHROUT. Write byte to default output.
$FFE4 GETIN. Read byte from default input.

And here's "default" close routines.
$FFCC CLRCHN. Close default input/output file.
$FFE7 CLALL - close all open files.


Assuming I define global data like this:

typedef struct {
FILE* fp;
char* filename; // or an array?
int write:1;
int device;
int address;
} LogicalFile;

LogicalFile file[15];
int currentFile;
int defaultInput;
int defaultOutput; // not sure if the can be defaults for both in and out at the same time...


Then
SETLFS sets the current file #, and its device number and 2ry address.
SETNM sets the current file's name.
OPEN calls fopen()... it probably opens it as wb+, or if it already exists, as rb+.
CLOSE calls fclose on the indicated logical file.
CHKIN and CHKOUT set defaultInput and defaultOutput, respectively, and sets the file mode. (Can they be different?)
CHRIN and GETIN call fread() and read one byte only. A later enhancement could buffer this.
CHROUT calls fwrite() and writes one byte only.
CLRCHN closes the default file.
CLALL of course closes everything.
 
Last edited:

christo

New Member
Sep 16, 2019
13
4
3
What's actually going on in 70 is this: the keyboard buffer is 10 bytes long, and I'm filling all 10 bytes with the return character (13). Location 670 is the number of bytes outstanding in the buffer; I poke 4 in there to tell the computer that there are 4 keypresses waiting.
Tom this is absolutely adorable. Nice hack.
 
May 22, 2019
486
250
43
Tom this is absolutely adorable. Nice hack.
hehe. Never had my code called "adorable" before. :D

It's only temporary until we get proper file I/O on the emulator, but it's enough for what I want to do. Mostly, I'm testing my tokenizer/editor right now, so I'm not so much programming ON the Commander as I am programming "around" it. But I'm hoping to get my adventure kit going sometime soon.... and my space trader game... and my boot menu... I have lots of things in mind. :)
 

BruceMcF

Active Member
May 19, 2019
139
42
28
Me too, hence my noodling with the code. At this point, though, I don't know *where* these traps would go. I mean, it seems to me that LOAD and SAVE are OS commands, whereas OPEN, CLOSE, INPUT#, PRINT#, GET# are (typically) expected to show up in a BASIC program itself. Therefore, the trap might not be in "the same place".

Maybe. I don't know.
There is no distinction in the Basic Interpreter between using them as OS commands and using them in a program. The distinction is whether the interpreter sees a line number so it knows to parse it and save it as a program line or to parse it and execute it immediately. But in the C64 you can, eg, LOAD a set of machine language routines in a PRG file with a $C000 load address, to call them with SYS calls as part of a program.

So the trap would happen somewhere in the execution of the command. If it is possibly to trap after it has finished parsing it's operands that might simplify things, since then the information will be in whatever place that routine puts it's parameters.
 
Last edited:

rje

New Member
Nov 6, 2019
7
0
1
There is no distinction in the Basic Interpreter between using them as OS commands and using them in a program. The distinction is whether the interpreter sees a line number so it knows to parse it and save it as a program line or to parse it and execute it immediately. But in the C64 you can, eg, LOAD a set of machine language routines in a PRG file with a $C000 load address, to call them with SYS calls as part of a program.

So the trap would happen somewhere in the execution of the command. If it is possibly to trap after it has finished parsing it's operands that might simplify things, since then the information will be in whatever place that routine puts it's parameters.
I am probably misunderstanding you, but I think we need to fiddle with the C code that runs the ROM, not the BASIC ROM itself. In other words, intercept KERNEL calls. (I could be wrong).

Otherwise, I think this helps me a bit. Your mention of "trap after it's finished" with setup, makes me think maybe patches are needed only for the KERNEL calls which directly touch files.

In other words, patches probably aren't needed for SETLFS or SETNM, or CHKIN and CHKOUT.

But they probably are for OPEN, CLOSE, CHRIN, GETIN, CHROUT, CLRCHN, and CLALL.
 
Last edited:

BruceMcF

Active Member
May 19, 2019
139
42
28
I am probably misunderstanding you, but I think we need to fiddle with the C code that runs the ROM, not the BASIC ROM itself. In other words, intercept KERNEL calls. (I could be wrong).
I wasn't talking about changing the BASIC ROM, I was talking about trapping the emulator execution of the 65c02 opcode at that BASIC ROM location.

Otherwise, I think this helps me a bit. Your mention of "trap after it's finished" with setup, makes me think maybe patches are needed only for the KERNEL calls which directly touch files.

In other words, patches probably aren't needed for SETLFS or SETNM, or CHKIN and CHKOUT.

But they probably are for OPEN, CLOSE, CHRIN, GETIN, CHROUT, CLRCHN, and CLALL.
Yes, that sounds like it's both cleaner and easier than what I was referring to. In particular, if you need to look at the result of the SETLF and/or SETNM to determine whether to allow execution of the Kernel routine to continue or to run the replacement operation, just go look at how they have been set up in the emulated RAM space.
 
  • Like
Reactions: rje

rje

New Member
Nov 6, 2019
7
0
1
I wasn't talking about changing the BASIC ROM, I was talking about trapping the emulator execution of the 65c02 opcode at that BASIC ROM location.
...
Yes, that sounds like it's both cleaner and easier than what I was referring to. In particular, if you need to look at the result of the SETLF and/or SETNM to determine whether to allow execution of the Kernel routine to continue or to run the replacement operation, just go look at how they have been set up in the emulated RAM space.
Good, that's what I hoped you meant (and upon reflection what else could it have been?).

I just had a thought -- a worthy goal would be to port PetDraw to the Commander X16 emulator. I know it has Load and Save functions, and I bet they use those KERNAL routines.
 

christo

New Member
Sep 16, 2019
13
4
3
Not sure traps etc. are necessary, the production ROM already needs to read SDs and there is an SD card implementation in the emulator which enables an sd card filesystem image to be attached, thereby mimicking a physical sd in the real machine. Firstly, the implementation is incomplete or buggy and secondly, it's convenient to pass a local filesystem mountpoint to the emulator for it to act as if it is an sd card in the emulator. It seems clear to me that this is where the fix belongs (ROM traps seem kludgy in comparison) though I am not quite at the point of committing to do it.