Chapter One ,
Chapter Two ,
Chapter Three ,
Chapter Four ,
Chapter Five ,
EpilogueDisclaimer
Scrolling through the image editor (it was very convenient to navigate between images in a folder) screenshots that the utility did for me, I ... woke up from the alarm clock. On the monitor screen shone another screenshot, which was on the list somewhere in the first third of the total. The question had to be resolved in some other way.
In the evening of the same day ...
')
Looking into the bank. Method number 2.
When we look at the hexadecimal data set in memory (as stated above, it is enough to look at the first 2 kB of data) - it is important to “look out” things that are important to us. But which ones?
Take the following obvious assumptions:
- Data is read from the ROM immediately prior to its use. Argument: there is little free memory, especially there is no place to store data, so we take them only when they are needed, and put them back if the need has disappeared;
- The game is simple, so the probability that the data is encrypted is rather small;
- The data is stored in an abstract form to save ROM, which is then converted into tile blocks when displayed on the screen.
How data of a room and / or level can be stored we cannot say with certainty. We can again assume that the structure is quite simple.
Based on these assumptions, let's try to imagine what we need to find:
- The data will be read from the ROM file at the time of transition between rooms or levels (when the screen is black);
- Data is read in small chunks (maybe even 1 byte) for translation into a block of tiles when displayed on the screen;
So we will look when we change the room or the serial number of the level. What changes when moving to another room, we do not yet know. Therefore, we will search for the cell in which the level number is stored.
How to search? You can apply the RAM Filter in FCEUXDSP, but you can see it with your eyes. I resorted to the second method, because I could not exactly suggest what to look for.
- First, the number can be stored as a number. If it is stored as a number, then the countdown is from 0 or from 1?
- Secondly, it can be stored as a pointer to some data structure of this level. Here with a swoop it will be difficult to find anything.
You can sort through these options, or you can save the state (Save state) before going to the next level, go, look again. And so several times. Frankly, this is a good luck method. If you are unlucky, then apply RAM Filter.
Develop eagle vision
Start the game, run to exit the level, save state, open Tools-> Hex editor, and go on ...
"Twitching" bytes immediately missed. Starting at about $ 80, the current image of the main character’s sprite is obviously stored. And the rest, in principle, is foreseeable. After a couple of passes from the first to the second, and then from the second to the third, the eagle's eye caught a cell of $ 70, the value of which changed from 0 to 1, and then from 1 to 2. Let's try to play with it.
We start the first level. We notice that in the cell it is really 0, change it by one and go down from the first room to the next one. But what is it? Getting into the lower room we fall into the abyss, which is already at the end of the second level! After the prince crashed, the game asks to click Start. We press and get to the beginning of the second level, as if we started with it.
Most likely, this is it. All this confuses only one thing: when we start the game in this cell is the number 9. If we have a countdown from zero, does this mean that we are at level 10? A little later, it turns out that this is true, but for now we need to find the bricks from which the level is being built.
Open the can with a can opener. Method number 1
I will not consider this method in its pure form, since it is still fun to study the code sheet without applying to the gameplay. Anyway, we will rest against a bunch of different procedures for playing back the background melody, drawing sprites, and other official procedures, but they are of little interest to us now. Only one procedure is needed (for now).
Open the debugger (Tools-> Debugger) and add a breakpoint.
We currently do not have a specific address where it could be put, but the debugger allows us to set a breakpoint not only at a specific address. Including we can make it stop when the memory cell (or range of cells) changes.
Add a point by clicking Add. We indicate in the window the address “70”, we note that we are interested in writing to this cell. We also indicate that we are interested in CPU Memory. Somehow call a point in the Name field and wait.

Indeed, when moving from the starting room to the first level, the game freezes and we transfer to the debugger.
$D0E6:A2 00 LDX #$00 $D0E8:86 15 STX $0015 = #$00 $D0EA:8E 35 07 STX $0735 = #$00 $D0ED:86 70 STX $0070 = #$09 ;;; <<<<<< $D0EF:8E 01 20 STX $2001 = #$18 $D0F2:CA DEX $D0F3:9A TXS
Frankly, there is no useful information here at all. Here you can see the reset of some variables and ... stack. After DEX, in X we have #FF, which is placed in the stack register, which means that we now have a maximum stack size, which means there is nothing there. If there is no information here, then we can use this point as a reference. From this moment we can go to Trace Logger, click Start, and wait until the first room of the first level is displayed on the screen. In addition, we can switch the breakpoint to
read from the $ 70 cell. While the data in the tracer will be read, we will see where we are accessing the cell.
Click Edit, and move the checkmark from Write to Read.
We get acquainted with the content
The very first hit will lead us here:
$CB2E:A6 70 LDX $0070 = #$00 ;;; <<<<<< $CB30:BD F5 EA LDA $EAF5,X @ $EAF9 = #$01 $CB33:8D BB 04 STA $04BB = #$00
It can be seen that the level sequence number is used as an index in an array of some data, from which a byte is extracted and placed in the $ 04BB cell. Here I propose to recall how our memory is organized and how our mapper works.
We remember that the last bank is located at $ C000- $ FFFF addresses, but the rest are dynamically placed in the previous 16 kB of RAM, that is, from $ 8000 to $ BFFF. This memory is immutable and it is taken from the ROM file. Consequently, any reading from cells with addresses $ 8000- $ FFFF can be considered as reading directly from a ROM file. Is that we have to translate the addresses of the AP in the ROM file offset. How to do it?
Arithmetic
If we hit the addresses $ C000- $ FFFF, then everything is extremely simple. At these addresses we have the last bank from the ROM file, which means it is the last 16 KB in it. Calculate: 16 (header size) + 8 (number of banks) * 16 kB (size of one bank) = 131088 bytes, which exactly corresponds to the size of the source file. We consider the shift in the bank:
$EAF5-$C000=0x2AF5
In the file, the bank begins with an offset:
131088 - 16 = 114704 = 0x1C010
Now, quite simply:
0x1C010+0x2AF5=0x1EB05
To bypass all these calculations, we simply find the difference:
0x1EB05-0xEAF5=0x10010
In the future, if we need to translate the address in memory into an offset in the file (this can only be done for $ C000- $ FFFF addresses), then we simply add 0x10010, otherwise we subtract.
With the rest of the banks will be a little more difficult.
Hit
So, open the hex editor, go to offset 0x1EB05 and see:
00 00 00 01 01 01 00 00 00 01 01 00 00 01 03 12 14 ...
Obviously, the last (of those specified here) three bytes are knocked out of a sequence of zeros and ones. Maybe this is a coincidence, but maybe not. We discard what is knocked out and count the remaining zeros and ones.
They are exactly 14. Levels are also 14. If you look at the code where execution stopped, then we see that the level number is an index in this sequence, which means that every zero or one is responsible for something that is characteristic of the whole level .
You can try to change the zero on one and vice versa. Personally, when I saw this sequence, I immediately remembered that from the first to the third level we have a dungeon, from the fourth to the sixth - the palace, then 7, 8, 9 again a dungeon ... Hmm. Yes, it's the appearance of the level!
We swing the first zero on one and run the emulator:

