Changing the NMI code without messing with the library in CC65?

Are you new to 6502, NES, or even programming in general? Post any of your questions here. Remember - the only dumb question is the question that remains unasked.

Moderator: Moderators

Post Reply
gaiablade
Posts: 2
Joined: Wed Feb 01, 2023 10:24 pm

Changing the NMI code without messing with the library in CC65?

Post by gaiablade »

Hello, newb here!

I recently learned how to write a small program and display graphics in assembly using CA65. Now I want to try doing the same in C using CC65 along with the default "nes.lib".

Looking at the source code for the library, the NMI handler looks like this:

Code: Select all

nmi:    pha
        tya
        pha
        txa
        pha

        lda     #1
        sta     VBLANK_FLAG ($70)

        inc     tickcount
        bne     @s
        inc     tickcount+1

@s:     jsr     ppubuf_flush

        ; Reset the video counter.
        lda     #$20
        sta     PPU_VRAM_ADDR2
        lda     #$00
        sta     PPU_VRAM_ADDR2

        ; Reset scrolling.
        sta     PPU_VRAM_ADDR1
        sta     PPU_VRAM_ADDR1

        pla
        tax
        pla
        tay
        pla
The provided NMI handler doesn't copy any OAM data to the PPU. I would like it to include some additional code like this:

Code: Select all

	lda #$00
	sta $2003
	lda #$02
	sta $4014
Is there anyway for me to modify the behavior of the NMI handler in a separate C or assembly file or would I need to just change the library itself?
User avatar
rainwarrior
Posts: 8732
Joined: Sun Jan 22, 2012 12:03 pm
Location: Canada
Contact:

Re: Changing the NMI code without messing with the library in CC65?

Post by rainwarrior »

You can use ar65 to delete the module containing nmi from your library, and then substitute your own.

Alternatively you might be able to place your own nmi module earlier in the link command (cl65) than the library, and it might prevent the one from the library from getting linked (a library only adds modules which contain an external reference that has not yet found a link). Since the nmi routine is in crt0.s with other stuff, though, it might be a little trickier to pull that off.

Instead of doing either of those things, my preference is to create an alternative library that contains only the things I need for C, and allows me to provide all the "NES" stuff myself. My Boing Ball Demo project is one example of this, though it's not presented as a tutorial.
gaiablade
Posts: 2
Joined: Wed Feb 01, 2023 10:24 pm

Re: Changing the NMI code without messing with the library in CC65?

Post by gaiablade »

rainwarrior wrote: Wed Feb 01, 2023 11:10 pm You can use ar65 to delete the module containing nmi from your library, and then substitute your own.

Alternatively you might be able to place your own nmi module earlier in the link command (cl65) than the library, and it might prevent the one from the library from getting linked (a library only adds modules which contain an external reference that has not yet found a link). Since the nmi routine is in crt0.s with other stuff, though, it might be a little trickier to pull that off.

Instead of doing either of those things, my preference is to create an alternative library that contains only the things I need for C, and allows me to provide all the "NES" stuff myself. My Boing Ball Demo project is one example of this, though it's not presented as a tutorial.
Just tested this and it works! Got a sprite drawn to the screen! Thank you so much, I would've never thought of this :D
Your preference is definitely the better method and I'll consider doing that long term!
User avatar
DRW
Posts: 2225
Joined: Sat Sep 07, 2013 2:59 pm

Re: Changing the NMI code without messing with the library in CC65?

Post by DRW »

Am I missing something or does what rainwarrior says sound a bit too complicated?
Deleting a module from a library, consciously placing one custom module before an existing module in the link command in a tricky way, creating an alternate minimalistic library?

Why is all of this needed?

I include "nes.lib" as always and simply never reference any header files from the compiler suite into my project. With this, isn't the following criteria already met?
rainwarrior wrote: Wed Feb 01, 2023 11:10 pm only the things I need for C, and allows me to provide all the "NES" stuff myself.

I have one Assembly file that I call "Main.s" which has my self-written Reset interrupt, my NMI interrupt and some other basic stuff (so you could say that is this pretty much my version of crt0).
And that's it. From within the Reset interrupt, I can call any of my functions, whether they're written in Assembly or in C.

Command line call:

Code: Select all

cl65 -o Game.nes --standard c89 -O -t nes -C NES.cfg Main.s Common.s MainLoop.c OtherStuff.c OtherAsmStuff.s
Why would you ever fiddle with the existing "nes.lib" file or make your own one? Doesn't the compiler omit everything from that file that isn't used anyway?

So, I would say: Just include "nes.lib" with the -t nes command, so that the compiler doesn't give you a bazillion errors for common C-related stuff (parameter passing, multiplication etc.). But just don't call any of the functions from the compiler's header files in your code. (Or call only standard C functions like strcpy or rand, but not stuff like WaitForVBlank or whatever such a function would be called in cc65.)

