+  RHDN Forum Archive
|-+  Romhacking
| |-+  ROM Hacking Discussion
| | |-+  Inserting new code in a ROM (NES)
Pages: [1] 2
Author Topic: Inserting new code in a ROM (NES)  (Read 2 times)
PolishedTurd
Guest
« on: March 17, 2009, 08:13:08 am »

I'd like to add some new functionality to Ninja Gaiden (NES). I've searched the documents section and this forum. I have read Disch's document on memory mapping, which I presume I'll need to do, but I haven't found anything regarding a basic insertion of new code.

The 'Select' button is often unused in normal NES gameplay. Let's say I want to make it so that when you push select, your jump height is doubled on your next jump. After that jump, the jump height is reset to normal. I suspect I need to add instructions for the following:
  • 'Listen' for the select key, which is currently not used for anything
  • Set a byte in memory that indicates whether select key has been pressed since last player jump
  • Inside the player jumping subroutine, check the status of the 'select key pressed' byte, and set the jump height accordingly. Then clear the 'select key pressed' byte.
So, I need to find space in the ROM to add these new instructions, add pointers (JMP or JSR) to the new space I am using, and make sure they either exist on a page currently in memory, or that I load the page they are on before trying to access them. Can someone point me to some documents to help me, or provide an example? It could be something simple like adding 100 points to the score when select is pressed. If it helps at all, the jump height in Ninja Gaiden is at 0x01EBA4 and is 37 by default. The score is in $0060.

Another thing that may help is that I already need to obliterate the stuff that happens when pausing and unpausing, in this and probably subsequent ROMs that I hack. So that gives me some space to use that is always available during regular gameplay, although I admit it is not pretty.

I'd also like some help on listening for more complex button combinations, such as simultaneous keypresses (select + start gives you a powerup) or button sequences where there is a limited time to execute the sequence (e.g., A-B-B-A pressed during 1 second adds a life). How can I find the main subroutine that is constantly checking for buttons? Is there a usual location for it? I see a few values in the CPU being set to precise values while I press buttons, but putting read breakpoints on them leads to a constant series of rapid snaps at different locations.



Thanks.
KingMike
Guest
« Reply #1 on: March 17, 2009, 08:32:20 am »

Many games I've seen automatically read the controller and store both buttons "pushed" and "held" as bitfields.
So, you'd just have to find it (usually somewhere within the first $100 bytes of RAM). (or you can use the debugger and set a breakpoint for a read on $4016, and step through a few instructions. Usually there will be a shift instruction (ror/rol) used to store the button-press to a certain address.
Jigglysaint
Guest
« Reply #2 on: March 17, 2009, 06:09:33 pm »

How about Up and Jump to make a higher jump?  I know the select button is unused, but since Up is also not used while walking except for subweapons, it would fit perfectly into the controls.  I don't know anything about the Ninja Gaiden rom, but I did do somthing a little while back in Megaman 4 where I made it so you can press up and B to shoot a fully charged mega buster shot.  What I do is I find where the jump routine is called, then I add a JSR to free space.  You will overwrite bytes, so you need to include them after at some point. Basically what you want to do is first make a check to see if the Up button is held.  If it's true, keep going.  If it's false, regular jump value is used.  Then you re-define the new jump value after A is pressed.  Of course this is assuming you know how the jump mechanics work.  Some games have different ways of coding jumps than others.  Doing a Select powerup requires at least one free byte in ram.  Basically, you need to insert this routine somewhere where it will read button commands.  It obviously must be somewhere within the main program for the Ryu sprite.  You define that the select button has been pressed, and if it has, then a check is made to see if the ram slot is empty or not.  If it's not, then it moves on.  If so, do an INC for either Zero Page or Absolute(depends on where the byte is).  Now, in the jump call routine, it can check for that one address and branch off as desired.  If the value is detected, then load the new jump value.  When the actual jump button is pressed, that value is always erased.  The only stipulation is that there has to be some space left in the main PRG bank, or know what bank is also loaded while you are jumping.
PolishedTurd
Guest
« Reply #3 on: October 26, 2010, 04:43:29 pm »

