Special events in Level 13 are a little flawed

Discussions about all other tools (CusPop, SAV/HOF editors) and hex editing.
T0mmiTheGreat
Sheikh
Sheikh
Posts: 25
Joined: February 22nd, 2021, 8:48 pm
Location: Czech Republic

Special events in Level 13 are a little flawed

Post by T0mmiTheGreat »

You can skip this section. I just wanted to make a cool introduction. ;)
Level 13 (12b) is in fact the last level in Prince of Persia. There you fight against something what is (in gamer jargon) called "the final boss". It's obvious that this level is going to contain many special events.
It is not accessed the same way as most of the previous ones; instead of walking up the staircase, you access it by simply leaving a certain room in the previous level. The kid makes a running entry into a room where the ceiling immediatelly collapses.
Then, when meeting Jaffar – the main antagonist – a tune is played before he draws a sword and the epic final fight begins. When the Grand Vizier falls dead on the floor, another tune is played, accompanied by a flashing screen. Now, the countdown is stopped, the last door opens (because a certain button has been automatically pressed) and the kid can finally meet his princess.


Okay, that was the introduction, now to my problems. There are 3 in total:
  1. The loose tiles don't wobble when the player lands on the same floor where they are. They only wobble when they are about to fall. This might be intentional, due to the "falling tiles event". Maybe it wouldn't work properly, but I'm not really sure whether these are actually connected. If so, then that's bad. I use this event in my levelset. Is there a way to fix this issue? And if not, can I at least make them do the wobble, so the player can distinguish them?
  2. I contend with yet another trouble with checkpoint placement. On this forum I have posted a question about moving the checkpoint and received a solution which works alright in all levels except this one (or most of them I suppose; I tested it only in Level 7, where I initially planned to place it). In Level 13 (12b) it works almost alright. The checkpoint is triggered, the kid respawns in the correct room, but he looks in the wrong direction – always left. I also tried to make him start the level looking to the right, and setting the respawn direction to both options in CusPop, nothing did work.
  3. This thing:

    The player can draw his sword before Jaffar does, being able to kill the Vizier in one hit. And even though you can move Jaffar a little further away from the room entrance, the player can still hit him before he manages to block it. Not speaking of how weird it looks when an epic tune plays as an introduction to an epic battle, and the kid ruins the moment by swishing his sword. Sure, a simple solution would be to swap this room with another one, so the Jaffar doesn't wait before fighting, but that wouldn't make a good atmosphere for a final battle. So the answer I expect is something like "kidSwordDrawDelay == vizierSwordDrawDelay" in hex or so.
Big thanks for any answer. Even bigger for a correct answer :D
David
The Prince of Persia
The Prince of Persia
Posts: 2846
Joined: December 11th, 2008, 9:48 pm
Location: Hungary

Re: Special events in Level 13 are a little flawed

Post by David »

T0mmiTheGreat wrote: July 17th, 2021, 5:01 pm 1. The loose tiles don't wobble when the player lands on the same floor where they are.
They only wobble when they are about to fall.
This might be intentional, due to the "falling tiles event".
Maybe it wouldn't work properly, but I'm not really sure whether these are actually connected.
If so, then that's bad. I use this event in my levelset. Is there a way to fix this issue?
And if not, can I at least make them do the wobble, so the player can distinguish them?
You guessed right, the non-shaking floors are connected to the falling tiles event.
I found that out when I recreated the falling tiles event in SNES PoP for Shauing's 30th Anniversary Mod.
This is what I wrote then: viewtopic.php?p=29766#p29766

There is a way to make the loose floors shake even on level 13, but this will break the special falling tiles event.

Here are the hacks:
Spoiler: show

Code: Select all

in animate_loose():
seg007:0DBC 83 3E 9E 0F 0D           cmp  current_level, 13 ; don't shake on level 13
seg007:0DC1 75 03                    jnz  loc_9316
Search: 83 3E 9E 0F 0D 75 03
Change: 75 03 to EB 03
Offset in unpacked 1.0: 0xABC1

Code: Select all

in loose_make_shake():
seg007:0FC4 83 3E 9E 0F 0D           cmp  current_level, 13 ; don't shake on level 13
seg007:0FC9 74 13                    jz   loc_952E
Search: 83 3E 9E 0F 0D 74 13
Change: 74 13 to 90 90
Offset in unpacked 1.0: 0xADC9

I found a workaround to fix the falling tiles event, based on the "Another solution" from the post I linked above:
Spoiler: show

Code: Select all

seg000:102F B8 FF 00                 mov   ax, 0FFh ; <-- change to 8
seg000:1032 50                       push  ax      ; max
seg000:1033 9A 0A 04 79 0C           call  random
seg000:1038 2A E4                    sub   ah, ah
seg000:103A 25 0F 00                 and   ax, 0Fh
seg000:103D F7 D8                    neg   ax ; <-- change to NOP
seg000:103F 50                       push  ax      ; modifier
seg000:1040 9A D5 0E 55 08           call  make_loose_fall
At 0x28E0 write: 08
At 0x28ED write: 90 90

