📜 ⬆️ ⬇️

The story of the classic game hacking on Dendy or Contra with the spreadgan in the beginning

Since my last article , to my great surprise, you are interested. I decided to supplement its result, the hacked version of the game "Contra (J) [T + Rus_Chronix]", with a bit of functionality, at the same time showing "code injection" on NES. This time I will make the players start the game with the Spreadgun pumped in order to get it in the game, you need to select the "S" icon, and behind it the "R".



All interested welcome under cat.


Traditionally:


boring and tedious process video

And traditionally plan out a sequence of actions.


  1. Find the right addresses
  2. Find out the value of pumped Spreadgun
  3. Find out what he writes to these addresses at the beginning of the game.
  4. Rewrite ROM
    • Easyway - change the value of the base weapon on the pumped spread
    • Hardway - use full-fledged code injection if the easy way doesn't work.
  5. save the result to a new file

To search for addresses, we use the previously described method, but remembering that lives were in neighboring addresses, we will only look for the weapon of the first player in the hope that the second player will be near. After opening the "Ram watch" window after the start of the first level, we are looking for an unknown value. I assume that the base weapon is 0, but I don’t know for sure.
We run around the level, not running ahead, shoot in all directions and weed out changing values. Weapon has not changed.


Let's use another window option, or rather, in the "Compare To / By" field, select the radio number "Number of Changes" checkbox in the field 0. Type of comparison is of course "Equals to". The weapon still hasn't changed.


So jumping from the option "equal to the previous value", the option "the number of changes is 0" can reach about 10,000 addresses. When further screenings will reduce the list of addresses too little or not reduce at all, you can go far enough forward to knock out the first weapon.


Picking it up, we immediately use the search method "not equal to the previous value", and additionally reduce the list by searching for "the number of changes is 1", the weapon has changed exactly once.


In the place where we pick up our first weapon, the weapon amplifier also appears. By knocking out and picking it up, you can reduce the number of addresses you need to 1, but if you don’t succeed, just kill your character and thus change the weapon again. (Do not forget about the search after each change).


With the weapon booster there is some risk that it will change not the value of the weapon itself, but the flag elsewhere in the memory, but let's hope that the authors of the game saved memory and instructions. And, I was lucky the bonus "R" changed the very value of the weapon and the address was in the coordinate AA 16 . (I may be mistaken, but I repeatedly monitored the hero’s weapons in different versions of the Contra game, it seems that this address was AA 16 everywhere).


I also noticed that the bonus "R" increased the value in the address by 16 10 or 10 16 , that is, increased by 1 the first digit of the hexadecimal number.


After the restart in the "Ram watch" window, you can see that the base value is indeed 00 16 , the bonus "M" increased the second digit by 1, and the bonus "R" the first.


You can go for the spreading machine, it will definitely meet in this level, or you can change the value of the address to see which numbers and which weapon they are creating. Empirically, I found out that 01 16 is “Machinegun”, 02 16 is “Fire”, 03 16 is “Spreadgun”, and 04 16 is “Laser”. When you enter other values ​​in the second digit, various glitches appear.


If you reset the game and enter the value 1x 16 in the address (where "x" is any of the valid options) before selecting the "Rapid" bonus, you can find out that re-selecting the bonus does not change anything.


Now you can restart the game, start the game for two and try to change the addresses adjacent to AA 16 . (There are two of them, the search will not be long) After shooting the second player, I very quickly found out that the weapon of the second player is really kept nearby in the address AB 16 . And here we know the addresses we are interested in and the value that we should put there, it's time to find out what it writes to these addresses.


Having thrown a breakpoint at the recording of this address, I found out that the recording goes there several times and one of them after the splash screen. This entry makes the following code:


AddressOpcodeMnemonicArgumentsAX
C307A2 28Ldx# $ 28????
C309A9 00Lda# $ 00??28 or 29 or ... or F0
C30B95 00Sta$ 00, X0028 or 29 or ... or F0
C30DE8INX0028 or 29 or ... or F0
C30EE0 F0CPX# $ F00029 or 30 or ... or F0
C310D0 F9Bne$ C30B0029 or 30 or ... or F0

If you carefully read the game here it zeros the range of addresses from 0028 16 to 00F0 16 , obviously both addresses of interest in the range. So there will be no easy way. We'll have to use "Code Injection" and the simplest solution that I see to find out where we get here, redirect the execution to some free space from code and data, write my own version of the cycle there, which zooms in the whole range except the addresses 00AA 16 and 00AB 16 and the return carriage execution back. By the way, this is the most classic version of the injection. It is also possible to assume that we get here from the JSR instruction (Jump to SubRoutine), this is easy to check with the stack.


How the stack works

