📜 ⬆️ ⬇️

[NES] Writing level editor for Prince of Persia. Chapter Four He himself runs! Or a skeleton in the closet

Chapter One , Chapter Two , Chapter Three , Chapter Four , Chapter Five , Epilogue

Disclaimer

If we are editing a game, we must align all its details. It would be foolish to show your version of the game to the world if fragments of the original version come out of it, conflicting with the new meaning we have invested in it. Editing the levels of the game does not allow you to change some of the plot twists that the developers initially invested.

With a slight delay, we will correct the plot.

')
What are we going to edit?

Before moving on, I would like to limit myself in the redrawing of the game. Indeed, if we want to change everything and everything, then it will be easier to just write our game from the very beginning. But as originally planned, I still want to do something new, retaining, however, the old base. We will be defined with what we will edit.

I decided to leave the text, music, views and configuration of the blocks in their original form in order to preserve the spirit of the game. This can be changed as easily as everything else, since all objects are tied to arrays of pointers to fairly simple data.

Mouse, by the way, I left in its place.

Code

Dynamic elements of the game is not so easy to change simply by enumeration, as we did earlier. Accordingly, it is time to actively study the code. With some of its elements, we met. Now, let's see what happens during the game.

By pressing the “Step over” button in the debugger, we will inevitably move on to the main loop, where its main component looks like this:
label_CC1A: $CC1A:20 F7 F2 JSR $F2F7 $CC1D:20 10 D0 JSR $D010 $CC20:20 1E 86 JSR $861E $CC23:20 00 CB JSR $CB00 $CC26:20 E8 A4 JSR $A4E8 $CC29:20 10 D0 JSR $D010 $CC2C:20 04 8B JSR $8B04 $CC2F:20 10 D0 JSR $D010 $CC32:20 F9 80 JSR $80F9 $CC35:20 FC D8 JSR $D8FC $CC38:20 DF BA JSR $BADF $CC3B:20 00 CB JSR $CB00 $CC3E:20 12 9F JSR $9F12 $CC41:20 DD A3 JSR $A3DD $CC44:4C 1A CC JMP $CC1A 

Above this cycle there is also a call to a multitude of procedures that are called when moving between rooms or levels. The procedures listed in the above code change the state of the game, as well as perform various kinds of checks. Of these, the following are not of interest:
- $ F2F7 - waiting for a change in a certain state in the interruption of VBlank;
- $ D010, $ CB00 - switching banks before calling procedures that are located in the corresponding bank.

I will not give the code of all the remaining procedures, since their consideration with all the branches will result in another ten posts. I will cite only that which is of interest now. I will not translate into pseudocode, but I will give comments.

A4E8. He himself runs!

Before calling this procedure, the procedure for switching on the bank # 02 - $ D010 is called. We turn on this bank by writing the number # 02 to the cell $ FFF2 and proceed to this procedure:
 $A4E8:AD E4 04 LDA $04E4 = #$01 $A4EB:D0 13 BNE $A500 ;;    $04E4. ;;   0,      $A4ED:60 RTS ;; ...  $A4EE:4C FF A4 JMP $A4FF $A4F1:A9 00 LDA #$00 $A4F3:8D E3 04 STA $04E3 = #$44 $A4F6:8D E0 04 STA $04E0 = #$76 $A4F9:8D E4 06 STA $06E4 = #$0A $A4FC:4C 14 DC JMP $DC14 ;;         ;; ,       -  . $A4FF:60 RTS ;;      $DC14. ;;             label_A500: $A500:AD 0A 04 LDA $040A = #$01 $A503:29 30 AND #$30 $A505:D0 EA BNE $A4F1 ;;       , ;;        $A507:AE E3 04 LDX $04E3 = #$44 ;;        $04E3 $A50A:BD 0F A7 LDA $A70F,X @ $A763 = #$AA ;;    ROM- ,     $04E3 $A50D:C9 FF CMP #$FF $A50F:F0 E0 BEQ $A4F1 ;;      #FF, ;;        $A511:CD E0 04 CMP $04E0 = #$76 ;;        $04E0 $A514:D0 0E BNE $A524 ;;   , ... (. ) $A516:A9 00 LDA #$00 ;;    ... $A518:8D F4 04 STA $04F4 = #$00 $A51B:8D E0 04 STA $04E0 = #$76 $A51E:EE E3 04 INC $04E3 = #$44 ;;      2 ( ) $A521:EE E3 04 INC $04E3 = #$44 label_A524: $A524:BD 10 A7 LDA $A710,X @ $A764 = #$01 $A527:8D 0A 04 STA $040A = #$01 ;;      ROM-  $040A $A52A:EE E0 04 INC $04E0 = #$76 ;;    $A52D:60 RTS ;;  


