Getting started again!! Romhacker to NES Game Programmer

Discuss technical or other issues relating to programming the Nintendo Entertainment System, Famicom, or compatible systems. See the NESdev wiki for more information.

Moderator: Moderators

User avatar
Gil-Galad
Posts: 321
Joined: Sat Nov 13, 2004 9:43 pm
Location: Ohio, USA
Contact:

Getting started again!! Romhacker to NES Game Programmer

Post by Gil-Galad »

I have made demos before but more to understand how the NES works rather than trying to make games. This was done to to increase my skills as a Romhacker. I am far better at reverse engineering games than I am making them which seems to require a new set of skills which I'm willing to learn.

I have also ripped a lot of NSFs which is also a long the lines of reverse engineering games to extract the sound code out of the game. I believe that I've done that for long enough and want to do some game programming.

One advantage I have from this is that I can rip a game apart to determine how it works, if I see something similar that I may need in my own game.

I have my assembler of choice and that's ASM6 because it is very easy to use and doesn't require too much setup to get started. In fact, if I wanted to, it would output even a small binary chunk which is very useful for Romhacking. So, I'm not really interested in another assembler. I'm also not interested in any of the C compilers for NES either because I am having such a difficult time learning C and it's associated IDE that I don't want to spend the years needed to learn both.

If I could learn C, I would just use it to make tools that I need. However I don't want to spend the time to learn it right now.

There are more than enough tile editors out there for my needs. There is a nice attribute table editor by Shiru that I really like a lot.

Plenty of sound drivers and Famitracker, although I've heard of people making the tunes in Famitracker and importing the data to a custom driver because Famitracker drivers are too expensive for a game to use.

I can also use that NES Music tracker NTRQ but again that is not really for games even though I can definitely use it well enough.

I've skimmed through the NESdev wiki and can use it again and gain as a reference when needed.

My plan so far is to take NROM and program various demos using the features from that configuration so that I can get the hang of and get started actually making games. For example, I would do background drawing, vertical or horizontal scrolling, either one but not both. Learning to use the IRQ, use CHR-ROM on some and CHR-RAM on some others to practice various techniques. I definitely want to do this to practice development. Once I build up code libraries I think it may help me.

Due to the fact that I haven't been around for quite awhile, I am not up to date on tools and other information that has been released in the past 2-3 years. So, I feel like I'm starting again and trying to refresh my memory about various things about the NES and programming.

I hope I have the right ideas what I'm doing to get started. If anyone has ideas I would like to hear them.
tepples
Posts: 22708
Joined: Sun Sep 19, 2004 11:12 pm
Location: NE Indiana, USA (NTSC)
Contact:

Re: Getting started again!! Romhacker to NES Game Programmer

Post by tepples »

Gil-Galad wrote:I have made demos before but more to understand how the NES works rather than trying to make games. This was done to to increase my skills as a Romhacker. I am far better at reverse engineering games than I am making them which seems to require a new set of skills which I'm willing to learn.

I have also ripped a lot of NSFs which is also a long the lines of reverse engineering games to extract the sound code out of the game. I believe that I've done that for long enough and want to do some game programming.
Have you done much with reverse engineering the data formats of existing games' sound drivers? I know that used in a few Capcom games is documented, as are most of the major Super NES drivers and that of my own music engine Pently (concepts and macro definitions).
I've heard of people making the tunes in Famitracker and importing the data to a custom driver because Famitracker drivers are too expensive for a game to use.
Drivers that play converted FamiTracker music include FamiTone2, GGSound, and now Pently. Each plays only a subset of FT features.
User avatar
Gil-Galad
Posts: 321
Joined: Sat Nov 13, 2004 9:43 pm
Location: Ohio, USA
Contact:

Re: Getting started again!! Romhacker to NES Game Programmer

Post by Gil-Galad »

I'll take a note of that for Famitone2, GG Sound and Pently. I hope I can use them. Thank you.

I have some disassembled drivers for various games and Final Fantasy I being more complete than the others but mostly I kept notes in my head because it slows you down when you have to document everything. I don't know where these files are right now. I think they are in my PC that I have in my closet.