A slight difference is that the auto-falling floors will now fall sooner than originally.

T0mmiTheGreat wrote: July 17th, 2021, 5:01 pm 2. I contend with yet another trouble with checkpoint placement.
On this forum I have posted a question about moving the checkpoint and received a solution which works alright in all levels except this one (or most of them I suppose; I tested it only in Level 7, where I initially planned to place it). In Level 13 (12b) it works almost alright.
The checkpoint is triggered, the kid respawns in the correct room, but he looks in the wrong direction – always left.
I also tried to make him start the level looking to the right, and setting the respawn direction to both options in CusPop, nothing did work.
I could make the prince restart looking (and running) to the right.
(Remember, level 13 has a running entry, and it's active even if restarting at a checkpoint.)
See the attachment.
T0mmi_level_13.zip
(78.65 KiB) Downloaded 97 times

I changed the bytes at the following offsets:
0x5599: level
0x55A0: trigger room
0x624E: level
0x625C: respawn direction (Actually I left its original value, but it can be changed here.)
0x6261: respawn room
0x6266: respawn tilepos
And I applied the hack from here: viewtopic.php?p=32536#p32536
T0mmiTheGreat
Sheikh
Sheikh
Posts: 25
Joined: February 22nd, 2021, 8:48 pm
Location: Czech Republic

Re: Special events in Level 13 are a little flawed

Post by T0mmiTheGreat »

Excellent! Both solutions work how they are supposed to. Thank you! :)
David
The Prince of Persia
The Prince of Persia
Posts: 2846
Joined: December 11th, 2008, 9:48 pm
Location: Hungary

Re: Special events in Level 13 are a little flawed

Post by David »

T0mmiTheGreat wrote: July 18th, 2021, 2:45 pm Excellent! Both solutions work how they are supposed to. Thank you! :)
That's good to hear! :)
David wrote: July 17th, 2021, 9:19 pm I found a workaround to fix the falling tiles event, based on the "Another solution" from the post I linked above:
[...]
A slight difference is that the auto-falling floors will now fall sooner than originally.
I found a better workaround.
This one does not make the auto-falling floors fall sooner.

First undo the previous hack:
At 0x28E0 write: FF
At 0x28ED write: F7 D8

Code: Select all

in animate_loose():
seg007:0DC6 80 3E 28 43 84           cmp  curr_modifier, 84h
seg007:0DCB 72 0A                    jb   loc_9327 ; <-- Change JB to JNE/JNZ.
Search: 80 3E 28 43 84 72 0A
Change: 72 to 75 (offset: 0xABCB)
T0mmiTheGreat
Sheikh
Sheikh
Posts: 25
Joined: February 22nd, 2021, 8:48 pm
Location: Czech Republic

Re: Special events in Level 13 are a little flawed

Post by T0mmiTheGreat »

David wrote: July 24th, 2021, 9:01 am I found a better workaround.
This one does not make the auto-falling floors fall sooner.
Great job! Though, I'll think it over whether I'll use this solution, or keep it as it is. To be honest, I think that the tiles falling early look better in my level. Still, thanks for this solution :)

And, out of curiosity, have you attempted to find a solution to the third problem? Do you think there is any way to fix it?
T0mmiTheGreat wrote: July 17th, 2021, 5:01 pm The player can draw his sword before Jaffar does, being able to kill the Vizier in one hit.
It isn't urgent, I'm not rushing you, I'm just curious :D
David
The Prince of Persia
The Prince of Persia
Posts: 2846
Joined: December 11th, 2008, 9:48 pm
Location: Hungary

Re: Special events in Level 13 are a little flawed

Post by David »

T0mmiTheGreat wrote: July 24th, 2021, 9:29 am And, out of curiosity, have you attempted to find a solution to the third problem? Do you think there is any way to fix it?
T0mmiTheGreat wrote: July 17th, 2021, 5:01 pm The player can draw his sword before Jaffar does, being able to kill the Vizier in one hit.
It isn't urgent, I'm not rushing you, I'm just curious :D
So the answer I expect is something like "kidSwordDrawDelay == vizierSwordDrawDelay" in hex or so.
I found a way to do that, but it breaks something else.

"vizierSwordDrawDelay" is stored in the guard_notice_timer variable.
It's initialized to 28 when you enter Jaffar's room, and it's decremented in every frame.
When it reaches zero, Jaffar becomes active.

We can use use this variable to prevent the prince from drawing his sword early.

The following part of the code decides if the prince should draw his sword:

Code: Select all

