Jump to content
  • 0

Does Commander X16 emulator support binary byte-oriented file I/O?


CX16UserSteveC
 Share

Question

Has anyone been able to get binary byte-oriented file I/O working in cc65 C applications? If so, could you please point me to an example of some working code?

I've only been able to get the cbm_load() and cbm_save() functions working which loads or saves an entire file.

I noticed C3c posted assembly source code below which loads an entire file into memory. As I said above I've been able to accomplish this with the cbm_load function defined in the cbm.h file in the cc65 include directory.

To clarify I'd like to load and save a file a byte at a time which is what I meant by byte-oriented file I/O. This provides the ability to save and load files where different portions go to or come from different portions of memory. This also provides the ability to extract the relevant portion of very large files while discarding the remainder. My primary interest is a working example of cc65 C source code, but a working example of cc65 assembly source code might provide some insight.

I've tried various level of APIs including the following.

  • c - fopen() seems to work but fgetc() seems to hang
  • cbm - cbm_load() and cbm_save() seem to work as mentioned above, but cbm_open() returns error code 6.
  • cbm_k: cbm_k_open() returns error code 6 after first executing cbm_k_setnam() and cbm_k_setlfs()

I believe the cbm_k level API above is the direct interface to the kernel API which is claimed to work by desertfish  below, but perhaps there's an error in cc65 (or my code).

I've also implemented my own C interface to the kernel API which works for everything I've tested except file I/O.

Assuming the following definitions

#define INFILE_STR "celestemap.dat"
#define lfn            0
#define device        8
#define sec_addr    0

cbm_load(INFILE_STR, device, inputBuffer) works but cbm_open(lfn, device, sec_addr, INFILE_STR) returns error code 6 and

cbm_k_open() also returns error code 6 after first executing cbm_k_setnam(INFILE_STR) and cbm_k_setlfs(lfn, device, sec_addr).

I also tried using

#define lfn            1

which just seems to lead to error code 5 instead of 6 returned by cbm_open() and cbm_k_open().

I also tried various BASIC OPEN statements and they all returned a DEVICE NOT PRESENT error.

I noticed the Commander X16 Emulator User Guide indicates the following: "Note that this feature is very limited! Manually reading and writing files (e.g. OPEN in BASIC) is not supported by the host filesystem interface. Use SD card images for this."

So perhaps I need to create an SD card image for my data file instead of using the host computer's local filesystem.

Edited by CX16UserSteveC
Clarified may need to use SD card image instead of using host computer's local filesystem.
Link to comment
Share on other sites

4 answers to this question

Recommended Posts

  • 0

Below is a worked example (for the CA65 assembler) on how to load a file called "MAPS.BIN" into a memory location (map_location) in assembly. As you can see, you first have to call the SETLFS and SETNAM functions, after which you can call the LOAD function. See this page for some documentation on these Commodore 64 API calls. 

IMPORTANT: if you are using the emulator, the file name on your file system should be in UPPER CASE. The file name in the code should be in lower case.

B.t.w. Can anybody tell me how to properly format code on this forum?

Edit: sorry, didn't read your post properly,  I assumed you were talking about using assembly, not C.

 

.org $080D
.segment "STARTUP"
.segment "INIT"
.segment "ONCE"
.segment "CODE"

jmp start

.macro ram_load filename_start, filename_end, ram_address ; overwrites A, X, Y
.scope
SETLFS = $FFBA
SETNAM = $FFBD
LOAD   = $FFD5
filename_length = filename_end - filename_start
   lda #01 
   ldx #08
   ldy #0
   jsr SETLFS
   lda #filename_length
   ldx #<filename_start
   ldy #>filename_start
   jsr SETNAM
   lda #0
   ldx #<ram_address
   ldy #>ram_address
   jsr LOAD
.endscope
.endmacro

start:
   ram_load map_filename, map_filename_end, map_location
   rts

map_filename:
   .byte "maps.bin"
map_filename_end:
map_location:
 

Edited by C3c
Link to comment
Share on other sites

  • 0

In my experience, byte-wise I/O currently only works properly with files on an SD card image.

