Integer overflow bug

Discuss PoP1 for DOS here.
User avatar
Norbert
The Prince of Persia
The Prince of Persia
Posts: 5743
Joined: April 9th, 2009, 10:58 pm

Re: Integer overflow bug

Post by Norbert »

Norbert wrote:[...] the special stuff that happens [...]
I think I know what happens when the player kills a guard on a raise button in the Apple II version. :D
(Assuming the event numbers haven't changed in the DOS version, see below.)

In level 2, the events being executed - that permanently open gates - start with event 1:

Image
(mirror)

In level 8, the events being executed also start with event 1:

Image
(mirror)

My hypothesis therefore is: killing a guard on a raise button in the Apple II version will execute event 1 (and subsequent events, if the events are set to execute next events).

By the way, you can see in the Apple II screenshot collages that I've created that the raise buttons that have event 1 attached to them are pressed down in the variants where I'd killed a guard on a raise button.
tacosalad
Sheikh
Sheikh
Posts: 48
Joined: October 3rd, 2005, 6:03 am
Location: Oregon, USA
Contact:

Re: Integer overflow bug

Post by tacosalad »

Norbert wrote:My hypothesis therefore is: killing a guard on a raise button in the Apple II version will execute event 1 (and subsequent events, if the events are set to execute next events).
That's a credible hypothesis. Let me take it a little further, thinking about this from a progammer's perspective: perhaps killing the guard places a raise tile connected to event 1 at that location. Placing tiles (in response to special events) is the type of behavior the game is already known to employ, so this would just be another example of existing behavior. It wouldn't affect the visuals because a raise tile was simply replaced with another raise tile, but the new tile would operate event 1 without any further programming tweaks because the guard would thus be operating an event 1 raise tile. This would also explain why the tile stops raising the door it had originally been wired to, because the original tile (with its original event) is effectively gone.

If this conjecture holds, then any other instances of the same trick would undoubtedly be wired through Event 1, too, although the originating tile (the one that's replaced) would initially be wired to another event. That might offer a quick clue about any other candidates in other levels. At the very least, it would instantly rule out any tile that's initially wired to event 1 in the level map.

I think I've lost my old PoP disk, and it might have been too old to boot anyhow, so I'll have to dig up an emulator like you suggested. So many memories!
Norbert wrote:By the way, you can see in the Apple II screenshot collages that I've created that the raise buttons that have event 1 attached to them are pressed down in the variants where I'd killed a guard on a raise button.
Great, another mystery solved! My college roommate had noticed 'stuck' tiles occasionally, but we never recognized the cause. After we switched to playing the PC version it stopped happening.
User avatar
Norbert
The Prince of Persia
The Prince of Persia
Posts: 5743
Joined: April 9th, 2009, 10:58 pm

Re: Integer overflow bug

Post by Norbert »

What you write about the raise button being replaced makes sense.
tacosalad wrote:Level ? - During the 90's I used to tell people there was a third switch that exhibited this behavior, but I can't remember where it could have been. But it's been so long, maybe I just remember searching for a third--maybe there wasn't another.
Well, let's see...

Here is where event 1 is used:
- - - - - - - - - -
level 1: room 20 (raise)
level 2: room 18 (raise)
level 3: room 14 (raise)
level 4: room 24 (drop)
level 5: room 24 (drop)
level 6: room 24 (raise)
level 7: room 19 (drop)
level 8: room 24 (raise)
level 9: room 23 (raise)
level 10: rooms 11 (raise) and 23 (raise, but room is not in use)
level 11: room 13 (raise)
level 12 (12a): room 21 (raise)
level 13 (12b): room 24 (raise)
level 14: rooms 4 (raise) and 18 (raise, but room is not in use)
potions level: room 8 (raise)
demo level: room 6 (raise)
- - - - - - - - - -

Whether forcing these buttons down (by killing a guard on a raise button) will make the level easier:
- - - - - - - - - -
level 1: It opens a gate near a raise button with event 1 (that can also easily be opened from the other side), so not very useful. Plus, killing a guard on a raise button would be a detour. In fact, most of us will take the 'shortcut' in level 1 to begin with.
level 2: As we've seen, not very useful, since the room where the gates stay open has a raise button for event 1 and there's no reason to return to that section of the level.
level 3: This is the skeleton level and event 1 opens the gate you need to jump to, to reach the next section, so it could be useful. However, there is no guard that can be killed on a raise button.
level 4: Even assuming it doesn't matter that the button that's actually in the level is a drop button, it wouldn't be very useful, since event 1 opens a gate to a section you've just come from and don't need to return to.
level 5: Even assuming it doesn't matter that the button that's actually in the level is a drop button, it wouldn't be very useful, because it's where the shadow takes the life potion.
level 6: Event 1 opens a gate that is very close to a raise button with event 1, so not very useful.
level 7: Even assuming it doesn't matter that the button that's actually in the level is a drop button, it wouldn't be all that useful, since all you do in the level is avoid it to prevent a gate from closing (which is quite easy to do).
level 8: As we've seen, it definitely helps a lot, because 3 gates stay open in tricky rooms.
level 9: Event 1 opens a gate that is more or less next to a raise button with event 1 (and the gate doesn't need to stay open), so not very useful.
level 10: Event 1 opens a gate that is more or less next to a raise button with event 1 (and the gate doesn't need to stay open), so not very useful.
level 11: Event 1 opens the level exit door, so this could also be useful!
level 12 (12a): Event 1 opens a gate that is more or less next to the raise button with event 1, so not very useful. Also, there is just the shadow in this level and no guard.
level 13 (12b): Event 1 opens the level exit door. The only enemy in the level is Jaffar. Killing him will opens the exit door, so not useful.
level 14: Event 1 opens a gate that is more or less next to the raise button with event 1, so not very useful. Also, this level has no guards, so it wouldn't work anyways.
potions level: Event 1 opens the level exit door, so in theory this could be useful, because you don't need to look up which potion to drink, but the level has no guards.
demo level: Obviously not useful, because the demo level is automated.
- - - - - - - - - -

So, if the theory we've come up with is correct, then: yes, there are other places this can be used.
The only other place besides level 8 where it could be useful is level 11.
I'll check that out tomorrow...
User avatar
Norbert
The Prince of Persia
The Prince of Persia
Posts: 5743
Joined: April 9th, 2009, 10:58 pm

Re: Integer overflow bug

Post by Norbert »

Norbert wrote:level 5: Even assuming it doesn't matter that the button that's actually in the level is a drop button, it wouldn't be very useful, because it's where the shadow takes the life potion.
On second thought, this could also be interesting.
If triggering event 1 means the gates there open, the prince could take the life potion from the direction that the shadow normally enters the screen.
Interesting...
Norbert wrote:level 9: Event 1 opens a gate that is more or less next to a raise button with event 1 (and the gate doesn't need to stay open), so not very useful.
[Edit: Also...]
It saves a little bit of time though, when going through the gate from the other direction (from the left).
User avatar
Norbert
The Prince of Persia
The Prince of Persia
Posts: 5743
Joined: April 9th, 2009, 10:58 pm

Re: Integer overflow bug

Post by Norbert »

Norbert wrote:
Norbert wrote:level 5: Even assuming it doesn't matter that the button that's actually in the level is a drop button, it wouldn't be very useful, because it's where the shadow takes the life potion.
On second thought, this could also be interesting.
If triggering event 1 means the gates there open, the prince could take the life potion from the direction that the shadow normally enters the screen.
Interesting...
Haha... cool, it works. :mrgreen:

tacosalad
Sheikh
Sheikh
Posts: 48
Joined: October 3rd, 2005, 6:03 am
Location: Oregon, USA
Contact:

Re: Integer overflow bug

Post by tacosalad »

Gosh you're fast!

It's been coming back to me a bit at a time. I've long had a vague memory of someone successfully grabbing the potion on level 5 but I had trouble remembering the details because I wasn't the one who'd solved it back in 1989...it was my roommate, Darren. Now that I see the video I remember the first time he got there he was so startled when the shadow came in behind him that he accidentally kicked out the plug and knocked out the computer!

Norbert you're just amazing for documenting all this stuff so well--with pictures and videos--working from dodgy scraps of my distant memories. I regret I've been hoarding these details for years because I wasn't sure where to share it or how to document it.

This has inspired me to document and share any other Prince of Persia details I've got. I don't think it'll be nearly as exciting as sharing these Apple II bonus shortcuts, or that nerve-wracking but rarely-seen cutscene. But here's a hint...
Notice what's unique about my copy of princed.com?
Notice what's unique about my copy of princed.com?
Sorry for getting this particular thread so far off-topic, but I think it was worth it!
User avatar
Norbert
The Prince of Persia
The Prince of Persia
Posts: 5743
Joined: April 9th, 2009, 10:58 pm

Re: Integer overflow bug

Post by Norbert »

tacosalad wrote:But here's a hint...
Yeah, lots of ways to successfully pass the potions level with various tricks and cracks. :)
Your screenshot reminds me of how mk1995's Persian Training Grounds had changed text in the potions level (see the second screenshot here).
Norbert wrote:level 11: Event 1 opens the level exit door, so this could also be useful!
I've also looked into this, by the way, and unfortunately there are no raise buttons near the guards that they can be killed on.
In other words, this one is a no-go. So, the 'event 1 trick' is the most useful in levels 5 (LP) and 8 (3 gates).
David
The Prince of Persia
The Prince of Persia
Posts: 2846
Joined: December 11th, 2008, 9:48 pm
Location: Hungary

Re: Integer overflow bug

Post by David »

Norbert wrote:My hypothesis therefore is: killing a guard on a raise button in the Apple II version will execute event 1 (and subsequent events, if the events are set to execute next events).
I think I found the responsible part in the source: (comments by me)
In MOVER.S:

Code: Select all

*-------------------------------
*
* Jam pressplate (dead weight)
*
* In: Same as PUSHPP
*
*-------------------------------
JAMPP
 lda (BlueType),y
 and #idmask
 sta pptype
 cmp #pressplate ; if it's a drop button
 beq :1 ; then jump

 lda #floor
 sta (BlueType),y
 lda #0 ; modifier is zeroed!
 sta (BlueSpec),y
 lda #rubble
 sta pptype
 bne pushpp1 ; #rubble is not zero -> jumps always

:1 lda #dpressplate
 sta (BlueType),y
 bne pushpp1 ; #dpressplate is not zero -> jumps always
And here is the target of the jump: (this is still MOVER.S)

Code: Select all

*-------------------------------
*
*  Depress pressplate
*
*  In: results of RDBLOCK
*     (tempblockx-y, tempscrn refer to pressplate)
*
*-------------------------------
PUSHPP
 lda (BlueType),y
 and #idmask
 sta pptype ;pressplate/upressplate/rubble
pushpp1
 lda (BlueSpec),y ;LINKLOC index
 sta linkindex
So, the game sets the modifier to zero*, and then it reads the modifier to decide which event should be triggered.
(*: Apoplexy displays door event ID 0 as 1, 1 as 2, etc.)
This zeroing is not done if the guard is killed on a drop button ("cmp #pressplate" + "beq :1"), probably because stuck drop buttons look the same no matter what is their modifier.

The bug could be fixed by copying the "lda (BlueSpec),y" + "sta linkindex" after the "JAMPP" label, and moving the "pushpp1" label two lines down.
tacosalad
Sheikh
Sheikh
Posts: 48
Joined: October 3rd, 2005, 6:03 am
Location: Oregon, USA
Contact:

Re: Integer overflow bug

Post by tacosalad »

Sure enough, it's a bug. All these years I assumed it was a deliberate feature, which is why I was so mystified by its behavior on level 2.

Thanks for pointing me to the Apple II source! I just used it to locate the source of another bug that I discovered in the IBM PC version two weeks ago: the exit door on level 4 doesn't open as far as the exits the other levels, and an arbitrary tile in the mirror room may change depending on what it's original modifier byte was. As shown below, this bug was present in the Apple II version and apparently got ported into the IBM PC version.

In the samples below I have used C++ style comments marked with // to distinguish my comments from those already present in the github source.

In MOVER.S the animobj subroutine is called on a tile-by-tile basis to update each tile's animation. animobj calls calcblue to update the BlueType and BlueSpec pointers for the room that corresponds to the tile being animated, and it copies the tile's current animation state from BlueSpec[tile] to state for convenient access by the various animation subroutines. Then it identifies what type of tile is being animated and calls the appropriate subroutine. In the case of an exit it calls out to animexit at line 751.

Code: Select all

686 *-------------------------------
687 *
688 * Animate TROB #x
689 *
690 *-------------------------------
691 animobj lda trloc,x
692  sta trloc
693  lda trscrn,x
694  sta trscrn
695  lda trdirec,x
696  sta trdirec
697 
698 * Find out what kind of object it is
699 
700  lda trscrn                // get room # of object being animated
701  jsr calcblue              // point BlueSpec/BlueType at the specified room
702 
703  ldy trloc                 
704  lda (BlueSpec),y          // copy tile spec (modifier) into 'state' variable
705  sta state ;original state // until epilog of animobj, where it will be copied back
706 
707  lda (BlueType),y          // load the tile's type byte and mask it down to the ID bits
708  and #idmask ;objid
709 
710 * and branch to appropriate subroutine
.
. [edit]
.
749 :6 cmp #exit
750  bne :7
751  jsr animexit  // exit is being animated, call the subroutine...
752  jmp :done
Also in MOVER.S the animexit subroutine updates the exit door's position and ordinarily marks it to be redrawn. But each time animexit is called it also tests to see if the door has reached the top, in which case line 805 calls subroutine mirappear.

Code: Select all

772 *-------------------------------
773 *
774 * Animate exit
775 *
776 *-------------------------------
777 animexit
778  ldx trdirec
779  bmi :cont
780  cpx #3
781  bcs :downfast ;>= 3: coming down fast
782 
783  lda #RaisingExit
784  jsr addsound
785 
786  lda state         // get current exit position from 'state'
787  clc
788  adc #exitinc      // update the position
789  sta state         // store updated position in 'state'
790 
791  cmp #emaxval      // did it reach the top?
792  bcs :stop         // if so, go stop it...
793 
794 :cont jmp redexit
795 
796 :stop jsr stopobj
797 
798  lda #GateDown
799  jsr addsound      // queue 'gate bump' sound
800  lda #s_Stairs
801  ldx #15
802  jsr cuesong       // queue 'exit open' leitmotif music
803  lda #1
804  sta exitopen      // set 'exitopen' to TRUE
805  jsr mirappear     // go check if the mirror should appear...
806  jmp :cont
In SUBS.S the mirappear subroutine checks if the game is currently playing level 4. If so, it places a mirror by calling the rdblock which updates the BlueType and BlueSpec pointers to point to room 4, the room denoted by mirscrn. And here's the bug: the mirappear subroutine changes BlueSpec to point at room 4, and it doesn't restore it to the room that's actually being animated!

Code: Select all

1740 *-------------------------------
1741 * Mirror appears (called by MOVER when exit opened)
1742 *-------------------------------
1743 MIRAPPEAR
1744  do DemoDisk
1745  rts
1746  else
1747
1748  lda level
1749  cmp #4              // Are we playing level 4?
1750  bne ]rts            // If not, return without doing anything.
1751
1752  lda #mirscrn        // If so, get mirror room number.
1753  ldx #mirx           // Mirror position.
1754  ldy #miry           // Mirror tile.
1755  jsr rdblock         // Read tiles (updates BlueType/BlueSpec)
1756  lda #mirror
1757  sta (BlueType),y    // Place a mirror tile in the specied room (spec not important)
1758  rts                 //***BUG*** returning without restoring BlueType/BlueSpec to room # being animated
Control eventually filters back to animobj by way of the redexit and markmove helper routines. Finally animobj finishes its work by copying the updated values in state back into BlueSpec[tile] for the tile position of the exit door...but because BlueSpec now points to room 4 it actually modifies a tile in the mirror room! This also means the updated door position is never written to the exit's tile spec, so the animation stops one frame too soon. (This can be verified by comparing screen captures of level 4 and level 5.)

Code: Select all

749 :6 cmp #exit
750  bne :7
751  jsr animexit
752  jmp :done
.
. [edit]
.
766 :done lda state     // get 'state' in case it was modified during animation
767  ldy trloc
768  sta (BlueSpec),y   // update tile spec (modifier) with new 'state'
769 
770 :rts rts
This bug went unnoticed because you wouldn't ordinarily notice that the exit door was just 1 piexel too low. And in the original Prince of Persia there was no other effect because it just overwrote a plain floor tile with a modifier (spec) of a plain floor tile. But when building a new levels.dat this bug can be turned into a feature by choosing a tile whose modifier serves a useful purpose: when the exit opens a switch tile at this location would become stuck, a gate at this location (whether open or shut) would become jammed mostly-closed, a tapestry will turn black, etc.
David
The Prince of Persia
The Prince of Persia
Posts: 2846
Joined: December 11th, 2008, 9:48 pm
Location: Hungary

Re: Integer overflow bug

Post by David »

I have made videos of some bugs mentioned in the first post. (they use the ZMBV codec)
duke wrote:If you knock a guard into a blade trap, and then hit him with your sword just as the blade closes, he'll survive (given he has health left), and the blade will have blood on it.
guard_survives_chomper.zip
(49.6 KiB) Downloaded 250 times
duke wrote:In employing the above two tricks, we discovered that you can actually back up through other live guards. What happens is that once the screen changes (and it's one where there's another guard), you'll find yourself behind the undisturbed guard, out of combat. [...] Making any noise will obviously wake this guard up, but if you time things just so, you can get the screen to flip so you're within striking distance of the out of combat guard while he's facing the other way. Striking any guard who's not in combat will kill him in a single blow.
backstab2.zip
(42.8 KiB) Downloaded 196 times
duke wrote:Another trick had to do with freefalling, though I don't remember exactly what we did. I believe we knocked a guard off a high ledge and used the screen flipping trick (detailed below) to time when we'd change screens. If changed as he was 1 ledge length or less from the floor, you could stop him from dying, as it would somehow see it as a 1 ledge length fall.
I edited the level for this one, because I don't know where can this be done on the original levels.
immortaldrop_guard2.zip
(52.08 KiB) Downloaded 189 times
And some bugs I don't remember reading about:

Falling through the floor while going offscreen:
throughfloor_opendoor.zip
(96.99 KiB) Downloaded 267 times
Guard suddenly appearing at the other side of the screen: (the gate is required)
I first saw this with the first yellow guard, on level 1 of Extrem.
guard_wrap.zip
(39.52 KiB) Downloaded 259 times
Tapestry disables guard:
If I remember correctly, I seen this in Return to Persia, level 6, with the guard at the big potion.
tapestry_disables_guard.zip
(24.7 KiB) Downloaded 209 times
Guard disappears after pushed offscreen:
guard_gone.zip
(26.17 KiB) Downloaded 261 times
Guard spiked offscreen appears in the wrong room.
spike_2.zip
(39.74 KiB) Downloaded 207 times
User avatar
Norbert
The Prince of Persia
The Prince of Persia
Posts: 5743
Joined: April 9th, 2009, 10:58 pm

Re: Integer overflow bug

Post by Norbert »

What follows are the videos that David attached to his last post.

backstab2:


guard_gone:


guard_survives_chomper:


guard_wrap:


immortaldrop_guard2:


spike_2:


tapestry_disables_guard:


throughfloor_opendoor:
David
The Prince of Persia
The Prince of Persia
Posts: 2846
Joined: December 11th, 2008, 9:48 pm
Location: Hungary

Re: Integer overflow bug

Post by David »

If you bump into a wall, while the shown room is not the room where the kid is (because you used the H/J/U/N cheat keys), the kid may "teleport" to some other location.
But what is the logic behind that location?

I made a level that contains only one wall, and made 3 screenshots for each position of the wall.
Below you'll see these combined.

First, I made a picture about where will the kid move if I just bump him into the wall.
Notice that the kid is centered at wall edges.
bumpM.png
bumpM.png (4.63 KiB) Viewed 11857 times
Then, I pressed J before the bump: this will show the right room.
bumpR.png
bumpR.png (9.54 KiB) Viewed 11857 times
Then, I pressed H before the bump: this will show the left room.
bumpL.png
bumpL.png (9.35 KiB) Viewed 11857 times
The red lines mark room edges.

First, I noticed that adjacent positions are equally spaced.
Then I noticed that some positions are centered above the floor separators.
Their positions match the wall edges, except that they are in the other room: in the *opposite* neighbor compared to the direction key I pressed. (These are marked "correct".)
But what about the others?
According to Mechner's documents (page 7), the screen is 140 units wide (320 pixels), and there are 58 units offscreen.
The latter is 58*(320/140)=132.57 pixels. I put the blue lines 132 pixels away from the red lines.
The distance between the blue lines in 58+140+58=256 units.
The positions wrap at the blue lines, so it looks like we have yet another integer overflow.

Note that the images don't show what happens if the wall is on the edge of the room.
David
The Prince of Persia
The Prince of Persia
Posts: 2846
Joined: December 11th, 2008, 9:48 pm
Location: Hungary

Re: Integer overflow bug

Post by David »

tacosalad wrote:For what it's worth, I can confirm the Integer Overflow bug was already present in the earliest Apple II version of the game.

There are two places you can trigger it in level 12: at the platform shown in Norbert's diagram above, or from the platform at the top of the same tower above. In 1989 my college dorm-mates liked the second variation because it looked so spectacular like the "prince just popped out of the ceiling!"
I just tried: On the Apple II, this is buggier than in the DOS version: after you grabbed the "nothing", you can climb up!
If I want to go left, you'll bump into a wall. If you step right, you'll find the edge of the floor.
It seems that the hit-test thinks that the kid is where you wanted to jump, but at the same time a different room is shown!
This won't even fix itself if you go to another room. If you bump into a wall, the kid may move up even more.
Attachments
on_nothing.png
on_nothing.png (858 Bytes) Viewed 11771 times
User avatar
Norbert
The Prince of Persia
The Prince of Persia
Posts: 5743
Joined: April 9th, 2009, 10:58 pm

Re: Integer overflow bug

Post by Norbert »

(This is in response to David's post in another thread. He referred to a video in this thread.)
David wrote:Repeat this: Press left and esc together, then press esc again.

The "throughfloor_opendoor" bug can be reproduced in the original game in the same way.
I'm trying this in the original game and I'm unable to replicate this behavior.
As soon as I enter room 8 for more than a tiny bit, the game displays that room. I also have no idea when exactly to press left+ESC, when I'm furthest in the new room?
I noticed you're using the caped prince in that throughfloor_opendoor video. Certain tricks, like 83 are only possible with those graphics.
David
The Prince of Persia
The Prince of Persia
Posts: 2846
Joined: December 11th, 2008, 9:48 pm
Location: Hungary

Re: Integer overflow bug

Post by David »

I could reproduce it right now with he original prince.
Perhaps my description was not precise enough.

"Press left and esc together, then press esc again."
"left and esc" means first press left, and then immediately press esc.
You need to start that before the prince leaves the room, and repeat it until the other room is shown.
Post Reply