What I usually do is rip a NSF as usual. Most of them are in the range of 8K to 32K so I can do it all in one bank. I disassemble them into readable format. That takes awhile by itself. Then I start trying to figure out what the code does and note them on the disassembly as well as creating readable labels. It's a slow process but it can get a driver RE'd eventually.
User avatar
dougeff
Posts: 3079
Joined: Fri May 08, 2015 7:17 pm

Re: Getting started again!! Romhacker to NES Game Programmer

Post by dougeff »

Pently is currently geared for ca65. I tried making an asm6 version, but tepples has added features to pently since then, so the asm6 version should br considered incomplete.
nesdoug.com -- blog/tutorial on programming for the NES
User avatar
Gil-Galad
Posts: 321
Joined: Sat Nov 13, 2004 9:43 pm
Location: Ohio, USA
Contact:

Re: Getting started again!! Romhacker to NES Game Programmer

Post by Gil-Galad »

Well, I hope you consider updating the asm6 version when you have time and interest. That would be great.

I have been working on a basic template for NROM. Nothing more than getting the header in place and defining the interrupt vectors. I'm going to fine tune it some more until I am happy with it and then I will have a easy to use template from now on, I hope.

Code: Select all

;****************************************
; NES Template 1.0
;****************************************



;****************************************
; iNES Header 1.0
;****************************************

.DB $4E,$45,$53,$1A    ; NES, MS-DOS EOF End of file

.DB $02                ; PRG-ROM size 32K - byte 04

.DB $01                ; CHR-ROM size 8K - byte 05

.DB %00000010          ; flags byte 06
                       ; horizontal mirroring bit 0
                       ; battery backed PRG RAM bit 1
                       ; No trainer bit 2
                       ; No 4 screen VRAM bit 3

.DB $00                ; flags byte 07
                       ; No Vs. Unisystem, PlayChoice-10

.DB %00000000          ; flags byte 08
                       ; NTSC mode bit 0, unused by most emulators

.DB $00                ; flags byte 09
                       ; Unoffical TV System byte. 0 NTSC, 1 PAL bit 0, bit 1 set dual compatible 
                       ; Bus conflict flag for mapper bit 5

.DSB 6                 ; padding unused space 00

;----------------------------------------------

;**********************************************
; CONSTANTS
;**********************************************


;**********************************************
; VARIABLES
;**********************************************

;Zero Page

.enum $0000

.ende

sprendisflag=$0100

;**********************************************
; PRG-ROM Bank 0 NROM
;**********************************************

.ORG $8000

RESET:

SEI            ; disable IRQs
CLD            ; disable decimal mode
LDA #$40
STA $4017      ; disable APU frame IRQ
LDX #$FF
TXS            ; set up the stack
INX            ; X=0
STX $2000      ; disable NMI
STX $2001      ; disable rendering
STX $4010      ; disable DMC IRQs

JSR vblankwait ; wait for vertical blank #1

JSR clrmem1    ; clear out WRAM on boot

JSR clrmem2    ; clear out SPR-RAM

JSR vblankwait ; wait for vertical blank #2

; Bootup sequence ended.

LDA $2002         ; set PPU palette address
LDA #$3F
STA $2006
LDA #$10
STA $2006

LDX #$00

PaletteLoop:

LDA Palette, x    ; load palette data table sprite and background colors
STA $2007
INX
CPX #$20          ; 32 bytes
BNE PaletteLoop

LDA #%10010000
STA $2000
LDA #%0001000
STA $2001

startloop:

JMP startloop

vblankwait:

BIT $2002
BPL vblankwait
RTS

clrmem1:

LDA #$00
LDX #$00
LDY #$00

clrmem1a:

STA $00,x      ; lower page 1 will be used for reset variables
STA $0300,x
STA $0400,x
STA $0500,x
STA $0600,x
STA $0700,x
INX
BNE clrmem1a
RTS

clrmem2:       ; clear out SPR-RAM

LDA #$EF
clrmem2a
STA $0200,x
INX
BNE clrmem2a
RTS

Palette:

.DB $0F,$31,$32,$33,$0F,$35,$36,$37,$0F,$39,$3A,$3B,$0F,$3D,$3E,$0F
.DB $0F,$1C,$15,$14,$0F,$02,$38,$3C,$0F,$1C,$15,$14,$0F,$02,$38,$3C