If you look into the include/cbm.h file in CC65, you will find the CBM Kernal functions as C functions, which (I think) you should be able to call from C. As @desertfish mentioned above, you would need SETNAM, SETLFS, OPEN, CHKIN, CHRIN, READST, CLRCHN and CLOSE. These are all available (CLRCHN being called CLRCH, and CHRIN might be replaced by GETIN), except for CHROUT, which writes one byte at a time. So I would guess (without having tried) that you can read byte-wise with GETIN, but cannot write byte-wise, unless you somehow get a C version of the CHROUT Kernal call.

Doing assembly file I/O can be tricky, at least for a Commodore noob like me.

For working assembly code that does byte-wise read/write, there are several examples. One I would recommend looking at is the one from @Stefan 's X16edit text editor. I have the impression that whenever possible, Stefan tries to do things the "proper" way, i.e. try to catch all possible errors along the way. His code can be found here.

I myself have also done byte-wise file I/O. I am using a more simplistic approach and I messed around until I got it somehow working, without knowing whether that's the "proper" way of doing things. Look here. (Oh, the source comment saying it's opening "PRESET.COP" is outdated, I extended the routine to be able to open a file with varying file name).

There is also a discussion about best practices regarding file I/O in this thread.

Edited by kliepatsch
Inserted link to CC65 source
  • Thanks 1
Link to comment
Share on other sites

  • 0
On 11/8/2021 at 7:35 AM, kliepatsch said:

In my experience, byte-wise I/O currently only works properly with files on an SD card image.

@CX16UserSteveC: What kliepatsch says above is definitely the issue.

Whenever you're using the "host FS", in the emulation, this is equivalent to having no media inserted into the SD slot.

When using host FS, the emulator intercepts calls to LOAD and SAVE and "hyperloads" the data to/from memory. The Kernal is skipped entirely, and the program counter is adjusted to point at whatever address it would after Kernal finishes the LOAD or SAVE routine. So to your program, it's LIKE the Kernal ran, but it actually did not.

However:

The emulator ONLY hooks load and save. It does NOT hook all other calls that read/write logical channels. Any such calls still execute in the Kernal. Thus, when using the host FS, the Kernal will see device 8 as having no media inserted, and throw an error. That's why your attempts all fail. Because there's no disk in the drive, so to speak. LOAD and SAVE work even when there's no disk in the system because the emulator "miracles" the data directly into (V)RAM when it sees a call to LOAD or SAVE about to happen. There's still no disk. The Kernal just gets skipped so that no error happens.

When using SD image:

When you DO mount an SD image, the emulator does not perform the hyperload behavior. In this case, calls to LOAD and SAVE are processed by the Kernal as normal along with the other calls for individual byte I/O. Now that there is media inserted into device 8, everything should work properly, including fopen() type actions. You'll find that LOAD and SAVE operations are much slower when using SD images. Hyperloads take "zero" CPU time and the data just appears in memory. Regular loads have to actually retrieve the data by bit banging the SPI connection between VERA and the SD reader. I believe the emulator's transfer speeds to/from SD are an accurate reflection of the real system's loading and saving performance - or at least that's what's been said here in other threads.

Further complicating matters:

I think there are bugs in the LOAD/SAVE routines in the r38 Kernal as well if the memory address is HIRAM. I think it fails to properly switch banks when loading into HIRAM. I could be wrong in remembering this, as it's been 6 months since I ran afoul of this "SD vs HostFS" quirk. Assuming I'm remembering correctly, it means that if you DO use an SD image, LOAD and SAVE via the Kernal to/from Banked RAM will fail, but bytewise IO should be fine.

This whole situation had me really frustrated because I was still getting my sea legs in C and file IO. I thought I was doing something wrong, and it turned out that nope - my program was fine - it was just a nuance of the emulator's behavior.

Personally, I think the emulator's behavior should be updated to only do these hyperloads if LOAD and SAVE are being called from interactive-mode BASIC, and that host FS should be treated exactly the same as an SD image. The hyperload feature was obviously intended to be a convenience so you don't have to wait for your program to load.

  • Like 1
  • Thanks 1
Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Answer this question...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

 Share

×
×
  • Create New...

Important Information

Please review our Terms of Use