In 6502 processors, the stack is always located in the address range 0100 16 - 01FF 16 for all computers based on this processor, and grows from a larger address to a smaller one. There is a separate register pointing to the top of the stack, initially it is equal to FF 16 since the more significant byte never changes. The register itself always indicates the highest unused bytes of useful data.


The emulator debugger does not show the value of the "Stack Pointer" register, instead it shows the address to which the register refers and right now is the address 01F2 16 , some simple calculations show that the last data to go to the stack is C3 16 and C2 16 , which could impose the idea of ​​the address C3C2 16 but 6502 processor type "Little Endian" and therefore when you execute instructions and save the address in memory or stack, the less significant byte is written first. And if at the top of the stack is really the address is the address of C2C3 16 . And this is the address of the last argument of the JSR instruction, if again this is the general address. It is very easy to check, just look at what is written two bytes above the C2C3 16 address.


AddressOpcodeMnemonicArguments
C2C120 07 C3Jsr$ C307

As you can see, this JSR instruction is addressed to C307 16 , which means the assumption about the subroutine is true.


Now you need to write the injection code correctly, find a suitable place for it, write it to this place and redirect the JSR instruction to this injection.


Reference Sites

Opcodes , instructions , a lot of information about the 6502 assembly .


For this, it is very convenient to use a notebook, I have Visual Studio Code for it. Everyone has his own style of writing, personally, I first write the JSR instruction with its address, to know where to change and the full opcode, to know what to change.


After a couple of indents, I duplicate the cycle code, it can already be used without addresses, but it is extremely useful to see mnemonics with arguments besides opcodes, and it is useful to grab the instruction following the entire cycle, along with the address, to know where to return from the injection.


C2C1:20 07 C3 JSR $C307 A2 28 LDX #$28 A9 00 LDA #$00 95 00 STA $00,X E8 INX E0 F0 CPX #$F0 D0 F9 BNE $C30B C312:A2 07 LDX #$07 

Another couple of indents below you can write the code of the injection itself, in fact it is a duplicate of the cycle itself, with some additions.


 C312:A2 07 LDX #$07 A2 28 LDX #$28 A9 00 LDA #$00 95 00 STA $00,X E8 INX E0 AA CPX #$AA D0 F9 BNE -7 A9 13 LDA #$13 95 00 STA $00,X E8 INX E0 AC CPX #$AC D0 F9 BNE -7 A9 00 LDA #$00 95 00 STA $00,X E8 INX E0 F0 CPX #$F0 D0 F9 BNE -7 

For greater clarity, I instead of the address of the place of the jump indicated indentation.


If you could not read this code

Here, the same cycle, but divided into three parts, in the first cycle we compare register X with the value AA 16 , since we need to zero all addresses to address 00AA 16 , then put register 16 A in register 16 16 , the value of the spredgan is pumped and the second cycle is written it to the address 00AC 16 starting from which the rest of the range should again be zeroed. We return to the register A zero and zero the rest of the range.


It is extremely important to add a return instruction from the injection at the end.


 E0 F0 CPX #$F0 D0 F9 BNE -7 4C 12 C3 JMP $C312 

Now, for convenience, I prefer to write opcodes below in the file.


 4C 12 C3 JMP $C312 A2 28 A9 00 95 00 E8 E0 AA D0 F9 A9 13 95 00 E8 E0 AC D0 F9 A9 00 95 00 E8 E0 F0 D0 F9 4C 12 C3 

And having calculated opcodes it is easy to find out that there are 32 10 . Therefore you need to find 20 16 unused addresses on ROM. As a rule, unallocated addresses are large spaces of the same values, most often zeros or FF 16. Just such a big piece should be found, it must be at least 35 10 addresses, so that there is some stock.


In the "Hex Editor" I found such a range in B29E 16 -BFFF 16 . Using the beginning of such free sites for injections can be dangerous, therefore I advise you to write the code for injections at its end. The most convenient address to start the injection is BFE0 16 , but this is the address in the console’s memory. To find out where it is in the ROM file, right-click on it and select "Go Here In ROM File".


Now you can copy here the whole opcode (32 values). The final touch is to change the instructions


 C2C1:20 07 C3 JSR $C307 

to jump to the injection I have is BFE0 16 .


 C2C1:20 E0 BF JSR $BFE0 

Of course, having found the true place of the instruction on ROM.


The answer to the question of inattentive professionals.

The address of the JSR instruction gets into the stack, so regardless of the jump location, there will be the same return address, and from the injection we return to the JMP instruction that does not affect the stack in any way. Thus, the RTS instruction works in the same place as without the injection, and returns to the same place as without the injection. As a result, this injection does not break the work of the stack.


PS For good, you still need to do so that after death the characters are reborn with the pumped spreads, but I am sure that you will be able to cope with it yourself with the knowledge gained. I will turn my gaze to something else. Unity engine for example.


')

Source: https://habr.com/ru/post/447990/


All Articles