Indeed the palace, but it looks suspicious. A kind of underground palace.
Thus, we found plasterers, but did not find painters and builders. Apparently, in order to turn the dungeon into a palace, changing the zero to one is not enough.
We dig further. Click Run ...
$C0D5:A5 70 LDA $0070 = #$00 ;; A $C0D7:0A ASL ;; $C0D8:AA TAX ;; (!) $C0D9:60 RTS ;; .
Here is not so simple. In the index register, we place the doubled level number (once the index number, then again we will go to some array) and exit. We proceed further:
$C0DD:BD 56 EB LDA $EB56,X @ $EB56 = #$30 $C0E0:85 6D STA $006D = #$1C $C0E2:BD 57 EB LDA $EB57,X @ $EB57 = #$83 $C0E5:85 6E STA $006E = #$30 $C0E7:60 RTS
Again, read something at the addresses from the last bank and make it into $ 6D: $ 6E. Again we climb into the editor (
0xEB56+0x10010=0x1EB66
):
D9 82 61 86 91 89 F1 8C ...
We see that in $ 6D: $ 6E we should have # D9: # 82. We leave and from here:
$F225:B1 6D LDA ($6D),Y @ $82D9 =
Now we use $ 6D cells: $ 6E is used for indirect addressing (moreover, we have an index register Y and 0 in it).
Complicated arithmetic
We can look directly into the memory of the contents of the bank at $ 82D9 (and then find the search in the editor), but let's see. The bank number, as we remember from the bank switching procedure, is stored at $ 06D1. Now there is the number # 06, which means we have the bank # 6 turned on (the seventh one), that is, the penultimate one. Its beginning is in memory at $ 8000, so the offset of our cell is $ 82D9- $ 8000 = $ 02D9. We consider the offset in the file:
16()+6( )*16 ( ) + 0x2D9( ) = 0x182E9
This will be the offset in the file.
Porridge...
01 00 00 9E 1E 11 1E 1E...
Doesn't say anything, right? Then experiment.
By changing 01 to 02 we start to start from the room below. That is, the first byte of the sequence is somehow responsible for the room where the level begins. Going through the values, we will find ourselves here and there without any specific logic. Changing the next two zeroes, we do not get any result at all. And if we change the fourth byte from 9E to something smaller (02 or 03), then we start the game, then upon entering the level ... the guard immediately kills us!
Total nonsense. We can continue to press the button "Run ..." getting into different parts of the ROM-file, but at some point I was tired of this.
Make a combination
In order not to walk on the code, I decided to see what data was collected in the tracer from the moment of changing the $ 70 cell until the picture appeared on the screen. We (by the way!) Cannot be caught by the debugger when an image appears, so we’ll train our skills. This should be done on time, because if we are late, we will get a surplus, and if we hurry, we will get a shortage.
We climb in Tools -> Trace logger ...

We do not need the code read from the file, only data is needed. Put a check mark, select a file to save the data, and click Start Logging after we have a breakpoint on writing to the $ 70 cell.
After we stop collecting data, the output will be a file that is identical in size to the original ROM file, but it will contain only those data that were taken from the original file during the operation of the router. The remaining sections will be filled with zeros.
Obviously, we can skip the first two banks, since the tiles lie there, and also skip the data that we found during debugging. By trial and error I stopped at offset 0x18010.
Analysis
Of course, a more "direct" way is to analyze the code in the debugger, run through the procedures, watch what happens next with the data, but so far there is little experience and the straw is only one - cell number 70. Studying the data in any of the above methods, become more and more, and such rude methods can be resorted to less and less. In the end, we can limit ourselves only to a debugger and a hex editor.
But it will be later, but for now, ahead of the third chapter "The first lines of the code."