in control_standing():
seg005:0393 83 3E A2 34 00           cmp  have_sword, 0
seg005:0398 74 57                    jz   loc_61D1
seg005:039A 83 3E EE 42 00           cmp  word_1EFCE, 0 ; <-- change to guard_notice_timer
seg005:039F 74 0A                    jz   loc_618B
seg005:03A1 80 3E C6 4C 00           cmp  control_shift, 0
seg005:03A6 7C 03                    jl   loc_618B
seg005:03A8 E9 88 00                 jmp  loc_6213
seg005:03AB                loc_618B:
Search: 83 3E EE 42 00 74 0A
Change: EE 42 to 34 3D (offset: 0x7A2C)

The problem:
Now the prince will draw his sword immediately every time you put it away with the down arrow. (In any fight, not just with Jaffar.)
This means you can't run away from fights.

The word_1EFCE variable (called offguard in SDLPoP) becomes 1 if the player sheathed the sword by pressing the down arrow,
but this hack overwrites the check which prevents the prince from drawing his sword if that's the case.

There is just not enough place to keep all existing checks and add another one.
dmitry_s
Developer
Developer
Posts: 148
Joined: July 27th, 2021, 7:22 am

Re: Special events in Level 13 are a little flawed

Post by dmitry_s »

The way I got around the second problem (in SDLPOP) is to check on Jaffar's level whether the "guard_notice_timer" is 0 or the "ABS(char_opp_dist())" is <= 42.

I am not sure if it is possible to do in the DOS file due to an extra operation.
T0mmiTheGreat
Sheikh
Sheikh
Posts: 25
Joined: February 22nd, 2021, 8:48 pm
Location: Czech Republic

Re: Special events in Level 13 are a little flawed

Post by T0mmiTheGreat »

David wrote: July 24th, 2021, 4:45 pm Now the prince will draw his sword immediately every time you put it away with the down arrow. (In any fight, not just with Jaffar.)
This means you can't run away from fights.
This problem is actually way bigger. Not only you cannot sheathe your sword, it also breaks the fight against shadow. You can't keep your sword away, can't merge with the shadow, and no tiles will appear.


I had been thinking, and came up with another (potential) solution:

It may be possible to apply the "mirror appearing" event here.
  • In the level editor set the tile where Jaffar stands to the fake loose tile.
  • Use CusPoP to modify the Mirror event – Mirror tile code will be 1 => a normal floor.
  • Edit the "place mirror" procedure in the .exe file. Somehow…
And this is where I am stuck. I took a look into your disassemblies, David, and I'm slowly starting to understand what's going on in there :D.
If we move the bytes that are responsible for the mirror event to a procedure which would be triggered when the guard_notice_timer reaches 0, and change the leveldoor_open check to guard_notice_timer check, it might do the trick.

Code: Select all

seg002:08BF			 loc_3F3F:				 ; CODE	XREF: autocontrol_guard_inactive+22j
seg002:08BF								 ; autocontrol_guard_inactive+35j
seg002:08BF 83 3E 4C 59 00			 cmp	 can_guard_see_kid, 0
seg002:08C4 74 12				 jz	 return
seg002:08C6 83 3E 9E 0F 0D			 cmp	 current_level,	13 ; Jaffar waits
seg002:08CB 75 07				 jnz	 loc_3F54
seg002:08CD 83 3E 34 3D 00			 cmp	 guard_notice_timer, 0
seg002:08D2 75 04				 jnz	 return
seg002:08D4			 
seg002:08D4			 loc_3F54:				 ; CODE	XREF: autocontrol_guard_inactive+55j
seg002:08D4 0E					 push	 cs
seg002:08D5 E8 85 FE				 call	 near ptr move_down_forw
seg002:08D8			
	; <--- Paste the "loc_8BB3" section here
seg002:08D8			 return:				 ; CODE	XREF: autocontrol_guard_inactive+Bj
seg002:08D8								 ; autocontrol_guard_inactive+3Bj ...
seg002:08D8 8B E5				 mov	 sp, bp
seg002:08DA 5D					 pop	 bp
seg002:08DB CB					 retf

...

seg007:064A			 loc_8B9A:				 ; CODE	XREF: animate_leveldoor+18j
seg007:064A FE 06 28 43				 inc	 curr_modifier
seg007:064E 80 3E 28 43 2B			 cmp	 curr_modifier,	43
seg007:0653 72 41				 jb	 loc_8BE6
	; I think this piece of code would be unnecessary after the action, so we can move these 7 bytes too and change it to something we need
seg007:0655 83 3E 9C 40 00			 cmp	 leveldoor_open, 0 
seg007:065A 74 07				 jz	 loc_8BB3
	; ^ This piece
seg007:065C 83 3E 9C 40 02			 cmp	 leveldoor_open, 2
seg007:0661 75 41				 jnz	 loc_8BF4
seg007:0663
	; Move this whole "loc_8BB3" section to the autocontrol_guard_inactive procedure		 
