The game hangs on certain irregular room links

Discuss PoP1 for DOS here.
Post Reply
David
The Prince of Persia
The Prince of Persia
Posts: 2850
Joined: December 11th, 2008, 9:48 pm
Location: Hungary

The game hangs on certain irregular room links

Post by David »

Make a level with one room and set the "room below" and the "room to the right" links to point to the current room.
The game will hang after you enter this room.
It seems to me that the hang can be delayed or disabled by putting floors into the room. Sometimes the game hangs when the kid is about to jump off the floors.

I noticed this bug originally in the DOS version, but it is also present in other versions:
Ths SNES version is also affected by this bug.
I also tried this in the Mac version (using the Total Pack), and it also hangs!
Then I tried the Apple II (using this), and it also hangs.
The level format of the following versions is the same as that of the DOS version. I overwrote the original level 1 with the edited using a hex editor.
The Atari ST version also hangs.
The C64 version also hangs.
The SEGA-CD version also hangs.

So it looks like this bug was carried over to many versions.

If I convert the level to PoP2 (DOS), it does not hang.
User avatar
Norbert
The Prince of Persia
The Prince of Persia
Posts: 5746
Joined: April 9th, 2009, 10:58 pm

Re: The game hangs on certain irregular room links

Post by Norbert »

David wrote:Make a level with one room and set the "room below" and the "room to the right" links to point to the current room. [...] It seems to me that the hang can be delayed or disabled by putting floors into the room. Sometimes the game hangs when the kid is about to jump off the floors.
Interesting. I've tried this and I think only the room below needs to point to the current room.
As soon as the prince moves to a column that doesn't have a tile (does not need to be in the row the prince is on), the game hangs.
After the game hangs, if you wait 36 or so seconds, you hear the prince scream and then you can either keep playing (even though the screen doesn't update; probably also depends on what your room looks like) or press Ctrl+r to restart and see the main screen with different colors. Then, when you enter level 1, the prince has 232 or 132 lives. (The number of lives may be random, I've just tested it twice.)
David
The Prince of Persia
The Prince of Persia
Posts: 2850
Joined: December 11th, 2008, 9:48 pm
Location: Hungary

Re: The game hangs on certain irregular room links

Post by David »

