Implementing a "high score" function

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

Post Reply
User avatar
Jason Frudnick
Posts: 12
Joined: Wed Jul 11, 2018 2:24 am
Location: Batrachia, New York

Implementing a "high score" function

Post by Jason Frudnick »

Again guys Im a noob here so forgive me. What would the easiest possible way to implement a points system with "high score" function into the existing hex code of a rom? Could this be done in FCEUX?

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

Re: Implementing a "high score" function

Post by tokumaru »

It can definitely be done, but it's not a very trivial thing to do if you don't have much experience with ROM hacking. If you know the location in RAM where the score is kept (this should be easy to find using debugging tools), you can inject some code in the ROM to copy it to a high score table when the game ends, and then do whatever you want with the table. Coding new screens to display the high score table would be the hard part, if you don't have experience with NES programming and the basic architecture of the game you're hacking.

If you want to keep the high score table between power cycles, you'll obviously need battery-backed RAM in the cartridge. If you're just using ROMs and emulators, adding a battery is as easy as setting a bit in the ROM header, but when working with battery-backed RAM it's common practice to use some form of error detection to handle corrupted data, because data loss does happen and you really don't want to use/display garbage data.
User avatar
koitsu
Posts: 4201
Joined: Sun Sep 19, 2004 9:28 pm
Location: A world gone mad

Re: Implementing a "high score" function

Post by koitsu »

Jason, because of your recent posts, I would suggest you start hanging out on the romhacking.net forum. What you've asked in a couple posts now is exactly what folks there do, and may provide you with more/better insights into how to go about it. Not saying don't ask here, just saying there is an actual site dedicated to these types of endeavours.

You are going to need to learn 6502 assembly, get familiar with the NES architecture, and get familiar with command-line tools as well as the FCEUX or Mesen debugger (deeply!) to do something like this.

If you don't want to, then your best bet is to go on the romhacking.net forum and start hoping you can find an assembly programmer there who is willing to help with the project/idea you have (i.e. find unused space in the ROM, do the disassembly, reverse-engineer the game, implement what you describe, etc. -- all of which are very time consuming (often several months if being done by a single person)); I cannot stress enough the phrase "time consuming". Large projects sometimes take years.

Romhacking projects are almost always driven by passion/dedication (read: voluntary), but there are some folks who do paid work.
User avatar
dougeff
Posts: 3079
Joined: Fri May 08, 2015 7:17 pm

Re: Implementing a "high score" function

Post by dougeff »

I'm just gonna throw this in here. If you DID know 6502 assembly...Ans let's say you wanted to write a score every frame at the top of the screen...

Writing to the PPU (the screen) is done by writing a position to $2006 and then sending data to $2007. It must be done during the vertical blanking period...which also coincides with the "nmi" routine.

So find the NMI routine, which in FCEUX means hitting the back slash button, which pauses execution at the end of rendering, and then hit "step into" a few times in the debugger, and look for writes to 2000,2001,2005,2006 or 2007. That's probably the NMI code. (look at the scanline number, it should be higher than 240)

Now locate a blank part of the ROM to write new code.

Now somewhere in the NMI code find a JSR. Write down the original destination. Change the destination of that JSR to point to the blank patch. Write your new code. End it with a JMP to the original destination of that JSR that you overwrote.

If you don't know 6502 asm, then spend a few months learning that first.


Oh, and a warning. Many games have almost NO blank space available. Super Mario Bros...the most hacked game, has exactly 3 bytes of unused space. Which means you would have to remove something. Knowing what you can safely remove from the ROM is much, much, much harder.

Back in those days, they would fill blank space with FF or 00. But, a few games had strange patches of unrelated code in random places, because the test ROM had been overwritten several times, and never properly erased.
nesdoug.com -- blog/tutorial on programming for the NES
User avatar
koitsu
Posts: 4201
Joined: Sun Sep 19, 2004 9:28 pm
Location: A world gone mad

Re: Implementing a "high score" function

Post by koitsu »

...not to mention: expanding a game's size (expanding ROM size) sounds easy, but it's often more complicated than it sounds. Three things I've run into doing romhacks with mapper-based games:

1) Finding spare room in the fixed or hard-wired PRG bank is often difficult -- all the "commonly-used" routines or "critical guts" throughout the game are usually stored here. In most cases that's 16KBytes (often $C000-FFFF),