seg007:0663			 loc_8BB3:				 ; CODE	XREF: animate_leveldoor+69j
seg007:0663 C6 06 BE 4C FF			 mov	 trob_type, 0FFh
seg007:0668 9A 14 72 79 0C			 call	 stop_sounds
seg007:066D C7 06 9C 40 01 00			 mov	 leveldoor_open, 1
seg007:0673 83 3E 9E 0F 04			 cmp	 current_level,	4 ; Special event: place mirror
seg007:0678 75 2A				 jnz	 loc_8BF4
seg007:067A B8 04 00				 mov	 ax, 4
seg007:067D 50					 push	 ax		 ; room
seg007:067E 50					 push	 ax		 ; col
seg007:067F 2B C0				 sub	 ax, ax
seg007:0681 50					 push	 ax		 ; row
seg007:0682 9A 06 00 CB 06			 call	 get_tile
seg007:0687 8A 1E ED 42				 mov	 bl, curr_tile_pos
seg007:068B 2A FF				 sub	 bh, bh
seg007:068D 8B 36 8A 65				 mov	 si, curr_room_tiles
seg007:0691 C6 00 0D				 mov	 byte ptr [bx+si], tiles_13_mirror ; mirror
seg007:0694 EB 0E				 jmp	 short loc_8BF4
If I understand "jumps" (jz, jmp) correctly, they'd become a little messed after this action, so we'd have to rectify them.

However, I'm not experienced enough to make this work. If it's even possible. Can someone help?

dmitry_s wrote: July 27th, 2021, 7:25 am The way I got around the second problem (in SDLPOP) is to check on Jaffar's level whether the "guard_notice_timer" is 0 or the "ABS(char_opp_dist())" is <= 42.

I am not sure if it is possible to do in the DOS file due to an extra operation.
David has already figured out the solution to the second problem in DOS. But judging by the variables you mentioned, you might have mistaken the 3rd problem for the 2nd one, didn't you? :D
I think you're right, the extra operation might be a problem. I'm not sure though. Thanks anyway :)
T0mmiTheGreat
Sheikh
Sheikh
Posts: 25
Joined: February 22nd, 2021, 8:48 pm
Location: Czech Republic

Re: Special events in Level 13 are a little flawed

Post by T0mmiTheGreat »

T0mmiTheGreat wrote: July 17th, 2021, 5:01 pm The player can draw his sword before Jaffar does, being able to kill the Vizier in one hit.
Another possible solution has occured to me.

It seems like I can't achieve what I want without breaking something else. So the best thing to do is to sacrifice the most superfluous thing. In the PoP exe file there are full 51 bytes of code, merely responsible for playing a tune when encountering a mirror. That is expandable. And most importantly, it will make way for a solution a lot easier to execute than my previous idea.

The plan is: Jaffar stands on a fake loose tile. The moment he strikes his fighting pose, the tile beneath him changes to a regular one. This prevents the kid from drawing his sword until Jaffar starts fighting. How to do it:

Code: Select all

; First change a few subroutine calls:
in leave_room():
seg002:060C			 leave_left:				
seg002:060C 0E				 push	 cs				; <-- change to NOP
seg002:060D E8 C3 00			 call	 near ptr play_mirr_mus		; <-- this too
seg002:0610 0E				 push	 cs
seg002:0611 E8 51 00			 call	 near ptr level3_set_chkp
seg002:0614 0E				 push	 cs
seg002:0615 E8 2B 00			 call	 near ptr Jaffar_exit
...
in autocontrol_guard_inactive():
seg002:08BF			 loc_3F3F:	
seg002:08BF 83 3E 4C 59	00		 cmp	 can_guard_see_kid, 0
seg002:08C4 74 12			 jz	 return
seg002:08C6 83 3E 9E 0F	0D		 cmp	 current_level,	13 ; Jaffar waits
seg002:08CB 75 07			 jnz	 loc_3F54
seg002:08CD 83 3E 34 3D	00		 cmp	 guard_notice_timer, 0
seg002:08D2 75 04			 jnz	 return
seg002:08D4			 
seg002:08D4			 loc_3F54:	
seg002:08D4 0E				 push	 cs
seg002:08D5 E8 85 FE			 call	 near ptr move_down_forw		; <-- change to "near ptr play_mirr_mus"