Norbert wrote:Interesting. I've tried this and I think only the room below needs to point to the current room.
You're right, only the "down" link is needed.
I started with all directions linked to itself, and removed each until the hang disappeared; then I put that back.
Norbert wrote:After the game hangs, if you wait 36 or so seconds, you hear the prince scream
This time becomes shorter if you increase the cycles in DOSBox.
Norbert wrote:and then you can either keep playing (even though the screen doesn't update; probably also depends on what your room looks like)
If I press shift-L a few times, the colors of the room (if it's not empty) change to palace colors - I guess that's when I reach level 4.
Norbert wrote:or press Ctrl+r to restart and see the main screen with different colors.
The title screen will use some already loaded palette.
If you make a screenshot with Ctrl-F5, it will be saved as an indexed (paletted) image.
Open the image in GIMP and open the Colormap Dialog. The palette that the title screen should use is the 4th or 5th row from the bottom.
The wrong palette is either the 1st row (hardcoded palette) or the 6th (palace, if you pressed shift-L until level 4).
Norbert wrote:Then, when you enter level 1, the prince has 232 or 132 lives. (The number of lives may be random, I've just tested it twice.)
How did you check that? The easiest way I can think of is to save the game and look into prince.sav.

All these make me think that the game overwrites some memory that it shouldn't.
There is an unofficial version of DOSBox that can save states: http://ykhwong.x-y.net/xe/143
I saved the state before and after the hang, and for example, one of the hidden reviews are changed: from "game so" to "game!so".
There are other changes, but this is the only one that is obviously wrong.

Not long after my first post I found (with a debugger) the code that hangs:

Code: Select all

seg006:1199 check_spike_below proc far              ; CODE XREF: check_stuff+7D↑P
seg006:1199                                         ; sub_F48+60↑P
seg006:1199 
seg006:1199 right_col       = word ptr -0Ah
seg006:1199 tile_col        = word ptr -8
seg006:1199 tile_row        = word ptr -6
seg006:1199 room            = word ptr -4
seg006:1199 not_finished    = word ptr -2
seg006:1199 
seg006:1199                 push    bp
seg006:119A                 mov     bp, sp
seg006:119C                 sub     sp, 0Ah
seg006:119F                 push    word_1E9F0
seg006:11A3                 push    cs
seg006:11A4                 call    near ptr get_tile_div_mod_m7
seg006:11A7                 mov     [bp+right_col], ax
seg006:11AA                 or      ax, ax
seg006:11AC                 jl      loc_7EDD
seg006:11AE                 mov     al, char_curr_row
seg006:11B1                 cbw
seg006:11B2                 mov     [bp+tile_row], ax
seg006:11B5                 mov     al, char_room
seg006:11B8                 cbw
seg006:11B9                 mov     [bp+room], ax
seg006:11BC                 push    xpos
seg006:11C0                 push    cs
seg006:11C1                 call    near ptr get_tile_div_mod_m7
seg006:11C4                 mov     [bp+tile_col], ax
seg006:11C7                 jmp     short loc_7EA7
seg006:11C9 ; ───────────────────────────────────────────────────────────────────────────
seg006:11C9 
seg006:11C9 loc_7E79:                               ; CODE XREF: check_spike_below+81↓j
seg006:11C9                 mov     al, curr_tile2
seg006:11CC                 sub     ah, ah
seg006:11CE                 push    ax
seg006:11CF                 push    cs
seg006:11D0                 call    near ptr tile_is_floor
seg006:11D3                 or      ax, ax
seg006:11D5                 jnz     reached_bottom
seg006:11D7                 cmp     curr_room, 0    ; have we reached a bottomless pit?
seg006:11DC                 jz      reached_bottom
seg006:11DE                 mov     ax, curr_room
seg006:11E1                 cmp     [bp+room], ax   ; are we in a different room?
seg006:11E4                 jnz     reached_bottom
seg006:11E6                 inc     [bp+tile_row]   ; look further down
seg006:11E9                 mov     [bp+not_finished], 1
seg006:11EE 
seg006:11EE reached_bottom:                         ; CODE XREF: check_spike_below+3C↑j
seg006:11EE                                         ; check_spike_below+43↑j ...
seg006:11EE                 cmp     [bp+not_finished], 0
seg006:11F2                 jnz     loc_7EB6
seg006:11F4                 inc     [bp+tile_col]   ; look further right
seg006:11F7 
seg006:11F7 loc_7EA7:                               ; CODE XREF: check_spike_below+2E↑j
seg006:11F7                 mov     ax, [bp+right_col]
seg006:11FA                 cmp     [bp+tile_col], ax ; are we over the right edge?
seg006:11FD                 jg      loc_7EDD
seg006:11FF                 mov     al, char_curr_row
seg006:1202                 cbw
seg006:1203                 mov     [bp+tile_row], ax ; in this column, go to the original row
seg006:1206 
seg006:1206 loc_7EB6:                               ; CODE XREF: check_spike_below+59↑j
seg006:1206                 mov     [bp+not_finished], 0
seg006:120B                 push    [bp+room]       ; room
seg006:120E                 push    [bp+tile_col]   ; tile_col
seg006:1211                 push    [bp+tile_row]   ; tile_row
seg006:1214                 push    cs
seg006:1215                 call    near ptr get_tile
seg006:1218                 cmp     al, tiles_2_spike
seg006:121A                 jnz     loc_7E79
seg006:121C                 push    curr_room
seg006:1220                 mov     al, curr_tile_pos
seg006:1223                 sub     ah, ah
seg006:1225                 push    ax
seg006:1226                 call    animate_spike_2
seg006:122B                 jmp     short reached_bottom
seg006:122D ; ───────────────────────────────────────────────────────────────────────────
seg006:122D 
seg006:122D loc_7EDD:                               ; CODE XREF: check_spike_below+13↑j
seg006:122D                                         ; check_spike_below+64↑j
seg006:122D                 mov     sp, bp
seg006:122F                 pop     bp
seg006:1230                 retf
seg006:1230 check_spike_below endp
This procedure looks down from where the kid is, and searches down until one of these happens:
- The search reaches room 0.
- The search leaves the current room. More precisely, after the game checked the first tile that is not in the current room. (That is, if the spike is in the top row of the room below, it will still come out.)
- The search finds a tile that has floor. If it's a spike then it comes out.

The second could be replaced with: The search goes below row 2.
If the room below is the current room, then the first two can't happen. If the current column is empty then the third cannot happen.

I examined the saved states a bit more:
It seems to me that this is a stack overflow. One of the functions called by get_tile() calls itself everytime it crosses a room boundary.
When tile_row reaches 159Dh, the search stops, because the stack has overwritten the room links! - And of course everything after it.
The "spill" consists of sequences of "E8 00 nn nn", where nnnn is the segment where get_tile was relocated.

With hex codes:

Code: Select all

000096F9:i55                             push      bp
000096FA:i8BEC                           mov       bp,sp
000096FC:i83EC0A                         sub (w)   sp,+0A
000096FF:iFF36103D                       push (w)  [+3D10]
00009703:i0E                             push      cs
00009704:iE837F2                         calln     file:0000893E
00009707:i8946F6                         mov       [bp-0A],ax
0000970A:i0BC0                           or        ax,ax
0000970C:i7C7F                           jl        file:0000978D
0000970E:iA0273D                         mov       al,[+3D27]
00009711:i98                             cbw
00009712:i8946FA                         mov       [bp-06],ax
00009715:iA02B3D                         mov       al,[+3D2B]
00009718:i98                             cbw
00009719:i8946FC                         mov       [bp-04],ax
0000971C:iFF361C3D                       push (w)  [+3D1C]
00009720:i0E                             push      cs
00009721:iE81AF2                         calln     file:0000893E
00009724:i8946F8                         mov       [bp-08],ax
00009727:iEB2E                           jmps      file:00009757
00009729:iA0F942                         mov       al,[+42F9]
0000972C:i2AE4                           sub       ah,ah
0000972E:i50                             push      ax
0000972F:i0E                             push      cs
00009730:iE855F4                         calln     file:00008B88
00009733:i0BC0                           or        ax,ax
00009735:i7517                           jne       file:0000974E
00009737:i833E2A4300                     cmp (w)   [+432A],+00
0000973C:i7410                           je        file:0000974E
0000973E:iA12A43                         mov       ax,[+432A] ; this
00009741:i3946FC                         cmp       [bp-04],ax ; this
00009744:i7508                           jne       file:0000974E ; this
00009746:iFF46FA                         inc       [bp-06]
00009749:iC746FE0100                     mov       [bp-02],0001
0000974E:i837EFE00                       cmp (w)   [bp-02],+00
00009752:i7512                           jne       file:00009766
00009754:iFF46F8                         inc       [bp-08]
00009757:i8B46F6                         mov       ax,[bp-0A]
0000975A:i3946F8                         cmp       [bp-08],ax
0000975D:i7F2E                           jg        file:0000978D
0000975F:iA0273D                         mov       al,[+3D27]
00009762:i98                             cbw
00009763:i8946FA                         mov       [bp-06],ax
00009766:iC746FE0000                     mov       [bp-02],0000
0000976B:iFF76FC                         push (w)  [bp-04]
0000976E:iFF76F8                         push (w)  [bp-08]
00009771:iFF76FA                         push (w)  [bp-06]
00009774:i0E                             push      cs
00009775:iE8EEED                         calln     file:00008566
00009778:i3C02                           cmp       al,02
0000977A:i75AD                           jne       file:00009729
0000977C:iFF362A43                       push (w)  [+432A]
00009780:iA0ED42                         mov       al,[+42ED]
00009783:i2AE4                           sub       ah,ah
00009785:i50                             push      ax
00009786:i9AE3085508                     callf     file:00008E33
0000978B:iEBC1                           jmps      file:0000974E
0000978D:i8BE5                           mov       sp,bp
0000978F:i5D                             pop       bp
00009790:iCB                             retf
Search for A1 2A 43 39 46 FC 75 08 and replace it with 83 7E FA 02 90 90 7F 08.
This seems to fix the problem but the behavior of the spikes doesn't seem to change. (Including the case when the spike is in the top row of the room below.)

The corresponding part in the Apple II source: (CTRLSUBS.S)

Code: Select all

*-------------------------------
*
*  C H E C K   S P I K E S
*
*  Spikes spring out when char passes over them (at any
*  height).
*
*-------------------------------
CHECKSPIKES
 lda rightej
 jsr getblockxp
 bmi ]rts
 sta tempright

* for blockx = leftblock to rightblock

 lda leftej
 jsr getblockxp
:loop sta blockx

 jsr sub

 lda blockx
 cmp tempright
 beq ]rts
 clc
 adc #1
 jmp :loop

sub sta tempblockx
 lda CharBlockY
 sta tempblocky
 lda CharScrn
 sta tempscrn
:loop jsr rdblock1

 cmp #spikes
 bne :again
 jmp trigspikes

:again jsr cmpspace
 bne ]rts

 lda tempscrn
 beq ]rts ;null scrn
 cmp CharScrn
 bne ]rts ;wait till he's on same screen

 inc tempblocky
 jmp :loop ;check 1 level below
David
The Prince of Persia
The Prince of Persia
Posts: 2850
Joined: December 11th, 2008, 9:48 pm
Location: Hungary

Re: The game hangs on certain irregular room links

Post by David »

I see that your Prince of Wateria contains an endless fall on level 12 and 13.
It uses two similar rooms. Did you use two rooms to avoid this bug, or because it looks better?

Repetition of Time also contains an endless fall on level 3, but there it consists of three rooms.
User avatar
Norbert
The Prince of Persia
The Prince of Persia
Posts: 5746
Joined: April 9th, 2009, 10:58 pm

Re: The game hangs on certain irregular room links

Post by Norbert »

David wrote:Did you use two rooms to avoid this bug, or because it looks better?
Because it probably looks a little bit better when the player ends up in that endless fall.
I never tested it with just one room, so I never ran into the bug this thread is about.
Another impressive analysis you've done in your previous post here, by the way.
Post Reply