"sound off" does not turn all sounds off

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

"sound off" does not turn all sounds off

Post by David »

If I start PoP1 1.0 or 1.3 with digital sound (not pc speaker) in DOSBox, Ctrl-S seems to turn off only the music (MIDI). Is this a bug?
In version 1.4 Ctrl-S works as it should.
Andrew
Wise Scribe
Wise Scribe
Posts: 313
Joined: July 16th, 2009, 4:39 pm

Re: "sound off" does not turn all sounds off

Post by Andrew »

I can reproduce with PoP1 1.4 too with SB Pro selected in DOSBox (it's the only audio option besides PC Speaker). Does seem to be a bug.

Edit: Speaking of which, while testing this I came across another very funny bug! First run PoP1 1.4's Setup.exe and select PC Speaker for both options:
Prince turns into Mouse.png
Prince turns into Mouse.png (4.2 KiB) Viewed 9497 times
Next run Prince.exe (I used the uncracked/original file) with prince improved 7. In my DOSBox the Prince turns into a mouse after dying, and when I press any key to restart the level he even falls in a crouching position and then again turns into a mouse! :lol: As you can see by the version text at the end, clearly there's some memory corruption. Perhaps it is not a game but a DOSBox bug?

Image

Can someone confirm?
Attachments
Prince turns into Mouse.rar
DOSBox video recording
(211.31 KiB) Downloaded 344 times
David
The Prince of Persia
The Prince of Persia
Posts: 2877
Joined: December 11th, 2008, 9:48 pm
Location: Hungary

Re: "sound off" does not turn all sounds off

Post by David »

I tried with DOSBox 0.73, 0.74, 0.60, plus some unofficial versions from http://ykhwong.x-y.net/safe.php (20110705 and 20130124) but I could not reproduce this.
Which version of DOSBox were you using?
Andrew wrote:As you can see by the version text at the end, clearly there's some memory corruption.
It seems that every fourth byte was increased by one:

Code: Select all

PRINCE OF PERSIA  V1.4
QRINDE OG PESSIA! V1/4
!   !   !   !   !   !
I can't extract the RAR you attached. I suppose it would show the same thing as the GIF, right?

However, I found something else: In joystick mode (*), PoP1 v1.4 fills the screen with debug messages, which scrolls the screen up, messing up the room:
This can be redirected into a file (or NUL), though.
(*) You don't need to have an actual joystick, just set joysticktype=2axis in dosbox.conf.
Attachments
joystick-debug.png
joystick-debug.png (2.88 KiB) Viewed 9490 times
Andrew
Wise Scribe
Wise Scribe
Posts: 313
Joined: July 16th, 2009, 4:39 pm

Re: "sound off" does not turn all sounds off

Post by Andrew »

David wrote:I tried with DOSBox 0.73, 0.74, 0.60, plus some unofficial versions from http://ykhwong.x-y.net/safe.php (20110705 and 20130124) but I could not reproduce this.
Which version of DOSBox were you using?
0.74 official.
David wrote:It seems that every fourth byte was increased by one:

Code: Select all

PRINCE OF PERSIA  V1.4
QRINDE OG PESSIA! V1/4
!   !   !   !   !   !
Not always, the corruption is random.
David wrote:I can't extract the RAR you attached. I suppose it would show the same thing as the GIF, right?
Yes, it contains the DOSBox AVI. The archive uses the latest RAR5 format.

I've attached my DOSBox conf with this post. It might help you reproduce the bug. If not, perhaps it's a DOSBox bug triggered by something on my system. I'll check on my other systems and see if I can reproduce.
Attachments
dosbox-0.74.rar
(3.51 KiB) Downloaded 362 times
David
The Prince of Persia
The Prince of Persia
Posts: 2877
Joined: December 11th, 2008, 9:48 pm
Location: Hungary

Re: "sound off" does not turn all sounds off

Post by David »

Now I could reproduce it!
Try to grab the ledge!
Also, the bug works on all levels. Just wait. The kid won't turn into a mouse, but lean forward and become uncontrollable. After that the game will show the frames of the jumping but *very* slowly.
And: there is a repeating high pitch beep but only when there are no other sounds playing.
Perhaps the sound player has a bug?
This works only if I configure the game to pc speaker, using the stdsnd command line parameter is not enough.

The problem seems to be caused by the SET statements in the [autoexec] section.
They don't have to be there, the same thing happens if I enter a SET directly in the command line.
Even a small "set a=b" will cause this.
But there are some environment variables already set by default!
Andrew wrote:Not always, the corruption is random.
I have seen these:

Code: Select all

QRINDE OG PESSIA! V1/4
PRJNCE!OF QERSJA  W1.4
PRINCE OF QERSJA  W1.4
The changed characters are always 4 bytes apart.
If you hold ctrl-V, you'll see how the characters become corrupted one by one.
The other texts are also corrupted.

If I use DOSBox 0.73, the game will constantly pause itself.

If I use the unofficial version of DOSBox that has a debugger, I first have to delete the GLIDE and PROMPT variables that are not present in the official version, and then use set a=b.
Now I am going to debug the game. This may take a while so I submit this post now.
Andrew
Wise Scribe
Wise Scribe
Posts: 313
Joined: July 16th, 2009, 4:39 pm

Re: "sound off" does not turn all sounds off

Post by Andrew »

David wrote:Now I could reproduce it!
Try to grab the ledge!
Also, the bug works on all levels. Just wait. The kid won't turn into a mouse, but lean forward and become uncontrollable. After that the game will show the frames of the jumping but *very* slowly.
And: there is a repeating high pitch beep but only when there are no other sounds playing.
Ha! Yes, I can confirm.
David wrote: Perhaps the sound player has a bug?
It's quite possible, but 1.4 in PC speaker mode must also be doing something different because 1.0 (no setup, PC speaker by default), 1.1 (no setup, SB by default?) and 1.3 (has setup) aren't affected. Need not necessarily be a game bug, just some difference in behavior that's triggering a DOSBox bug.
David wrote: This works only if I configure the game to pc speaker, using the stdsnd command line parameter is not enough.
Yep, that's why I specifically provided the setup screenshot.
David wrote: The problem seems to be caused by the SET statements in the [autoexec] section.
They don't have to be there, the same thing happens if I enter a SET directly in the command line.
Even a small "set a=b" will cause this.
But there are some environment variables already set by default!
Good catch! Also, the bug's not manifested if I run 1.4 from a DOS window in 32-bit Win7, but of course sound doesn't work properly there anyway. It would be nice if someone can confirm in actual real mode DOS.
David wrote: I have seen these:

Code: Select all

QRINDE OG PESSIA! V1/4
PRJNCE!OF QERSJA  W1.4
PRINCE OF QERSJA  W1.4
The changed characters are always 4 bytes apart.
If you hold ctrl-V, you'll see how the characters become corrupted one by one.
The other texts are also corrupted.
Ok, so the predictability points to a DOSBox bug, right?
David wrote: If I use the unofficial version of DOSBox that has a debugger, I first have to delete the GLIDE and PROMPT variables that are not present in the official version, and then use set a=b.
Now I am going to debug the game. This may take a while so I submit this post now.
I look forward eagerly to the results of your investigation. :) Moreover if you can figure it out and let the DOSBox developers know, perhaps they will fix it in the next release for everyone.
David wrote:However, I found something else: In joystick mode (*), PoP1 v1.4 fills the screen with debug messages, which scrolls the screen up, messing up the room:
This can be redirected into a file (or NUL), though.
(*) You don't need to have an actual joystick, just set joysticktype=2axis in dosbox.conf.
This makes me wonder, just how many 'bugs' that we find, document here and attribute to the game are actually DOSBox bugs instead? For example, is the joystick bug you mentioned above 1.4 or DOSBox's fault? I regret now that my old DOS machine for game playing is no longer available. I need to replace it anyway, since no emulator can perfectly emulate all the hardware features and even bugs that program authors sometimes creatively used.