;*************************************************
; NMI Handler Code
;*************************************************

NMI:

PHA
TXA
PHA
TYA
PHA

JSR sprdmarun

PLA
TAY
PLA
TAX
PLA

RTI

sprdmarun:

LDA sprendisflag  ; 0=enable sprite DMA 1=disable sprite DMA
BNE skipsprdma

LDA #$00
STA $2003
LDA #$02
STA $4014    ; allocate page 2 for sprite ram +513 cycles

skipsprdma:

RTS
;------------------------------------------------

;************************************************
; IRQ Handler Code
;************************************************

IRQ:

PHA
TXA
PHA
TYA
PHA

NOP

PLA
TAY
PLA
TAX
PLA

RTI

;------------------------------------------------


;***********************************************
; INTERRUPT VECTOR TABLE
;***********************************************

.ORG $FFFA

.DW NMI
.DW RESET
.DW IRQ

;***********************************************
; CHR-ROM Bank 0
;***********************************************

.incbin "chr.chr"

           
User avatar
tokumaru
Posts: 12427
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Re: Getting started again!! Romhacker to NES Game Programmer

Post by tokumaru »

Try to declare your variables by reserving space, not assigning hardcoded addresses. Instead of this:

Code: Select all

sprendisflag = $0100
othervariable = $0101
yetanothervar = $0103
Do this:

Code: Select all

.enum $0100
	sprendisflag .dsb 1
	othervariable .dsb 2
	yetanothervar .dsb 1
.ende
This you you won't risk declaring overlapping variables or leaving gaps, and you can quickly rearrange and resize the variables if necessary, without having to go through the tedious and error-prone task of manually adjusting dozens of addresses.
User avatar
Memblers
Site Admin
Posts: 4044
Joined: Mon Sep 20, 2004 6:04 am
Location: Indianapolis
Contact:

Re: Getting started again!! Romhacker to NES Game Programmer

Post by Memblers »

FWIW, I think the concern about Famitracker's replay engine can appear a bit overblown for most cases. If you want to make something that's going to push the NES to it's limits, sure. But if you're making a game for the first time it's really advisable to keep it simple as much as possible. Unless it's a pretty intense program, you probably won't be in danger of running out of CPU cycles or RAM. I may be wrong, but I seem to recall that even quite a few commercial game sound engines were bigger hogs than Famitracker's.
User avatar
thefox
Posts: 3134
Joined: Mon Jan 03, 2005 10:36 am
Location: 🇫🇮
Contact:

Re: Getting started again!! Romhacker to NES Game Programmer

Post by thefox »

Does FamiTracker's sound engine have sound effect support?
Download STREEMERZ for NES from fauxgame.com! — Some other stuff I've done: fo.aspekt.fi
User avatar
rainwarrior
Posts: 8735
Joined: Sun Jan 22, 2012 12:03 pm
Location: Canada
Contact:

Re: Getting started again!! Romhacker to NES Game Programmer

Post by rainwarrior »

Memblers wrote:FWIW, I think the concern about Famitracker's replay engine can appear a bit overblown for most cases. If you want to make something that's going to push the NES to it's limits, sure. But if you're making a game for the first time it's really advisable to keep it simple as much as possible. Unless it's a pretty intense program, you probably won't be in danger of running out of CPU cycles or RAM. I may be wrong, but I seem to recall that even quite a few commercial game sound engines were bigger hogs than Famitracker's.
I don't think CPU or RAM are the biggest problems with it.

