https://bugs.winehq.org/show_bug.cgi?id=48919
Bug ID: 48919 Summary: Balabolka (TTS app) reports SAPI Error Code 0x80045004 since Wine 2.18 with native SAPI 5.1 via 'winetricks -q speedcsdk' Product: Wine Version: 5.6 Hardware: x86-64 OS: Linux Status: NEW Severity: normal Priority: P2 Component: -unknown Assignee: wine-bugs@winehq.org Reporter: focht@gmx.net Distribution: ---
Hello folks,
a user named "HaJones" visited IRC #winehq some hours ago. He asked how to install multiple Wine versions. He wanted old Wine 2.17 alongside with 5.x. It turns out the real reason was this:
https://forum.winehq.org/viewtopic.php?t=30711 ("Balabolka and SAPI5 TTS doesn't work on Wine 3.0 and 3.0.1.").
Apparently Balabolka broke with Wine 2.18
The users suspicion was that the addition of SAPI component to Wine broke it
https://www.winehq.org/announce/2.18
--- snip --- Huw D. M. Davies (16): include: Add sapi.idl. include: Add sapiddk.idl. include: Add sperror.h. sapi: Add a stub dll. sapi: Register the typelib. sapi: Register the classes from sapiddk.idl that would otherwise not get registered. sapi: Create the Voices registry key. sapi: Add a stub SpDataKey object implementation. sapi: Implement SpDataKey::SetKey(). sapi: Add a stub SpObjectTokenEnum object implementation. sapi: Implement SpObjectTokenEnum::SetAttribs(). sapi: Implement SpObjectTokenEnum::GetCount(). sapi: Add a partial implementation of SpObjectTokenEnum::Next(). sapi: Add a stub SpObjectTokenCategory object implementation. sapi: Implement SpObjectTokenCategory::SetId(). sapi: Add a partial implementation of SpObjectTokenCategory::EnumTokens(). --- snip ---
Native SAPI was installed via 'winetricks -q speechsdk' so the reason must be something else unless there is some override problem or weird COM class registry accident/mixup.
Download:
https://web.archive.org/web/20200406005249/http://balabolka.site/balabolka.z...
"test 123" is the sample text to synthesize.
--- snip --- $ pwd /home/focht/.wine/drive_c/Program Files (x86)/Balabolka
$ WINEDEBUG=+seh,+relay,+ole,+variant wine ./balabolka.exe >>log.txt 2>&1 ... 0062:Ret window proc 0x2a20d8c (hwnd=0x1073a,msg=CB_GETLBTEXT,wp=00000000,lp=02a0b384) retval=00000028 0062:Ret user32.SendMessageW() retval=00000028 ret=0045e740 0062:Call user32.CharUpperBuffW(02a0ee04 L"Microsoft Mary [English (United States)]",00000028) ret=0040ef32 0062:Ret user32.CharUpperBuffW() retval=00000028 ret=0040ef32 0062:Call user32.CharLowerBuffW(0e23d1cc L"test 123",00000008) ret=0040efc2 .... 0062:Call user32.InvalidateRect(00010794,0032f7f8,00000000) ret=004aaf92 0062:Ret user32.InvalidateRect() retval=00000001 ret=004aaf92 0062:Call oleaut32.SysAllocStringLen(0e23d1cc L"test 123",00000008) ret=004065dc 0062:Call KERNEL32.IsBadStringPtrW(0e23d1cc,00000008) ret=100060d7 0062:Ret KERNEL32.IsBadStringPtrW() retval=00000000 ret=100060d7 0062:trace:ole:SysAllocStringLen L"test 123" 0062:Call ucrtbase.memcpy(02b83e14,0e23d1cc,00000010) ret=10006269 0062:Ret ucrtbase.memcpy() retval=02b83e14 ret=10006269 0062:Ret oleaut32.SysAllocStringLen() retval=02b83e14 ret=004065dc ... 0062:Call ole32.GetErrorInfo(00000000,0032f7f4) ret=004cfe3e 0062:trace:ole:GetErrorInfo (0, 0032F7F4, 00000000) 0062:Ret ole32.GetErrorInfo() retval=00000001 ret=004cfe3e 0062:Call KERNEL32.FormatMessageW(00003200,00000000,80045004,00000000,0032f538,00000100,00000000) ret=00416070 0062:Ret KERNEL32.FormatMessageW() retval=00000000 ret=00416070 ... 0062:Call KERNEL32.RaiseException(0eedfade,00000001,00000007,0032f7a8) ret=004d2d94 0062:Call ntdll.memcpy(0032f718,0032f7a8,0000001c) ret=7b00dbc1 0062:Ret ntdll.memcpy() retval=0032f718 ret=7b00dbc1 0062:trace:seh:raise_exception code=eedfade flags=1 addr=0x7b00dbd1 ip=7b00dbd1 tid=0062 0062:trace:seh:raise_exception info[0]=004d2d94 0062:trace:seh:raise_exception info[1]=0e22bd30 0062:trace:seh:raise_exception info[2]=80045004 0062:trace:seh:raise_exception info[3]=004d2d94 0062:trace:seh:raise_exception info[4]=0000008b 0062:trace:seh:raise_exception info[5]=0032f7f8 0062:trace:seh:raise_exception info[6]=0032f7c4 0062:trace:seh:raise_exception eax=0032f704 ebx=80045004 ecx=0032f7a8 edx=0032f704 esi=00000007 edi=0032f770 0062:trace:seh:raise_exception ebp=0032f758 esp=0032f704 cs=320023 ds=ffff002b es=002b fs=f7c70063 gs=006b flags=00000212 0062:trace:seh:call_stack_handlers calling handler at 0x4cfef8 code=eedfade flags=1 0062:trace:seh:call_stack_handlers handler at 0x4cfef8 returned 1 0062:trace:seh:call_stack_handlers calling handler at 0x4d2dad code=eedfade flags=1 ... 0062:Call winex11.drv.SetWindowText(00010bee,04d02c70 L"Can not synthesize the speech: OLE error 80045004.\r\n\r\nThe caller has specified invalid flags for this operation.\r\n[SAPI Error Code 0x80045004]") ret=7e7ccc65 --- snip ---
Unfortunately not much to see from trace log. Commence debugging action.
In the working case (Wine 2.17), the app calls 'ISpVoice::Speak' like this:
ISpVoice::Speak(text, 0xb, NULL);
0xb = speak flags = (SPF_DEFAULT | SPF_PURGEBEFORESPEAK | SPF_IS_XML)
In the "broken" case, that is starting with Wine 2.18, the app passes a different speak flags value which SAPI 5.1 refuses to accept. This resulted in SAPI error code 0x80045004.
--- snip --- 009F79D7 | mov edx,dword ptr ss:[ebp-180] | UNICODE "test 123" 009F79DD | mov ecx,dword ptr ds:[AAEE84] | 009F79E3 | mov ecx,dword ptr ds:[ecx] | ECX = 0x8B = speak flags(!) 009F79E5 | mov eax,dword ptr ss:[ebp-8] | 009F79E8 | mov eax,dword ptr ds:[eax+A94] | 009F79EE | call balabolka.4D2D54 --- snip ---
https://docs.microsoft.com/en-us/previous-versions/windows/desktop/ee431843(...)
0x8b = speak flags = (SPF_PARSE_SAPI | SPF_DEFAULT | SPF_PURGEBEFORESPEAK | SPF_IS_XML)
009F79E3: ECX = ptr 0x00AB940C
--- snip --- ... 00AB93FC 012A28E4 ; ASCII "JPop" 00AB9400 0129CD9C ; ASCII "Synthpop" 00AB9404 00000000 00AB9408 FFFFFFFF 00AB940C 0000000B ; speak flags 0xB = '1011 00AB9410 00000000 00AB9414 00000000 ... --- snip ---
Using the debugger's "find all references to address" for 0xAB940C:
--- snip --- Address Disassembly
00A7A867 mov dword ptr ds:[AB940C],8B 00A7A8B2 mov dword ptr ds:[AB940C],B --- snip ---
one comes across this piece of code:
--- snip --- 00A7A81A | mov byte ptr ds:[AAD968],al 00A7A81F | cmp byte ptr ds:[AB9435],0 00A7A826 | je balabolka.A7A873 ; set default speak flag 0xb
00A7A828 | lea eax,dword ptr ss:[ebp-3C] 00A7A82B | call balabolka.67AD8C 00A7A830 | mov edx,dword ptr ss:[ebp-3C] 00A7A833 | mov eax,balabolka.AAD96C 00A7A838 | call balabolka.4068F0 00A7A83D | lea eax,dword ptr ss:[ebp-40] 00A7A840 | call balabolka.67AEF0 00A7A845 | mov edx,dword ptr ss:[ebp-40] 00A7A848 | mov eax,balabolka.AAD970 00A7A84D | call balabolka.4068F0 00A7A852 | lea eax,dword ptr ss:[ebp-44] 00A7A855 | call balabolka.67B3DC 00A7A85A | mov edx,dword ptr ss:[ebp-44] 00A7A85D | mov eax,balabolka.AAD974 00A7A862 | call balabolka.4068F0 00A7A867 | mov dword ptr ds:[AB940C],8B ; speak flags = questionable 00A7A871 | jmp balabolka.A7A8BC
00A7A873 | lea eax,dword ptr ss:[ebp-48] 00A7A876 | call balabolka.67B054 00A7A87B | mov edx,dword ptr ss:[ebp-48] 00A7A87E | mov eax,balabolka.AAD96C 00A7A883 | call balabolka.4068F0 00A7A888 | lea eax,dword ptr ss:[ebp-4C] 00A7A88B | call balabolka.67B218 00A7A890 | mov edx,dword ptr ss:[ebp-4C] 00A7A893 | mov eax,balabolka.AAD970 00A7A898 | call balabolka.4068F0 00A7A89D | lea eax,dword ptr ss:[ebp-50] 00A7A8A0 | call balabolka.67B598 00A7A8A5 | mov edx,dword ptr ss:[ebp-50] 00A7A8A8 | mov eax,balabolka.AAD974 00A7A8AD | call balabolka.4068F0 00A7A8B2 | mov dword ptr ds:[AB940C],B ; speak flags = ok --- snip ---
The decision is made from 00A7A81F, based on value of [AB9435]. Again, using the debugger's "find all references to address" for 0xAB9435:
--- snip --- Address Disassembly
00A7A13A setg byte ptr ds:[AB9435] ; set internal app flag 00A7A6A5 cmp byte ptr ds:[AB9435],0 00A7A81F cmp byte ptr ds:[AB9435],0 --- snip ---
One comes across this piece of code:
--- snip --- 00A7A0F3 | mov byte ptr ds:[AB9423],al 00A7A0F8 | mov dl,1 00A7A0FA | mov eax,dword ptr ds:[427088] 00A7A0FF | call balabolka.404604 00A7A104 | mov dword ptr ds:[AB9430],eax 00A7A109 | mov eax,dword ptr ds:[AAE8F4] 00A7A10E | cmp dword ptr ds:[eax],5 00A7A111 | jne balabolka.A7A11D 00A7A113 | mov eax,dword ptr ds:[AAF1CC] 00A7A118 | cmp dword ptr ds:[eax],0 00A7A11B | jg balabolka.A7A12B 00A7A11D | mov eax,dword ptr ds:[AAE8F4] 00A7A122 | cmp dword ptr ds:[eax],5 00A7A125 | jg balabolka.A7A12B 00A7A127 | xor eax,eax 00A7A129 | jmp balabolka.A7A12D 00A7A12B | mov al,1 00A7A12D | mov byte ptr ds:[AB9434],al 00A7A132 | mov eax,dword ptr ds:[AAE8F4] ; ptr -> 00A7CC74 00A7A137 | cmp dword ptr ds:[eax],5 ; OS Major version 5.x ? 00A7A13A | setg byte ptr ds:[AB9435] ; set flag if Windows Vista+ (6.x) 00A7A141 | mov eax,dword ptr ds:[AAE8F4] 00A7A146 | cmp dword ptr ds:[eax],6 ; Windows Vista 6.0 ? 00A7A149 | jne balabolka.A7A155 00A7A14B | mov eax,dword ptr ds:[AAF1CC] --- snip ---
Repeating the recipe of backwards resolving address references over and over again. Fortunately there is no dynamic heap memory allocation involved here, so the memory references are stable across multiple debugger sessions.
The final place (when going backwards):
--- snip --- 00A7CC64 00000000 00A7CC68 00000000 00A7CC6C 00000000 00A7CC70 00000002 00A7CC74 00000006 ; OS Major version 00A7CC78 00000001 00A7CC7C 00001DB1 00A7CC80 029D675C L"Service Pack 1" --- snip ---
--- snip --- 004176D4 | add esp,FFFFFEEC | 004176DA | mov dword ptr ss:[esp],114 | 004176E1 | push esp | 004176E2 | call balabolka.40A094 | call to jmp GetVersionExW 004176E7 | test eax,eax | 004176E9 | je balabolka.41773B | 004176EB | mov eax,dword ptr ss:[esp+10] | 004176EF | mov dword ptr ds:[A7CC70],eax | 004176F4 | mov eax,dword ptr ss:[esp+4] | 004176F8 | mov dword ptr ds:[A7CC74],eax | store OS Major version 004176FD | mov eax,dword ptr ss:[esp+8] | 00417701 | mov dword ptr ds:[A7CC78],eax | 00417706 | cmp dword ptr ds:[A7CC70],1 | 0041770D | jne balabolka.41771F | --- snip ---
Trace log for reference (return address):
--- snip --- 0062:Call KERNEL32.GetVersionExW(0032fd80) ret=004176e7 0062:Ret KERNEL32.GetVersionExW() retval=00000001 ret=004176e7 --- snip ---
Apparently the application detects 'Windows 7' and assumes SAPI 5.3 (Vista+) or 5.4, hence the speak flag value of 0xb8. This obviously can't work with SAPI 5.1 which was installed via winetricks.
Why did it "work" on Wine 2.17 then?
'winetricks -q speechsdk' with Wine 5.6:
--- snip --- ... regsvr32: Successfully registered DLL 'C:\Program Files (x86)\Microsoft Speech SDK 5.1\Bin\SREng.dll' Executing ln -s /home/focht/.wine/dosdevices/c:/Program Files (x86)/Common Files/Microsoft Shared/Speech/sapi.dll /home/focht/.wine/dosdevices/c:/windows/syswow64/Speech/Common Using native override for following DLLs: sapi ... The operation completed successfully Setting Windows version to default Executing wine regedit /S C:\windows\Temp\set-winver.reg Executing wine64 regedit /S C:\windows\Temp\set-winver.reg ... --- snip ---
Recent 'winetricks -q speechsdk' with Wine 2.17:
--- snip --- ... Using winetricks 20191224-next - sha256sum: 895b74ea65452b02d32fe9190a63a8cac492760834c1951607005ba32045d59e with wine-5.6 and WINEARCH=win64 ... regsvr32: Successfully registered DLL 'C:\Program Files (x86)\Microsoft Speech SDK 5.1\Bin\SREng.dll' Executing ln -s /home/focht/.wine/dosdevices/z:/home/focht/%CommonProgramFiles(x86)%/Microsoft Shared/Speech/sapi.dll /home/focht/.wine/dosdevices/c:/windows/syswow64/Speech/Common ln: failed to create symbolic link '/home/focht/.wine/dosdevices/c:/windows/syswow64/Speech/Common': No such file or directory ------------------------------------------------------ Note: command ln -s /home/focht/.wine/dosdevices/z:/home/focht/%CommonProgramFiles(x86)%/Microsoft Shared/Speech/sapi.dll /home/focht/.wine/dosdevices/c:/windows/syswow64/Speech/Common returned status 1. Aborting. ------------------------------------------------------
--- snip ---
'syswow64/Speech/Common' only exists starting with Wine 2.18. The failure of the verb causes winetricks to abort, not restoring the Windows version from 'Windows 2000' to default 'Windows 7'. The application detects WinVer < Windows Vista (6.x) magically works (passing speak flags -> 0xb).
Additional point: Even if the an old release of 'winetricks' script restored the Windows version (before https://github.com/austin987/winetricks/commit/84b50038f090ec262d6cdb48ed794...), many users created WINEPREFIXes with older Wine versions and WinVer is not changed/reset by an upgrade. Last time the default WinVer was bumped with Wine 2.2 from 'Windows XP SP3' to 'Windows 7 SP1'.
Multiple options:
(1)
When installing SAPI 5.1, the Windows version must be kept at 'Windows 2000' or 'Windows XP' and not restored to default 'Windows 7'. This might impact some TTS apps that require Windows 7 but on the other hand with Windows 7 you would never encounter SAPI 5.1. SAPI 5.4 -> distributed as part of OS.
The app works again with WinVer set to 'Windows XP'.
(2)
Upgrade the 'speechsdk' verb to install SAPI 5.3 or later (5.4) from Windows SDK.
---
Anyway, this is not a Wine problem but a Winetricks one. This ticket is only for Winetricks reference and documenation. Will create a ticket at https://github.com/Winetricks/winetricks asap.
$ sha1sum balabolka.zip fb63e6232acba9bf0bfae70e8d5155d347a47292 balabolka.zip
$ du -sh balabolka.zip 19M balabolka.zip
$ wine --version wine-5.6
Regards