; Then rewrite completely this subroutine...
seg002:06D3			 ; ŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰ S U B R O U T I N E ŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰ
seg002:06D3			 
seg002:06D3			 
seg002:06D3			 ; void	__pascal far play_mirr_mus()
seg002:06D3			 play_mirr_mus	 proc far		 ; CODE	XREF: leave_room+109p
seg002:06D3 83 3E 9C 40	00		 cmp	 leveldoor_open, 0
seg002:06D8 74 2B			 jz	 locret_3D85
seg002:06DA 83 3E 9C 40	4D		 cmp	 leveldoor_open, 4Dh ; 'M' ; was the music played already?
seg002:06DF 74 24			 jz	 locret_3D85
seg002:06E1 83 3E 9E 0F	04		 cmp	 current_level,	4 ; Special event: mirror music
seg002:06E6 75 1D			 jnz	 locret_3D85
seg002:06E8 80 3E 27 3D	00		 cmp	 char.curr_row,	0 ; only if he is in the top row
seg002:06ED 75 16			 jnz	 locret_3D85
seg002:06EF 80 3E 2B 3D	0B		 cmp	 char.room, 11
seg002:06F4 75 0F			 jnz	 locret_3D85
seg002:06F6 B8 19 00			 mov	 ax, sound_25_presentation
seg002:06F9 50				 push	 ax		 ; sound_id
seg002:06FA 9A C5 12 00	00		 call	 play_sound
seg002:06FF C7 06 9C 40	4D 00		 mov	 leveldoor_open, 4Dh ; 'M'
seg002:0705			 
seg002:0705			 locret_3D85:				 ; CODE	XREF: play_mirr_mus+5j
seg002:0705								 ; play_mirr_mus+Cj ...
seg002:0705 CB				 retf
seg002:0705			 play_mirr_mus	 endp

; ... like this:
seg002:06D3			 ; ŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰ S U B R O U T I N E ŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰ
seg002:06D3			 
seg002:06D3			 
seg002:06D3			 ; void	__pascal far play_mirr_mus()
seg002:06D3			 play_mirr_mus	 proc far		 ; CODE	XREF: leave_room+109p
seg002:06D3 E8 87 00			 call	 near ptr move_down_forw
seg002:06D6 83 3E 9E 0F	0D		 cmp	 current_level, 13
seg002:06DB 75 28			 jnz	 {40 bytes => 0x28 bytes}
seg002:06DD B8 06 00			 mov	 ax, 6	; room
seg002:06E0 50				 push	 ax
seg002:06E1 B8 06 00			 mov	 ax, 6	; column
seg002:06E4 50				 push	 ax
seg002:06E5 B8 01 00			 mov	 ax, 1	; row
seg002:06E8 50				 push	 ax
seg002:06E9 9A 06 00 CB	06		 call	 get_tile
seg002:06EE 8A 1E ED 42			 mov	 bl, curr_tile_pos
seg002:06F2 2A FF			 sub	 bh, bh
seg002:06F4 8B 36 8A 65			 mov	 si, curr_room_tiles
seg002:06F8 C6 00 01			 mov	 byte ptr [bx+si], tiles_1_floor	; regular empty floor tile
seg002:06FB E9 07 00			 jmp	 {7 bytes}
seg002:06FE 90 90 90 90 90 90 90	 ; these bytes don't need to be changed as they will always be skipped
seg002:0705 CB					 retf
seg002:0705			 play_mirr_mus	 endp
At 0x553C write: 90 90 90 90
At 0x5806 write: FB FD
At 0x5603 write: E8 87 00 83 3E 9E 0F 0D 75 28 B8 06 00 50 B8 06 00 50 B8 01 00 50 9A 06 00 CB 06 8A 1E ED 42 2A FF 8B 36 8A 65 C6 00 01 E9 07 00

And here is what it does. Or actually, what it is supposed to do. For a better comprehensibility I'll call the modified subroutine "can_draw_sword" instead of "play_mirr_mus"):
Spoiler: show
  • 90 90 90 90: Prevent calling the can_draw_sword (originally: playing mirror music) when leaving a room to the left.
  • (E8) FB FD: In the autocontrol_guard_inactive subroutine, if the guard_notice_timer is 0, call can_draw_sword instead of move_down_forw. "cds" is -517 bytes away from this call => -517 decimal is FDFB hex.
  • E8 87 00: There do call the move_down_forw – at the beginning of the can_draw_sword subroutine. "mdf" is 135 bytes away from this call => 135 = 0x0087.
  • 83 3E 9E 0F 0D 75 28: if the current level is 13 (0x0D), continue with replacing the tiles. Else, skip 40 (0x28) bytes (to the end of the subroutine).
  • B8 06 00 50: set the room (06) of the tile, then write it to the stack
  • B8 06 00 50: set the column (06) of the tile, then write it to the stack
  • B8 01 00 50: set the row (01) of the tile, then write it to the stack
  • 9A 06 00 CB 06 8A 1E ED 42 2A FF 8B 36 8A 65 C6 00 01: stuff copied from the sword_disappears subroutine. I'm not 100% sure what it does, but I know it is responsible for choosing the correct tile and then changing it.
  • E9 07 00: skip the unutilized 7 bytes