Now you can write your game in C without the compiler doing any NES-related things by itself.

By the way, I need to point out that I don't have any kind of int main() function this way. The Reset interrupt is pretty much my main function. Or maybe the last part of it where it goes into an infinite loop whenever it's waiting for NMI and where it calls ProcessNextFrame whenever NMI is finished.
My game "City Trouble":
Gameplay video: https://youtu.be/Eee0yurkIW4
Download (ROM, manual, artworks): http://www.denny-r-walter.de/city.html
User avatar
rainwarrior
Posts: 8732
Joined: Sun Jan 22, 2012 12:03 pm
Location: Canada
Contact:

Re: Changing the NMI code without messing with the library in CC65?

Post by rainwarrior »

DRW wrote: Sun Feb 12, 2023 8:11 amconsciously placing one custom module before an existing module in the link command in a tricky way
This appears to be exactly what you're doing?

NES.lib contains a module with reset vector. You're pre-empting it with your own Main.s.

I assume your "NES.cfg" eliminates the STARTUP segment which will normally force inclusion of the crt0 module.


There are many ways you can block its inclusion. Of the 3 alternatives mentioned I prefer this the least. There's nothing in the "NES" part of nes.lib that I ever want included in my program, and I consider it a liability to have all of its stuff occupying namespace in my link, ready to be included by accident.

All I want is the runtime parts of the library needed for C support, but cc65 does not package those by themselves, so, I just do it myself.


Building that alternative library isn't very complicated. Just assemble the .s files in the runtime folders, and add the resulting .o files to a library. That's how all the libraries are built in the first place, but they just each include a third folder with the platform-specific components.

My script to do this has a small additional step that removes a few additional files which avoid cc65's built in "constructor/destructor" feature, which prevents me from ever accidentally including anything from the library that needs it. If you include a module that needs CONDES without actually supporting it with crt0.s it will break that module without any compile-time knowledge. By deleting it I know it won't ever accidentally be a problem.

It also includes "copydata.s" from the common folder, which is just a convenient routine to set up the C DATA segment, because I'd rather just use the official one than write my own replacement.
User avatar
DRW
Posts: 2225
Joined: Sat Sep 07, 2013 2:59 pm

Re: Changing the NMI code without messing with the library in CC65?

Post by DRW »

rainwarrior wrote: Sun Feb 12, 2023 10:39 am
DRW wrote: Sun Feb 12, 2023 8:11 amconsciously placing one custom module before an existing module in the link command in a tricky way
This appears to be exactly what you're doing?
Well, the way you phrased it (place your own nmi module earlier in the link command, a little trickier to pull that off) made it sound more complicated.
"If you omit the STARTUP segment, the compiler won't include crt0 and you can program your own Reset vector and NMI": I would have understood this better.

rainwarrior wrote: Sun Feb 12, 2023 10:39 am There's nothing in the "NES" part of nes.lib that I ever want included in my program, and I consider it a liability to have all of its stuff occupying namespace in my link, ready to be included by accident.
I don't want the NES-specific stuff either. But is there actually a way to accidentally include the stuff? How would this ever happen?

rainwarrior wrote: Sun Feb 12, 2023 10:39 am My script to do this has a small additional step that removes a few additional files which avoid cc65's built in "constructor/destructor" feature, which prevents me from ever accidentally including anything from the library that needs it.
Isn't the constructor and destructor stuff only for variables on the heap? So, this only gets used if you use malloc and free, right?

rainwarrior wrote: Sun Feb 12, 2023 10:39 am It also includes "copydata.s" from the common folder, which is just a convenient routine to set up the C DATA segment, because I'd rather just use the official one than write my own replacement.
I myself never really found a use for the DATA segment. Variables that have a definite startup value and that are not reset during a reset, but that are variables and not constants: What is this ever needed for? The only thing I could ever imagine is maybe a highscore list that gets set with pre-defined values.
My game "City Trouble":
Gameplay video: https://youtu.be/Eee0yurkIW4
Download (ROM, manual, artworks): http://www.denny-r-walter.de/city.html
User avatar
rainwarrior
Posts: 8732
Joined: Sun Jan 22, 2012 12:03 pm
Location: Canada
Contact:

Re: Changing the NMI code without messing with the library in CC65?

Post by rainwarrior »

DRW wrote: Sun Feb 12, 2023 1:52 pmI don't want the NES-specific stuff either. But is there actually a way to accidentally include the stuff? How would this ever happen?
Every module in NES.lib has exports, and a module will be included if that export is ever included by name. So, there's are two ways I think this is error prone:

1. If you unknowingly create your label with the same name as one of those exports, it creates a conflict of ambiguity. For example if you forgot to export your new label, the library version would be imported instead with no warning.

