Jump to content
  • 0

Reading byte by byte in cc65


ZeroByte
 Share

Question

I think I'm trying to learn on too many fronts at once here, and I'm starting to go blind on the information I'm trying to absorb - lol....

So reading a file into RAM,VRAM,HIRAM is relatively straightforward using kernal calls. However, I'm a stubborn guy and I don't like the idea of having to pad my files with 2 dummy bytes because of the assumed PRG header, especially when the Kernal easily ignores these and loads the file wherever you tell it to. So I'm trying to write my own loadbin() function that will actually read the first two bytes of the file by using OPEN, CHKIN, and CHRIN.

My code is as follows:

Quote

    cbm_k_setnam(filename);
    cbm_k_setlfs(0,8,0);
    cprintf("OPEN returned  (%d)\n\r",(unsigned char)cbm_k_open);
    cprintf("CHKIN returned (%d)\n\r",(unsigned char)cbm_k_chkin(0));
    cprintf("%02x%02x\n\r",cbm_k_getin,cbm_k_getin);
    cbm_k_close(0);

However, the data I get back seems like some random byte repeated however many times I do cbm_k_getin();

The values returned by cbm_k_open() and chkin(0) are 172, and 3. I'm trying to track down what those return values mean, but just in case I'm doing something dumb and one of the wizards here can easily point out my folly, I thought I'd post here while I keep digging on my own...

My understanding is that the first zero in setlfs is just the "file handle ID" - I could make it be 7 or 3 or whatever, so long as it isn't already in use elsewhere, and I'm consistent in chkin() and close().

I also think the second zero in setlfs() "extra address" that gets all the nuance of what the kernal will do.... and this is still kind of opaque to me -

... so, is this a fool's errand? Will CHRIN also skip the first two bytes of a PRG file, or does this seem like the way to achieve my goal?