So, here we have a couple of cells with some states, another cell with an index, and a certain array in the ROM file. From the code it can be seen that it is executed only if the flag is set in the $ 04E4 cell. Let's try to put it.

We start the game again, enter the first level and set it to $ 04E4 unit.


A second passes and ... he runs himself! Even the buttons do not need to press.
If we wait for Demo play, we will find that there is already a unit in the $ 04E4 cell, and if we reset this cell during the demo, the demo play will disappear and control will pass into our hands. Very convenient: the demo passes the main part for us, then we can reset $ 04E4 and continue the game on our own.

Now we can, relying on the game, parse the code. Obviously, the $ DC14 procedure sends us to the game menu. The $ A70F array stores some structures two bytes each, and ends with the #FF marker. Moreover, the first byte is a certain time interval, at the completion of which the index increases, and the second byte from the following structure is transferred to the $ 040A cell. What does he mean?
Take a look at the array:
64 00 . A0 01 . 28 00 . E6 84 . E6 02 ... FF
At first we expect # 64 u.e. (conditional time units), then # A0 Iu, then # 28 and so on until the #FF marker. Let's change the time in the first pair of bytes to #FE and in demo play we will find that the character stands like a graven image after the start for a long time. If we put # 02 there, then it will turn to the left and it will be a bit to hammer into the wall for some time. Therefore, the second byte describes the action that it will perform during the specified time interval. Since the game is deterministic, we only need to rigidly define a sequence of certain actions, and it will be “played” by itself.
In fact, the second byte is an imitation of pressing the buttons on the gamepad, where the first bit encodes the button to the right, the second bit to the left, and so on. The combination of these bits determines the pseudo-state of the gamepad during demo-play. By editing this array, we can adjust the demo play to our new level.
If we monitor what happens then with the value in the $ 040A cell, then we will come to the data structure, which is located at the address $ 060E. It is described as follows:
 struct CHARACTER { char bCharType; unsigned short X; unsigned short Y; unsigned short ptrAction; char bDirection; char bActionIndex; char bPoseIndex; char bReserved[4]; }; 

Having reached this structure, we learn that there is an array of “actions” consisting of pointers to certain structures, which then unfold into a sequence of sprite displays on the screen. The “action” means what the character performs on the screen: running, jumping, crouching, and other actions. Similarly, everything with the index of “posture”: whether it stands or sits, is determined by this member of the structure. We will need this structure in the future, but for now we need to figure out how we get from level to level.

Running through the maze

Here, as before, we will have a starting point - a cell of $ 70. When moving from level to level, we will see the code (as before, we catch at the breakpoint by the condition of writing to the $ 70 cell):
 $86F3:AD 35 07 LDA $0735 = #$00 $86F6:D0 0D BNE $8705 $86F8:A5 70 LDA $0070 = #$00 $86FA:18 CLC $86FB:69 01 ADC #$01 $86FD:C9 0E CMP #$0E $86FF:90 02 BCC $8703 $8701:A9 00 LDA #$00 $8703:85 70 STA $0070 = #$00 ;; <<<  $8705:A9 00 LDA #$00 $8707:8D 01 20 STA $2001 = #$18 $870A:85 15 STA $0015 = #$18 $870C:4C 1D CB JMP $CB1D 