P.S. Speaking of bugs, did you manage to reproduce the original bug you started this thread with in 1.4 too? I may have found a hint in the PoP1 manual:
PoP1.0_Sound_Keys.png
The game setup allows you to select different sound and music devices. As you can see, in the Apple version there's an extra key combo i.e. Ctrl-N to turn off the music. This is similar to PoP2 which has Alt-S and Alt-M. In the DOS version of PoP1 Ctrl-N isn't implemented and Ctrl+S seems to actually turn off the music (there's no title or level loading music). So the description in the manual for Ctrl-S for the DOS version seems to be incorrect since it acts like Ctrl-N on Macs. There seems to be no way to turn off all sounds in PoP1.
David
The Prince of Persia
The Prince of Persia
Posts: 2877
Joined: December 11th, 2008, 9:48 pm
Location: Hungary

Re: "sound off" does not turn all sounds off

Post by David »

Andrew wrote:There seems to be no way to turn off all sounds in PoP1.
Unless you're using the PC speaker.
Andrew wrote:For example, is the joystick bug you mentioned above 1.4 or DOSBox's fault?
There is a "JoyX: %2d JoyY: %2d Btn: %2d" string in PRINCE.EXE. And the code in fact refers to that string.
Andrew wrote:I look forward eagerly to the results of your investigation.
Here it is: (very long!)
First I added memory-change breakpoints on the version string (BPLM 00019502 and BPLM 00019500).
The breakpoint was triggered by this code:

Code: Select all

01A3:140B  5E                  pop  si
01A3:140C  0000                add  [bx+si],al ; <-- overwritten!
01A3:140E  3E2843A7            sub  ds:[bp+di-59],al
01A3:1412  7407                je   0000141B ($+7)
Those two statements look very suspicious, so I compared the code with the contents of the EXE:

Code: Select all

.0000140B:5E                             pop       si
.0000140C:CB                             retf ; <-- original!
.0000140D:803E2843A7                     cmp       [+4328],A7
.00001412:7407                           je        .0000141B
Ah! Something writes zero to 01A3:140C, that is 1A30+140C=2E3C. So I set a memory-change breakpoint there (BPLM 00002E3C).
The debugger stopped here:

Code: Select all

0E17:208A  55                  push bp
0E17:208B  8BEC                mov  bp,sp
0E17:208D  1E                  push ds
0E17:208E  B84219              mov  ax,1942
0E17:2091  8ED8                mov  ds,ax
0E17:2093  33C0                xor  ax,ax
0E17:2095  A3342E              mov  [2E34],ax
0E17:2098  A3362E              mov  [2E36],ax
0E17:209B  A33E2E              mov  [2E3E],ax
0E17:209E  A3402E              mov  [2E40],ax
0E17:20A1  C55606              lds  dx,[bp+06]
0E17:20A4  B8003D              mov  ax,3D00
0E17:20A7  CD21                int  21
0E17:20A9  A33C2E              mov  [2E3C],ax ; <-- this
0E17:20AC  7308                jnc  000020B6 ($+8)
The contents of the registers:

Code: Select all

EAX=00000002  ESI=0000008D  DS=0000   ES=0000   FS=0000   GS=0000   SS=1942 Real
EBX=000077FC  EDI=00007555  CS=0E17   EIP=000020AC  C1 Z1 S0 O0 A0 P1 D0 I1 T0
ECX=00000000  EBP=00007490                                          IOPL3  CPL0
EDX=00000000  ESP=0000748E                                  182627883
Hey! Why is DS=0?
It was the LDS instruction!
Let's look at the stack: (BP+6=7496)

Code: Select all

1942:7490     AE 74 50 1F 17 0E 00 00 00 00 42 19 55 75 AE 74
It is a far pointer argument passed on the stack.
Who called this procedure?
The answer is:

Code: Select all

0E17:1F32  833E242E00          cmp  word [2E24],0000
0E17:1F37  740D                je   00001F46 ($+d)
0E17:1F39  B8FC2D              mov  ax,2DFC
0E17:1F3C  8946F0              mov  [bp-10],ax
0E17:1F3F  8C5EF2              mov  [bp-0E],ds
0E17:1F42  8CDA                mov  dx,ds
0E17:1F44  EB03                jmp  short 00001F49 ($+3)
0E17:1F46  2BC0                sub  ax,ax ; <- ax=0
0E17:1F48  99                  cwd        ; <- dx=0
0E17:1F49  52                  push dx
0E17:1F4A  50                  push ax
0E17:1F4B  90                  nop
0E17:1F4C  0E                  push cs
0E17:1F4D  E83A01              call 0000208A ($+13a) ; <-- this
0E17:1F50  83C404              add  sp,0004
Let's look at the annotated disassembly:

Code: Select all

seg009:1F32		    cmp	    word_1A814,	0
seg009:1F37		    jz	    loc_E686
seg009:1F39		    mov	    ax,	offset aDigi_drv ; "DIGI.DRV"
seg009:1F3C		    mov	    word ptr [bp+var_10], ax
seg009:1F3F		    mov	    word ptr [bp+var_10+2], ds
seg009:1F42		    mov	    dx,	ds
seg009:1F44		    jmp	    short loc_E689
seg009:1F46 ; ───────────────────────────────────────────────────────────────────────────
seg009:1F46 
seg009:1F46 loc_E686:				    ; CODE XREF: load_drivers+31j
seg009:1F46		    sub	    ax,	ax	    ; NULL
seg009:1F48		    cwd
seg009:1F49 
seg009:1F49 loc_E689:				    ; CODE XREF: load_drivers+3Ej
seg009:1F49		    push    dx
seg009:1F4A		    push    ax		    ; filename
seg009:1F4B		    nop
seg009:1F4C		    push    cs
seg009:1F4D		    call    near ptr load_driver
seg009:1F50		    add	    sp,	4

Code: Select all

seg009:208A load_driver	    proc far		    ; CODE XREF: load_drivers+47p
seg009:208A					    ; load_drivers+6Fp
seg009:208A 
seg009:208A filename	    = dword ptr	 6
seg009:208A 
seg009:208A		    push    bp
seg009:208B		    mov	    bp,	sp
seg009:208D		    push    ds
seg009:208E		    mov	    ax,	seg seg016
seg009:2091		    mov	    ds,	ax
seg009:2093		    xor	    ax,	ax
seg009:2095		    mov	    word ptr driver_code_ptr, ax
seg009:2098		    mov	    word ptr driver_code_ptr+2,	ax
seg009:209B		    mov	    word ptr driver_header_ptr,	ax
seg009:209E		    mov	    word ptr driver_header_ptr+2, ax
seg009:20A1		    lds	    dx,	[bp+filename] ;	ahem...
seg009:20A4		    mov	    ax,	3D00h
seg009:20A7		    int	    21h		    ; DOS - 2+ - OPEN DISK FILE	WITH HANDLE
seg009:20A7					    ; DS:DX -> ASCIZ filename
seg009:20A7					    ; AL = access mode
seg009:20A7					    ; 0	- read
seg009:20A9		    mov	    driv_pres_handle, ax
seg009:20AC		    jnb	    loc_E7F6
seg009:20AE		    xor	    ax,	ax
seg009:20B0		    mov	    driv_pres_handle, ax
seg009:20B3		    jmp	    loc_E8C7
So, what happens is that the load_driver procedure expects a far pointer, but assumes that it points to the default data segment.
The procedure that calls load_driver passes either "DIGI.DRV" or NULL. In the latter case, load_driver should not be called at all. Or if it is called with NULL, it should check if this is the case.
Or it should always use "DIGI.DRV", and silently fail if that file doesn't exist.
(DIGI.DRV and MIDI.DRV are copied by SETUP.EXE from the SNDDRVRS folder if you choose Sound Blaster Pro, and they are deleted if you choose PC Speaker.)

Now, how do the SET statements come into play?
The EXE is loaded immediately after the area that stores the SET variables.
That is, if there are more or longer variables, the EXE is loaded at a different place.
But the rogue write always overwrites the same address, 0000:2E3C.
Various codes may end up at this address, depending on the size of the SET area.

Under real DOS (and in the DOS emulator built into Windows), some part of DOS (ntio.sys) is here, so this bug may end up breaking the system (or the DOS emulation)!

And which procedure is overwritten?
This:

Code: Select all

seg000:1400		    call    play_sound_from_buffer
seg000:1405 
seg000:1405 loc_1405:				    ; CODE XREF: play_current_sound+6j
seg000:1405					    ; play_current_sound+1Ej ...
seg000:1405		    mov	    current_sound, 0FFFFh
seg000:140B		    pop	    si
seg000:140C		    retf ; <-- this becomes add  [bx+si],al
seg000:140C play_current_sound endp
seg000:140D 
seg000:140D ; int __pascal far check_sword_vs_sword()
seg000:140D check_sword_vs_sword proc far	    ; CODE XREF: sub_994+35p
seg000:140D		    cmp	    byte_1BD18,	0A7h ; 'ž' ; <-- this becomes sub  ds:[bp+di-59],al
seg000:1412		    jz	    loc_141B
seg000:1414		    cmp	    byte_1BC98,	0A7h ; 'ž'
seg000:1419		    jnz	    locret_1423
seg000:141B 
seg000:141B loc_141B:				    ; CODE XREF: check_sword_vs_sword+5j
seg000:141B		    mov	    ax,	0Ah
seg000:141E		    push    ax		    ; sound_id
seg000:141F		    push    cs
seg000:1420		    call    near ptr play_sound
seg000:1423 
seg000:1423 locret_1423:			    ; CODE XREF: check_sword_vs_sword+Cj
seg000:1423		    retf
seg000:1423 check_sword_vs_sword endp ;	sp =  2
Oh! Everytime play_current_sound is called, some random byte is changed, and the check_sword_vs_sword is also executed.
Perhaps that repeating beep is the sword vs. sword sound?
According to the debugger, that JZ does jump every second time, which causes that sound to be played.
SI and DI are always 4 bigger than last time.
(Debugging corrupted code is hard, because it follows no logic, so the following may be unclear.)
check_sword_vs_sword and play_current_sound are called in an alternating way.
I also found out what increases SI and DI: when check_sword_vs_sword is actually called, this code gets executed: (this is the overwritten code)

Code: Select all

01A3:140D  003E2843            add  [4328],bh
01A3:1411  A7                  cmpsw
01A3:1412  7407                je   0000141B ($+7)
CMPSW increases both SI and DI by 2, but then the sound is not played, and the "add [bx+si],al" and "sub ds:[bp+di-59],al" are not executed.
When play_current_sound is called, it does reach the add and sub, and at every second call (when AL=0, and the pointed byte is 0 and remains 0), the sound is played. At the other calls, AL is 1, so the pointed byte is changed.

Whew! That was long!
To sum up things:
0000:2E3C is always overwritten with zero. What it does depends on what is loaded there.
In your case it overwrites the boundary between a procedure that is called for every sound (play_current_sound), and a procedure that plays a certain sound effect (check_sword_vs_sword).
It is overwritten because DS is loaded from a far NULL pointer, but DS is not restored before the next access to a (global) variable.
Andrew
Wise Scribe
Wise Scribe
Posts: 313
Joined: July 16th, 2009, 4:39 pm

Re: "sound off" does not turn all sounds off

Post by Andrew »

David wrote:
Andrew wrote:There seems to be no way to turn off all sounds in PoP1.
Unless you're using the PC speaker.
Well yes, but I guess if we use the PC speaker for sound with another device for music or vice versa, we're likely to have the same problem (I haven't tested this though).
David wrote:There is a "JoyX: %2d JoyY: %2d Btn: %2d" string in PRINCE.EXE. And the code in fact refers to that string.
Yep, I've seen that in the EXE, but surely the effect in 1.4 has to be a bug, either of the game or DOSBox? Ideally that string should be displayed in the status bar where other strings such as game version appear, and be updated in place when the joystick is moved and its buttons pressed. Also, ideally it should be a debug parameter (or perhaps be displayed only when cheats are enabled) and not show up every time you turn on joystick mode!
David wrote:Now, how do the SET statements come into play?
The EXE is loaded immediately after the area that stores the SET variables.
That is, if there are more or longer variables, the EXE is loaded at a different place.
But the rogue write always overwrites the same address, 0000:2E3C.
Various codes may end up at this address, depending on the size of the SET area.

Under real DOS (and in the DOS emulator built into Windows), some part of DOS (ntio.sys) is here, so this bug may end up breaking the system (or the DOS emulation)!

...

To sum up things:
0000:2E3C is always overwritten with zero. What it does depends on what is loaded there.
In your case it overwrites the boundary between a procedure that is called for every sound (play_current_sound), and a procedure that plays a certain sound effect (check_sword_vs_sword).
It is overwritten because DS is loaded from a far NULL pointer, but DS is not restored before the next access to a (global) variable.
Wow, that was some effort David and it'll take me time to digest that disassembled code properly. You seem to suggest that this is purely a game bug and not DOSBox's fault at all, but I guess one really needs to confirm by using a debugger in real mode DOS. I'm not entirely convinced that the emulator is totally blameless here, and that 1.4 has the potential to break even actual DOS when PC speaker is selected (a bug I've never encountered in all my years of playing in DOS so far, but which is a major issue in DOSBox).

P.S. Is it just me or do PoP1 1.3 and 1.4 sound quite different (not the music but sounds like gate opening/closing, tile falling etc.) in DOSBox even with both configured to use SB?
Post Reply