But there are (again) some problems. When I enter a room containing a guard, the game freezes. And that isn't the only error. I tried to force the program to skip the move_down_forw call, and then walk into the room with Jaffar. When the guard_notice_timer had reached 0, the game reported "run-time error R6003 - integer divide by 0" and froze.

I am self-taught in assembly so I probably misunderstood something about it. Could somebody please explain where's the mistake?
David
The Prince of Persia
The Prince of Persia
Posts: 2846
Joined: December 11th, 2008, 9:48 pm
Location: Hungary

Re: Special events in Level 13 are a little flawed

Post by David »

I can see two problems:

1. Before "call near ptr move_down_forw" there should be a PUSH CS.
It's a far procedure and you are calling it with CALL NEAR.

At 0x5603 write: 0E E8 86 00 83 3E 9E 0F 0D 75 27 B8 06 00 50 B8 06 00 50 B8 01 00 50 9A 06 00 CB 06 8A 1E ED 42 2A FF 8B 36 8A 65 C6 00 01 E9 06 00 (90 90 90 90 90 90 CB)

2. You need a relocation entry for the segment in the far call "call get_tile".
We can change the entry for the "call play_sound" which you have overwritten.
(We need to get rid of the old entry anyway.)

seg002 = 0x0368
Old target: 0368:06FD = 0x03680+0x06FD = 0x3D7D
("call play_sound" starts at seg002:06FA, the segment address is 3 bytes after that.)
New target: 0368:06ED = 0x03680+0x06ED = 0x3D6D
("call get_tile" starts at seg002:06E9 in your code, but we added a 1-byte PUSH CS before that, and the 3-byte offset is needed here as well.)

At 0x082C change: 7D 3D 00 00 to 6D 3D 00 00


After these changes it works for me.
T0mmiTheGreat
Sheikh
Sheikh
Posts: 25
Joined: February 22nd, 2021, 8:48 pm
Location: Czech Republic

Re: Special events in Level 13 are a little flawed

Post by T0mmiTheGreat »

For me it works too. Thank you :)
David wrote: August 7th, 2021, 12:37 pm 2. You need a relocation entry for the segment in the far call "call get_tile".
We can change the entry for the "call play_sound" which you have overwritten.
(We need to get rid of the old entry anyway.)
So, there is a code segment that returns the "cursor" back to the place where a procedure was "far called" when it reaches the retf instruction. Do I get it right?

Also, you said:
David wrote: March 7th, 2021, 10:23 am To find hacks, I usually use my disassemblies.
but I don't see those bytes anywhere in the disassemblies.
David wrote: August 7th, 2021, 12:37 pm At 0x082C change: 7D 3D 00 00 to 6D 3D 00 00
How did you find this hack?
David
The Prince of Persia
The Prince of Persia
Posts: 2846
Joined: December 11th, 2008, 9:48 pm
Location: Hungary

Re: Special events in Level 13 are a little flawed

Post by David »