Here we see that a unit is added to the $ 70 cell, and then we move somewhere to the last bank. Moreover, there is a check: if the number is greater than 13, then reset this value. This means that if we set the output at level 14, we will move back to the first one. This is the initiating procedure to proceed to the next level. Then we will show the password and we will find ourselves in the next level. Let's see where it comes from.

Hardcode

As in the x86 architecture, so here, the procedure call occurs by entering the argument JSR instruction into the Instruction Pointer register and placing the return address on the stack. In the stack we have the following:

22, CC, ...
Consequently, the instruction in front of the instruction at $ CC22 caused us:
 $CC20:20 1E 86 JSR $861E 

It is cumbersome and uninteresting to trace all of it. Switch the breakpoint from writing to $ 70 to read and start debugging. First, we will stop at the familiar $ C0D5 procedure - it is not interesting for us, but the next stop will lead us here:
 $D0B8:A5 70 LDA $0070 = #$0B $D0BA:C9 0B CMP #$0B $D0BC:D0 0C BNE $D0CA $D0BE:A5 51 LDA $0051 = #$16 $D0C0:C9 16 CMP #$16 $D0C2:D0 06 BNE $D0CA $D0C4:20 10 D0 JSR $D010 $D0C7:4C F3 86 JMP $86F3 

The value in our cell is compared with the number # 0B == 11 (recall that the levels are numbered from zero), and then comes reading from cell $ 51, where we have # 16 = 22. Based on the fact that we are in room 22 ( if you look at the level map), then at $ 51 we have the room number we did not look for last time. If these checks are performed, then we go to the address $ 86F3 - that is, the procedure that initiates the transition. Developers tritely stitched into the code to go to another level from this room, if the character leaves the room to the left.
We can find a similar test if we look for a transition from the sixth level to the seventh, when a character falls into the abyss. That is, it is enough for us to edit the instructions of the CMP, and these properties will have a different room. This is the only condition that must be met: the transition to the next level will take place if we go left (for this check) or fall down (for the second).

After studying the launch code of the new level, we will also see that for the first level, the coordinates of the character are also overwritten with the values ​​strictly specified in the code, so we could not move it when we changed the level header at the beginning of our research. Similar checks are performed after the defeat of the guard at level 13, opening the way for us. All of them are quite simply in a similar way.

We take out the skeleton from the cabinet and paint the walls

The editor is almost finished. Studying the transitional moments from level to level or from room to room, we will find the remaining parameters that can be edited. Since the approaches to the search for such data have already been considered, I will not dwell here in detail. I will give only a general description, and at the end of the last chapter I will give a list of shifts in ROM to the structures of the game and their description.

0x13BEB: 00 00 24 00 00 00 00 00 00 00 00 24 00 00 - an array containing the code for the set of tiles with which we will draw the guard (the guard or the skeleton itself). This structure is similar to the one that describes the kind of level, and it is similar to the one that determines the amount of health in the level.

The palette is built from two arrays of pointers and the actual array of the palettes themselves. The palette itself is stored exactly in the form in which it is sent to the PPU as a sequence of 32 bytes. The second array contains only 7 pointers, which lead us to the palettes that are acceptable for the level, and the first pointer is equal to the sixth one. And the first array consists of 14 pointers (according to the number of levels), each of which leads us to one or another element of the second array.

Almost ready


At this stage, I have already sketched an approximate shell for the editor, only one minor detail remained. Later, I'll tell you what the magic numbers in the status line mean.

In the fifth and final chapter, called “Reflection,” we will try to learn how to manage the prince’s reflection. From the secondary element, which had virtually no effect on the course of the game, we can turn it into one of the main ones, without which it would be impossible to complete the game, and the prince would not be so alone in those cold walls in which he was enclosed.

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


All Articles