I've recently resumed work on this. I have figured out the assembly code I need to insert, but the original problem remains: how can I find some unused space and make sure it is always available at any time when the user can push the Select button? At this point, I would rather keep the pausing functionality that I originally was willing to overwrite. I would also prefer not to expand the ROM unless it's critical. So far, I've tried overwriting a couple of sections in the ROM that appeared to be junk (several rows of FF between blocks of code), but this causes the game to freeze.

The documents in the Docs section don't seem to provide the clarification or examples I need, but I will keep trying.

EDIT: I believe this topic was made before the Newbie forum was created. It probably belongs there.
snarfblam
Guest
« Reply #4 on: October 26, 2010, 07:50:21 pm »

It's the last bank that's fixed, right? Anyways, I overwrote the FFs at 0x1ff20 with no apparent ill effects. I'd say the end of the bank is your best bet for unused memory.
Trax
Guest
« Reply #5 on: October 26, 2010, 09:30:24 pm »

You must know exactly what are the banks loaded at 0x8000 and 0xC000 when your JSR is called. Most games have the last Bank "hard-wired" to 0xC000, and then the 0x8000 space juggles between other banks to fetch whatever data they need...
snarfblam
Guest
« Reply #6 on: October 27, 2010, 06:21:44 am »

That's why I pointed out data between 0x1C000 and 0x1FFFF, this is the last (i.e. presumably fixed) PRG bank.
Trax
Guest
« Reply #7 on: October 27, 2010, 04:22:18 pm »

The important word is "presumably". Very common, but not automatic. Some mappers (i.e. Mapper 7: AOROM) load 2 successive banks (32k) in 0x8000 and 0xC000. For example, if you send 2 to the switch port, bank 2 and 3 will be loaded in 0x8000 and 0xC000 respectively. This is why some banks is these games seem to start directly with irrelevant code; it's actually a direct continuation of the previous bank's code. Yet, it is still true that MOST of the time, the last bank is hard-wired to 0xC000. This bank usually holds fundamental code that is used repeatedly...
PolishedTurd
Guest
« Reply #8 on: October 27, 2010, 05:01:38 pm »

Hmmm... When I insert my code at 0x01FF20, it causes a freeze immediately after returning from execution. Looking at the registers, stack and processor status flags, everything is the same as when my subroutine is called as when it is bypassed. Somehow I'm violating something important and killing the game (graphics get scrambled, music freezes on current sound, I can still move my garbled sprite, but it's slow and there are no enemies, etc.). Comparing stack traces of the clean and hacked ROM, the clean ROM appears to do a bunch of "redundant" writes just before the point where the game crashes in the hacked ROM, which seems strange to me. Why would it do this after reading that Select was pressed, when the Select button is 100% unused in the game?
Clean ROM executes this set of writes at the point shortly before the hacked ROM crashes, whereas the hacked ROM does not:
Code:
$C02A:8D FF FF  STA $FFFF = #$FA           A:02 X:FF Y:00 P:nvUbdIzc
$C02D:4A        LSR                        A:02 X:FF Y:00 P:nvUbdIzc
$C02E:8D FF FF  STA $FFFF = #$FA           A:01 X:FF Y:00 P:nvUbdIzc
$C031:4A        LSR                        A:01 X:FF Y:00 P:nvUbdIzc
$C032:8D FF FF  STA $FFFF = #$FA           A:00 X:FF Y:00 P:nvUbdIZC
$C035:4A        LSR                        A:00 X:FF Y:00 P:nvUbdIZC
$C036:8D FF FF  STA $FFFF = #$FA           A:00 X:FF Y:00 P:nvUbdIZc
$C039:4A        LSR                        A:00 X:FF Y:00 P:nvUbdIZc
$C03A:8D FF FF  STA $FFFF = #$FA           A:00 X:FF Y:00 P:nvUbdIZc
$C03D:60        RTS                        A:00 X:FF Y:00 P:nvUbdIZc