2. Using many library functions will pull from the NES-specific part of nes.lib, and that code is written with specific assumptions about what its crt0 provides, as well as the memory layout provided by the .cfg. This code is not guaranteed to work when these things are bypassed. By eliminating that part of the library entirely, it will prevent it from ever being used by accident.
DRW wrote: Sun Feb 12, 2023 1:52 pmIsn't the constructor and destructor stuff only for variables on the heap? So, this only gets used if you use malloc and free, right?
The constructor/destructor is used for module initialization by crt0. Malloc is merely the only example mentioned by the docs, but the platform libraries use it for a lot of different things. The runtime portion of the library doesn't use it for much, currently just the debug stkchk feature, which I don't use.

I remove it myself because I don't need it or want it, but it's not particularly important to remove as long as you make sure to run the constructor code in your startup (jsr initlib).
DRW wrote: Sun Feb 12, 2023 1:52 pmI myself never really found a use for the DATA segment. Variables that have a definite startup value and that are not reset during a reset, but that are variables and not constants: What is this ever needed for?
Every C variable that has an initializer value goes in the DATA segment, and this is very explicitly expected to be initialized at reset. That's exactly what the copydata routine does. Variables that are not inialized go in the BSS segment instead.

If you can guarantee for yourself that you never write C variables with an initializer, I guess you don't need it, but it's a fundamental C language feature and there is no possible warning to inform you that the code is wrong because you aren't setting up the DATA segment in your crt0.

Maybe I'd suggest deleting the DATA segment from your CFG to make sure you don't use it by mistake if you don't run copydata.
User avatar
DRW
Posts: 2225
Joined: Sat Sep 07, 2013 2:59 pm

Re: Changing the NMI code without messing with the library in CC65?

Post by DRW »

Thanks for the clarifications.

rainwarrior wrote: Sun Feb 12, 2023 2:42 pm Every C variable that has an initializer value goes in the DATA segment, and this is very explicitly expected to be initialized at reset. That's exactly what the copydata routine does. Variables that are not inialized go in the BSS segment instead.

If you can guarantee for yourself that you never write C variables with an initializer, I guess you don't need it, but it's a fundamental C language feature and there is no possible warning to inform you that the code is wrong because you aren't setting up the DATA segment in your crt0.

Maybe I'd suggest deleting the DATA segment from your CFG to make sure you don't use it by mistake if you don't run copydata.
I don't have a DATA segment in my config file anyway, so everything is fine here.

O.k., it's for initialization of variables. But what I wanted to know:
What actual practical purpose would this have in the context of NES development?
I know that you can initialize global variables in C with any value. But do you know any actual example where this might be relevant? After all, after finishing a game, it usually gets you back to the title screen and you re-initialize all the stuff in your manual code anyway.

What variables could there be
that get initialized with a specific value at startup (int MyValue = 25;),
that can be changed during runtime (++MyValue;, MyValue = 43;),
that don't get set back to their default value when the title screen appears (MyValue is still 43),
but that do get set back during a soft reset when the whole startup code is executed again (int MyValue = 25;)?

I can imagine this comes in handy in Win32 console applications that run once and exit afterwards. But I can't think of any purpose in game development.
Maybe a highscore list that has pre-defined values, like in "Paperboy". But this specific thing is something that you'd most likely want keep at a soft reset.
My game "City Trouble":
Gameplay video: https://youtu.be/Eee0yurkIW4
Download (ROM, manual, artworks): http://www.denny-r-walter.de/city.html
User avatar
rainwarrior
Posts: 8732
Joined: Sun Jan 22, 2012 12:03 pm
Location: Canada
Contact:

Re: Changing the NMI code without messing with the library in CC65?

Post by rainwarrior »

I don't really understand the question. Are you asking me to justify variable initializers in C? This is a very normal and standard thing in C code. Variables need starting values, and an initializer is the most appropriate place for it.

The cc65 DATA segment is to be able to do the initialization in one efficient and compact step. For the very small code cost of the "copydata" routine, every variable gets initialized at the same time in one block copy. Only the minimum amount of initialization value data takes up space in ROM, and it doesn't have to generate load/store code for every single variable initialization, which happens if you assign them values manually elsewhere.

In most of my programs, returning to the title screen is handled by some sort of soft-reset, usually a form of JMP ($FFFC). This allows the expected initializations at reset to apply automatically. You can expose this functionality to C in various of ways. I tend to make my crt0 do the soft-reset if main returns, or I might make a library function they can call to do it, like exit() or some substitute. If you just want the variable initialization without any of the other reset stuff, you could even write a wrapper for copydata that can be called from C to do it.

If I want to keep things at reset like a high score, continue password, PRNG seed, etc., I wouldn't use C initializers for it. I'd store that in a special place that crt0 knows not to wipe at reset.
Post Reply