2) Misidentifying "free space". As dougeff mentions, "free space" in a game is usually denoted by long sequences of $FF or $00. However, it's plausible (and often true!) that, for example, 16 linear bytes of $FF or $00 could be actual data used by the game and is not free space. The only way you can truly identify what's unused vs. used is by i) reverse-engineering the entire game (and when I say entire, I mean ENTIRE -- not parts, but all of it), and/or ii) using an emulator that has code/data logging and doing literally EVERY SINGLE THING you possibly can (I've talked about this before).

Experience and gut feeling tell me that people today probably use a combination of both, in addition to what we did in the late 90s/early 2000s: did disassembly and reverse-engineering, determined what we could, and took a chance/hoped that it really was free space.

You wouldn't immediately know if it wasn't either -- it might have been data that was only used in a super obscure part of the game, so during your own development/RE'ing/testing you never encountered it, but after the IPS patch is released, some random person is like "this romhack freaks out and maybe crashes when you go into Derpderp's house when you have the gold key equipped and you're wearing the invisibility cloak!" It's problems like this that really suck with romhacking, because you as one person literally cannot test every single thing. Welcome to the joys of romhacking.

3) Sometimes you get lucky, where the original code swaps PRG or CHR banks using "sane" bank numbers. That way you know, for example, bank numbers 0-3 are original code or graphics, and bank numbers 4-7 are OK to use for new efforts; i.e. the code does (effectively) lda #2 / sta $chrpageselect to select the 3rd CHR page.

However, there are times where programmers were trying to be smart or maybe lazy or left in a bug/quirk from development, and they end up doing lda #6 / sta $chrpageselect (maybe not literal value 6, but based on some other value), and it works because the hardware (in that configuration) might effectively be doing chrpage & 0x03 (or chrpage & %0011), thus 6 (%0110) is the same as 2 (%0010). You start using CHR page 6 in your code, looks great/works great, but then somewhere later in the game (in code you haven't touched) suddenly your new graphics show up (page 6) rather than the originally-intended ones (page 2) and you're like "?!?!".

This can apply to PRG pages too, not just CHR. It's worse with PRG because code then crashes, rather than with CHR where you might just see incorrect graphics (and thus have visual indicator of what might be wrong).

Anyway, more often than not, I tend to recommend folks *not* expand the game/ROM size if they can avoid it. But there are absolute cases where expansion is the only possibility due to new code/graphics/things being introduced and not having enough "free space" in the original. In that scenario, you get to reorganise the layout (the fixed or hard-wired PRG bank/page is the most important), followed by changing any PRG/CHR swapping code to use the now-potentially-different PRG/CHR page/bank numbers (depends on mapper and what you moved, in combo with what I described above). Sometimes it's easy and Just Works(tm), other times (as denoted in (3)) it's painful.

Jason's other thread is about Blaster Master, so I imagine this thread is probably discussing the same game (re: how to implement a high score). Blaster Master is a mapper 1 (MMC1 (MMC1B2)) SLROM game (128KB PRG, 128KB CHR).
User avatar
dougeff
Posts: 3079
Joined: Fri May 08, 2015 7:17 pm

Re: Implementing a "high score" function

Post by dougeff »

Blaster Master would be a poor choice for a first project.

I know what the hell I'm doing, and I wouldn't try it. Not that it couldn't be done, but the months of staring at disassembly, and testing and retesting would be a big enough disincentive, that I would skip it and do something else.

A first project should be an NROM, non banking game with no scrolling. Like Donkey Kong, or Pac-man. Not that I would encourage anyone to hack any game. But, theoretically, it would be easier to modify a much simpler game.

Edit -
This reminds me of the movie "Son In Law" where the grandfather is always whittling wood with a knife into horses and things, and Pauly Shore is like, can I whittle too? And then he grabs a piece of wood that is WAY too big to whittle.

That.
nesdoug.com -- blog/tutorial on programming for the NES
User avatar
koitsu
Posts: 4201
Joined: Sun Sep 19, 2004 9:28 pm
Location: A world gone mad

Re: Implementing a "high score" function

Post by koitsu »

Yeah, I'd suggest a "simple" first-gen game for learning, which mostly are NROM (often 40KBytes (32KB PRG + 8KB CHR)); games like Pinball, Kung-Fu, Donkey Kong, Balloon Fight, Clu Clu Land, etc.. There's less "stuff" involved, thus learning the basics becomes easier.

There are some games around that same time frame that are kind of technical nightmares though (good example I think is Capcom's Trojan / Tatakai no Banka, which is one of Capcom's first titles, and the code is weird and nebulous in many ways, doing things oddly. Capcom was still learning about the Famicom... :-) ).

People often go for Super Mario Bros, which I feel is an extremely bad choice given how incredibly packed the game is (not to mention the tricks it does to try and work around it). However, Super Mario Bros has already been disassembled and reverse-engineered fully, with much of the code commented, so in some ways that can help with learning. But it's a very complicated game. Zelda often comes in at a second choice, which is equally complicated just in different ways (and uses a mapper).

If you aren't sure which games are larger and use what mappers, BootGod's database is the place to go.
Post Reply