Looking at Disch's document about Mapper 1, I'm seeing the same thing, but I don't understand why the redundant writes are necessary...
Code:
MMC1 is unique in that not only must the registers be written to *one bit at a time*, but also you cannot
write to the registers directly.

Internal registers are 5 bits wide.  Meaning to complete a "full" write, games must write to a register 5
times (low bit first).  This is usually accomplished with something like the following:

   LDA value_to_write
   STA $9FFF    ; 1st bit written
   LSR A
   STA $9FFF    ; 2nd bit written
   LSR A
   STA $9FFF    ; 3rd bit written
   LSR A
   STA $9FFF    ; 4th bit written
   LSR A
   STA $9FFF    ; final 5th bit written -- full write is complete

Writing to anywhere in $8000-FFFF will do -- however the address you write to on the last of the 5 writes
will determine which internal register gets filled.  The address written to for the first 4 writes *does not
matter at all*... though games generally write to the same address anyway (like in the above example).

Anyway, it may be that my subroutine is writing to memory values it really shouldn't be, or maybe preventing a bank switch that needs to happen. I was not rigorous enough to make sure I tested under exactly the same conditions between the clean and hacked ROMs, so the bank switch might even be irrelevant. I'll try a more precise test, and I'll try rewriting my subroutine if necessary.

« Last Edit: October 27, 2010, 05:08:51 pm by PolishedTurd »
KingMike
Guest
« Reply #9 on: October 27, 2010, 05:37:48 pm »

As mentioned, MMC1 requires 5 writes because on real hardware, only one BIT will be written to the register each time (not the full byte), and the register write will only take effect after all 5 bits are written.
snarfblam
Guest
« Reply #10 on: October 27, 2010, 05:46:29 pm »

Those five writes in a row are for MMC1 bank-switching. The bank number is specified by writing it to a certain address (or range of addresses). With MMC1 the bank number is transmitted serially, i.e. one bit at a time. There are five bits. Each write to $FFFF (actually $E000-$FFFF) sends the lowest bit of the value "stored" to the MMC controller.

So the simplest way to do it is store the bank number in the accumulator, and write five times to the same address, with a bit shift (LSR) between each write to cycle through the bits.
Gil Galad
Guest
« Reply #11 on: October 27, 2010, 08:44:25 pm »

Welcome to serial port hell for both controllers and MMC1 registers.

It's really hard to say what is going on without looking at whatever code you wrote and how it is integrated with the rest of the game code.
PolishedTurd
Guest
« Reply #12 on: October 27, 2010, 10:06:48 pm »

It seems so simple...

Here is where I insert my jump to subroutine for checking the select key:
Code:
$C091:A5 29     LDA $0029 = #$20      ;$0029 holds the rising edge of a controller button; #20 = Select key
$C093:20 11 FF  JSR $FF11   ; <---------- If you NOP this instruction, the game is bug-free.

Here's my subroutine. It is intended to function this way: Push select once, and the player gets the shuriken powerup (throwing star). Press select again and the player gets the windmill star powerup. Press select again and the player gets the fireball powerup. Press select again, and the cycle starts over with the shuriken powerup, and so on.

$C9 is the address that holds the powerup value. #81 = shuriken, #82 = windmill and #80 = fireball.

$FF10 (0x01FF20) is where I store a counter variable to watch how many times Select is pushed.
The subroutine:
Code:
$FF11:48        PHA
$FF12:C9 20     CMP #$20 ;check select key
$FF14:D0 16     BNE $FF2C ;exit
$FF16:A2 01     LDX #$01
$FF18:EC 10 FF  CPX $FF10 = #$01
$FF1B:F0 16     BEQ $FF33 ; go to shuriken
$FF1D:E8        INX
$FF1E:EC 10 FF  CPX $FF10 = #$01
$FF21:F0 1A     BEQ $FF3D ; go to windmill
$FF23:E8        INX
$FF24:EC 10 FF  CPX $FF10 = #$01
$FF27:F0 1E     BEQ $FF47 ; go to fireball
$FF29:8E 10 FF  STX $FF10 = #$01
exit:
$FF2C:68        PLA
$FF2D:A2 FF     LDX #$FF ; these are the values in X
$FF2F:A0 0D     LDY #$0D ; and Y when the subroutine is supposed to return
$FF31:18        CLC ; carry is also clear when subroutine is supposed to return
$FF32:60        RTS

