Konami codes w grze Contra (NES) - jak to zostało zaimplementowane?
Konami wypuściło grę arcade Contra w 1987 roku. Później stworzyło wersję na NES, która zyskała dużą popularność. Przez lata różne wersje tej gry, w tym japońska, północnoamerykańska i europejska, różniły się od siebie. W tej analizie omówimy kluczowe różnice między tymi wersjami oraz zanurzymy się w kodzie wersji NES na rynek USA. Dodatkowo rozważymy małe modyfikacje ROM-u, które mogą zwiększyć poziom zabawy. Wersja Famicom zawiera intro z fabułą, mapę aktualnej lokalizacji, animacje i przerywniki, których brak w wersjach wydanych za granicą. Warto zauważyć, że płyty i chipy używane w tych kartridżach są różne, co znacząco wpłynęło na jakość i zawartość gry. Można wręcz argumentować, że wersja Famicom Contra działa na lepszym sprzęcie, a oprogramowanie w pełni z tego korzysta.
Podczas analizy wersji USA skupimy się na Kodeksie Konami. To klasyczny sposób wprowadzania cheatów, który znacząco zmienia rękę gracza. Wystarczająco wpisując odpowiednie kombinacje przy tytule, gracz zaczyna z 30 życiami zamiast standardowych 3. Dziś przyjrzymy się kodowi, który przetwarza Kodeks Konami i zrozumiemy, jak gra rejestruje wprowadzenie kombinacji. Jako ciekawostkę, kod ma zaledwie 17 linii, ale pomoże nam we właściwym zrozumieniu zaplecza kodu.
Podczas gdy analizujemy wartości w RAM, skupimy się na kilku konkretach, które będą pomocne w zrozumieniu pozostałych elementów kodu. W kolejnych sekcjach przyjrzymy się wartościom RAM, takim jak F1 i F5, które śledzą inputy gracza oraz wiele innych parametrów, które pomagają w aktywacji Kodeksu. Aby wyjaśnić, wartości w RAM są podzielone na różne obszary. Każda naciśnięta kombinacja przycisku aktualizuje liczby i na podstawie ich wartości kod Kodeksu jest weryfikowany. Co ciekawe, вводząc błędny przycisk, gra po prostu ustawia parametr do FF, przez co reszta weryfikacji zostaje pominięta.
W trakcie analizy omówimy także różnorodność broni i ich działanie, a tym samym zostaną wprowadzone pytania o to, jak przyspiesza się tempo ognia. Power-up „R” zmienia broni, ale efekty są różne w zależności od zastosowanego oręża. Oglądając szybkości strzałów i ich zmieniając, można zauważyć, jak zmieniają się wartości w RAM, co pokazuje, że wideo jest kolejną zasobem do stworzenia modów. Można także zmienić wartość ognia broni, co wprowadza dodatkowe efekty wizualne, a także potrafi wpłynąć na detekcję kolizji. Takie zmiany mogą wprowadzać błędy w grze, które wynikają z nadmiernych prędkości.
Na koniec warto zauważyć, że film od Displaced Gamers przyciągnął już 239020 wyświetleń i 7995 polubień w momencie pisania tego artykułu. To świetna okazja, aby zgłębić wtajemniczenia w świat retro gier, a także skorzystać z zawartych w filmie wiedzy. Dla fanów retro oraz kodu, Contra pozostaje nie tylko klasyką, ale także niezgłębianą wciąż historią kodu, która zasługuje na uwagę.
Toggle timeline summary
-
Konami wydało grę arcade Contra w 1987 roku.
-
Popularyzowana wersja na NES, która ma unikalną historię.
-
Istnieją wyraźne różnice między japońskimi, północnoamerykańskimi i europejskimi wydaniami.
-
Prelegent podkreśli różnice i zagłębi się w kod NES dla Północnej Ameryki.
-
Wersja Famicom zawiera wprowadzenie fabularne, mapę lokalizacji, cutscenki i animacje.
-
Te funkcje są nieobecne w zagranicznych wydaniach z powodu różnic w sprzęcie.
-
Różne kartridże wykorzystywały unikalne płytki obwodowe i chipy.
-
Dodatkowe wykończenie wersji Famicom nie zostało uwzględnione w wydaniach zagranicznych.
-
Wersja europejska, nazwana Probotector, ma roboty zamiast ludzi.
-
Wprowadzenie ikonicznego Kodu Konami, który daje graczom 30 żyć.
-
Gracze wprowadzają określoną sekwencję przycisków, aby zrealizować cheat.
-
Prelegent dokładnie przeanalizuje kod do aktywacji Kodu Konami.
-
Kod assemblera dla Kodu Konami składa się z 17 linii.
-
Rozpoczyna się wyjaśnienie wartości RAM związanych z wejściem gracza.
-
Omówiono zrozumienie wartości binarnych w RAM i ich znaczenie.
-
Jak weryfikacja wejścia gracza odbywa się poprzez odniesienie do lokalizacji ROM.
-
Podsumowanie śledzenia kodu w celu walidacji użycia Kodu Konami.
-
Opis wzmocnienia szybkostrzelności i jego wpływu na rozgrywkę.
-
Monitorowanie wartości RAM w celu analizy szybkości strzału broni w grze.
-
Podkreślono różne szybkości strzału dla każdej broni.
-
Badane są zmiany szybkości broni i ich wpływ na rozgrywkę.
-
Prelegent kończy z nadzieją na przyszłe filmy na pokrewne tematy.
Transcription
Konami released the arcade game Contra in 1987. Later, they created a port for the NES that is arguably more popular. The Nintendo 8-bit port has a very interesting history to it, as the Japanese, North American, and European releases are distinctly different from one another. I'll highlight just a few of the differences, and then dive into the code behind the North American NES release. We may even start a small ROM modification that boosts the fun factor along the way. In addition to its US counterpart, the Famicom version features an intro with a story, a map with your current location, cutscenes between levels, and numerous animations throughout gameplay, such as palm trees blowing in the wind, or the squirming of the alien lair. These features weren't present in the overseas releases. The circuit board and chips used for the cartridges are different. Konami's own memory mapper chip, the VRC2, was not used in the overseas releases, and the extra polish present in the Famicom version was not included in order to accommodate this difference. This is an often overlooked component of NES games. It isn't only about what the machine's hardware alone can do, or the programming trickery developers pull off. Many games use different mappers to help overcome limitations. With these different cartridge components that separate the different versions of Contra in mind, you could arguably say that the Famicom version of Contra runs on superior hardware, versus the NES version, and the software takes advantage of this. As one last bit of trivia, the European PAL release was named Probotector, and the humans were replaced with robots. Now, let's dive into some behind-the-scenes fun with Contra by way of the US version. First up is the Konami Code. We are going to look at the code behind the code. The Konami Code is iconic, and many gamers first used it with Contra. At the title screen, input the following buttons in order to start with 30 lives instead of only 3. Up, Up, Down, Down, Left, Right, Left, Right, B, A. People will often append Start or Select Start to the end of the code when reciting it. I want to dissect the activation of the Konami Code for Contra by showing how the game keeps track of input validation, and finally how it activates the cheat. We may also clear up some misconceptions about the code. The assembly code used to process Konami Code input is 17 lines. I'll walk you through all of it, but it is easier to follow if I first give a basic overview of what it does. Okay, let's set up our screen. I'll place Contra on the left. We need to watch some values in RAM, but I don't want to show the full RAM watch window, it takes up too much space. I've placed three specific values on the right side of the screen. The topmost RAM value is for Player 1 input. There are multiple places in Contra's RAM that indicate this. For the moment, we'll look at F1. If I start the game and begin to press buttons, you can see the value at F1 changes based on which buttons I pressed. These numbers aren't arbitrary. Let me explain. There are 8 bits here, which is a byte. In our case, the byte is actually split into two half bytes, or nibbles. Four bits each. Ideally, you are familiar with binary. Our RAM window here represents two 4-bit values next to each other in hexadecimal. It is their binary value, however, that tells the story. If I press up, the value on the right side turns into 8. That is 1, 0, 0, 0 in binary. But why 8? In a previous video, we examined the circuits of an NES controller. It uses an 8-bit shift register that clocks out the button presses in a particular order. If we split this order of button presses into 4-bit values, the numbers from those results correspond to the numbers we see in RAM. Since I pressed up by itself, the corresponding bit for up turns into 1. No other bits were pressed, and they remain 0. Up and right at the same time results in a binary value of 9 on the right side. A and B at the same time results in a binary value of 12 on the left side. Pretty simple stuff once you know where the numbers come from. Now that you understand the correlation between the NES controller and the values we see in RAM, let me show you 10 consecutive values found in the ROM. If we translate these 10 hex values into controller inputs, they are up, up, down, down, left, right, left, right, B, A. So we have a location in the ROM that holds our desired button combination. The code that checks player input on the title screen can reference this area when verifying the Konami code. Naturally, the order of values in the ROM correspond to the order of input values you need to press. With this knowledge, you could edit the ROM file at this location and change the Konami code if you so desired. Without making any changes to the program's code, you want to make sure your custom version still uses 10 inputs. For this controller input explanation, we monitored RAM address F1. When the Konami code is validated, however, a snapshot of user input is temporarily placed in RAM at address F5. This is what the Konami code validation checks, so I'll change the value we are monitoring to F5 to set us up for our code walk. Now for the remaining two addresses you see. The sender value of the three is a counter for how many buttons we have pressed correctly when entering the Konami code. If I begin to enter the code correctly, you can see the number increases by one for each correct input I provide. If I press the wrong button during code entry, the value is set to FF. I'll explain why later on. Finally, the value on the bottom represents a flag that is set to 1 if the 30 lives code has been activated. The program will change it to 1 once all 10 inputs have been entered correctly. Let's place all of this on the right side of the screen and set up our code walk. I've placed the accumulator A and the index Y to the left of Contra. These are used to hold the values we are interested in manipulating and comparing. You'll see movement between these boxes and our RAM addresses during code validation. On the left side of the screen is a segment of code I have isolated for us to examine. This code verifies the Konami code. Each input pressed passes through this section of assembly. Let's walk through each line as it executes just after I have pressed the first input for the Konami code, UP. Some code will be skipped. We'll come back to those statements later. Time to walk all 17 lines of code, so get ready. If you look inside the code window, on the left we have the address for the code. To the right of that are the hexadecimal values that represent the instructions in opcode form. To the right of those are the same instructions in assembly code form, with addresses and values as provided. Let's begin. LDY3F loads the number of successful Konami code presses we have made so far into register Y. I'll execute that statement, and Y is now 0, because no presses yet. We are just now processing our first input. BMI branches to a different place in the code, memory location C350 specifically, if the most recent value we manipulated is negative. Y is not negative, so we just move to the next line. LDA, as you may have guessed, loads a value from memory F5 into A. F5 shows the input we pressed. I'll execute the instruction, and now we've loaded up our UP press into A, our accumulator. So we have loaded two main values we need to examine and manipulate into Y and A. Pretty simple so far. The next statement, ANDCF, performs a bitwise AND operation between the value in the accumulator and the binary value for CF. That was a bit of a mouthful. Let's break this down. If you take two binary values and AND them, the result can only be 1 if both values are 1. If either of the two values is 0, the result is 0. So why are we performing an AND operation between the user input currently stored in our accumulator and CF? Let's return to our controller input screen. We pressed UP so the bits for each input are set to 0, except for the bit for UP, which is set to 1. If we AND the controller input value with the bits for CF, we get the same input value, 8, the value for UP. So the value of the accumulator A does not change. But notice that CF has two bits that are 0 for the two columns associated with the controller inputs of SELECT and START. So if I pressed one of those buttons, what would happen? The AND operation between my SELECT or START bits and the corresponding bit values of 0 in CF will turn the accumulator value into 0. Essentially, the AND and CF updates the accumulator so that any presses of SELECT or START are erased before the next statement is executed. That next statement, BEQC350, branches to another place in code if the resulting value in the accumulator we just ANDed with CF is 0. In other words, it skips the rest of the Konami code check for the current input if that input was SELECT or START. I'll say it again, SELECT and START are ignored in the Konami code input validation sequence. So if you power up Contra and press UP-UP, DOWN-DOWN, LEFT-RIGHT, LEFT-RIGHT, and then sprinkle some SELECT presses in before finally pressing B then A, the code will still activate because SELECT is considered irrelevant when verifying the Konami code. The next statement is CMPC351, Y. It looks like a lot. It looks complicated. It isn't. Ignore the Y at the end of it for now. Let's talk about CMP and the memory address next to it. The CMP instruction means to compare memory with the accumulator. We know the accumulator is 08, or in controller speak, UP. So we are comparing the value at memory location C351 to our input value of 8. Memory location C351 contains the first input expected for the Konami code. We looked at the 10 values for the code earlier in that snippet from the hex editor. UP is 08. The first value we expect is 08. So we have a match. Some of you may have noticed that our current instruction is at location C33B. Shouldn't the C351 location with the Konami code be nearby? You are correct. If you look further down in the debugger code window, you can see the start of the Konami code at C351. Two UP values, followed by two DOWN values, etc. The debugger attempts to translate it into assembly code on the right side of the code box for our benefit, but we know that these locations don't contain code to execute, they contain our lookup values for our Konami code input sequence. Now, let's talk about that Y at the end of the instruction. Y serves as an offset here to help move us through our code lookup area that starts at C351. Remember that it holds the number of successful inputs we have pressed so far. Since this is our first button press, we start at an offset of 0, C351. The next time through the loop, we have pressed one button correctly, so Y is 1, and we move to the next lookup location, C352. A second check for UP. The next time through, when Y is 2, we move to C353 and check for DOWN. You get the idea. Y helps move us through our Konami code lookup table. Let's return to our first input press of UP and continue our code walk. We know that our input and the expected value are equal, so our BEQ, or branch of equal statement, skips us ahead to the instruction at C345. INY increments the value in Y by 1, so Y changes from 0 to 1, adding 1 to our number of successful input presses. STY stores the value in Y back to memory location 3F. We've saved it to RAM. CPY0A compares the value in Y to the hex value of A. This statement checks to see if we have pressed all 10 inputs for the Konami code correctly. BCC underneath it takes us to location C350 if the result from the statement above is not equal. Since the two values are not equal, we skip ahead to C350. RTS stands for return from subroutine. We are done with our code check for our current input. That was a lot. I hope it was easy to follow. Now that we have walked through it, we can take a look at a few of the things I skipped over. Let's back up a few statements and look at that CPY0A statement again. When we finally reach the 10th successful input, when the user has pressed the A button, the instruction CPY0A compares Y to A, and this time through, Y is equal to A. The Konami code entry is complete. Instead of the next statement jumping us ahead to C350 as usual, we execute the lines underneath. LDA01 loads a 1 into the accumulator, and STA24 stores that 1 in the accumulator into memory address 24 hex. That sets the Konami code activated flag to 1. This is the location in RAM that Contra looks to to see whether or not to set the lives to 3 or to 30 when you start or continue. One more code entry item to tackle. Do you remember what happens if you press the wrong button during Konami code entry? The compare statement that checks your input against the expected input would not skip you ahead, and Contra will load FF into your input counter value in RAM. From this point forward, FF is loaded into Y in the first statement of the Konami code check, and the BMI statement, branch of negative, skips the rest of the Konami code routine because FF is considered negative. Wait, why is FF considered negative? Isn't FF in hexadecimal the same as 255 in decimal? Well, yes. However, you could use the 8-bit range of 0 to FF to hold signed numbers. Negative values. If, as the programmer, you elected to treat the value as signed, the ranges would look like this. Obviously, you can't count up to 255 because a chunk of the hex values that would let you do that now represent negative numbers. In our case here, FF is treated as a negative value and indicates that we should skip past the Konami code check. Negative numbers will come into play a bit more in our next Contra segment. And that finishes up our code trace. That was a lot. I hope it wasn't too difficult to follow, and that it also gave you some insight into the code behind the Konami code. Alright. We had some fun with the Konami code. Let's get into something that affects gameplay and doesn't require as much assembly knowledge. One of the power-ups in Contra is labeled R. This will often prompt an observer to ask, what is R? To which the player responds, rapid fire. A few seconds later, that same observer may ask, what does rapid fire do? Perhaps it doesn't have as obvious of an impact on the weapon speed as one would expect, but it does speed up all weapons except the laser. As to how much it speeds up the rate of fire, let's take a look. For this examination, we'll monitor a few RAM values. Address AA holds the weapon information for player 1. I can change this on the fly by editing the RAM. 0 is the regular gun. 1 is the machine gun. 2 is the fireball. 3 is spread. And 4 is laser. If I place a 1 in front of them, it activates rapid fire. With the exception of the laser. Contra is zoomed and cropped in the top right corner. In the bottom half of the screen are 20 locations in RAM. Each column represents a bullet fired by player 1. The top row indicates how far to move the bullet across the screen horizontally for each frame after it is fired. The bottom row does the same, but on the vertical axis. If I fire the default weapon once to the right while standing, you can see that the horizontal position is set to update at a rate of plus 3 pixels per frame. To help maximize sprites shown on the screen at a time, Contra animates the bullets at 30 frames per second. Using slow motion, we can see that this bullet is not shown every other frame. It moves 3 pixels to the right and is not shown, and then moves another 3 pixels to the right and is shown. So each time we see it, it has moved 6 pixels to the right since we last saw it. Earlier, I mentioned letting an 8-bit value represent negative numbers. This certainly comes into play here. We need these negative values since we can fire opposite directions on the same axis. If I face left and fire, the X position changes now FD, or negative 3. It makes sense that the travel speed would be the same, but it needs to update at a rate of minus 3 because it is moving left this time. The vertical position change works the same way. Positive values when firing down, and negative values when firing up. If I fire down and right at the same time, both the horizontal and vertical values have a rate of change. Multiple shots need multiple columns, up to 10 at a time if the weapon allows it. So there is your basic operation of moving the bullets across the screen. If I change the current weapon to be the default, but with rapid fire, the rate of change on the horizontal axis when firing to the right is now 4 instead of 3. So you can see that rapid fire does make a difference. I appear to be limited to 4 bullets at a time, even when using turbo. Let's check out the remaining weapons while facing right. If I change the weapon to the machine gun, the rate of fire is the same as the default weapon, but I can now fire up to 6 on screen when I hold down the B button. The fireball is the slowest and has a firing rate of 1. Rapid fire increases this to 2. Spread is unique as it fires 5 bullets at a time, and they have different rates of travel. Spread moves horizontally at a rate of 2 when facing left or right. As for the vertical change, it depends on the bullet. There is an extra bit of vertical logic here, but I won't go into detail on it. Rapid fire increases the horizontal change of spread from 2 to 3. For the laser, the horizontal rate of change is 4. Manually setting the weapon to rapid fire laser makes no difference in firing rate. You'll also notice that the laser consists of 4 shots that are graphically stitched together. Note that stage 2 and stage 4 are a little different than what I just described due to their pseudo-3D approach. So with that simplified background on the rate of fire of Contra in mind, the question is, can we change these rates? And the answer is yes. The way Contra determines the horizontal and vertical rate of change when the player fires a weapon is by looking up the numbers it should use. Just as we looked at a particular location earlier when we compared the Konami code to NES controller inputs, we do the same when it comes to finding the horizontal and vertical rate of change for the player's weapon. The ROM location we use to retrieve the rates is different depending on not only which weapon we have, but also the player's orientation. Standing while facing left loads a set of values from a different location in our table versus standing while facing right. Even facing left and pointing up loads from a different location versus facing right and pointing up. And with all of these unique addresses in mind for the various directions to fire a single weapon, they all change when you acquire a different weapon, or if you acquire rapid fire. An exception to rapid fire is, of course, the laser, which references the same addresses regardless of if you have rapid fire or not. Since we know the address locations in the table that holds the horizontal and vertical rate of fire, among other things, we can alter the speed of the weapons. I altered the speed of the horizontal rate of change when standing and facing right for all of the rapid fire weapons. Let's check them out. The default weapon. The machine gun. The fireball. This one is rather interesting because our change had the side effect of altering the results of the formula that determines the shape of the weapon's fire. Spread. I didn't modify the vertical rate of fire, so Spread's shot is not as tall. And the laser. The four rectangular sprites are a bit separated thanks to this change. There are a few side effects that will result from messing with the rate of fire. Collision detection and screen edge detection are likely to fail, and these result in enemies being missed or bullets wrapping around to the other side of the screen. So if these modifications were to become a completed mod of some sort, you'd want to make sure to alter all of the necessary places in the table for each direction you can fire for any weapon you modify. Take a look at the boundary code for the bullets and dive into the memory locations and bullet behavior for the base stages as well. Or you could play it and just let everything go nuts. Well, that was a lot of Contra talk. I hope you have enjoyed our dive into the plumbing of the Konami code as well as the analysis of how the rapid fire modifier works. I plan on making more videos like this one, so I hope to see you again in the future. Thanks for watching.