(edited to add: I'm using the host FS and not SDcard images)

Edited by ZeroByte
Link to comment
Share on other sites

21 answers to this question

Recommended Posts

  • 0

Yep.  LOAD and SAVE are the only things that you can do on the host's file-system.  If you really want to avoid those extra bytes, then you must work with an SD card image.  You had the right idea with cbm_read().  Here's some example code:

#include <stdint.h>
#include <stdio.h>
#include <peekpoke.h>
#include <cbm.h>
#include <errno.h>

uint8_t readbin2bank(char name[], uint8_t bank)
{
    uint8_t err;
    int len;

    POKE(REG_RAMBANK, bank);

    err = cbm_open(2, 8, 2, name);
    if (err != 0)
    {
        return err;
    }

    len = cbm_read(2, 0xA000, 0x2000);  // Read as much as allowed into current RAM bank.
    if (len < 0)
    {
        cbm_close(2);
        return _oserror;
    }

    printf("Read %d bytes into RAM bank %u.\n", len, bank);
    cbm_close(2);
    return 0;
}
Edited by Greg King
Changed the loadbin2bank function name into readbin2bank (it doesn't "load").
  • Thanks 1
Link to comment
Share on other sites

  • 0

So... after some digging through the cc65 libraries, I found cbm_open() and cbm_read() which act kind of like setlfs and get#....

Quote

    unsigned char f[16];
    uint8_t i,err;
    POKE(REG_RAMBANK,bank);

    err = cbm_open(1,8,0,"rom.bin");
    if (err == 0)
    {
        cbm_read(1,f,16);
        for (i = 0 ; i < 16 ; i++ )
        {
            cprintf("%02x ",f);
        };
        cprintf("\n\r");
    };   
   

This works - but only if you use the sdcard image. If using the host FS, then I get an error code (either 5 or 6 depending on what file channel # I use).

I figured this out when I realized that I could just try to OPEN the file in BASIC until I got it to work - not having to keep trying different numbers in C and recompiling for every guess.

This whole split reality where the EMU does some things right instead of the Kernal which does some things wrong, but only if you're using an SD card or not.... it makes it tough for me when I'm not experienced at fileIO in the first place....

If anyone has any suggestions about how to do this without resorting to sdcard images, I'm all ears, because I think in SD card land, the LOAD kernal API doesn't properly load a file to banked RAM ... which is my actual goal. I may just end up making my own loop out of this 'hello file world' loader and not worry about calling the kernal's LOAD routine at all.

Edited by ZeroByte
got rid of annoying italics
Link to comment
Share on other sites

  • 0
9 hours ago, Greg King said:

You had the right idea with cbm_read().  Here's some example code:

Since I cracked the code (so to speak) late last night, that was where I stopped, but proceding, I will definitely be doing pretty much this, except that my program will be dealing with files larger than a single bank, so I'm going to make a loop that fills banks until the file is completely read. Of course, my next challenge after that is going to be porting some functionality from zlib because the files I want to deal with are typically gzip encoded. Sure, the user can pre-decompress the files but I want my program to be easy to use and not put such requirements on the user. I've done some scouting in that library, and it looks like I'm going to have my work cut out for me. What sux is that all this file I/O stuff is dreadfully boring to me. It stands between me and what I really want to do, which is PCM sample mixing, but before I can start coding that, I have to have the data in RAM in a meaningful arrangement. I've bootstrapped some stuff into ram with bin2c.py but that was just for my "hello world" of PCM playback.

Link to comment
Share on other sites

  • 0
17 hours ago, ZeroByte said:

except that my program will be dealing with files larger than a single bank, so I'm going to make a loop that fills banks until the file is completely read.

That's actually not necessary. The Kernal does this for you, I've used this in my Brixx and Invaderz games. The Kernal is intelligent enough to switch banks when you load into 0xA000.

Link to comment
Share on other sites

  • 0
4 hours ago, AndyMt said:

That's actually not necessary. The Kernal does this for you, I've used this in my Brixx and Invaderz games. The Kernal is intelligent enough to switch banks when you load into 0xA000.

But it skips the 2-byte header, and I want to read files that aren't specifically prepped for the X16 (.VGZ files, to be precise) and their first two bytes is a GZIP signature, which is definitely required reading for the algorithm. I _was_ going to just read the first two bytes and let the kernal do the rest, but I figured, since I already have the file open, why not go ahead and write a quick loop myself. 😉

It turns out that they already have zlib functionality built in to cc65, which is really cool - and I built the sample program for CX16 and it works. Unfortunately though, it doesn't know how to handle banked memory and can only handle files that decompress completely into low RAM, which VGM files containing PCM/ADPCM data certainly will not.

So right now, I'm going to have to modify the stock inflatemem.s routine to work in banked RAM instead. Being the cc65 neophyte that I am, I'm having trouble just figuring out how to tell it to link in my version instead of the stock one from cx16.lib - at least it's a good learning exercise even though it slows down my progress to a snail's pace.

Edited by ZeroByte
Link to comment
Share on other sites

  • 0
31 minutes ago, ZeroByte said:

But it skips the 2-byte header, and I want to read files that aren't specifically prepped for the X16 (.VGZ files, to be precise)

Oh sorry, I missed that point. And I'd be interested in loading VGZ, too. Right now I'm loading uncompressed VGM files for music.

Link to comment
Share on other sites

  • 0
16 minutes ago, AndyMt said:

And I'd be interested in loading VGZ, too.

I'll share my code if I get it working. It might actually make a nice submission to the cc65 project itself, if zlib supported hi-ram for the platform. It's not going to be a trivial task for me, though - but I'm going to get something working, by Jove! 🙂

Edited by ZeroByte
  • Like 1
  • Thanks 1
Link to comment
Share on other sites

  • 0
7 minutes ago, AndyMt said:

Do you already have a VGM parser then?

I've written two from scratch myself already - one was for Kevin to test the YM2151 @ 8MHz on the prototype1 board, and the other was written in C on Raspberry Pi because I want to drive a real YM from the RPI myself.

Stew's approach is similar to what my first approach was - pre-process the VGM with an external script that boils it down to just the YM messages and pause values. Frank Buss's player #include's the entire VGM file as a byte array, and then parses it properly. That's how my RPi code works, too.

My goal is to add support for the PCM audio, which means I have to implement a transcoder ADPCM -> PCM, and a mixer which can up/downsample the PCM to a unified PCM stream to the VERA. I've been looking at how the libvgm reference player does its thing, and unfortunately it is able to take advantage of the fact that modern computers have "infinite" resources relative to the task at hand, and flippantly allocate RAM in blocks of several megabytes where I'm going to have to do some pre-parsing and only load PCM data that really does get used, and index it relative to banked memory vs what "real" players do malloc(8MB) and paste the data blocks directly into that alloc'd memory, however sparse it may be. I may even need to pre-transcode the ADPCM if the 65c02 isn't fast enough to do it on the fly. For a generalized playback module useful in games, etc, I'd 100% opt for pre-decompressing the ADPCM but for a standalone player, I can be a little more lax in my CPU performance requirements.

When done, I'll be able to have those nice little audio waveform scopes on the screen so you can see the drums and cymbals playing back visually. 😉

 

  • Like 1
Link to comment
Share on other sites

  • 0
8 hours ago, AndyMt said:

That's actually not necessary. The Kernal does this for you, I've used this in my Brixx and Invaderz games. The Kernal is intelligent enough to switch banks when you load into 0xA000.

I never could make that work.  The Kernal always switched to bank 00 and I couldn't get it to load and save directly to/from banked RAM.  I had to do a workaround where I copied $7000-8FFF to bank FF and then do the load/save on $7000-8FFF, copying back and forth to banks as needed. and then finally copying bank FF back down to $7000-8FFF.  Are you telling me I missed something obvious and didn't have to do all that nonsense?  Fill me in please.

Link to comment
Share on other sites

  • 0
20 minutes ago, Ed Minchau said:

Are you telling me I missed something obvious and didn't have to do all that nonsense?  Fill me in please.

I can give you a code snipped written in CC65:

uint32_t loadMusic(charfileNameuint8_t deviceuint8_t bank)
{
    uint16_t addr = 0xa000;
    uint32_t totalRead = 0;
 
    uint8_t lfn = 3;
    setHighBank(bank);
 
    // Use cc65 kernal wrappers directly
    cbm_k_setlfs(lfn,device,0);
    cbm_k_setnam(fileName);    
    totalRead = cbm_k_load(0,addr) - addr;
 
    if (!totalRead)
    {
        ScreenRestore();
 
        printf("BRIXX: Error loading music for [%s]"fileName);
        exit(0);
    }
 
    return totalRead;
}

The music files I load in Invaderz or Brixx are around 40 to 80 kBytes. And are loaded totally fine, starting with the active high-bank, wrapping into the following banks seamlessly.

Link to comment
Share on other sites

  • 0
15 minutes ago, AndyMt said:

I can give you a code snipped written in CC65:

uint32_t loadMusic(charfileNameuint8_t deviceuint8_t bank)
{
    uint16_t addr = 0xa000;
    uint32_t totalRead = 0;
 
    uint8_t lfn = 3;
    setHighBank(bank);
 
    // Use cc65 kernal wrappers directly
    cbm_k_setlfs(lfn,device,0);
    cbm_k_setnam(fileName);    
    totalRead = cbm_k_load(0,addr) - addr;
 
    if (!totalRead)
    {
        ScreenRestore();
 
        printf("BRIXX: Error loading music for [%s]"fileName);
        exit(0);
    }
 
    return totalRead;
}

The music files I load in Invaderz or Brixx are around 40 to 80 kBytes. And are loaded totally fine, starting with the active high-bank, wrapping into the following banks seamlessly.

I was using a logical file number of 1 for SLFS.  So 3 sends it to banked RAM?  That would have made saving files in my assembly language editor much easier. I guess I can go patch up that spot of wall where I was banging my head now.

Link to comment
Share on other sites

  • 0

The LFN makes no difference. You just need to set the current RAM bank and do a LOAD to the banked RAM window ($A000-$BFFF). It will automatically keep loading across banks if necessary.

Just be aware that this is currently broken in the emulator for SD card images. It does work for the host file system.

Edited by SlithyMatt
Link to comment
Share on other sites

  • 0

I'm actually not sure, I chose 3 

5 minutes ago, Ed Minchau said:

I was using a logical file number of 1 for SLFS.  So 3 sends it to banked RAM? 

As Matt already wrote: the LFN doesn't matter. The load address does. But "save" is not possible with the host file system and with the SD card neither does in the emulator.

Oh - and the files you load need to have the 2 byte header with the target address in, or just 0x0000.

Link to comment
Share on other sites

  • 0
6 minutes ago, AndyMt said:

I'm actually not sure, I chose 3 

As Matt already wrote: the LFN doesn't matter. The load address does. But "save" is not possible with the host file system and with the SD card neither does in the emulator.

Oh - and the files you load need to have the 2 byte header with the target address in, or just 0x0000.

Ah ok, it was the save that was giving me the most problems anyhow, and the kludge I came up with worked.  And that was before I started exploring VERA, which I now realize I can use as a virtual disk. 

Link to comment
Share on other sites

  • 0

All this "Host FS works in the emu for LOAD/SAVE but not anything else / The SD card doesn't use the emu, but the Kernal works, but it doesn't auto-inc the banks /" madness is making me crazy. lol. Since I came to the table not having any experience with this facet of 'cbm' development, it's made learning into an extra-painful ordeal because whenever something doesn't work, I first assume that it's me. For the 25% of the time I'm wrong, it makes for a lot of reading and so forth.

Has anyone made a matrix of what works / is broken  in the various combinations of SD / Kernal / Host FS / BASIC / cc65 / etc. where file I/O is concerned? Would such a table be worthwhile?

Link to comment
Share on other sites

  • 0
5 minutes ago, ZeroByte said:

All this "Host FS works in the emu for LOAD/SAVE but not anything else / The SD card doesn't use the emu, but the Kernal works, but it doesn't auto-inc the banks /" madness is making me crazy. lol. Since I came to the table not having any experience with this facet of 'cbm' development, it's made learning into an extra-painful ordeal because whenever something doesn't work, I first assume that it's me. For the 25% of the time I'm wrong, it makes for a lot of reading and so forth.

Has anyone made a matrix of what works / is broken  in the various combinations of SD / Kernal / Host FS / BASIC / cc65 / etc. where file I/O is concerned? Would such a table be worthwhile?

Using kernal/no kernal is the same as using "host fs" vs SD card image.  All that means is, when you run the emulator without the "-sdcard" argument, and you call LOAD in your program, the emulator detects the call to load, halts execution of the kernal, does the LOAD code itself, pops the return address off the stack, and resumes execution where it was called from. Thus, it's not using the kernal to do the loading.  However, if you're using an SD card image as your filesystem (like it would be in the actual hardware), the kernal actually is handling all loading and saving, there's no interception from the emulator.  Unfortunately, there's a couple bugs in the kernal implementation. Namely, you can't specify the load address using X/Y, and the banks won't increment automatically if you're loading into high RAM.  There's no bugs with the "host fs"/non-SD card code (except saving across multiple banks apparently).

Link to comment
Share on other sites

  • 0
18 hours ago, Ender said:

There's no bugs with the "host fs"/non-SD card code (except saving across multiple banks apparently).

So I think I just had an "a-ha" moment. The reason that BASIC OPEN / <stdlib.h> fopen() / <cbm.h> cbm_open() / etc all fail when not using an SD image is that it is like running your program with no SD card inserted into the computer. Of course those operations would fail on real HW if no card were inserted. LOAD would fail too, but the emulator intercepts the call and does what LOAD would do, but using the host FS. So it "appears" to work but in reality, it's jujst the emulator being nice and teleporting the data into RAM as if the Kernal had done it. Furthermore, the Kernal LOAD and SAVE have some bugs regarding banking which get exposed when the EMU doesn't get itself involved.

It all makes sense now.

I guess because until now, I pretty much only interacted with files using load/save operations, I had the illusion that the emulator treats the host FS as if it were an inserted SD card if you haven't specified one.

Link to comment
Share on other sites

  • 0
On 3/16/2021 at 11:28 AM, ZeroByte said:

I think I'm trying to learn on too many fronts at once here, and I'm starting to go blind on the information I'm trying to absorb - lol....

So reading a file into RAM,VRAM,HIRAM is relatively straightforward using kernal calls. However, I'm a stubborn guy and I don't like the idea of having to pad my files with 2 dummy bytes because of the assumed PRG header, especially when the Kernal easily ignores these and loads the file wherever you tell it to. ...

Another approach is to put something there that ensures that if the file is ever accidentally LOAD"*",8,1 nothing gets trashed ... $C000 might be good, as loading to ROM seems like it would not affect anything in the real hardware ... and have a utility that changes, eg, MYDATA.VGM to MYDATA.VGM.PRG and prepends the $00 and $C0.

Of course, the CX16 Kernel is still a work in progress, so there is going to be temporary scaffolding and unfinished rooms lying around for a little while yet. I postponed doing anything with sequential text or BLOCK files in my xForth until after those things have settled down a bit.

Link to comment
Share on other sites

  • 0
3 minutes ago, BruceMcF said:

Of course, the CX16 Kernel is still a work in progress, so there is going to be temporary scaffolding and unfinished rooms lying around for a little while

This is my thinking as well. I don't want the user to have to do anything more than load an SD card with their favorite VGM files, pop them in the X16 and start them playing.

I'm on a side quest at the moment trying to see what the YM sounds like if you just dump the most basic of OPL commands into it - over on the Raycasting (Wolf3d port, let's not kid ourselves) demo project.

  • Like 2
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