shuriken:
$FF33:A9 81     LDA #$81
$FF35:85 C9     STA $00C9 = #$81
$FF37:EE 10 FF  INC $FF10 = #$01
$FF3A:4C 2C FF  JMP $FF2C

windmill:
$FF3D:A9 82     LDA #$82
$FF3F:85 C9     STA $00C9 = #$81
$FF41:EE 10 FF  INC $FF10 = #$01
$FF44:4C 2C FF  JMP $FF2C

fireball:
$FF47:A9 80     LDA #$80
$FF49:85 C9     STA $00C9 = #$81
$FF4B:A2 01     LDX #$01
$FF4D:8E 10 FF  STX $FF10 = #$01
$FF50:4C 2C FF  JMP $FF2C

This code executes as expected and returns without any apparent problems, but a few hundred lines after it executes, the game freezes as described above.

If you really want to have a crack at it, try the patch here. Push select at any time and see for yourself. To see what should ideally happen, whack the first lantern on the building and pick up the shuriken powerup.
Gil Galad
Guest
« Reply #13 on: October 28, 2010, 01:33:10 am »

First of all, I've noticed a couple mistakes you've made and I'll explain a few of them. You can try to work this out yourself since this is your project. Once you get the program flow functioning properly, then you can go from there in working out whatever is wrong after that.

First of all, you overwrote or inserted a couple of instructions that you did not replace or find a work around for. Also, the code has been shifted by one byte. Even though you did account for it in some cases, not everything has been corrected.

Code:
07:C091:A5 29     LDA $0029 = #$00
07:C093:24 1E     BIT $001E = #$80
07:C095:30 0F     BMI $C0A6

Should be replaced with this:

Code:
C091: A5 29     LDA $0029
C093: 20 11FF  JSR $FF11
C096: EA          NOP

The two instructions that you wiped out, you need to account for.

BIT $1E
BMI LC0A6

Now, we'll go to your routine.

Code:
07:FF16:A2 01     LDX #$01
07:FF18:EC 10 FF  CPX $FF10 = #$01
07:FF1B:F0 16     BEQ $FF33

This doesn't make much sense to me. It's probably always going to branch. If you want to use variables, use $00 - $07FF instead.

Code:
07:FF29:8E 10 FF  STX $FF10 = #$01

This is where you are breaking the game and you try to modify this address more than once in your code. You can't use $8000 - $FFFF as variable address, this area is write protected and it won't modify that number at all. What happens instead is that the mapper controller has the first write that you made and whenever it actually does get to the mapper controller code, the wrong bank gets switched. Because and again, it's a serial port. It takes 5 writes to switch a bank, you already made one or more of those writes. Believe me, that is what is happening. Others say it don't matter, but it does.

Again, use variables in $0 - $07FF range. Any that are unused. Use FCEUX's hex editor to find one but be careful.

Here is the spec for the MMC1 register you were writing to

Code:
E000h-FFFFh  Register 3
Bit3-0 Select 16K or 2x16K ROM bank (see Reg0/Bit3-2)
Bit4   Unused ?

Iron all that stuff out and we can proceed.




« Last Edit: October 28, 2010, 02:43:48 pm by Gil Galad »
KingMike
Guest
« Reply #14 on: October 28, 2010, 07:25:44 pm »

Quote from: Gil Galad on October 28, 2010, 01:33:10 am
Again, use variables in $0 - $07FF range. Any that are unused. Use FCEUX's hex editor to find one but be careful.
You can also use $6000-7FFF (cart RAM), but it's probably better to only use those if the game is already using it.
Pages: [1] 2  


Powered by SMF 1.1.4 | SMF © 2006-2007, Simple Machines LLC