T0mmiTheGreat wrote: August 7th, 2021, 4:27 pm
David wrote: August 7th, 2021, 12:37 pm 2. You need a relocation entry for the segment in the far call "call get_tile".
We can change the entry for the "call play_sound" which you have overwritten.
(We need to get rid of the old entry anyway.)
So, there is a code segment that returns the "cursor" back to the place where a procedure was "far called" when it reaches the retf instruction. Do I get it right?
No, that's a different thing.
CALL stores the return address on the stack, and RETF gets it from there.
(I'm assuming "cursor" means the address of the current instruction, pointed by the CS and the IP registers.)

Instead, the relocations are needed so that the CALL points to the correct address.

Take this instruction for example:

Code: Select all

seg002:06FA 9A C5 12 00 00      call  play_sound
The names "play_sound" and "seg002" are only for human readability, the call really looks like this:

Code: Select all

0368:06FA  9A C5 12 00 00   CALL FAR 0000:12C5
The 0x0000 is the segment address, and the 0x12C5 is the offset within that segment.

To calculate a physical (linear) address from a segment and an offset, multiply the segment by 16 (0x10), and add the offset.
(This also means that the same linear address can be represented by multiple different segment-offset pairs. There will be an example of this later.)

The segment addresses in EXE files are relative to the beginning of the code section within the EXE. (Offset 0x18B0 in PRINCE.EXE)

Now, when an EXE file is loaded into RAM, it will not start from segment 0x0000, but from some higher address, called the base address.
But the CALL above points to segment 0x0000, so it needs to be fixed by DOS when it loads the EXE.
To make this possible, there is a table in the EXE header which tells where do segment addresses occur in the EXE file.
(In PRINCE.EXE it starts at offset 0x001C and ends at 0x18AF.)
When an EXE is loaded, DOS goes through those addresses, and adds the base address to the numbers stored at the locations specified by those addresses.

So for example, if the EXE is loaded starting from segment 0x01A4 in RAM then the 0x0000 in the CALL above is increased by 0x01A4.
Then the CALL will become this:

Code: Select all

050C:06FA  9A C5 12 A4 01   CALL FAR 01A4:12C5
(Note how the segment address of the instruction itself changes as well: 0x0368 + 0x01A4 = 0x050C)

That's why relocations are needed.

T0mmiTheGreat wrote: August 7th, 2021, 4:27 pm Also, you said:
David wrote: March 7th, 2021, 10:23 am To find hacks, I usually use my disassemblies.
but I don't see those bytes anywhere in the disassemblies.
True, the relocation table doesn't appear in the disassembly.
T0mmiTheGreat wrote: August 7th, 2021, 4:27 pm
David wrote: August 7th, 2021, 12:37 pm At 0x082C change: 7D 3D 00 00 to 6D 3D 00 00
How did you find this hack?
I explained it above the hack, though I admit it was a bit dense.

"seg002 = 0x0368" comes from the .map file which I posted here: viewtopic.php?p=33346#p33346

Code: Select all

 Start  Stop   Length Name               Class
 03680H 047EFH 01170H seg002             CODE
In this file the start address of segments is given in bytes.
To get the actual segment address, just remove the last digit. We get 0x0368.

The 0x0000 in call play_sound (CALL FAR 0000:12C5) is at seg002:06FA + 3 = seg002:06FD = 0368:06FD.
But the relocation table doesn't contain that address in this form. (Remember what I wrote about segment-offset pairs?)
(The relocation table consists of segment-offset pairs as well.)
The relocation table in the unpacked PRINCE.EXE was reconstructed by UPACKEXE, which puts the lower 4 hex digits of the linear address into the offset part and the rest into the segment part.

0368:06FD is equal to the linear address 0x0368 * 0x10 + 0x06FD = 0x3D7D, which UPACKEXE wrote into the relocation table as 0000:3D7D.
I searched for this address in the EXE.
The address can be found at offset 0x082C, as 7D 3D 00 00.

The new value to be entered there can be calculated similarly, based on the location of the new CALL in the rewritten play_mirr_mus.
T0mmiTheGreat
Sheikh
Sheikh
Posts: 25
Joined: February 22nd, 2021, 8:48 pm
Location: Czech Republic

Re: Special events in Level 13 are a little flawed

Post by T0mmiTheGreat »

(Edited August 11th, 2021, 11:14 pm)

Great! I think now I understand how the procedure calls work.

Just would like to have this checked up; to find the address of a procedure call relocation (let's say call get_tile in bumped procedure),

Code: Select all

seg004:04BD 9A 06 00 CB 06			 call	 get_tile
I need to:
  • find the address* of the segment which the call is located in (with the help of the .MAP file, I found out that the seg004 starts at 0x05324; remove the last digit – the segment address* is 0x0532),
  • find the "segment" part of the call instruction (4th & 5th byte – here it's the CB 06) and calculate its offset within the current segment (the instruction starts at 0x04BD, the "segment" part is at 0x04C0 (and 0x04C1)),
  • multiply the segment address by 0x10, then increase by the offset of the "segment" part (in hex: 0532 × 10 + 04C0 = 57E0).
  • Now I have this byte combination: E0 57 00 00, which is at offset 0x0AA4 in the .EXE. Is this correct?
*When talking about segment addresses, I guess we mean "within the relocation table"…?

I have some doubts about the last two 00 bytes. At first, I thought they represent the address of the segment which the called procedure (not the call instruction) lies in. But I was wrong, they are always zero, aren't they? Though, in the .EXE file there are addresses that end with 10 (e.g. B2 BC 00 10) starting from 0x13B8 offset. Are they related to procedure calls?

And one last thing:
David wrote: August 7th, 2021, 12:37 pm 2. You need a relocation entry for the segment in the far call "call get_tile".
We can change the entry for the "call play_sound" which you have overwritten.
(We need to get rid of the old entry anyway.)
As far as I understand, all the segment parts of far calls are increased by the "base address" value. And so that the DOS knows which values to increase, it looks up their byte offsets in the table. So, if I want to delete a procedure call, I need to change the relocation table entry to its own offset within the EXE file. This way it doesn't change anything important. That means:
  • If I want to get rid of the relocation in your example, I need to set it to 2C 08 00 00.
  • If I want to get rid of the relocation in my example, I need to set it to A4 0A 00 00.
Again, correct me if I'm wrong. :)
T0mmiTheGreat
Sheikh
Sheikh
Posts: 25
Joined: February 22nd, 2021, 8:48 pm
Location: Czech Republic

Re: Special events in Level 13 are a little flawed

Post by T0mmiTheGreat »

T0mmiTheGreat wrote: August 9th, 2021, 10:26 pm So, if I want to delete a procedure call, I need to change the relocation table entry to its own offset within the EXE file.
Now that I think about it, this is wrong. If I want to change the relocation table entry to its address, I would have to use negative numbers, because:
David wrote: August 8th, 2021, 9:57 pm The segment addresses in EXE files are relative to the beginning of the code section within the EXE. (Offset 0x18B0 in PRINCE.EXE)
So the entries in the table point to the address that is relative to the beginning of the code section. The addresses are increased by the 0x18B0 offset. But the relocation table is situated before the offset.

So now my question is: How to get rid of a relocation table entry?
David
The Prince of Persia
The Prince of Persia
Posts: 2846
Joined: December 11th, 2008, 9:48 pm
Location: Hungary

Re: Special events in Level 13 are a little flawed

Post by David »

T0mmiTheGreat wrote: August 9th, 2021, 10:26 pm Just would like to have this checked up; to find the address of a procedure call relocation (let's say call get_tile in bumped procedure),

Code: Select all

seg004:04BD 9A 06 00 CB 06			 call	 get_tile
I need to:
  • find the address* of the segment which the call is located in (with the help of the .MAP file, I found out that the seg004 starts at 0x05324; remove the last digit – the segment address* is 0x0532),
  • find the "segment" part of the call instruction (4th & 5th byte – here it's the CB 06) and calculate its offset within the current segment (the instruction starts at 0x04BD, the "segment" part is at 0x04C0 (and 0x04C1)),
  • multiply the segment address by 0x10, then increase by the offset of the "segment" part (in hex: 0532 × 10 + 04C0 = 57E0).
  • Now I have this byte combination: E0 57 00 00, which is at offset 0x0AA4 in the .EXE. Is this correct?
Correct.

T0mmiTheGreat wrote: August 9th, 2021, 10:26 pm *When talking about segment addresses, I guess we mean "within the relocation table"…?
What do you mean?

T0mmiTheGreat wrote: August 9th, 2021, 10:26 pm I have some doubts about the last two 00 bytes. At first, I thought they represent the address of the segment which the called procedure (not the call instruction) lies in. But I was wrong, they are always zero, aren't they?
They represent the segments of the call instruction.

Two things from my previous post are relevant here:
1. The same linear address can be represented by multiple different segment-offset pairs.
For very simple example, 0001:0000 and 0000:0010 both point to linear address 0x0010.

2. The PRINCE.EXE which comes with Apoplexy is unpacked for easier editing, but PRINCE.EXE was distributed in packed (compressed) form.
Before Broderbund packed their PRINCE.EXE with EXEPACK, the entry in your example probably was 0532:04C0, or C0 04 32 05.
EXEPACK compresses the relocation table as well, it stores linear addresses in the packed EXE (at the end of the file).
When UPACKEXE decompresses the relocation table, it only knows the linear addresses, 57E0 in your example.
It converts them to segment-offset addresses in a simple way I described in my previous post: A linear address like 0x12345 becomes 1000:2345.
57E0 becomes 0000:57E0.

If you look at other DOS EXEs (which are not packed or extracted from a packed EXE), you can see non-zero segments in relocation table entries.

T0mmiTheGreat wrote: August 9th, 2021, 10:26 pm Though, in the .EXE file there are addresses that end with 10 (e.g. B2 BC 00 10) starting from 0x13B8 offset. Are they related to procedure calls?
They are for procedure calls with linear addresses above 0xFFFF.
B2 BC 00 10 means 1000:BCB2, i.e. 1000 × 10 + BCB2 = 1BCB2.

0x1BCB2 + 0x18B0 = 0x1D562 is the offset in PRINCE.EXE.
This is the address of the segment part of this function pointer:

Code: Select all

data:0FD0 25 05	4F 02			       dd cutscene_12
T0mmiTheGreat wrote: August 14th, 2021, 9:52 am
T0mmiTheGreat wrote: August 9th, 2021, 10:26 pm So, if I want to delete a procedure call, I need to change the relocation table entry to its own offset within the EXE file.
Now that I think about it, this is wrong. If I want to change the relocation table entry to its address, I would have to use negative numbers, because:
David wrote: August 8th, 2021, 9:57 pm The segment addresses in EXE files are relative to the beginning of the code section within the EXE. (Offset 0x18B0 in PRINCE.EXE)
So the entries in the table point to the address that is relative to the beginning of the code section. The addresses are increased by the 0x18B0 offset. But the relocation table is situated before the offset.

So now my question is: How to get rid of a relocation table entry?
You could point it to uninitialized data, i.e. to an address marked with question marks in the disassembly.

But the "proper" way of removing an entry is this:
1. Move all the following entries back by 4 bytes.
That is: delete the bytes of the entry (this makes the file 4 bytes shorter) and then add four zero bytes after the end of the relocation table (before the 0x55 byte which was at 0x18B0).
2. Decrease the relocations count field at offsets 6-7 in the EXE file.
Post Reply