Famitracker has no sound effect support. This is probably the most important reason to use something else. (Unless you want to write your own separate sound effect engine and learn how to patch that on top of Famitracker's music.)

The second biggest reason I think is its ROM footprint. It's more than 5k. The common alternatives are more like 1k.

It does use a lot more RAM than the alternatives too, but it thankfully keeps that mostly off the ZP, so I think this is not too bad, really.

The CPU usage isn't too bad, either. Maybe on average you lose 1000 cycles per frame compared to something lighter? Probably not the dealbreaker.
User avatar
Gil-Galad
Posts: 321
Joined: Sat Nov 13, 2004 9:43 pm
Location: Ohio, USA
Contact:

Re: Getting started again!! Romhacker to NES Game Programmer

Post by Gil-Galad »

That's good information to know about Famitracker. Although, I definitely need a sound driver that can use sound effects and be somewhat efficient memory and cycle wise to keep my options open with as much time as I can get. Although, it's not a big concern but the sound effects are.

I've been working on my template again and changed some variables to .enum. I also added two hard coded variables for controller ports. I don't see a good reason to change the ports to .enum.

I also added controller code that is for one player. It's the controller code from the Wiki. As a matter of fact I have been using stuff from the Wiki and template ideas from that ASM6 template thread. And other tutorials just to refresh my memory.

The controller code I'm running in NMI to run once per frame to keep it well timed with the game. I don't know if this is the best way to do it. I've seen games do it and run controller code elsewhere.

I've also been debugging the code as I'm going along by trial and error. It hasn't taken me long to track down some blunders I made. I'm sure it will be much more difficult later on to track down bugs.

Code: Select all

;****************************************
; NES Template 1.0
;****************************************



;****************************************
; iNES Header 1.0
;****************************************

.DB $4E,$45,$53,$1A    ; NES, MS-DOS EOF End of file

.DB $02                ; PRG-ROM size 32K - byte 04

.DB $01                ; CHR-ROM size 8K - byte 05

.DB %00000010          ; flags byte 06
                       ; horizontal mirroring bit 0
                       ; battery backed PRG RAM bit 1
                       ; No trainer bit 2
                       ; No 4 screen VRAM bit 3

.DB $00                ; flags byte 07
                       ; No Vs. Unisystem, PlayChoice-10

.DB %00000000          ; flags byte 08
                       ; NTSC mode bit 0, unused by most emulators

.DB $00                ; flags byte 09
                       ; Unoffical TV System byte. 0 NTSC, 1 PAL bit 0, bit 1 set dual compatible 
                       ; Bus conflict flag for mapper bit 5

.DSB 6                 ; padding unused space 00

;----------------------------------------------

;**********************************************
; CONSTANTS
;**********************************************


;**********************************************
; VARIABLES
;**********************************************

; Controller Ports

JOYPAD1=$4016
JOYPAD2=$4017

; Zero Page

.enum $0000

.ende

; Page 1 flag variables

.enum $0100

sprendisflag .DSB 1
ctrlendis    .DSB 1
buttons      .DSB 1


.ende

;**********************************************
; PRG-ROM Bank 0 NROM
;**********************************************

.ORG $8000

RESET:

SEI            ; disable IRQs
CLD            ; disable decimal mode
LDA #$40
STA $4017      ; disable APU frame IRQ
LDX #$FF
TXS            ; set up the stack
INX            ; X=0
STX $2000      ; disable NMI
STX $2001      ; disable rendering
STX $4010      ; disable DMC IRQs

JSR vblankwait ; wait for vertical blank #1

JSR clrmem1    ; clear out WRAM on boot

JSR clrmem2    ; clear out SPR-RAM

JSR vblankwait ; wait for vertical blank #2

; Bootup sequence ended.

LDA $2002         ; set PPU palette address $3F10
LDA #$3F
STA $2006
LDA #$10
STA $2006

LDX #$00

PaletteLoop:

LDA Palette, x    ; load palette data table sprite and background colors
STA $2007
INX
CPX #$20          ; 32 bytes
BNE PaletteLoop

LDA #%10010000    ; enable NMI, background $1000 VRAM, sprites $0000 VRAM
STA $2000         ; sprite size 8x8, PPU increment 1, name table $2000 VRAM
LDA #%0011000     ; background visible, sprites visible, background & sprites clipped
STA $2001         ; color display

startloop:

JMP startloop

vblankwait:

BIT $2002
BPL vblankwait
RTS

clrmem1:

LDA #$00
TAX
TAY

clrmem1a:

STA $00,x      ; lower page 1 will be used for reset variables
STA $0300,x
STA $0400,x
STA $0500,x
STA $0600,x
STA $0700,x
INX
BNE clrmem1a
RTS

clrmem2:       ; clear out SPR-RAM

LDA #$EF
clrmem2a
STA $0200,x
INX
BNE clrmem2a
RTS

Palette:

.DB $0F,$31,$32,$33,$0F,$35,$36,$37,$0F,$39,$3A,$3B,$0F,$3D,$3E,$0F
.DB $0F,$1C,$15,$14,$0F,$02,$38,$3C,$0F,$1C,$15,$14,$0F,$02,$38,$3C

;*************************************************
; NMI Handler Code
;*************************************************

NMI:

PHA
TXA
PHA
TYA
PHA

JSR sprdmarun     ; run sprite DMA       513
JSR joycontrol    ; run controller code  132

PLA
TAY
PLA
TAX
PLA

RTI

sprdmarun:

LDA sprendisflag  ; 0=enable sprite DMA 1=disable sprite DMA
BNE skipsprdma

LDA #$00
STA $2003
LDA #$02
STA $4014    ; allocate page 2 for sprite ram +513 cycles

skipsprdma:

RTS

joycontrol:

LDA ctrlendis    ; 0=enable 1=disable controller port 1 code
BNE skipctrl     ; skip controller code if 1

LDA #$01         ; 132 cycle controller port 1 ring counter
STA JOYPAD1
STA buttons
LSR A
STA JOYPAD1

joyloop:

LDA JOYPAD1
LSR A
ROL buttons
BCC joyloop

skipctrl

RTS

;------------------------------------------------

;************************************************
; IRQ Handler Code
;************************************************

IRQ:

PHA
TXA
PHA
TYA
PHA

NOP

PLA
TAY
PLA
TAX
PLA

RTI

;------------------------------------------------


;***********************************************
; INTERRUPT VECTOR TABLE
;***********************************************

.ORG $FFFA

.DW NMI
.DW RESET
.DW IRQ

;***********************************************
; CHR-ROM Bank 0
;***********************************************

.incbin "chr.chr"

           
User avatar
Memblers
Site Admin
Posts: 4044
Joined: Mon Sep 20, 2004 6:04 am
Location: Indianapolis
Contact:

Re: Getting started again!! Romhacker to NES Game Programmer

Post by Memblers »

Gil-Galad: Looks good so far. I see the main loop is an infinite JMP loop, that's one of those eternally debated program structures (vs having a minimal NMI routine and most code in the main loop). Either way can work just fine if you're careful, I tend to go for the minimal NMI, myself. The thing to consider is what happens if/when your code takes longer than a frame. With the "big NMI" setup, one easy way out is to allow recursive NMIs, but at the start of NMI you check to see if the last NMI had completed or not, and if it hasn't you can play the music and skip all the other processing. Then the game will slow down, but not the music. In that case, hopefully the music code is not what was being interrupted, heheh. :P
rainwarrior wrote:
Memblers wrote:FWIW, I think the concern about Famitracker's replay engine can appear a bit overblown for most cases. If you want to make something that's going to push the NES to it's limits, sure. But if you're making a game for the first time it's really advisable to keep it simple as much as possible. Unless it's a pretty intense program, you probably won't be in danger of running out of CPU cycles or RAM. I may be wrong, but I seem to recall that even quite a few commercial game sound engines were bigger hogs than Famitracker's.
I don't think CPU or RAM are the biggest problems with it.

Famitracker has no sound effect support. This is probably the most important reason to use something else. (Unless you want to write your own separate sound effect engine and learn how to patch that on top of Famitracker's music.)

The second biggest reason I think is its ROM footprint. It's more than 5k. The common alternatives are more like 1k.

It does use a lot more RAM than the alternatives too, but it thankfully keeps that mostly off the ZP, so I think this is not too bad, really.

The CPU usage isn't too bad, either. Maybe on average you lose 1000 cycles per frame compared to something lighter? Probably not the dealbreaker.
I did this little bit of music engine benchmarking, I was having trouble with Famitone that day so that's why it didn't make onto that short list. Though anyone can check it with NintendulatorDX, I was hoping to build them all with the same example song.
viewtopic.php?f=6&t=13580
It's worth noting that max CPU per frame usage is critical when using sprite zero hit, otherwise average CPU usage is probably what matters.

Yeah sound effects are a big deal. I've been doing a soundtrack for a game I'm making and I haven't even started considering sound effects yet, but I've had my old Nerdtracker 2 habits in mind. For NT2 I once made a sound effects engine with this simple mod: redirect all the sound register writes to RAM, then my own sound effects code overrides them in RAM, finally in the end all those buffered registers gets dumped to the actual registers. So I figure I'll probably end up doing the same thing in Famitracker. In the case of my current game project I have the luxury of it being a turn-based game, tons of ROM, and the spartan channel effects support in other engines is a deal-breaker for me. Anymore I can't live without the Gxx effect, duty-cycle envelopes, and often enough the volume column. Though those can be mostly worked around the hard way, I'm just taking the easy way out because this game has a lot of music (35 tracks so far..) that I don't want to optimize unless I have to.

ROM usage I can see being a concern for some people, definitely not for me though since I make my own hardware. 512kB ROM now costs 75 cents (edit: checking again it seems to be 97 cents, not sure what the deal was with 75 cent price I saw), and I've pretty much adopted that as my entry-level cartridge.
User avatar
tokumaru
Posts: 12427
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Re: Getting started again!! Romhacker to NES Game Programmer

Post by tokumaru »

Memblers wrote:The thing to consider is what happens if/when your code takes longer than a frame. With the "big NMI" setup, one easy way out is to allow recursive NMIs, but at the start of NMI you check to see if the last NMI had completed or not, and if it hasn't you can play the music and skip all the other processing.
Don't forget about raster effects. If you have a sprite 0 status bar, effects based on IRQs, and stuff like that, you also need to reset them for the new frame, otherwise you'll get glitched frames every time there's slowdown.
tepples
Posts: 22708
Joined: Sun Sep 19, 2004 11:12 pm
Location: NE Indiana, USA (NTSC)
Contact:

Re: Getting started again!! Romhacker to NES Game Programmer

Post by tepples »

Memblers wrote:I've been doing a soundtrack for a game I'm making and I haven't even started considering sound effects yet, but I've had my old Nerdtracker 2 habits in mind.
As far as I know, of the current game-oriented homebrew NES sound drivers, only Pently supports NT2-style decaying envelopes.
Anymore I can't live without the Gxx effect, duty-cycle envelopes
Pently supports both, though envelopes can't loop.
and often enough the volume column.
Pently doesn't support this yet, but it's the next thing that I plan to put in after The Curse of Possum Hollow wraps.
User avatar
Gil-Galad
Posts: 321
Joined: Sat Nov 13, 2004 9:43 pm
Location: Ohio, USA
Contact:

Re: Getting started again!! Romhacker to NES Game Programmer

Post by Gil-Galad »

That's one thing I've been trying to figure out is how to layout my code between NMI or in the Reset code. In other demos I've put most of the code in reset. I've had problems doing that before.

I'm trying a bulk of the code in NMI but I am setting up flags to enable and disable code routines. Not sure how well this is going to work out but I'm going to try and make sure the timing is right by putting in DMA and controller code so far to run once per frame. I think I need to set up more flag variables later on to pick and choose which code I'm going to run. Not really sure what I should do different.
User avatar
DRW
Posts: 2225
Joined: Sat Sep 07, 2013 2:59 pm

Re: Getting started again!! Romhacker to NES Game Programmer

Post by DRW »

Gil-Galad wrote:That's one thing I've been trying to figure out is how to layout my code between NMI or in the Reset code. In other demos I've put most of the code in reset. I've had problems doing that before.
It's basically this (the example is pseudo code and done from my head):

In reset:

Code: Select all

start:

if (waitForNmi == false)
{
    DoGameLogic();
    waitForNmi = true;
    goto start;
}
And then in NMI:

Code: Select all

SaveRegistersToStack();

if (waitForNmi == true)
{
    waitForNmi = false;
    DoNmiStuff();
}

DoMusicStuff();
RestoreRegistersFromStack();
This should be it.
Did I remember anything incorrectly?
My game "City Trouble":
Gameplay video: https://youtu.be/Eee0yurkIW4
Download (ROM, manual, artworks): http://www.denny-r-walter.de/city.html
Post Reply