diff --git a/Makefile b/Makefile index 5089ec5..8612c14 100644 --- a/Makefile +++ b/Makefile @@ -28,31 +28,34 @@ krkrwine-i386.dll: krkrwine.cpp Makefile $(MINGW32) krkrwine.cpp -shared -fPIC -o krkrwine-i386.dll -lole32 prepare: - cp $(EGGROLL)/files/lib64/gstreamer-1.0/libgstmpegpsdemux.so Glorious-Eggroll/x86_64-libgstmpegpsdemux.so - cp $(EGGROLL)/files/lib64/gstreamer-1.0/libgstasf.so Glorious-Eggroll/x86_64-libgstasf.so - cp $(EGGROLL)/files/lib64/libgstcodecparsers-1.0.so.0 Glorious-Eggroll/x86_64-libgstcodecparsers-1.0.so.0 - cp $(EGGROLL)/files/lib64/libavcodec.so.58 Glorious-Eggroll/x86_64-libavcodec.so.58 - cp $(EGGROLL)/files/lib64/libavutil.so.56 Glorious-Eggroll/x86_64-libavutil.so.56 - cp $(EGGROLL)/files/lib64/libavfilter.so.7 Glorious-Eggroll/x86_64-libavfilter.so.7 - cp $(EGGROLL)/files/lib64/libavformat.so.58 Glorious-Eggroll/x86_64-libavformat.so.58 - cp $(EGGROLL)/files/lib64/libavdevice.so.58 Glorious-Eggroll/x86_64-libavdevice.so.58 - cp $(EGGROLL)/files/lib64/libswresample.so.3 Glorious-Eggroll/x86_64-libswresample.so.3 - cp $(EGGROLL)/files/lib64/libswscale.so.5 Glorious-Eggroll/x86_64-libswscale.so.5 - cp $(EGGROLL)/files/lib/gstreamer-1.0/libgstmpegpsdemux.so Glorious-Eggroll/i386-libgstmpegpsdemux.so - cp $(EGGROLL)/files/lib/gstreamer-1.0/libgstasf.so Glorious-Eggroll/i386-libgstasf.so - cp $(EGGROLL)/files/lib/libgstcodecparsers-1.0.so.0 Glorious-Eggroll/i386-libgstcodecparsers-1.0.so.0 - cp $(EGGROLL)/files/lib/libavcodec.so.58 Glorious-Eggroll/i386-libavcodec.so.58 - cp $(EGGROLL)/files/lib/libavutil.so.56 Glorious-Eggroll/i386-libavutil.so.56 - cp $(EGGROLL)/files/lib/libavfilter.so.7 Glorious-Eggroll/i386-libavfilter.so.7 - cp $(EGGROLL)/files/lib/libavformat.so.58 Glorious-Eggroll/i386-libavformat.so.58 - cp $(EGGROLL)/files/lib/libavdevice.so.58 Glorious-Eggroll/i386-libavdevice.so.58 - cp $(EGGROLL)/files/lib/libswresample.so.3 Glorious-Eggroll/i386-libswresample.so.3 - cp $(EGGROLL)/files/lib/libswscale.so.5 Glorious-Eggroll/i386-libswscale.so.5 + mkdir Glorious-Eggroll/ + cp $(EGGROLL)/files/lib64/gstreamer-1.0/libgstmpegpsdemux.so Glorious-Eggroll/x86_64-libgstmpegpsdemux.so + cp $(EGGROLL)/files/lib64/gstreamer-1.0/libgstasf.so Glorious-Eggroll/x86_64-libgstasf.so + cp $(EGGROLL)/files/lib64/gstreamer-1.0/libgstvideoparsersbad.so Glorious-Eggroll/x86_64-libgstvideoparsersbad.so + cp $(EGGROLL)/files/lib64/libgstcodecparsers-1.0.so.0 Glorious-Eggroll/x86_64-libgstcodecparsers-1.0.so.0 + cp $(EGGROLL)/files/lib64/libavcodec.so.58 Glorious-Eggroll/x86_64-libavcodec.so.58 + cp $(EGGROLL)/files/lib64/libavutil.so.56 Glorious-Eggroll/x86_64-libavutil.so.56 + cp $(EGGROLL)/files/lib64/libavfilter.so.7 Glorious-Eggroll/x86_64-libavfilter.so.7 + cp $(EGGROLL)/files/lib64/libavformat.so.58 Glorious-Eggroll/x86_64-libavformat.so.58 + cp $(EGGROLL)/files/lib64/libavdevice.so.58 Glorious-Eggroll/x86_64-libavdevice.so.58 + cp $(EGGROLL)/files/lib64/libswresample.so.3 Glorious-Eggroll/x86_64-libswresample.so.3 + cp $(EGGROLL)/files/lib64/libswscale.so.5 Glorious-Eggroll/x86_64-libswscale.so.5 + cp $(EGGROLL)/files/lib/gstreamer-1.0/libgstmpegpsdemux.so Glorious-Eggroll/i386-libgstmpegpsdemux.so + cp $(EGGROLL)/files/lib/gstreamer-1.0/libgstasf.so Glorious-Eggroll/i386-libgstasf.so + cp $(EGGROLL)/files/lib/lib/gstreamer-1.0/libgstvideoparsersbad.so Glorious-Eggroll/i386-libgstvideoparsersbad.so + cp $(EGGROLL)/files/lib/libgstcodecparsers-1.0.so.0 Glorious-Eggroll/i386-libgstcodecparsers-1.0.so.0 + cp $(EGGROLL)/files/lib/libavcodec.so.58 Glorious-Eggroll/i386-libavcodec.so.58 + cp $(EGGROLL)/files/lib/libavutil.so.56 Glorious-Eggroll/i386-libavutil.so.56 + cp $(EGGROLL)/files/lib/libavfilter.so.7 Glorious-Eggroll/i386-libavfilter.so.7 + cp $(EGGROLL)/files/lib/libavformat.so.58 Glorious-Eggroll/i386-libavformat.so.58 + cp $(EGGROLL)/files/lib/libavdevice.so.58 Glorious-Eggroll/i386-libavdevice.so.58 + cp $(EGGROLL)/files/lib/libswresample.so.3 Glorious-Eggroll/i386-libswresample.so.3 + cp $(EGGROLL)/files/lib/libswscale.so.5 Glorious-Eggroll/i386-libswscale.so.5 krkrwine.tar.gz: | Glorious-Eggroll/x86_64-libgstmpegpsdemux.so $(MAKE) clean - $(MAKE) OPT=1 + $(MAKE) OPT=1 -j4 -rm krkrwine.tar.gz tar -czvf krkrwine.tar.gz --transform='s%^%krkrwine/%' --show-transformed-names README.md install.py gstkrkr-i386.so gstkrkr-x86_64.so krkrwine-i386.dll krkrwine-x86_64.dll Glorious-Eggroll/ @@ -105,11 +108,11 @@ wai: ln -s $(shell realpath .)/krmovie.dll /games/wine/waga/waga/plugin/krmovie.dll X1_HOME = /home/walrus/mount/x1/home/x1/ -X1_PROTON = $(X1_HOME)steam/steamapps/common/"Proton 8.0"/ -X1_PFX = $(X1_PROTON)dist/share/default_pfx/ -X1_DLL = $(X1_PFX)drive_c/windows/syswow64/krkrwine.dll +X1_PROTON = $(X1_HOME)steam/steamapps/common/"Proton 9.0 (Beta)"/ +X1_TARGET = $(X1_PROTON)krkrwine/ +X1_DLL = $(X1_TARGET)krkrwine-i386.dll +X1_GST = $(X1_TARGET)gstkrkr-i386.so X1_EXE = $(X1_HOME)Desktop/a.exe -X1_GST = $(X1_PROTON)dist/lib/gstreamer-1.0/gstkrkr-i386.so ax1: run32.exe krkrwine-i386.dll gstkrkr-i386.so -rm $(X1_DLL) $(X1_EXE) $(X1_GST) cp krkrwine-i386.dll $(X1_DLL) @@ -119,12 +122,15 @@ ax1: run32.exe krkrwine-i386.dll gstkrkr-i386.so installx1: all ./install.py $(X1_PROTON) +X2_HOME = /home/walrus/mount/x2/home/x2/ +X2_PROTON = $(X2_HOME)steam/steamapps/common/"Proton 9.0 (Beta)"/ +X2_WAGA = $(X2_HOME)steam/steamapps/common/"WAGAMAMA HIGH SPEC" + installx2: all - ./install.py /home/walrus/mount/x2/home/x2/steam/steamapps/common/"Proton 8.0"/ + ./install.py $(X2_PROTON) -X2_WAGAPATH = /home/walrus/mount/x2/home/x2/steam/steamapps/common/"WAGAMAMA HIGH SPEC" waux2: - mv $(X2_WAGAPATH)/plugin/_rmovie.dll $(X2_WAGAPATH)/plugin/krmovie.dll + mv $(X2_WAGA)/plugin/_rmovie.dll $(X2_WAGA)/plugin/krmovie.dll waix2: krmovie.dll - mv -n $(X2_WAGAPATH)/plugin/krmovie.dll $(X2_WAGAPATH)/plugin/_rmovie.dll - cp krmovie.dll $(X2_WAGAPATH)/plugin/krmovie.dll + mv -n $(X2_WAGA)/plugin/krmovie.dll $(X2_WAGA)/plugin/_rmovie.dll + cp krmovie.dll $(X2_WAGA)/plugin/krmovie.dll diff --git a/README.md b/README.md index 562270a..febea59 100644 --- a/README.md +++ b/README.md @@ -1,38 +1,34 @@ krkrwine ======== -This program implements various missing functionality in Wine / Proton, such that videos work properly in Kirikiri-based visual novels. (Other games are not targetted by krkrwine, but they may be improved as well.) +This program implements various missing functionality in Wine / Proton, such that videos work properly in Kirikiri- and BURIKO-based visual novels. (Other software is not targetted by krkrwine, but may be improved as well.) + +While I am doing my best to upstream my fixes, Proton's release cycle is once a year; krkrwine allows me to run these VNs before Proton 10 releases in 2025. Installation - Linux/Proton --------------------------- -Simply download and extract [a release](https://github.com/Alcaro/krkrwine/releases), run ./install.py ~/steam/steamapps/common/Proton\ 8.0/, and it will install into every current and future game using that Proton. Installing krkrwine into Glorious Eggroll should work too, though this is untested. +Simply download and extract [a release](https://github.com/Alcaro/krkrwine/releases), run `./install.py ~/'steam/steamapps/common/Proton 9.0 (Beta)/'`, and it will install into every current and future game using that Proton. Installing krkrwine into Glorious Eggroll should work too, though this is untested. krkrwine does not replace any existing files, and Steam's updater ignores files it doesn't recognize; therefore, you don't need to reinstall krkrwine if Proton updates. (However, if you reinstall Proton, or download a new Proton version, you obviously need to reinstall krkrwine too.) -krkrwine is only tested in Proton 8.0; I don't think it'll break too hard in anything else, but it's untested. +krkrwine is only tested in Proton 9.0, and may misbehave in other Proton versions. For Proton 8.0, use an older release; for older Proton, upgrade. Installation - Linux/Wine ------------------------- -On Debian, - -- Download and extract [a release](https://github.com/Alcaro/krkrwine/releases) -- sudo apt install gstreamer1.0-plugins-good gstreamer1.0-plugins-bad gstreamer1.0-plugins-ugly gstreamer1.0-libav gstreamer1.0-plugins-good:i386 gstreamer1.0-plugins-bad:i386 gstreamer1.0-plugins-ugly:i386 gstreamer1.0-libav:i386 -- ./install.py --wine - -On other distros, the package names are different. +Not recommended. I recommend using the latest Wine, such that krkrwine is unnecessary. -Note that on Debian, gstreamer1.0-libav is not part of, or a dependency of, gstreamer1.0-plugins-good, -bad, nor -ugly; it must be installed separately. I haven't checked other distros. +If you or your distro feel otherwise, you can use `./install.py --wine`. (It will obey WINEPREFIX, if set.) -install.py will obey the WINEPREFIX environment variable, if set. +Make sure to use the correct krkrwine version; krkrwine 9.0 needs Wine 9.0 or higher. For older Wine, use an older release. -krkrwine's GStreamer component will not be installed under Wine; it does nothing useful if you have the above plugins and don't have protonmediaconverter. If you want them anyways for development purposes, you can copy or link them to ~/.local/share/gstreamer-1.0/plugins/. +Do not install krkrwine's codecs; instead (for Debian), `sudo apt install gstreamer1.0-plugins-good gstreamer1.0-plugins-bad gstreamer1.0-plugins-ugly gstreamer1.0-libav gstreamer1.0-plugins-good:i386 gstreamer1.0-plugins-bad:i386 gstreamer1.0-plugins-ugly:i386 gstreamer1.0-libav:i386`. For other distros, the packages have different names. (Note that on Debian, gstreamer1.0-libav is not part of, or a dependency of, gstreamer1.0-plugins-good, -bad, nor -ugly; it must be installed separately.) Installation - macOS -------------------- -See the Linux/Wine steps, it'll probably work. However, it's untested, and I can't help you if it breaks. +Not recommended. Feel free to try the Linux/Wine steps, but it's more untested than Linux/Wine; I can't help you if it breaks. Installation - Windows ---------------------- @@ -46,8 +42,10 @@ Compilation - sudo apt install make gcc libgstreamer1.0-dev gcc-multilib libgstreamer1.0-dev:i386 g++-mingw-w64-i686-win32 g++-mingw-w64-x86-64-win32 - make prepare EGGROLL=/home/user/steam/compatibilitytools.d/GE-Proton8-4/ - make -- ./install.py /home/user/steam/steamapps/common/Proton\ 8.0/ -If desired, you may acquire i686-w64-mingw32-g++-win32 and x86_64-w64-mingw32-g++-win32 programs from elsewhere. I use https://winlibs.com/ in Wine. (Compiling with *-mingw32-g++-posix is not recommended, since it adds dependencies on libwinpthread-1.dll, and possibly others; I don't know if installing that lib into system32 breaks anything, and I don't feel like finding out.) +- ./install.py ~/'steam/steamapps/common/Proton 9.0 (Beta)/' +If desired, you may acquire i686-w64-mingw32-g++-win32 and x86_64-w64-mingw32-g++-win32 from elsewhere. I use https://winlibs.com/ in Wine. (Compiling with *-mingw32-g++-posix is not recommended, since it adds dependencies on libwinpthread-1.dll, and possibly others; I don't know if installing that lib into system32 breaks anything, and I don't feel like finding out.) + +If you want to use krkrwine's GStreamer components under Wine for debugging purposes, copy or link them to ~/.local/share/gstreamer-1.0/plugins/. For anyone else, use gst-libav. License ------- @@ -59,22 +57,4 @@ The Glorious-Eggroll subdirectory of the releases is, as the name implies, copie Upstreaming ----------- -Ideally, this project would be unnecessary (other than the codec installation). As such, I'm offering bounties on fixing the Windows-side issues in upstream Wine. - -- $250 - CLSID_MPEG1Splitter video output, and CLSID_CMpegVideoCodec (these objects may use GStreamer, of course) -- $25 - CLSID_MPEG1Splitter IAMStreamSelect (doesn't need to be fully implemented, just needs the parts Kirikiri uses) -- ~~$25 - CLSID_VideoMixingRenderer9 ChangeD3DDevice and NotifyEvent~~ Resolved and claimed -- $500 - WMCreateSyncReader compressed output, CLSID_CWMADecMediaObject, and CLSID_CWMVDecMediaObject -- $100 - Direct3D 9 on WS_CHILD windows under wined3d in the Debian package - the bug doesn't reproduce if I compile Wine from source -- $25 - make WMSyncReader resize its allocator, so it can output RGB32 properly -- $100 - figure out what's going on with the memory allocator and VFW_E_NOT_COMMITTED, and solve it -- $0 - anything involving gstkrkr and Proton's GStreamer. That's a patent issue; it's a question for lawyers, not programmers. It's only needed in Proton, not vanilla Wine. -- Anything that Kirikiri needs but isn't in the above list - that's a bug in this readme, contact me - -though I'm working on them myself, so fair chance I'll finish first. - -You are allowed, but not required, to base such efforts on this project. My architecture is very different from Wine's existing objects, and many of them are implemented in an awful way, so most of my code is unusable; but you're welcome to look for hints on the objects' expected behavior, or otherwise use them to help implement yours. You may also use the Kirikiri source code, - -To claim a bounty, post at , or email me at sir@walrus.se. These bounties can be combined with similar offers made by others. - -If I see any of the above implemented, but nobody claims the bounty, I will wait one month, then donate it to Wine. +All relevant pieces of this project have been upstreamed. The bounties that were here have been claimed. diff --git a/fake-krmovie.cpp b/fake-krmovie.cpp index b8138ff..bf2d6cc 100644 --- a/fake-krmovie.cpp +++ b/fake-krmovie.cpp @@ -5,7 +5,19 @@ // (I'm not sure if headers and other required interopability data is covered by copyright, but better safe than sorry.) // (I also don't know if Kirikiri is 2.0 only or 2.0 or later, so I'll pick the safe choice.) -//#define DOIT +#ifdef __MINGW32__ +# define _FILE_OFFSET_BITS 64 +// mingw *really* wants to define its own printf/scanf, which adds ~20KB random stuff to the binary +// (on some 32bit mingw versions, it also adds a dependency on libgcc_s_sjlj-1.dll) +// extra kilobytes and dlls is the opposite of what I want, and my want is stronger, so here's some shenanigans +// comments say libstdc++ demands a POSIX printf, but I don't use libstdc++'s text functions, so I don't care +# define __USE_MINGW_ANSI_STDIO 0 // trigger a warning if it's enabled already - probably wrong include order +# include // include some random c++ header; they all include , +# undef __USE_MINGW_ANSI_STDIO // which ignores my #define above and sets this flag; re-clear it before including +# define __USE_MINGW_ANSI_STDIO 0 // (subsequent includes of c++config.h are harmless, there's an include guard) +#endif + +#define DOIT #define STRSAFE_NO_DEPRECATE #define INITGUID @@ -16,6 +28,22 @@ #include #include +// can't use stdout from wine/proton since 8.12 https://gitlab.winehq.org/wine/wine/-/commit/dcf0bf1f383f8429136cb761f5170a04503a297b +static void my_puts(const char * a, bool linebreak=true) +{ + static int __cdecl (*__wine_dbg_output)( const char *str ); + if (!__wine_dbg_output) + __wine_dbg_output = (decltype(__wine_dbg_output))GetProcAddress(GetModuleHandle("ntdll.dll"), "__wine_dbg_output"); + if (__wine_dbg_output) + { + __wine_dbg_output(a); + if (linebreak) + __wine_dbg_output("\n"); + } +} +#define puts my_puts +#define printf(...) do { char my_buf[4096]; sprintf(my_buf, __VA_ARGS__); my_puts(my_buf, false); } while(0) + #ifdef __i386__ // needs some extra shenanigans to kill the stdcall @12 suffix #define EXPORT_STDCALL(ret, name, args) \ @@ -322,7 +350,6 @@ BOOL __stdcall myMoveWindow(HWND hWnd, int X, int Y, int nWidth, int nHeight, BO return MoveWindow(hWnd, X, Y, nWidth, nHeight, bRepaint); } -IDirect3DDevice9* g_d3ddev; bool dump_texture(IDirect3DSurface9* renderTarget, const char * filename) { HRESULT hr; @@ -500,14 +527,15 @@ class fancy_iTVPVideoOverlay : public iTVPVideoOverlay // this is not a COM obje virtual void __stdcall GetStatus(tTVPVideoStatus* status) { puts("fancy_iTVPVideoOverlay GetStatus"); orig_overlay->GetStatus(status); } virtual void __stdcall GetEvent(long* evcode, long* param1, long* param2, bool* got) { - //puts("fancy_iTVPVideoOverlay GetEvent"); + puts("fancy_iTVPVideoOverlay GetEvent"); orig_overlay->GetEvent(evcode, param1, param2, got); + printf("ev %ld %ld %ld %d\n", *evcode, *param1, *param2, *got); } // Start: Add: T.Imoto virtual void __stdcall FreeEventParams(long evcode, long param1, long param2) { - //puts("fancy_iTVPVideoOverlay FreeEventParams"); + puts("fancy_iTVPVideoOverlay FreeEventParams"); orig_overlay->FreeEventParams(evcode, param1, param2); } @@ -515,7 +543,7 @@ class fancy_iTVPVideoOverlay : public iTVPVideoOverlay // this is not a COM obje virtual void __stdcall SetFrame( int f ) { puts("fancy_iTVPVideoOverlay SetFrame"); orig_overlay->SetFrame(f); } virtual void __stdcall GetFrame( int* f ) { - //puts("fancy_iTVPVideoOverlay GetFrame"); + puts("fancy_iTVPVideoOverlay GetFrame"); orig_overlay->GetFrame(f); printf("frame=%d\n", *f); } diff --git a/gstkrkr.c b/gstkrkr.c index 22bf8a9..14a7df4 100644 --- a/gstkrkr.c +++ b/gstkrkr.c @@ -256,15 +256,19 @@ static gboolean gst_krkr_video_sink_event(GstPad* pad, GstObject* parent, GstEve static GstFlowReturn gst_krkr_video_sink_chain(GstPad* pad, GstObject* parent, GstBuffer* buf) { +//fprintf(stderr, "gstkrkr: reading\n"); GstKrkrPlMpegVideo* filter = GST_KRKRPLMPEG_VIDEO(parent); for (size_t n=0;nbuf, meminf.data, meminf.size); + gst_memory_unmap(mem, &meminf); gst_memory_unref(mem); } + gst_buffer_unref(buf); while (true) { @@ -294,7 +298,7 @@ static GstFlowReturn gst_krkr_video_sink_chain(GstPad* pad, GstObject* parent, G size_t buflen = frame->width*frame->height*12/8; uint8_t* ptr = g_malloc(buflen); - GstBuffer* buf = gst_buffer_new_wrapped(ptr, buflen); + GstBuffer* buf2 = gst_buffer_new_wrapped(ptr, buflen); for (int y=0;yheight;y++) { memcpy(ptr, frame->y.data + frame->y.width*y, frame->width); @@ -311,11 +315,11 @@ static GstFlowReturn gst_krkr_video_sink_chain(GstPad* pad, GstObject* parent, G ptr += frame->width/2; } - buf->pts = frame->time * 1000000000; - buf->dts = frame->time * 1000000000; - buf->duration = 1000000000 / plm_video_get_framerate(filter->decode); -//fprintf(stderr, "gstkrkr: send frame, %lu bytes\n", gst_buffer_get_size(buf)); - gst_pad_push(filter->srcpad, buf); + buf2->pts = frame->time * 1000000000; + buf2->dts = frame->time * 1000000000; + buf2->duration = 1000000000 / plm_video_get_framerate(filter->decode); +//fprintf(stderr, "gstkrkr: send frame, %zu bytes\n", gst_buffer_get_size(buf2)); + gst_pad_push(filter->srcpad, buf2); } return GST_FLOW_OK; diff --git a/install.py b/install.py index 9226a04..7ec533d 100755 --- a/install.py +++ b/install.py @@ -44,11 +44,13 @@ def in_this_dir(name): def set_regkeys(wineprefix, dllpaths): registry = open(wineprefix+"/system.reg","rt").read() + orig_registry = registry registry = set_regkey(registry, key_clsid(CLSID_FilterGraph), dllpaths["x86_64"]) registry = set_regkey(registry, key_clsid(CLSID_FilterGraphNoThread), dllpaths["x86_64"]) registry = set_regkey(registry, key_clsid_wow64(CLSID_FilterGraph), dllpaths["i386"]) registry = set_regkey(registry, key_clsid_wow64(CLSID_FilterGraphNoThread), dllpaths["i386"]) - open(wineprefix+"/system.reg","wt").write(registry) + if registry != orig_registry: + open(wineprefix+"/system.reg","wt").write(registry) def wine_install(wineprefix, unixpaths, unsafe, mode): @@ -81,18 +83,39 @@ def wine_uninstall(wineprefix): silent_unlink(wineprefix + "/drive_c/windows/syswow64/krkrwine.dll") +eggroll_files = [ + "lib64/gstreamer-1.0/libgstmpegpsdemux.so", "lib/gstreamer-1.0/libgstmpegpsdemux.so", + "lib64/gstreamer-1.0/libgstasf.so", "lib/gstreamer-1.0/libgstasf.so", + "lib64/gstreamer-1.0/libgstvideoparsersbad.so", "lib/gstreamer-1.0/libgstvideoparsersbad.so", + "lib64/libgstcodecparsers-1.0.so.0", "lib/libgstcodecparsers-1.0.so.0", + "lib64/libavcodec.so.58", "lib/libavcodec.so.58", + "lib64/libavutil.so.56", "lib/libavutil.so.56", + "lib64/libavfilter.so.7", "lib/libavfilter.so.7", + "lib64/libavformat.so.58", "lib/libavformat.so.58", + "lib64/libavdevice.so.58", "lib/libavdevice.so.58", + "lib64/libswresample.so.3", "lib/libswresample.so.3", + "lib64/libswscale.so.5", "lib/libswscale.so.5", +] +eggroll_files = [ ( dst.replace("gstreamer-1.0/","").replace("lib/","i386-").replace("lib64/","x86_64-"), dst ) for dst in eggroll_files ] + if __name__ == "__main__": parser = argparse.ArgumentParser() parser.add_argument('path', type=str, nargs='?', action="store", help="Path to Proton installation") parser.add_argument('--wine', action="store_true", help="Install to non-Protom Wine at ~/.wine or WINEPREFIX") - parser.add_argument('--unsafe', action="store_true", help="Don't check if Wine is running before installing") - parser.add_argument('--uninstall', action="store_true", help="Uninstall instead (Wine only, not Proton - sorry about that)") + parser.add_argument('--unsafe', action="store_true", help="With --wine, don't check if Wine is running before installing; with --uninstall, don't look for affected Proton prefixes") + parser.add_argument('--uninstall', action="store_true", help="Uninstall instead") parser.add_argument('--devel', action="store_true", help="Point Wine to the DLLs' current location, instead of copying them into Wine's system32; allows easier updates of them, but you can't remove the krkrwine download directory (Wine only, not Proton)") + parser.add_argument('--reinstall', action="store_true", help="Force install GStreamer components into Proton, even if they seem to be present already") args = parser.parse_args() if args.path is None and not args.wine: print(""" +krkrwine v9.0 installer +Fixes some Wine bugs, making Kirikiri visual novels work under Wine and Proton 8.0 +(Those bugs are fixed upstream in Wine 9.0) +Also installs some necessary codecs to Proton, such that mediaconverter will never be chosen + Usage: ./install.py ~/steam/steamapps/common/Proton\ 8.0/ # install krkrwine into every current and future game installed into that Proton version ./install.py --wine # install into a non-Proton Wine @@ -116,16 +139,15 @@ def wine_uninstall(wineprefix): print("Installing krkrwine to Wine...") if not args.uninstall: - dllpaths = {} wine_install(wineprefix, unixpaths=unixpaths, unsafe=args.unsafe, mode=["copy", "z"][args.devel]) else: wine_uninstall(wineprefix) else: if os.path.isfile(args.path + "/dist/share/default_pfx/system.reg"): - # normal Proton + # older Proton (8.0) proton_files = args.path + "/dist" elif os.path.isfile(args.path + "/files/share/default_pfx/system.reg"): - # Glorious Eggroll (I don't know if the community Proton uses dist or files) + # newer Proton (Experimental, Glorious Eggroll) proton_files = args.path + "/files" elif os.path.isfile(args.path + "/pfx/system.reg"): # it's a Proton prefix @@ -135,66 +157,96 @@ def wine_uninstall(wineprefix): print("That doesn't look like Proton") exit(1) - if args.uninstall: - print("sorry, unimplemented - krkrwine installs itself to every Proton prefix where it's used, it's hard to identify which prefixes to uninstall from") - exit(1) - - print("Installing krkrwine to Proton...") - - # it's a Proton installation - wine_install(proton_files + "/share/default_pfx", unixpaths=unixpaths, unsafe=True, mode="copy") - + empty_settings = "user_settings = {}\n" import_me = "import krkrwine\nkrkrwine.install_within_proton()\n" - shutil.copy(__file__, args.path + "/krkrwine.py") - try: - prev_user_settings = open(args.path + "/user_settings.py", "rt").read() - except FileNotFoundError: - prev_user_settings = "user_settings = {}\n" - if import_me not in prev_user_settings: - open(args.path + "/user_settings.py", "wt").write(import_me + prev_user_settings) - shutil.copy(in_this_dir("gstkrkr-x86_64.so"), proton_files + "/lib64/gstreamer-1.0/gstkrkr-x86_64.so") - shutil.copy(in_this_dir("gstkrkr-i386.so"), proton_files + "/lib/gstreamer-1.0/gstkrkr-i386.so") + if args.uninstall: + if not args.unsafe: + if not os.path.isdir(args.path+"/../../compatdata/"): + print("No compatdata found") + exit(1) + first = True + for appid in os.listdir(args.path+"/../../compatdata/"): + compatdata = args.path+"/../../compatdata/"+appid + if os.path.isfile(compatdata+"/pfx/drive_c/windows/system32/krkrwine.dll"): + print("Uninstalling from appid "+appid) + if first: + print("(Don't worry if that's in another Proton version, it'll get reinstalled on next launch)") + first = False + wine_uninstall(compatdata+"/pfx") + + if os.path.isdir(args.path + "/krkrwine"): + shutil.rmtree(args.path + "/krkrwine") + wine_uninstall(proton_files + "/share/default_pfx") + + for src,dst in eggroll_files: + fn = proton_files + "/" + dst + if os.path.islink(fn) and not os.path.exists(fn): # dead symlink + os.unlink(fn) + + user_settings = open(args.path + "/user_settings.py", "rt").read() + user_settings = user_settings.replace(import_me, "") + if user_settings == empty_settings: + os.unlink(args.path + "/user_settings.py") + else: + open(args.path + "/user_settings.py", "wt").write(user_settings) - eggroll_files = [ - "lib64/gstreamer-1.0/libgstmpegpsdemux.so", "lib/gstreamer-1.0/libgstmpegpsdemux.so", - "lib64/gstreamer-1.0/libgstasf.so", "lib/gstreamer-1.0/libgstasf.so", - "lib64/libgstcodecparsers-1.0.so.0", "lib/libgstcodecparsers-1.0.so.0", - "lib64/libavcodec.so.58", "lib/libavcodec.so.58", - "lib64/libavutil.so.56", "lib/libavutil.so.56", - "lib64/libavfilter.so.7", "lib/libavfilter.so.7", - "lib64/libavformat.so.58", "lib/libavformat.so.58", - "lib64/libavdevice.so.58", "lib/libavdevice.so.58", - "lib64/libswresample.so.3", "lib/libswresample.so.3", - "lib64/libswscale.so.5", "lib/libswscale.so.5", - ] - if all([os.path.isfile(proton_files+"/"+f) for f in eggroll_files]): - print("Skipping Glorious Eggroll components, they're already in place") - pass # if the Glorious Eggroll components are already in place (for example because this is a GE, or it's installed already), just leave them - elif any([os.path.isfile(proton_files+"/"+f) for f in eggroll_files]): - print("Partial Glorious Eggroll components installation? Either Proton updated in a way krkrwine doesn't recognize, or your installation is damaged") - exit(1) else: - print("Installing Glorious Eggroll components...") - for dst_name in eggroll_files: - src_name = "Glorious-Eggroll/"+dst_name.replace("gstreamer-1.0/","").replace("lib/","i386-").replace("lib64/","x86_64-") - shutil.copy(in_this_dir(src_name), proton_files+"/"+dst_name) + print("Installing krkrwine to Proton...") + + if not os.path.isdir(args.path + "/krkrwine"): + os.mkdir(args.path + "/krkrwine") + for fn in [ "krkrwine-x86_64.dll", "krkrwine-i386.dll", "gstkrkr-x86_64.so", "gstkrkr-i386.so" ]: + shutil.copy(in_this_dir(fn), args.path + "/krkrwine/" + fn) + + shutil.copy(__file__, args.path + "/krkrwine/__init__.py") + try: + prev_user_settings = open(args.path + "/user_settings.py", "rt").read() + except FileNotFoundError: + prev_user_settings = empty_settings + if import_me not in prev_user_settings: + open(args.path + "/user_settings.py", "wt").write(import_me + prev_user_settings) + + eggroll_exists = [ os.path.isfile(args.path+"/krkrwine/"+src) for src,dst in eggroll_files ] + if not args.reinstall and all(eggroll_exists): + print("Not reinstalling Glorious Eggroll components, they're already in place") + pass # they can be in place because this is a GE, or because it's installed already; if so, just leave them + else: + print("Installing Glorious Eggroll components...") + for src,dst in eggroll_files: + shutil.copy(in_this_dir("Glorious-Eggroll/" + src), args.path + "/krkrwine/" + src) print("The operation completed successfully") +# This function is called from Proton user_settings.py def install_within_proton(): wineprefix = os.environ["STEAM_COMPAT_DATA_PATH"] + "/pfx" + proton_dir = os.path.dirname(os.path.dirname(__file__)) + + try: + proton_dist = in_this_dir("../dist") + except: + # in case it's a Glorious Eggroll + proton_dist = in_this_dir("../files") + + unixpaths = {} + unixpaths["i386"] = in_this_dir("krkrwine-i386.dll") + unixpaths["x86_64"] = in_this_dir("krkrwine-x86_64.dll") if os.path.isdir(wineprefix): - # if not, the registry and DLLs will be copied from the upstream Proton installation - unixpaths = {} - unixpaths["x86_64"] = in_this_dir("dist/share/default_pfx/drive_c/windows/system32/krkrwine.dll") - unixpaths["i386"] = in_this_dir("dist/share/default_pfx/drive_c/windows/syswow64/krkrwine.dll") + # game has been run already; install to the existing prefix wine_install(wineprefix, unixpaths=unixpaths, unsafe=True, mode="symlink") else: - # should be in place already, but may have been clobbered by an update - try: - wine_install(in_this_dir("dist/share/default_pfx"), unixpaths=None, unsafe=True, mode="reg_only") - except: - # in case it's a Glorious Eggroll - wine_install(in_this_dir("files/share/default_pfx"), unixpaths=None, unsafe=True, mode="reg_only") + # game is being run for the first time; default_pfx will soon be copied (with all files being symlinks), install to there instead + # (must check here, not in the main installer; the dlls occasionally get uninstalled on update) + # (if the dll is uninstalled after this stage, that's fine - the wine_install above will redirect the symlink on next launch) + default_pfx = proton_dist + "/share/default_pfx" + wine_install(default_pfx, unixpaths=unixpaths, unsafe=True, mode="copy") + os.environ["PROTON_NO_STEAM_FFMPEG"] = "1" + gstkrkr_files = [ + ("gstkrkr-i386.so", "lib/gstreamer-1.0/gstkrkr-i386.so"), + ("gstkrkr-x86_64.so", "lib64/gstreamer-1.0/gstkrkr-x86_64.so") + ] + for src,dst in eggroll_files + gstkrkr_files: + if not os.path.isfile(proton_dist+"/"+dst): + os.symlink(in_this_dir(src), proton_dist+"/"+dst) diff --git a/krkrwine.cpp b/krkrwine.cpp index e7e356d..96f6282 100644 --- a/krkrwine.cpp +++ b/krkrwine.cpp @@ -14,42 +14,47 @@ #define DEBUG 1 -// WARNING to anyone trying to use these objects to implement them for real in Wine: -// Wine's CLSID_CMpegAudioCodec demands packet boundaries to be in place. Therefore, this demuxer does that. -// It's as if there's a builtin mpegaudioparse element. -// However, the demuxer does NOT do the same for video data! -// The demuxer does parse the video info somewhat, but only to discover the resolution. There is no builtin mpegvideoparse; -// that functionality is instead part of the video decoder. -// This works fine for me, since this demuxer's video pin only needs to connect to the matching video decoder, -// but it may look confusing to anyone investigating either object on its own. -// Additionally, WMCreateSyncReader, CLSID_CWMVDecMediaObject and CLSID_CWMADecMediaObject send very different data -// between each other than the corresponding objects on Windows; you cannot use any of them to implement the others. -// Finally, these objects do not work on Windows; after attaching the output pin, -// they segfault somewhere deep inside quartz.dll. I have not been able to determine why. - #define INITGUID #define STRSAFE_NO_DEPRECATE #include #include #include #include -#include -#include #include #include #include +#include #include -#define PL_MPEG_IMPLEMENTATION -#include "pl_mpeg.h" - -static const GUID GUID_NULL = {}; // not defined in my headers, how lovely +#include -// some of these guids aren't defined in my headers, and some are only defined in some ways -// for example, __uuidof(IUnknown) is defined, but IID_IUnknown is not; IID_IAMStreamSelect is defined, but __uuidof(IAMStreamSelect) is not -DEFINE_GUID(IID_IUnknown, 0x00000000, 0x0000, 0x0000, 0xc0,0x00, 0x00,0x00,0x00,0x00,0x00,0x46); -DEFINE_GUID(IID_IAMOpenProgress,0x8E1C39A1, 0xDE53, 0x11cf, 0xAA, 0x63, 0x00, 0x80, 0xC7, 0x44, 0x52, 0x8D); -DEFINE_GUID(IID_IAMDeviceRemoval,0xf90a6130,0xb658,0x11d2,0xae,0x49,0x00,0x00,0xf8,0x75,0x4b,0x99); -__CRT_UUID_DECL(IAMStreamSelect, 0xc1960960, 0x17f5, 0x11d1, 0xab,0xe1, 0x00,0xa0,0xc9,0x05,0xf3,0x75) +#if DEBUG >= 1 +// can't use stdout from wine/proton since 8.12 https://gitlab.winehq.org/wine/wine/-/commit/dcf0bf1f383f8429136cb761f5170a04503a297b +static void my_puts(const char * a, bool linebreak=true) +{ + static int __cdecl (*__wine_dbg_output)( const char *str ); + if (!__wine_dbg_output) + __wine_dbg_output = (decltype(__wine_dbg_output))GetProcAddress(GetModuleHandle("ntdll.dll"), "__wine_dbg_output"); + if (__wine_dbg_output) + { + __wine_dbg_output(a); + if (linebreak) + __wine_dbg_output("\n"); + } + else + { + // probably shows up on windows. Maybe. Who cares. + fputs(a, stdout); + if (linebreak) + fputs("\n", stdout); + fflush(stdout); + } +} +#define puts my_puts +#define printf(...) do { char my_buf[4096]; sprintf(my_buf, __VA_ARGS__); my_puts(my_buf, false); } while(0) +#else +#define puts(x) do{}while(0) +#define printf(...) do{}while(0) +#endif static char* guid_to_str(const GUID& guid) { @@ -60,11 +65,6 @@ static char* guid_to_str(const GUID& guid) return ret; } -template T min(T a, T b) { return a < b ? a : b; } -template T max(T a, T b) { return a > b ? a : b; } - -static bool is_debian_build; - template class CComPtr { void assign(T* ptr) @@ -137,9 +137,6 @@ class com_base_embedded : public Tis... { public: HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void** ppvObject) override { -#if DEBUG >= 1 -//printf("plm QI %s\n", guid_to_str(riid)); -#endif *ppvObject = nullptr; if (riid == __uuidof(IUnknown)) { @@ -152,7 +149,6 @@ class com_base_embedded : public Tis... { } }; -//static uint32_t n_combase_objects = 0; template class com_base : public com_base_embedded { uint32_t refcount = 1; @@ -168,75 +164,8 @@ class com_base : public com_base_embedded { delete this; return new_refcount; } - com_base() - { - // there's some weird race condition here... haven't bothered tracking it down - // Wagamama High Spec Trial Edition segfaults on launch if I remove it - Sleep(1); - //DWORD n = InterlockedIncrement(&n_combase_objects); - //printf("created a %s at %p, now %lu objects\n", typeid(this).name(), this, n); - } - virtual ~com_base() - { - //DWORD n = InterlockedDecrement(&n_combase_objects); - //printf("deleted a %s at %p, now %lu objects\n", typeid(this).name(), this, n); - } -}; - -template -Tinner com_enum_helper(HRESULT STDMETHODCALLTYPE (Touter::*)(ULONG, Tinner**, ULONG*)); - -// don't inline this lambda into the template's default argument, gcc bug 105667 -static const auto addref_ptr = [](T* obj) { obj->AddRef(); return obj; }; -template -class com_enum : public com_base { - using Tret = decltype(com_enum_helper(&Tinterface::Next)); - - Tret** items; - size_t pos = 0; - size_t len; - - com_enum(Tret** items, size_t pos, size_t len) : items(items), pos(pos), len(len) {} -public: - com_enum(Tret** items, size_t len) : items(items), len(len) {} - - HRESULT STDMETHODCALLTYPE Clone(Tinterface** ppEnum) override - { - *ppEnum = new com_enum(items, pos, len); - return S_OK; - } - HRESULT STDMETHODCALLTYPE Next(ULONG celt, Tret** rgelt, ULONG* pceltFetched) override - { - size_t remaining = len - pos; - size_t ret = min((size_t)celt, remaining); - for (size_t n=0;n= celt) - return S_OK; - else - return S_FALSE; - } - HRESULT STDMETHODCALLTYPE Reset() override - { - pos = 0; - return S_OK; - } - HRESULT STDMETHODCALLTYPE Skip(ULONG celt) override - { - size_t remaining = len - pos; - if (remaining >= celt) - { - pos += celt; - return S_OK; - } - else - { - pos = len; - return S_FALSE; - } - } + com_base() {} + virtual ~com_base() {} }; @@ -264,34 +193,6 @@ HRESULT qi_release(T* obj, REFIID riid, void** ppvObj) } -static HRESULT CopyMediaType(AM_MEDIA_TYPE * pmtTarget, const AM_MEDIA_TYPE * pmtSource) -{ - *pmtTarget = *pmtSource; - if (pmtSource->pbFormat != nullptr) - { - pmtTarget->pbFormat = (uint8_t*)CoTaskMemAlloc(pmtSource->cbFormat); - memcpy(pmtTarget->pbFormat, pmtSource->pbFormat, pmtSource->cbFormat); - } - return S_OK; -} - -static AM_MEDIA_TYPE* CreateMediaType(const AM_MEDIA_TYPE * pSrc) -{ - AM_MEDIA_TYPE* ret = (AM_MEDIA_TYPE*)CoTaskMemAlloc(sizeof(AM_MEDIA_TYPE)); - CopyMediaType(ret, pSrc); - return ret; -} - -static void convert_rgb24_to_rgb32(uint8_t * dst, const uint8_t * src, size_t n_pixels) -{ - for (size_t n=0;n -class base_filter : public com_base { +template +class ClassFactory : public com_base { public: - void debug(const char * fmt, ...) - { -#if DEBUG >= 1 - char buf[1024]; - - va_list args; - va_start(args, fmt); - vsnprintf(buf, 1024, fmt, args); - va_end(args); - -#ifdef __cpp_rtti - const char * my_typename = typeid(Touter).name(); -#else - const char * my_typename = ""; -#endif - fprintf(stdout, "plm %lu %s %s\n", GetCurrentThreadId(), my_typename, buf); - fflush(stdout); -#endif - } - - Touter* parent() - { - return (Touter*)this; - } - - // several pointers in here aren't CComPtr, due to reference cycles - IFilterGraph* graph = nullptr; - - FILTER_STATE state = State_Stopped; - - WCHAR filter_name[128]; - - HRESULT STDMETHODCALLTYPE GetClassID(CLSID* pClassID) override - { - debug("IPersist GetClassID"); - *pClassID = {}; - return E_UNEXPECTED; - } - - HRESULT STDMETHODCALLTYPE GetState(DWORD dwMilliSecsTimeout, FILTER_STATE* State) override - { - //debug("IMediaFilter GetState %lx", state); - *State = state; - return S_OK; - } - HRESULT STDMETHODCALLTYPE GetSyncSource(IReferenceClock** pClock) override - { - debug("IMediaFilter GetSyncSource"); - *pClock = nullptr; - return S_OK; - } - HRESULT STDMETHODCALLTYPE Pause() override + HRESULT STDMETHODCALLTYPE CreateInstance(IUnknown* pUnkOuter, REFIID riid, void** ppvObject) override { - debug("IMediaFilter Pause"); - state = State_Paused; - return S_OK; +//printf("krkrwine: create %s (iface %s)\n", guid_to_str(clsid), guid_to_str(riid)); + if (pUnkOuter != nullptr) + return CLASS_E_NOAGGREGATION; + return qi_release(new T(), riid, ppvObject); } - HRESULT STDMETHODCALLTYPE Run(REFERENCE_TIME tStart) override + HRESULT STDMETHODCALLTYPE LockServer(BOOL lock) override { return S_OK; } // don't care +}; + +template +class WrappingClassFactory : public com_base { +public: + CComPtr parent; + WrappingClassFactory(IUnknown* parent) { - debug("IMediaFilter Run"); - state = State_Running; - return S_OK; + parent->QueryInterface(&this->parent); } - HRESULT STDMETHODCALLTYPE SetSyncSource(IReferenceClock* pClock) override + HRESULT STDMETHODCALLTYPE CreateInstance(IUnknown* pUnkOuter, REFIID riid, void** ppvObject) override { - debug("IMediaFilter SetSyncSource"); - return S_OK; +//printf("krkrwine: create %s (iface %s)\n", guid_to_str(clsid), guid_to_str(riid)); + if (pUnkOuter != nullptr) + return CLASS_E_NOAGGREGATION; + + IUnknown* inner; + HRESULT hr = parent->CreateInstance(pUnkOuter, __uuidof(IUnknown), (void**)&inner); + if (FAILED(hr)) + return hr; + IUnknown* ret = new T(inner); + inner->Release(); + hr = ret->QueryInterface(riid, ppvObject); + ret->Release(); + return hr; } - HRESULT STDMETHODCALLTYPE Stop() override + HRESULT STDMETHODCALLTYPE LockServer(BOOL lock) override { return S_OK; } // don't care +}; + +template +class AggregatingClassFactory : public com_base { +public: + HRESULT STDMETHODCALLTYPE CreateInstance(IUnknown* pUnkOuter, REFIID riid, void** ppvObject) override { - debug("IMediaFilter Stop"); - state = State_Stopped; +//printf("krkrwine: create %s (iface %s)\n", guid_to_str(clsid), guid_to_str(riid)); + if (pUnkOuter == nullptr) + return VFW_E_NEED_OWNER; // wrong if this isn't a VFW object, but who cares, it's an error, good enough + if (riid != __uuidof(IUnknown)) + return E_NOINTERFACE; + T* ret = new T(pUnkOuter); + *ppvObject = (void*)(IUnknown*)&ret->own_iunknown; return S_OK; } + HRESULT STDMETHODCALLTYPE LockServer(BOOL lock) override { return S_OK; } // don't care +}; + +class wrap_DMOObject final : public IMediaObject { +public: + class own_iunknown_t : public IUnknown { + public: + ULONG refcount = 1; + wrap_DMOObject* parent() { return container_of<&wrap_DMOObject::own_iunknown>(this); } + + HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void** ppvObject) override + { + return parent()->NonDelegatingQueryInterface(riid, ppvObject); + } + + ULONG STDMETHODCALLTYPE AddRef() override + { + return ++refcount; + } + ULONG STDMETHODCALLTYPE Release() override + { + uint32_t new_refcount = --refcount; + if (!new_refcount) + delete parent(); + return new_refcount; + } + }; + own_iunknown_t own_iunknown; + IUnknown* parent; - IEnumPins* enum_pins() requires requires { sizeof(parent()->pins); } - { - return new com_enum(parent()->pins, sizeof(parent()->pins) / sizeof(parent()->pins[0])); - } - HRESULT STDMETHODCALLTYPE EnumPins(IEnumPins** ppEnum) override - { - debug("IBaseFilter EnumPins"); - *ppEnum = parent()->enum_pins(); - return S_OK; - } - HRESULT STDMETHODCALLTYPE FindPin(LPCWSTR Id, IPin** ppPin) override + CComPtr the_real_one_iunk; + CComPtr the_real_one; + REFERENCE_TIME AvgTimePerFrame; + + wrap_DMOObject(IUnknown* outer) : parent(outer) { - debug("IBaseFilter FindPin"); - *ppPin = nullptr; - return VFW_E_NOT_FOUND; + auto* pDllGetClassObject = (decltype(DllGetClassObject)*)GetProcAddress(LoadLibrary("winegstreamer.dll"), "DllGetClassObject"); + CComPtr fac; + pDllGetClassObject(CLSID_CWMVDecMediaObject, __uuidof(IClassFactory), (void**)&fac); + fac->CreateInstance(outer, IID_PPV_ARGS(&the_real_one_iunk)); + the_real_one_iunk.QueryInterface(&the_real_one); } - HRESULT STDMETHODCALLTYPE JoinFilterGraph(IFilterGraph* pGraph, LPCWSTR pName) override + + HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void** ppvObject) override + { return parent->QueryInterface(riid, ppvObject); } + ULONG STDMETHODCALLTYPE AddRef() override + { return parent->AddRef(); } + ULONG STDMETHODCALLTYPE Release() override + { return parent->Release(); } + + HRESULT NonDelegatingQueryInterface(REFIID riid, void** ppvObject) { - debug("IBaseFilter JoinFilterGraph"); - if (pName) - wcscpy(filter_name, pName); - graph = pGraph; - return S_OK; + if (riid == __uuidof(IMediaObject)) + { + parent->AddRef(); + *ppvObject = (void*)(IMediaObject*)this; + return S_OK; + } + return E_NOINTERFACE; } - HRESULT STDMETHODCALLTYPE QueryFilterInfo(FILTER_INFO* pInfo) override + + HRESULT STDMETHODCALLTYPE AllocateStreamingResources() override + { return the_real_one->AllocateStreamingResources(); } + HRESULT STDMETHODCALLTYPE Discontinuity(DWORD dwInputStreamIndex) override + { return the_real_one->Discontinuity(dwInputStreamIndex); } + HRESULT STDMETHODCALLTYPE Flush() override + { return the_real_one->Flush(); } + HRESULT STDMETHODCALLTYPE FreeStreamingResources() override + { return the_real_one->FreeStreamingResources(); } + HRESULT STDMETHODCALLTYPE GetInputCurrentType(DWORD dwInputStreamIndex, DMO_MEDIA_TYPE* pmt) override + { return E_OUTOFMEMORY; } + HRESULT STDMETHODCALLTYPE GetInputMaxLatency(DWORD dwInputStreamIndex, REFERENCE_TIME* prtMaxLatency) override + { return E_OUTOFMEMORY; } + HRESULT STDMETHODCALLTYPE GetInputSizeInfo(DWORD dwInputStreamIndex, DWORD* pcbSize, + DWORD* pcbMaxLookahead, DWORD* pcbAlignment) override + { return E_OUTOFMEMORY; } + HRESULT STDMETHODCALLTYPE GetInputStatus(DWORD dwInputStreamIndex, DWORD* dwFlags) override + { return E_OUTOFMEMORY; } + HRESULT STDMETHODCALLTYPE GetInputStreamInfo(DWORD dwInputStreamIndex, DWORD* pdwFlags) override + { return E_OUTOFMEMORY; } + HRESULT STDMETHODCALLTYPE GetInputType(DWORD dwInputStreamIndex, DWORD dwTypeIndex, DMO_MEDIA_TYPE* pmt) override + { return E_OUTOFMEMORY; } + HRESULT STDMETHODCALLTYPE GetOutputCurrentType(DWORD dwOutputStreamIndex, DMO_MEDIA_TYPE* pmt) override + { return E_OUTOFMEMORY; } + HRESULT STDMETHODCALLTYPE GetOutputSizeInfo(DWORD dwOutputStreamIndex, DWORD* pcbSize, DWORD* pcbAlignment) override + { return the_real_one->GetOutputSizeInfo(dwOutputStreamIndex, pcbSize, pcbAlignment); } + HRESULT STDMETHODCALLTYPE GetOutputStreamInfo(DWORD dwOutputStreamIndex, DWORD* pdwFlags) override + { return E_OUTOFMEMORY; } + HRESULT STDMETHODCALLTYPE GetOutputType(DWORD dwOutputStreamIndex, DWORD dwTypeIndex, DMO_MEDIA_TYPE* pmt) override { - debug("IBaseFilter QueryFilterInfo"); - wcscpy(pInfo->achName, filter_name); - pInfo->pGraph = graph; - graph->AddRef(); - return S_OK; + HRESULT hr = the_real_one->GetOutputType(dwOutputStreamIndex, dwTypeIndex, pmt); + if (SUCCEEDED(hr) && IsEqualGUID(pmt->formattype, FORMAT_VideoInfo)) + { + VIDEOINFOHEADER* vih = (VIDEOINFOHEADER*)pmt->pbFormat; + vih->AvgTimePerFrame = AvgTimePerFrame; + } + return hr; } - HRESULT STDMETHODCALLTYPE QueryVendorInfo(LPWSTR* pVendorInfo) override + HRESULT STDMETHODCALLTYPE GetStreamCount(DWORD* pcInputStreams, DWORD* pcOutputStreams) override + { return the_real_one->GetStreamCount(pcInputStreams, pcOutputStreams); } + HRESULT STDMETHODCALLTYPE Lock(LONG bLock) override + { return E_OUTOFMEMORY; } + HRESULT STDMETHODCALLTYPE ProcessInput(DWORD dwInputStreamIndex, IMediaBuffer* pBuffer, DWORD dwFlags, + REFERENCE_TIME rtTimestamp, REFERENCE_TIME rtTimelength) override + { return the_real_one->ProcessInput(dwInputStreamIndex, pBuffer, dwFlags, rtTimestamp, rtTimelength); } + HRESULT STDMETHODCALLTYPE ProcessOutput(DWORD dwFlags, DWORD cOutputBufferCount, + DMO_OUTPUT_DATA_BUFFER* pOutputBuffers, DWORD* pdwStatus) override + { return the_real_one->ProcessOutput(dwFlags, cOutputBufferCount, pOutputBuffers, pdwStatus); } + HRESULT STDMETHODCALLTYPE SetInputMaxLatency(DWORD dwInputStreamIndex, REFERENCE_TIME rtMaxLatency) override + { return E_OUTOFMEMORY; } + HRESULT STDMETHODCALLTYPE SetInputType(DWORD dwInputStreamIndex, const DMO_MEDIA_TYPE* pmt, DWORD dwFlags) override + { return the_real_one->SetInputType(dwInputStreamIndex, pmt, dwFlags); } + HRESULT STDMETHODCALLTYPE SetOutputType(DWORD dwOutputStreamIndex, const DMO_MEDIA_TYPE* pmt, DWORD dwFlags) override { - debug("IBaseFilter QueryVendorInfo"); - return E_NOTIMPL; + HRESULT hr = the_real_one->SetOutputType(dwOutputStreamIndex, pmt, dwFlags); + if (dwFlags == 0 && hr == S_OK) + { + VIDEOINFOHEADER* vih = (VIDEOINFOHEADER*)pmt->pbFormat; + AvgTimePerFrame = vih->AvgTimePerFrame; + } + return hr; } }; -template -class base_pin : public Tbase { -public: - IPin* peer = nullptr; +static void dump_graph(IGraphBuilder* graph) +{ + puts("dumping graph"); + char buf[2048]; + char* buf_out = buf; + *buf_out = '\0'; + + CComPtr flts; + graph->EnumFilters(&flts); - void debug(const char * fmt, ...) + CComPtr flt; + while (flts->Next(1, &flt, nullptr) == S_OK) { -#if DEBUG >= 1 - char buf[1024]; + buf_out += sprintf(buf_out, "filter %p, vtbl=%p\n", (IBaseFilter*)flt, *(void**)(IBaseFilter*)flt); - va_list args; - va_start(args, fmt); - vsnprintf(buf, 1024, fmt, args); - va_end(args); + FILTER_INFO inf; + flt->QueryFilterInfo(&inf); + buf_out += sprintf(buf_out, " name=%ls\n", inf.achName); + inf.pGraph->Release(); -#ifdef __cpp_rtti - const char * my_typename = typeid(Touter).name(); -#else - const char * my_typename = ""; -#endif - fprintf(stdout, "plm %lu %s %s\n", GetCurrentThreadId(), my_typename, buf); -#endif + CComPtr pins; + flt->EnumPins(&pins); + + CComPtr pin; + while (pins->Next(1, &pin, nullptr) == S_OK) + { + buf_out += sprintf(buf_out, " pin %p, vtbl=%p\n", (IPin*)pin, *(void**)(IPin*)pin); + + PIN_INFO inf; + pin->QueryPinInfo(&inf); + buf_out += sprintf(buf_out, " name=%ls, dir=%s\n", inf.achName, inf.dir==PINDIR_INPUT ? "sink" : "src"); + inf.pFilter->Release(); + + CComPtr pin2; + pin->ConnectedTo(&pin2); + buf_out += sprintf(buf_out, " connected to %p\n", (IPin*)pin2); + } } - Touter* parent() + printf("graph:\n%s\nend of dump\n", buf); +} + +class funny_BaseFilter final : public IBaseFilter { + ULONG refcount = 1; +public: + ULONG STDMETHODCALLTYPE AddRef() override { - return (Touter*)this; + return ++refcount; } - - ULONG STDMETHODCALLTYPE AddRef() override { return parent()->parent()->AddRef(); } - ULONG STDMETHODCALLTYPE Release() override { return parent()->parent()->Release(); } - - HRESULT STDMETHODCALLTYPE BeginFlush() override + ULONG STDMETHODCALLTYPE Release() override { - debug("IPin BeginFlush"); - if (is_output) - return E_UNEXPECTED; - return E_OUTOFMEMORY; + ULONG new_refcount = --refcount; + if (!new_refcount) + delete this; + return new_refcount; } - HRESULT STDMETHODCALLTYPE Connect(IPin* pReceivePin, const AM_MEDIA_TYPE * pmt) override + + CComPtr the_real_one; + CComPtr the_real_ones_pin; + CComPtr the_real_ones_meminput_pin; + CComPtr prev_pin; + + funny_BaseFilter(IBaseFilter* real) { - debug("IPin Connect %p %p %d", pReceivePin, pmt, is_output); - if constexpr (is_output) - { - if (true) // TODO: this is debug code, delete it - { - CComPtr p; - debug("X=%.8lx", pReceivePin->EnumMediaTypes(&p)); - if (p) - { - AM_MEDIA_TYPE* pmt; - while (p->Next(1, &pmt, nullptr) == S_OK) - { - debug("CANCONNECT %p %p %p\n", guid_to_str(pmt->majortype), guid_to_str(pmt->subtype), guid_to_str(pmt->formattype)); - } - } - } - if (parent()->connect_output(pReceivePin)) - { - debug("OUTPUT PIN CONNECTED"); - peer = pReceivePin; - return S_OK; - } - return VFW_E_NO_ACCEPTABLE_TYPES; - } - else return E_UNEXPECTED; + real->QueryInterface(&the_real_one); + CComPtr p_e; + the_real_one->EnumPins(&p_e); + p_e->Next(1, &the_real_ones_pin, NULL); + the_real_ones_pin->QueryInterface(&the_real_ones_meminput_pin); } - HRESULT STDMETHODCALLTYPE ConnectedTo(IPin** pPin) override + + HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void** ppvObject) override { - debug("IPin ConnectedTo %p %p", pPin, peer); - if (!peer) - return VFW_E_NOT_CONNECTED; - *pPin = peer; - peer->AddRef(); + *ppvObject = NULL; + if (riid == __uuidof(IBaseFilter)) + *(IBaseFilter**)ppvObject = this; + else if (riid == __uuidof(IUnknown)) + *(IUnknown**)ppvObject = this; + else + return E_NOINTERFACE; + this->AddRef(); return S_OK; } - HRESULT STDMETHODCALLTYPE ConnectionMediaType(AM_MEDIA_TYPE* pmt) override - { - debug("IPin ConnectionMediaType"); - return E_OUTOFMEMORY; - } - HRESULT STDMETHODCALLTYPE Disconnect() override - { - debug("IPin Disconnect"); - peer = nullptr; - return S_OK; - } - HRESULT STDMETHODCALLTYPE EndFlush() override - { - if (is_output) - return E_UNEXPECTED; - debug("IPin EndFlush"); - return E_OUTOFMEMORY; - } - HRESULT STDMETHODCALLTYPE EndOfStream() override - { - debug("IPin EndOfStream"); - if constexpr (!is_output) - { - parent()->end_of_stream(); - return S_OK; - } - return E_UNEXPECTED; - } - HRESULT STDMETHODCALLTYPE EnumMediaTypes(IEnumMediaTypes** ppEnum) override - { - debug("IPin EnumMediaTypes"); - *ppEnum = nullptr; - return E_OUTOFMEMORY; - } - HRESULT STDMETHODCALLTYPE NewSegment(REFERENCE_TIME tStart, REFERENCE_TIME tStop, double dRate) override - { - debug("IPin NewSegment"); - return S_OK; - } - HRESULT STDMETHODCALLTYPE QueryAccept(const AM_MEDIA_TYPE * pmt) override - { - debug("IPin QueryAccept"); - return E_OUTOFMEMORY; - } - HRESULT STDMETHODCALLTYPE QueryDirection(PIN_DIRECTION* pPinDir) override - { - debug("IPin QueryDirection"); - *pPinDir = is_output ? PINDIR_OUTPUT : PINDIR_INPUT; - return S_OK; - } - HRESULT STDMETHODCALLTYPE QueryId(LPWSTR* Id) override - { - debug("IPin QueryId"); - return E_OUTOFMEMORY; - } - HRESULT STDMETHODCALLTYPE QueryInternalConnections(IPin** apPin, ULONG* nPin) override - { - debug("IPin QueryInternalConnections"); - return E_NOTIMPL; - } - HRESULT STDMETHODCALLTYPE QueryPinInfo(PIN_INFO* pInfo) override - { - debug("IPin QueryPinInfo"); - pInfo->pFilter = parent()->parent(); - pInfo->pFilter->AddRef(); - pInfo->dir = is_output ? PINDIR_OUTPUT : PINDIR_INPUT; - wcscpy(pInfo->achName, is_output ? L"source" : L"sink"); - return S_OK; - } - HRESULT STDMETHODCALLTYPE ReceiveConnection(IPin* pConnector, const AM_MEDIA_TYPE * pmt) override - { - debug("IPin ReceiveConnection %s %s %s", guid_to_str(pmt->majortype), guid_to_str(pmt->subtype), guid_to_str(pmt->formattype)); - if constexpr (!is_output) - { - if (parent()->connect_input(pConnector, pmt)) - { - debug("INPUT PIN CONNECTED"); - peer = pConnector; - return S_OK; - } - return VFW_E_TYPE_NOT_ACCEPTED; - } - else return E_UNEXPECTED; - } - - // IMemInputPin - HRESULT STDMETHODCALLTYPE GetAllocator(IMemAllocator** ppAllocator) - { - debug("IMemInputPin GetAllocator"); - return VFW_E_NO_ALLOCATOR; - } - HRESULT STDMETHODCALLTYPE GetAllocatorRequirements(ALLOCATOR_PROPERTIES* pProps) - { - debug("IMemInputPin GetAllocatorRequirements"); - return E_NOTIMPL; - } - HRESULT STDMETHODCALLTYPE NotifyAllocator(IMemAllocator* pAllocator, BOOL bReadOnly) - { - debug("IMemInputPin NotifyAllocator, readonly=%u", bReadOnly); - return S_OK; - } - HRESULT STDMETHODCALLTYPE Receive(IMediaSample* pSample) - { - BYTE* ptr; - pSample->GetPointer(&ptr); - size_t size = pSample->GetActualDataLength(); - //debug("IMemInputPin Receive %u", (unsigned)size); - parent()->receive_input(ptr, size); - return S_OK; - } - HRESULT STDMETHODCALLTYPE ReceiveCanBlock() - { - debug("IMemInputPin ReceiveCanBlock"); - return E_OUTOFMEMORY; - } - HRESULT STDMETHODCALLTYPE ReceiveMultiple(IMediaSample** pSamples, long nSamples, long* nSamplesProcessed) - { - debug("IMemInputPin ReceiveMultiple"); - return E_OUTOFMEMORY; - } - - void send_packet(IMediaSample* samp, const uint8_t * ptr, size_t len) - { - uint8_t* ptr2; - samp->GetPointer(&ptr2); - samp->SetActualDataLength(len); - memcpy(ptr2, ptr, len); - - CComPtr peer_mem; - peer->QueryInterface(IID_PPV_ARGS(&peer_mem)); - peer_mem->Receive(samp); - } -}; - -static void sample_set_time(IMediaSample* samp, double pts, double duration) -{ - if (pts != -1.0) - { - REFERENCE_TIME t = pts * 10000000; - if (duration != 0.0) - { - REFERENCE_TIME t2 = t + duration*10000000; - samp->SetTime(&t, &t2); - samp->SetMediaTime(&t, &t2); - } - else - { - samp->SetTime(&t, nullptr); - samp->SetMediaTime(&t, nullptr); - } - } - else - { - samp->SetTime(nullptr, nullptr); - samp->SetMediaTime(nullptr, nullptr); - } -} - -// Given the start of an MP2 packet, returns some information about this packet. -// Returns 1 if ok, 0 if incomplete packet, -1 if corrupt packet. -#define MP2_PACKET_MAX_SIZE 1728 -static inline int mp2_packet_parse(const uint8_t * ptr, size_t len, int* samplerate, int* n_channels, size_t* size) -{ - //size_t prefix_padding = 0; - //while (len > 0 && *ptr == 0x00) - //{ - //prefix_padding++; - //ptr++; - //len--; - //} - - //header is 48 bits - //11 bits sync, 0x7ff - //2 bits version, must be 3 - //2 bits layer, must be 2 - //1 bit hasCRC, can be whatever - - //4 bits bitrate index - //2 bits sample rate index - //1 bit padding flag - //1 bit private (ignore) - - //2 bits mode (stereo, joint stereo, dual channel, mono) - //2 bits mode extension (used only for joint stereo) - //4 bits copyright crap - - //16 bits checksum, if hasCRC - - if (len < 6) - return 0; - if (ptr[0] != 0xFF || (ptr[1]&0xFE) != 0xFC) - return -1; - - static const uint16_t bitrates[16] = { 0xFFFF, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384, 0xFFFF }; - static const uint16_t samplerates[4] = { 44100, 48000, 32000, 0xFFFF }; - - int bitrate = bitrates[(ptr[2]&0xF0)>>4]; - int sample_rate = samplerates[(ptr[2]&0x0C)>>2]; - if (bitrate == 0xFFFF || sample_rate == 0xFFFF) - return -1; - - bool has_pad = (ptr[2]&0x02); - - if (samplerate) - *samplerate = sample_rate; - if (n_channels) - *n_channels = ((ptr[3]&0xC0)>>6 == 3) ? 1 : 2; - if (size) - *size = (144000 * bitrate / sample_rate) + has_pad; - return 1; -} - -class CMPEG1Splitter : public base_filter { -public: - SRWLOCK srw; // threading: safe - CONDITION_VARIABLE wake_parent; // threading: safe - CONDITION_VARIABLE wake_video; // threading: safe - CONDITION_VARIABLE wake_audio; // threading: safe - bool have_video = false; // threading: parent only - bool have_audio = false; // threading: parent only - plm_demux_t* demux_video = nullptr; // threading: lock - plm_demux_t* demux_audio = nullptr; // threading: lock - - bool video_thread_exists = false; // threading: lock - bool video_thread_stop = false; // threading: lock - bool video_thread_active = false; // threading: lock; tells whether the child is currently writing anything - bool audio_thread_exists = false; // threading: lock - bool audio_thread_stop = false; // threading: lock - bool audio_thread_active = false; // threading: lock; tells whether the child is currently writing anything - - // FILTER_STATE state; // (from parent) threading: lock - - ~CMPEG1Splitter() - { - scoped_lock lock(&this->srw); - video_thread_stop = true; - audio_thread_stop = true; - WakeConditionVariable(&this->wake_video); - WakeConditionVariable(&this->wake_audio); - while (video_thread_exists || audio_thread_exists) - SleepConditionVariableSRW(&this->wake_parent, &this->srw, INFINITE, 0); - if (demux_video) - plm_demux_destroy(demux_video); - if (demux_audio) - plm_demux_destroy(demux_audio); - } - - class in_pin : public base_pin, in_pin> { - public: - CMPEG1Splitter* parent() { return container_of<&CMPEG1Splitter::pin_i>(this); } - - bool connect_input(IPin* pConnector, const AM_MEDIA_TYPE * pmt) - { - if (pmt->majortype == MEDIATYPE_Stream && pmt->subtype == MEDIASUBTYPE_MPEG1System) - { - CComPtr ar; - if (FAILED(pConnector->QueryInterface(&ar))) - return false; - - parent()->start_threads(ar); - return true; - } - - return false; - } - - void end_of_stream() - { - abort(); // we should get input only from IAsyncStream::SyncRead, EndOfStream() should be unreachable - } - }; - class out_pin_v : public base_pin, out_pin_v> { - public: - CMPEG1Splitter* parent() { return container_of<&CMPEG1Splitter::pin_v>(this); } - - MPEG1VIDEOINFO mvi_mediatype; - AM_MEDIA_TYPE am_mediatype; - - AM_MEDIA_TYPE* media_type() - { - int width = parent()->video_width; - int height = parent()->video_height; - double fps = parent()->video_fps; - - // why are some of those structs so obnoxiously deeply nested - mvi_mediatype = { - .hdr = { - .rcSource = { 0, 0, width, height }, - .rcTarget = { 0, 0, width, height }, - .dwBitRate = 0, - .dwBitErrorRate = 0, - .AvgTimePerFrame = (REFERENCE_TIME)(10000000/fps), - .bmiHeader = { - .biSize = sizeof(BITMAPINFOHEADER), - .biWidth = width, - .biHeight = height, - .biPlanes = 0, - .biBitCount = 0, - .biCompression = 0, - .biSizeImage = 0, - }, - }, - .dwStartTimeCode = 0, - .cbSequenceHeader = 0, - .bSequenceHeader = {}, - }; - am_mediatype = { - .majortype = MEDIATYPE_Video, - .subtype = MEDIASUBTYPE_MPEG1Payload, - .bFixedSizeSamples = false, - .bTemporalCompression = true, - .lSampleSize = 0, - .formattype = FORMAT_MPEGVideo, - .pUnk = nullptr, - .cbFormat = sizeof(mvi_mediatype), - .pbFormat = (BYTE*)&mvi_mediatype, - }; - - return &am_mediatype; - } - - bool connect_output(IPin* pReceivePin) - { - if (!parent()->have_video) - return false; - return SUCCEEDED(pReceivePin->ReceiveConnection(this, media_type())); - } - }; - class out_pin_a : public base_pin, out_pin_a> { - public: - CMPEG1Splitter* parent() { return container_of<&CMPEG1Splitter::pin_a>(this); } - - MPEG1WAVEFORMAT mwf_mediatype; - AM_MEDIA_TYPE am_mediatype; - - AM_MEDIA_TYPE* media_type() - { - int rate = parent()->audio_rate; - - mwf_mediatype = { - .wfx = { - .wFormatTag = WAVE_FORMAT_MPEG, - .nChannels = 2, - .nSamplesPerSec = (DWORD)rate, - .nAvgBytesPerSec = 4000, - .nBlockAlign = 48, - .wBitsPerSample = 0, - .cbSize = sizeof(mwf_mediatype), - }, - .fwHeadLayer = ACM_MPEG_LAYER2, - .dwHeadBitrate = (DWORD)rate, - .fwHeadMode = ACM_MPEG_STEREO, - .fwHeadModeExt = 0, - .wHeadEmphasis = 0, - .fwHeadFlags = ACM_MPEG_ID_MPEG1, - .dwPTSLow = 0, - .dwPTSHigh = 0, - }; - am_mediatype = { - .majortype = MEDIATYPE_Audio, - .subtype = MEDIASUBTYPE_MPEG1AudioPayload, // wine understands only MPEG1AudioPayload, not MPEG1Packet or MPEG1Payload - .bFixedSizeSamples = false, - .bTemporalCompression = true, - .lSampleSize = 0, - .formattype = FORMAT_WaveFormatEx, - .pUnk = nullptr, - .cbFormat = sizeof(mwf_mediatype), - .pbFormat = (BYTE*)&mwf_mediatype, - }; - return &am_mediatype; - } - - bool connect_output(IPin* pReceivePin) - { - if (!parent()->have_audio) - return false; - - return SUCCEEDED(pReceivePin->ReceiveConnection(this, media_type())); - } - }; - in_pin pin_i; - out_pin_v pin_v; // threading: parent only for most variables, read only while child exists for .peer - out_pin_a pin_a; // threading: parent only for most variables, read only while child exists for .peer - - IPin* pins[3] = { &pin_i, &pin_v, &pin_a }; - - int video_width; - int video_height; - double video_fps; - int audio_rate; - - HRESULT STDMETHODCALLTYPE Run(REFERENCE_TIME tStart) override - { - debug("IMediaFilter Run but better"); - scoped_lock lock(&this->srw); - state = State_Running; - WakeConditionVariable(&this->wake_video); - WakeConditionVariable(&this->wake_audio); - return S_OK; - } - HRESULT STDMETHODCALLTYPE Pause() override - { - debug("IMediaFilter Pause but better"); - scoped_lock lock(&this->srw); - state = State_Paused; - WakeConditionVariable(&this->wake_video); - WakeConditionVariable(&this->wake_audio); - return S_OK; - } - HRESULT STDMETHODCALLTYPE Stop() override - { - debug("IMediaFilter Stop but better"); - scoped_lock lock(&this->srw); - state = State_Stopped; - while (video_thread_active || audio_thread_active) - SleepConditionVariableSRW(&this->wake_parent, &this->srw, INFINITE, 0); - return S_OK; - } - - void start_threads(IAsyncReader* ar) - { - scoped_lock lock(&this->srw); - - // don't bother with async reading, just take the easiest solution - int64_t ignore; - int64_t size; - ar->Length(&ignore, &size); - uint8_t * ptr = (uint8_t*)malloc(size); - ar->SyncRead(0, size, ptr); - - // just tell one to free it, and the other not - demux_video = plm_demux_create(plm_buffer_create_with_memory(ptr, size, true), true); - demux_audio = plm_demux_create(plm_buffer_create_with_memory(ptr, size, false), true); - video_thread_active = false; - audio_thread_active = false; - - // - all pins must be connected before any filter can move to Paused - // - video decoder must know its output size before its pin can connect - // - video decoder won't have any input data before moving to Paused - // therefore, demuxer must provide this information to the decoder through another mechanism - // therefore, demuxer must START UP AN ENTIRE DECODER to read the headers and extract the video size - bool need_video = (plm_demux_get_num_video_streams(demux_video) > 0); - bool need_audio = (plm_demux_get_num_audio_streams(demux_video) > 0); - plm_buffer_t* video_buffer = plm_buffer_create_with_capacity(32768); - plm_video_t* video_checker = plm_video_create_with_buffer(video_buffer, false); - plm_buffer_t* audio_buffer = plm_buffer_create_with_capacity(32768); - plm_audio_t* audio_checker = plm_audio_create_with_buffer(audio_buffer, false); - - while (need_video || need_audio) - { - plm_packet_t* pack = plm_demux_decode(demux_video); - if (!pack) - break; - if (pack->type == PLM_DEMUX_PACKET_VIDEO_1 && need_video) - { - plm_buffer_write(video_buffer, pack->data, pack->length); - if (plm_video_has_header(video_checker)) - { - this->video_width = plm_video_get_width(video_checker); - this->video_height = plm_video_get_height(video_checker); - this->video_fps = plm_video_get_framerate(video_checker); - have_video = true; - need_video = false; - } - } - if (pack->type == PLM_DEMUX_PACKET_AUDIO_1 && need_audio) - { - plm_buffer_write(audio_buffer, pack->data, pack->length); - if (plm_audio_has_header(audio_checker)) - { - this->audio_rate = plm_audio_get_samplerate(audio_checker); - have_audio = true; - need_audio = false; - } - } - } - - plm_buffer_destroy(video_buffer); - plm_video_destroy(video_checker); - plm_buffer_destroy(audio_buffer); - plm_audio_destroy(audio_checker); - - plm_demux_rewind(demux_video); - - // need two threads, or the audio pin blocks while the video pin demands more input - // and I need two plm_demuxers, so they can go arbitrarily far out of sync - CreateThread(nullptr, 0, &CMPEG1Splitter::video_thread_fn_wrap, this, 0, nullptr); - CreateThread(nullptr, 0, &CMPEG1Splitter::audio_thread_fn_wrap, this, 0, nullptr); - } - - static DWORD WINAPI video_thread_fn_wrap(void* lpParameter) - { - ((CMPEG1Splitter*)lpParameter)->video_thread_fn(); - return 0; - } - void video_thread_fn() - { -//printf("VIDEOTHREAD=%.8lx\n", GetCurrentThreadId()); - scoped_lock lock(&this->srw); - bool first_packet = true; - - CComPtr mem_alloc; - mem_alloc.CoCreateInstance(CLSID_MemoryAllocator, NULL, CLSCTX_INPROC); - ALLOCATOR_PROPERTIES props = { 1, 65536, 1, 0 }; - mem_alloc->SetProperties(&props, &props); - mem_alloc->Commit(); - - while (!this->video_thread_stop) - { -//puts("vt1."); - while (this->state == State_Stopped) - { -//puts("vt2."); - if (this->video_thread_stop) - goto stop; -//puts("vt3."); - if (video_thread_active) - { -//puts("vt4."); - video_thread_active = false; - WakeConditionVariable(&this->wake_parent); - } -//puts("vt5."); - SleepConditionVariableSRW(&this->wake_video, &this->srw, INFINITE, 0); -//puts("vt6."); - } -//puts("vt7."); - video_thread_active = true; - plm_packet_t* pack = plm_demux_decode(demux_video); - if (!pack) - { - scoped_unlock unlock(&this->srw); - if (pin_v.peer) - pin_v.peer->EndOfStream(); - break; - } - if (pack->type == PLM_DEMUX_PACKET_VIDEO_1 && pin_v.peer) - { - scoped_unlock unlock(&this->srw); - - CComPtr samp; - mem_alloc->GetBuffer(&samp, nullptr, nullptr, 0); - - samp->SetDiscontinuity(first_packet); - samp->SetPreroll(false); - samp->SetSyncPoint(false); - sample_set_time(samp, pack->pts, 1.0 / video_fps); - pin_v.send_packet(samp, pack->data, pack->length); - - first_packet = false; - } - } - - stop: -//puts("vt999."); - this->video_thread_active = false; - this->video_thread_exists = false; - WakeConditionVariable(&this->wake_parent); - } - - static DWORD WINAPI audio_thread_fn_wrap(void* lpParameter) - { - ((CMPEG1Splitter*)lpParameter)->audio_thread_fn(); - return 0; - } - void audio_thread_fn() - { -//printf("AUDIOTHREAD=%.8lx\n", GetCurrentThreadId()); - scoped_lock lock(&this->srw); - bool first_packet = true; - - CComPtr mem_alloc; - mem_alloc.CoCreateInstance(CLSID_MemoryAllocator, NULL, CLSCTX_INPROC); - ALLOCATOR_PROPERTIES props = { 16, MP2_PACKET_MAX_SIZE, 1, 0 }; - mem_alloc->SetProperties(&props, &props); - mem_alloc->Commit(); - - size_t audio_chunk_pos = 0; - uint8_t audio_chunk[MP2_PACKET_MAX_SIZE]; - - while (!this->audio_thread_stop) - { -//puts("at1."); - while (this->state == State_Stopped) - { -//puts("at2."); - if (this->audio_thread_stop) - goto stop; -//puts("at3."); - if (audio_thread_active) - { -//puts("at4."); - audio_thread_active = false; - WakeConditionVariable(&this->wake_parent); - } -//puts("at5."); - SleepConditionVariableSRW(&this->wake_audio, &this->srw, INFINITE, 0); -//puts("at6."); - } -//puts("at7."); - audio_thread_active = true; - plm_packet_t* pack = plm_demux_decode(demux_audio); - if (!pack) - { - scoped_unlock unlock(&this->srw); - if (pin_a.peer) - pin_a.peer->EndOfStream(); - break; - } - if (pack->type == PLM_DEMUX_PACKET_AUDIO_1 && pin_a.peer) - { - scoped_unlock unlock(&this->srw); - - const uint8_t * new_buf = pack->data; - size_t new_len = pack->length; - - while (new_len) - { - size_t claim = min(new_len, MP2_PACKET_MAX_SIZE - audio_chunk_pos); - memcpy(audio_chunk + audio_chunk_pos, new_buf, claim); - audio_chunk_pos += claim; - new_buf += claim; - new_len -= claim; - - size_t pack_size = SIZE_MAX; - if (mp2_packet_parse(audio_chunk, audio_chunk_pos, NULL, NULL, &pack_size) < 0) - { - audio_chunk_pos = 0; // just discard it and see what happens - break; - } - if (pack_size <= audio_chunk_pos) - { - CComPtr samp; - mem_alloc->GetBuffer(&samp, nullptr, nullptr, 0); - - samp->SetDiscontinuity(first_packet); - samp->SetPreroll(false); - samp->SetSyncPoint(false); - sample_set_time(samp, pack->pts, 0.0); - pin_a.send_packet(samp, audio_chunk, pack_size); - - first_packet = false; - - memmove(audio_chunk, audio_chunk+pack_size, audio_chunk_pos-pack_size); - audio_chunk_pos -= pack_size; - } - } - } - } - - stop: - this->audio_thread_active = false; - this->audio_thread_exists = false; - WakeConditionVariable(&this->wake_parent); - } - - HRESULT STDMETHODCALLTYPE Count(DWORD* pcStreams) override - { - *pcStreams = have_video + have_audio; - return S_OK; - } - HRESULT STDMETHODCALLTYPE Enable(long lIndex, DWORD dwFlags) override - { - return E_OUTOFMEMORY; // I don't know if this is used - leave it unimplemented until I find something that uses it - } - HRESULT STDMETHODCALLTYPE Info(long lIndex, AM_MEDIA_TYPE** ppmt, DWORD* pdwFlags, LCID* plcid, - DWORD* pdwGroup, LPWSTR* ppszName, IUnknown** ppObject, IUnknown** ppUnk) override - { - bool is_video = (have_video && lIndex == 0); - if (have_video) lIndex--; - bool is_audio = (have_audio && lIndex == 0); - if (have_audio) lIndex--; - - if (!is_video && !is_audio) - return S_FALSE; - - if (ppmt) - { - if (is_video) - *ppmt = CreateMediaType(pin_v.media_type()); - if (is_audio) - *ppmt = CreateMediaType(pin_a.media_type()); - } - if (pdwFlags) - *pdwFlags = 0; // should be AMSTREAMSELECTINFO_ENABLED, but I don't know if it's used - if (pdwGroup) - *pdwGroup = is_audio; - // ignore the others, Kirikiri just leaves them as null (and it never uses the group, it just stores it somewhere) - return S_OK; - } - - // IMediaSeeking - HRESULT STDMETHODCALLTYPE CheckCapabilities(DWORD* pCapabilities) override - { puts("CheckCapabilities"); return E_OUTOFMEMORY; } - HRESULT STDMETHODCALLTYPE ConvertTimeFormat(LONGLONG* pTarget, const GUID* pTargetFormat, LONGLONG Source, const GUID* pSourceFormat) override - { puts("ConvertTimeFormat"); return E_OUTOFMEMORY; } - HRESULT STDMETHODCALLTYPE GetAvailable(LONGLONG* pEarliest, LONGLONG* pLatest) override - { puts("GetAvailable"); return E_OUTOFMEMORY; } - HRESULT STDMETHODCALLTYPE GetCapabilities(DWORD* pCapabilities) override - { puts("GetCapabilities"); return E_OUTOFMEMORY; } - HRESULT STDMETHODCALLTYPE GetCurrentPosition(LONGLONG* pCurrent) override - { puts("GetCurrentPosition"); return E_OUTOFMEMORY; } - HRESULT STDMETHODCALLTYPE GetDuration(LONGLONG* pDuration) override - { puts("GetDuration"); return E_OUTOFMEMORY; } - HRESULT STDMETHODCALLTYPE GetPositions(LONGLONG* pCurrent, LONGLONG* pStop) override - { puts("GetPositions"); return E_OUTOFMEMORY; } - HRESULT STDMETHODCALLTYPE GetPreroll(LONGLONG* pllPreroll) override - { puts("GetPreroll"); return E_OUTOFMEMORY; } - HRESULT STDMETHODCALLTYPE GetRate(double* pdRate) override - { puts("GetRate"); return E_OUTOFMEMORY; } - HRESULT STDMETHODCALLTYPE GetStopPosition(LONGLONG* pStop) override - { puts("GetStopPosition"); return E_OUTOFMEMORY; } - HRESULT STDMETHODCALLTYPE GetTimeFormat(GUID* pFormat) override - { puts("GetTimeFormat"); return E_OUTOFMEMORY; } - HRESULT STDMETHODCALLTYPE IsFormatSupported(const GUID* pFormat) override - { - puts("IsFormatSupported"); - if (*pFormat == TIME_FORMAT_MEDIA_TIME) // Wine ignores my IMediaSeeking unless this is supported - return S_OK; - return E_OUTOFMEMORY; - } - HRESULT STDMETHODCALLTYPE IsUsingTimeFormat(const GUID* pFormat) override - { puts("IsUsingTimeFormat"); return E_OUTOFMEMORY; } - HRESULT STDMETHODCALLTYPE QueryPreferredFormat(GUID* pFormat) override - { puts("QueryPreferredFormat"); return E_OUTOFMEMORY; } - HRESULT STDMETHODCALLTYPE SetPositions(LONGLONG* pCurrent, DWORD dwCurrentFlags, LONGLONG* pStop, DWORD dwStopFlags) override - { - puts("SetPositions"); // just do nothing - return S_OK; - } - HRESULT STDMETHODCALLTYPE SetRate(double dRate) override - { puts("SetRate"); return E_OUTOFMEMORY; } - HRESULT STDMETHODCALLTYPE SetTimeFormat(const GUID* pFormat) override - { puts("SetTimeFormat"); return E_OUTOFMEMORY; } -}; - -class CMpegVideoCodec : public base_filter { -public: - plm_buffer_t* buf; - plm_video_t* decode; - - CMpegVideoCodec() - { - buf = plm_buffer_create_with_capacity(PLM_BUFFER_DEFAULT_SIZE); - decode = plm_video_create_with_buffer(buf, false); - } - ~CMpegVideoCodec() - { - plm_buffer_destroy(buf); - plm_video_destroy(decode); - } - - class in_pin : public base_pin, in_pin> { - public: - CMpegVideoCodec* parent() { return container_of<&CMpegVideoCodec::pin_i>(this); } - - bool connect_input(IPin* pConnector, const AM_MEDIA_TYPE * pmt) - { - if (pmt->majortype == MEDIATYPE_Video && pmt->subtype == MEDIASUBTYPE_MPEG1Payload) - return parent()->update_size_from_mediatype(pmt); - return false; - } - - void receive_input(uint8_t* ptr, size_t size) - { - plm_buffer_write(parent()->buf, ptr, size); - - while (true) - { - plm_frame_t* frame = plm_video_decode(parent()->decode); - if (!frame) - break; - - parent()->dispatch_frame(frame); - } - } - - void end_of_stream() - { - parent()->pin_o.peer->EndOfStream(); - } - }; - class out_pin : public base_pin, out_pin> { - public: - CMpegVideoCodec* parent() { return container_of<&CMpegVideoCodec::pin_o>(this); } - - bool connect_output(IPin* pReceivePin) - { - if (FAILED(pReceivePin->ReceiveConnection(this, parent()->media_type(MEDIASUBTYPE_YV12))) && - FAILED(pReceivePin->ReceiveConnection(this, parent()->media_type(MEDIASUBTYPE_RGB24))) && - FAILED(pReceivePin->ReceiveConnection(this, parent()->media_type(MEDIASUBTYPE_RGB32)))) - { - return false; - } - - parent()->commit_media_type(); - - return true; - } - }; - in_pin pin_i; - out_pin pin_o; - - IPin* pins[2] = { &pin_i, &pin_o }; - - CComPtr mem_alloc; - - VIDEOINFOHEADER vih_mediatype = { - .rcSource = { 0, 0, -1, -1 }, - .rcTarget = { 0, 0, -1, -1 }, - .dwBitRate = 0, - .dwBitErrorRate = 0, - .AvgTimePerFrame = -1, - .bmiHeader = { - .biSize = sizeof(BITMAPINFOHEADER), - .biWidth = -1, - .biHeight = -1, - .biPlanes = 1, - .biBitCount = -1, - .biCompression = 0, - .biSizeImage = 0xFFFFFFFF, - }, - }; - AM_MEDIA_TYPE am_mediatype = { - .majortype = MEDIATYPE_Video, - .subtype = {}, - .bFixedSizeSamples = false, - .bTemporalCompression = true, - .lSampleSize = 0, - .formattype = FORMAT_VideoInfo, - .pUnk = nullptr, - .cbFormat = sizeof(vih_mediatype), - .pbFormat = (BYTE*)&vih_mediatype, - }; - - bool update_size_from_mediatype(const AM_MEDIA_TYPE * pmt) - { - if (pmt->formattype != FORMAT_MPEGVideo) - return false; - - const MPEG1VIDEOINFO * vi = (MPEG1VIDEOINFO*)pmt->pbFormat; - vih_mediatype.rcSource = vi->hdr.rcSource; - vih_mediatype.rcTarget = vi->hdr.rcTarget; - vih_mediatype.AvgTimePerFrame = vi->hdr.AvgTimePerFrame; - vih_mediatype.bmiHeader.biWidth = vi->hdr.bmiHeader.biWidth; - vih_mediatype.bmiHeader.biHeight = vi->hdr.bmiHeader.biHeight; - return true; - } - - static void update_mediatype_from_subtype(AM_MEDIA_TYPE* pmt) - { - VIDEOINFOHEADER* vi = (VIDEOINFOHEADER*)pmt->pbFormat; - BITMAPINFOHEADER* bmi = &vi->bmiHeader; - if (pmt->subtype == MEDIASUBTYPE_YV12) - { - bmi->biBitCount = 12; - bmi->biCompression = MAKEFOURCC('Y','V','1','2'); - } - if (pmt->subtype == MEDIASUBTYPE_RGB24) - { - bmi->biBitCount = 24; - bmi->biCompression = BI_RGB; - } - if (pmt->subtype == MEDIASUBTYPE_RGB32) - { - bmi->biBitCount = 32; - bmi->biCompression = BI_RGB; - } - bmi->biSizeImage = bmi->biWidth * bmi->biHeight * bmi->biBitCount / 8; - } - - AM_MEDIA_TYPE* media_type(GUID subtype) - { - am_mediatype.subtype = subtype; - update_mediatype_from_subtype(&am_mediatype); - return &am_mediatype; - } - - void commit_media_type() - { - mem_alloc.CoCreateInstance(CLSID_MemoryAllocator, NULL, CLSCTX_INPROC); - ALLOCATOR_PROPERTIES props = { 1, (int32_t)vih_mediatype.bmiHeader.biSizeImage, 1024, 0 }; - mem_alloc->SetProperties(&props, &props); - mem_alloc->Commit(); - } - - void dispatch_frame(plm_frame_t* frame) - { - CComPtr samp; - mem_alloc->GetBuffer(&samp, nullptr, nullptr, 0); - - samp->SetDiscontinuity(false); - samp->SetPreroll(false); - samp->SetSyncPoint(true); - sample_set_time(samp, frame->time, 1.0 / plm_video_get_framerate(decode)); - - uint8_t* ptr2; - samp->GetPointer(&ptr2); - samp->SetActualDataLength(frame->width * frame->height * vih_mediatype.bmiHeader.biBitCount / 8); - - if (vih_mediatype.bmiHeader.biBitCount == 12) - { - for (size_t y=0;yheight;y++) - { - memcpy(ptr2, frame->y.data + frame->y.width*y, frame->width); - ptr2 += frame->width; - } - for (size_t y=0;yheight/2;y++) - { - memcpy(ptr2, frame->cr.data + frame->cr.width*y, frame->width/2); - ptr2 += frame->width/2; - } - for (size_t y=0;yheight/2;y++) - { - memcpy(ptr2, frame->cb.data + frame->cb.width*y, frame->width/2); - ptr2 += frame->width/2; - } - } - if (vih_mediatype.bmiHeader.biBitCount == 24) - { - plm_frame_to_bgr(frame, ptr2+(frame->height-1)*frame->width*3, -frame->width*3); - } - if (vih_mediatype.bmiHeader.biBitCount == 32) - { - plm_frame_to_bgra(frame, ptr2+(frame->height-1)*frame->width*4, -frame->width*4); - } - - CComPtr peer_mem; - pin_o.peer->QueryInterface(IID_PPV_ARGS(&peer_mem)); - peer_mem->Receive(samp); - } -}; - - - -// This class mostly represents functionality that exists in Wine, but works around a few Wine bugs. -// - IVMRSurfaceAllocatorNotify9::ChangeD3DDevice - semi-stub -// once this one is fixed, delete members need_reinit, every the_*, and everything that uses them -// - IVMRSurfaceAllocatorNotify9::NotifyEvent - stub -// once this one is fixed, simply delete the E_NOTIMPL check from NotifyEvent -// - Direct3D 9 can't draw on child windows -// once this one is fixed, delete function is_window_visible, member parent_window, and everything that uses them -// The rest of the class is simply boilerplate to inject my hacks where I need them. -class CVideoMixingRenderer9 : public com_base { - static bool is_window_visible(HWND hwnd) - { - DWORD wnd_pid; - DWORD my_pid = GetProcessId(GetCurrentProcess()); - GetWindowThreadProcessId(hwnd, &wnd_pid); - if (wnd_pid != my_pid) - return false; - - if (!IsWindowVisible(hwnd)) - return false; - - RECT rect; - GetClientRect(hwnd, &rect); - // I don't know what a visible 0x0 window means, but they exist sometimes - if (rect.right == 0 || rect.bottom == 0) - return false; - - return true; - } - static BOOL CALLBACK EnumWindowsProc(HWND hwnd, LPARAM lParam) - { - HWND* ret = (HWND*)lParam; - - if (!is_window_visible(hwnd)) - return TRUE; - if (*ret) - return FALSE; - *ret = hwnd; - return TRUE; - } - static HWND find_the_only_visible_window() - { - HWND ret = nullptr; - if (!EnumWindows(EnumWindowsProc, (LPARAM)&ret)) - return nullptr; - return ret; - } - - class fancy_surfalloc : public IVMRSurfaceAllocator9, IVMRImagePresenter9 { - public: - IVMRSurfaceAllocator9* parent_alloc; - IVMRImagePresenter9* parent_pres; - - bool need_reinit = false; - IDirect3DDevice9* the_d3ddevice; - IDirect3DSurface9** the_surface; - DWORD the_surfaceflags; - DWORD_PTR the_userid; - VMR9AllocationInfo the_allocinfo; - - bool need_move_window; - //HWND parent_window = nullptr; - //DWORD parent_orig_style; - - //CVideoMixingRenderer9* parent() { return container_of<&CVideoMixingRenderer9::surfalloc_wrapper>(this); } - DWORD STDMETHODCALLTYPE AddRef() { return parent_alloc->AddRef(); } - DWORD STDMETHODCALLTYPE Release() { return parent_alloc->Release(); } - - HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void** ppvObject) override - { -//printf("QI(surfalloc)=%s\n", guid_to_str(riid)); - *ppvObject = nullptr; - if (riid == __uuidof(IVMRImagePresenter9)) - { - *ppvObject = (void*)(IVMRImagePresenter9*)this; - this->AddRef(); - return S_OK; - } - return parent_alloc->QueryInterface(riid, ppvObject); - } - - HRESULT STDMETHODCALLTYPE AdviseNotify(IVMRSurfaceAllocatorNotify9* lpIVMRSurfAllocNotify) - { return parent_alloc->AdviseNotify(lpIVMRSurfAllocNotify); } - HRESULT STDMETHODCALLTYPE GetSurface(DWORD_PTR dwUserID, DWORD SurfaceIndex, DWORD SurfaceFlags, IDirect3DSurface9** lplpSurface) - { - the_surface = lplpSurface; - the_surfaceflags = SurfaceFlags; - return parent_alloc->GetSurface(dwUserID, SurfaceIndex, SurfaceFlags, lplpSurface); - } - HRESULT STDMETHODCALLTYPE InitializeDevice(DWORD_PTR dwUserID, VMR9AllocationInfo* lpAllocInfo, DWORD* lpNumBuffers) - { - need_reinit = false; - need_move_window = is_debian_build; // https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1043052 (likely same as 1051492) - the_userid = dwUserID; - the_allocinfo = *lpAllocInfo; - return parent_alloc->InitializeDevice(dwUserID, lpAllocInfo, lpNumBuffers); - } - HRESULT STDMETHODCALLTYPE TerminateDevice(DWORD_PTR dwID) - { - return parent_alloc->TerminateDevice(dwID); - } - - - HRESULT STDMETHODCALLTYPE PresentImage(DWORD_PTR dwUserID, VMR9PresentationInfo* lpPresInfo) - { - if (need_move_window) - { - need_move_window = false; - - HWND parent = find_the_only_visible_window(); - HWND child = GetWindow(parent, GW_CHILD); - while (child) - { - char buf[256]; - GetClassName(child, buf, 256); - if (!strcmp(buf, "krmovie VMR9 Child Window Class")) - { - RECT rect; - GetClientRect(child, &rect); - POINT pt = { 0, 0 }; - ClientToScreen(parent, &pt); - - // need to hide the window before setting the parent - // don't know if windows limitation, wine limitation, x11 limitation, window manager limitation, or whatever - // don't really care either - ShowWindow(child, SW_HIDE); - - // WS_POPUP seems to do pretty much nothing in this year (other than mess with WS_CHILD), - // but it seems intended to be set on parented borderless windows like this, so let's do it - SetWindowLong(child, GWL_STYLE, (GetWindowLong(child, GWL_STYLE) & ~WS_CHILD) | WS_POPUP); - SetParent(child, nullptr); - // no clue why owner is set by setting PARENT, but that's what it is - SetWindowLongPtr(child, GWLP_HWNDPARENT, (LONG_PTR)parent); - - MoveWindow(child, pt.x, pt.y, rect.right, rect.bottom, FALSE); - - ShowWindow(child, SW_SHOW); - - //break; - } - child = GetWindow(child, GW_HWNDNEXT); - } - } - return parent_pres->PresentImage(dwUserID, lpPresInfo); - } - HRESULT STDMETHODCALLTYPE StartPresenting(DWORD_PTR dwUserID) - { - return parent_pres->StartPresenting(dwUserID); - } - HRESULT STDMETHODCALLTYPE StopPresenting(DWORD_PTR dwUserID) - { - return parent_pres->StopPresenting(dwUserID); - } - - void evil_reinit() - { - if (!need_reinit) - return; - -puts("DOING EVIL REINIT"); - need_reinit = false; - the_surface[0]->Release(); - the_surface[0] = nullptr; - this->TerminateDevice(the_userid); - DWORD one = 1; - this->InitializeDevice(the_userid, &the_allocinfo, &one); - this->GetSurface(the_userid, 0, the_surfaceflags, the_surface); - } - }; - - class fancy_san9 : public IVMRSurfaceAllocatorNotify9 { - public: - IVMRSurfaceAllocatorNotify9* parent; - - CVideoMixingRenderer9* container() { return container_of<&CVideoMixingRenderer9::san_wrapper>(this); } - - DWORD STDMETHODCALLTYPE AddRef() { return parent->AddRef(); } - DWORD STDMETHODCALLTYPE Release() { return parent->Release(); } - - HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void** ppvObject) override - { -//printf("QI(san)=%s\n", guid_to_str(riid)); - return parent->QueryInterface(riid, ppvObject); - } - - HRESULT STDMETHODCALLTYPE AdviseSurfaceAllocator(DWORD_PTR dwUserID, IVMRSurfaceAllocator9* lpIVRMSurfaceAllocator) - { - //puts("FSAN9 AdviseSurfaceAllocator"); - container()->surfalloc_wrapper.parent_alloc = lpIVRMSurfaceAllocator; - lpIVRMSurfaceAllocator->QueryInterface(&container()->surfalloc_wrapper.parent_pres); - container()->surfalloc_wrapper.parent_pres->Release(); - return parent->AdviseSurfaceAllocator(dwUserID, &container()->surfalloc_wrapper); - } - HRESULT STDMETHODCALLTYPE AllocateSurfaceHelper(VMR9AllocationInfo* lpAllocInfo, DWORD* lpNumBuffers, IDirect3DSurface9** lplpSurface) - { - //puts("FSAN9 AllocateSurfaceHelper"); -//DWORD gg=*lpNumBuffers; -HRESULT hr=parent->AllocateSurfaceHelper(lpAllocInfo, lpNumBuffers, lplpSurface); -//printf("FSAN9 AllocateSurfaceHelper %lu->%lu %.8lx\n", gg,*lpNumBuffers,hr); -return hr; - return parent->AllocateSurfaceHelper(lpAllocInfo, lpNumBuffers, lplpSurface); - } - HRESULT STDMETHODCALLTYPE ChangeD3DDevice(IDirect3DDevice9* lpD3DDevice, HMONITOR hMonitor) - { - //puts("FSAN9 ChangeD3DDevice"); - container()->surfalloc_wrapper.need_reinit = true; - container()->surfalloc_wrapper.the_d3ddevice = lpD3DDevice; - HRESULT hr = parent->ChangeD3DDevice(lpD3DDevice, hMonitor); - container()->surfalloc_wrapper.evil_reinit(); - return hr; - } - HRESULT STDMETHODCALLTYPE NotifyEvent(LONG EventCode, LONG_PTR Param1, LONG_PTR Param2) - { - //puts("FSAN9 NotifyEvent"); - HRESULT hr = parent->NotifyEvent(EventCode, Param1, Param2); - if (hr == E_NOTIMPL) - hr = container()->graph_mes->Notify(EventCode, Param1, Param2); - return hr; - } - HRESULT STDMETHODCALLTYPE SetD3DDevice(IDirect3DDevice9* lpD3DDevice, HMONITOR hMonitor) - { - //puts("FSAN9 SetD3DDevice"); - container()->surfalloc_wrapper.the_d3ddevice = lpD3DDevice; - return parent->SetD3DDevice(lpD3DDevice, hMonitor); - } - }; - -public: - CComPtr parent; - CComPtr parent_bmp; - IMediaEventSink* graph_mes; - - fancy_surfalloc surfalloc_wrapper; - fancy_san9 san_wrapper; - - CVideoMixingRenderer9() - { -//puts("CREATE VMR9"); - auto* pDllGetClassObject = (decltype(DllGetClassObject)*)GetProcAddress(GetModuleHandle("quartz.dll"), "DllGetClassObject"); - CComPtr fac; - pDllGetClassObject(CLSID_VideoMixingRenderer9, IID_IClassFactory, (void**)&fac); - fac->CreateInstance(nullptr, IID_IBaseFilter, (void**)&parent); - parent->QueryInterface(&parent_bmp); - } - - HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void** ppvObject) override - { -//printf("QI(vmr9)=%s\n", guid_to_str(riid)); - if (riid == __uuidof(IBaseFilter)) - { - *ppvObject = (void*)(IBaseFilter*)this; - this->AddRef(); - return S_OK; - } - if (riid == __uuidof(IVMRSurfaceAllocatorNotify9)) - { - parent->QueryInterface(&san_wrapper.parent); - // don't hold this ref, it's a ref cycle - // I don't think COM objects are allowed to have different refcount for different interfaces, but Windows and Wine both do this - san_wrapper.parent->Release(); - *ppvObject = (void*)(IVMRSurfaceAllocatorNotify9*)&san_wrapper; - san_wrapper.AddRef(); - return S_OK; - } - return parent->QueryInterface(riid, ppvObject); - } - - HRESULT STDMETHODCALLTYPE GetClassID(CLSID* pClassID) override - { return parent->GetClassID(pClassID); } - HRESULT STDMETHODCALLTYPE GetState(DWORD dwMilliSecsTimeout, FILTER_STATE* State) override - { return parent->GetState(dwMilliSecsTimeout, State); } - HRESULT STDMETHODCALLTYPE GetSyncSource(IReferenceClock** pClock) override - { return parent->GetSyncSource(pClock); } - HRESULT STDMETHODCALLTYPE Pause() override - { return parent->Pause(); } - HRESULT STDMETHODCALLTYPE Run(REFERENCE_TIME tStart) override - { return parent->Run(tStart); } - HRESULT STDMETHODCALLTYPE SetSyncSource(IReferenceClock* pClock) override - { return parent->SetSyncSource(pClock); } - HRESULT STDMETHODCALLTYPE Stop() override - { return parent->Stop(); } - HRESULT STDMETHODCALLTYPE EnumPins(IEnumPins** ppEnum) override - { return parent->EnumPins(ppEnum); } - HRESULT STDMETHODCALLTYPE FindPin(LPCWSTR Id, IPin** ppPin) override - { return parent->FindPin(Id, ppPin); } - HRESULT STDMETHODCALLTYPE JoinFilterGraph(IFilterGraph* pGraph, LPCWSTR pName) override - { - graph_mes = nullptr; - if (pGraph) + template + class my_Pin final : public IPin, public IMemInputPin { + funny_BaseFilter* parent() { - pGraph->QueryInterface(&graph_mes); - graph_mes->Release(); // don't hold a reference to the graph, that's a ref cycle + if constexpr (out) + return container_of<&funny_BaseFilter::pin_out>(this); + else + return container_of<&funny_BaseFilter::pin_in>(this); } - return parent->JoinFilterGraph(pGraph, pName); - } - HRESULT STDMETHODCALLTYPE QueryFilterInfo(FILTER_INFO* pInfo) override - { return parent->QueryFilterInfo(pInfo); } - HRESULT STDMETHODCALLTYPE QueryVendorInfo(LPWSTR* pVendorInfo) override - { return parent->QueryVendorInfo(pVendorInfo); } -}; - - - -class fancy_WMSyncReader : public com_base { -public: - CComPtr parent; - CComPtr parent_prof; - - static const constexpr GUID expected_magic = { 0xe23d171f, 0x3df0, 0x4a57, 0x8d, 0xb5, 0x1b, 0xc3, 0x4c, 0xca, 0x97, 0x7a }; - struct sneaky_information { - fancy_WMSyncReader* self; - WORD streamnumber; - GUID real_subtype; - GUID magic; - }; - - HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void** ppvObject) override - { - if (riid == IID_IWMProfile) + public: + ULONG STDMETHODCALLTYPE AddRef() override { - this->AddRef(); - *ppvObject = (void*)(IWMProfile*)this; - return parent->QueryInterface(&parent_prof); + return parent()->AddRef(); } - return parent->QueryInterface(riid, ppvObject); - } - - HRESULT STDMETHODCALLTYPE Close() override - { return parent->Close(); } - HRESULT STDMETHODCALLTYPE GetMaxOutputSampleSize(DWORD dwOutput, DWORD* pcbMax) override - { return parent->GetMaxOutputSampleSize(dwOutput, pcbMax); } - HRESULT STDMETHODCALLTYPE GetMaxStreamSampleSize(WORD wStream, DWORD* pcbMax) override - { return parent->GetMaxStreamSampleSize(wStream, pcbMax); } - HRESULT STDMETHODCALLTYPE GetNextSample(WORD wStreamNum, INSSBuffer** ppSample, QWORD* pcnsSampleTime, QWORD* pcnsDuration, - DWORD* pdwFlags, DWORD* pdwOutputNum, WORD* pwStreamNum) override - { - //printf("GETNEXTSAMPLE BEGIN %u\n", wStreamNum); - HRESULT hr = parent->GetNextSample(wStreamNum, ppSample, pcnsSampleTime, pcnsDuration, pdwFlags, pdwOutputNum, pwStreamNum); - //printf("GETNEXTSAMPLE END %.8lx\n", hr); - if (hr == VFW_E_NOT_COMMITTED) + ULONG STDMETHODCALLTYPE Release() override { - // this seems to happen while the filter graph is flushing during a seek - // haven't investigated it very closely - return NS_E_NO_MORE_SAMPLES; + return parent()->Release(); } - return hr; - } - HRESULT STDMETHODCALLTYPE GetOutputCount(DWORD* pcOutputs) override - { return parent->GetOutputCount(pcOutputs); } - HRESULT STDMETHODCALLTYPE GetOutputFormat(DWORD dwOutputNum, DWORD dwFormatNum, IWMOutputMediaProps** ppProps) override - { return parent->GetOutputFormat(dwOutputNum, dwFormatNum, ppProps); } - HRESULT STDMETHODCALLTYPE GetOutputFormatCount(DWORD dwOutputNum, DWORD* pcFormats) override - { return parent->GetOutputFormatCount(dwOutputNum, pcFormats); } - HRESULT STDMETHODCALLTYPE GetOutputNumberForStream(WORD wStreamNum, DWORD* pdwOutputNum) override - { return parent->GetOutputNumberForStream(wStreamNum, pdwOutputNum); } - HRESULT STDMETHODCALLTYPE GetOutputProps(DWORD dwOutputNum, IWMOutputMediaProps** ppOutput) override - { return parent->GetOutputProps(dwOutputNum, ppOutput); } - HRESULT STDMETHODCALLTYPE GetOutputSetting(DWORD dwOutputNum, LPCWSTR pszName, - WMT_ATTR_DATATYPE* pType, BYTE* pValue, WORD* pcbLength) override - { return parent->GetOutputSetting(dwOutputNum, pszName, pType, pValue, pcbLength); } - HRESULT STDMETHODCALLTYPE GetReadStreamSamples(WORD wStreamNum, BOOL* pfCompressed) override - { return parent->GetReadStreamSamples(wStreamNum, pfCompressed); } - HRESULT STDMETHODCALLTYPE GetStreamNumberForOutput(DWORD dwOutputNum, WORD* pwStreamNum) override - { return parent->GetStreamNumberForOutput(dwOutputNum, pwStreamNum); } - HRESULT STDMETHODCALLTYPE GetStreamSelected(WORD wStreamNum, WMT_STREAM_SELECTION* pSelection) override - { return parent->GetStreamSelected(wStreamNum, pSelection); } - HRESULT STDMETHODCALLTYPE Open(const WCHAR* pwszFilename) override - { return parent->Open(pwszFilename); } - HRESULT STDMETHODCALLTYPE OpenStream(IStream* pStream) override - { return parent->OpenStream(pStream); } - HRESULT STDMETHODCALLTYPE SetOutputProps(DWORD dwOutputNum, IWMOutputMediaProps* pOutput) override - { return parent->SetOutputProps(dwOutputNum, pOutput); } - HRESULT STDMETHODCALLTYPE SetOutputSetting(DWORD dwOutputNum, LPCWSTR pszName, - WMT_ATTR_DATATYPE Type, const BYTE* pValue, WORD cbLength) override - { return parent->SetOutputSetting(dwOutputNum, pszName, Type, pValue, cbLength); } - HRESULT STDMETHODCALLTYPE SetRange(QWORD cnsStartTime, LONGLONG cnsDuration) override - { return parent->SetRange(cnsStartTime, cnsDuration); } - HRESULT STDMETHODCALLTYPE SetRangeByFrame(WORD wStreamNum, QWORD qwFrameNumber, LONGLONG cFramesToRead) override - { return parent->SetRangeByFrame(wStreamNum, qwFrameNumber, cFramesToRead); } - HRESULT STDMETHODCALLTYPE SetReadStreamSamples(WORD wStreamNum, BOOL fCompressed) override - { return parent->SetReadStreamSamples(wStreamNum, fCompressed); } - HRESULT STDMETHODCALLTYPE SetStreamsSelected(WORD cStreamCount, WORD* pwStreamNumbers, WMT_STREAM_SELECTION* pSelections) override - { return parent->SetStreamsSelected(cStreamCount, pwStreamNumbers, pSelections); } - - class fancy_WMStreamConfig : public com_base { - public: - fancy_WMSyncReader* owner; - - CComPtr parent; - CComPtr parent_props; HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void** ppvObject) override { - if (riid == IID_IWMMediaProps) - { - this->AddRef(); - *ppvObject = (void*)(IWMMediaProps*)this; - return parent->QueryInterface(&parent_props); - } - return parent->QueryInterface(riid, ppvObject); + if (riid == __uuidof(IPin)) + *(IPin**)ppvObject = this; + if (riid == __uuidof(IMemInputPin)) + *(IMemInputPin**)ppvObject = this; + else if (riid == __uuidof(IUnknown)) + *(IUnknown**)ppvObject = (IPin*)this; + else + return E_NOINTERFACE; + this->AddRef(); + return S_OK; } - HRESULT STDMETHODCALLTYPE GetBitrate(DWORD* pdwBitrate) override - { return parent->GetBitrate(pdwBitrate); } - HRESULT STDMETHODCALLTYPE GetBufferWindow(DWORD* pmsBufferWindow) override - { return parent->GetBufferWindow(pmsBufferWindow); } - HRESULT STDMETHODCALLTYPE GetConnectionName(WCHAR* pwszInputName, WORD* pcchInputName) override - { return parent->GetConnectionName(pwszInputName, pcchInputName); } - HRESULT STDMETHODCALLTYPE GetStreamName(WCHAR* pwszStreamName, WORD* pcchStreamName) override - { return parent->GetStreamName(pwszStreamName, pcchStreamName); } - HRESULT STDMETHODCALLTYPE GetStreamNumber(WORD* pwStreamNum) override - { return parent->GetStreamNumber(pwStreamNum); } - HRESULT STDMETHODCALLTYPE GetStreamType(GUID* pguidStreamType) override - { return parent->GetStreamType(pguidStreamType); } - HRESULT STDMETHODCALLTYPE SetBitrate(DWORD pdwBitrate) override - { return parent->SetBitrate(pdwBitrate); } - HRESULT STDMETHODCALLTYPE SetBufferWindow(DWORD msBufferWindow) override - { return parent->SetBufferWindow(msBufferWindow); } - HRESULT STDMETHODCALLTYPE SetConnectionName(LPCWSTR pwszInputName) override - { return parent->SetConnectionName(pwszInputName); } - HRESULT STDMETHODCALLTYPE SetStreamName(LPCWSTR pwszStreamName) override - { return parent->SetStreamName(pwszStreamName); } - HRESULT STDMETHODCALLTYPE SetStreamNumber(WORD wStreamNum) override - { return parent->SetStreamNumber(wStreamNum); } - - HRESULT STDMETHODCALLTYPE GetMediaType(WM_MEDIA_TYPE* pType, DWORD* pcbType) override + HRESULT STDMETHODCALLTYPE Connect(IPin* pReceivePin, const AM_MEDIA_TYPE* pmt) override + { return S_OK; } // out pin is already connected + HRESULT STDMETHODCALLTYPE ReceiveConnection(IPin* pConnector, const AM_MEDIA_TYPE* pmt) override { - HRESULT hr = parent_props->GetMediaType(pType, pcbType); - // subtype must be one of - /* - if( type == WMMEDIASUBTYPE_MP43 || type == WMMEDIASUBTYPE_MP4S || type == WMMEDIASUBTYPE_MPEG2_VIDEO || - type == WMMEDIASUBTYPE_MSS1 || type == WMMEDIASUBTYPE_MSS2 || type == WMMEDIASUBTYPE_WMVP || - type == WMMEDIASUBTYPE_WMAudio_Lossless || type == WMMEDIASUBTYPE_WMAudioV2 || type == WMMEDIASUBTYPE_WMAudioV7 || - type == WMMEDIASUBTYPE_WMAudioV8 || type == WMMEDIASUBTYPE_WMAudioV9 || type == WMMEDIASUBTYPE_WMSP1 || - type == WMMEDIASUBTYPE_WMV1 || type == WMMEDIASUBTYPE_WMV2 || type == WMMEDIASUBTYPE_WMV3 ) - */ - // let's just take the first video and first audio format from the above - - // must also sneak in the real subtype somewhere - *pcbType += sizeof(sneaky_information); - - if (pType) + if (out) + return E_UNEXPECTED; + if (IsEqualGUID(pmt->formattype, FORMAT_VideoInfo)) // it's enough to call ReceiveConnection once, but too many doesn't hurt { - uint8_t* after_end = (uint8_t*)pType->pbFormat + pType->cbFormat; - sneaky_information sneak = { owner, 0, pType->subtype, expected_magic }; - parent->GetStreamNumber(&sneak.streamnumber); - memcpy(after_end, &sneak, sizeof(sneaky_information)); - pType->cbFormat += sizeof(sneaky_information); - - if (pType->majortype == MEDIATYPE_Video) - pType->subtype = WMMEDIASUBTYPE_MP43; // originally MEDIASUBTYPE_RGB24 - if (pType->majortype == MEDIATYPE_Audio) - pType->subtype = WMMEDIASUBTYPE_WMAudio_Lossless; // originally WMMEDIASUBTYPE_PCM + MPEG1VIDEOINFO fake_vi = { *(VIDEOINFOHEADER*)pmt->pbFormat }; + AM_MEDIA_TYPE fake_mt = *pmt; + fake_mt.formattype = FORMAT_MPEGVideo; + fake_mt.cbFormat = sizeof(MPEG1VIDEOINFO); + fake_mt.pbFormat = (uint8_t*)&fake_vi; + HRESULT hr = parent()->the_real_ones_pin->ReceiveConnection(&parent()->pin_out, &fake_mt); + if (SUCCEEDED(hr)) // won't happen, but... + { + pConnector->QueryInterface(&parent()->prev_pin); + return hr; + } } + HRESULT hr = parent()->the_real_ones_pin->ReceiveConnection(&parent()->pin_out, pmt); + if (SUCCEEDED(hr)) + pConnector->QueryInterface(&parent()->prev_pin); return hr; } - HRESULT STDMETHODCALLTYPE GetType(GUID* pguidType) override - { return parent_props->GetType(pguidType); } - HRESULT STDMETHODCALLTYPE SetMediaType(WM_MEDIA_TYPE* pType) override - { return parent_props->SetMediaType(pType); } - }; - - HRESULT STDMETHODCALLTYPE AddMutualExclusion(IWMMutualExclusion* pME) override - { return parent_prof->AddMutualExclusion(pME); } - HRESULT STDMETHODCALLTYPE AddStream(IWMStreamConfig* pConfig) override - { return parent_prof->AddStream(pConfig); } - HRESULT STDMETHODCALLTYPE CreateNewMutualExclusion(IWMMutualExclusion** ppME) override - { return parent_prof->CreateNewMutualExclusion(ppME); } - HRESULT STDMETHODCALLTYPE CreateNewStream(REFGUID guidStreamType, IWMStreamConfig** ppConfig) override - { return parent_prof->CreateNewStream(guidStreamType, ppConfig); } - HRESULT STDMETHODCALLTYPE GetDescription(WCHAR* pwszDescription, DWORD* pcchDescription) override - { return parent_prof->GetDescription(pwszDescription, pcchDescription); } - HRESULT STDMETHODCALLTYPE GetMutualExclusion(DWORD dwMEIndex, IWMMutualExclusion** ppME) override - { return parent_prof->GetMutualExclusion(dwMEIndex, ppME); } - HRESULT STDMETHODCALLTYPE GetMutualExclusionCount(DWORD* pcMutexs) override - { return parent_prof->GetMutualExclusionCount(pcMutexs); } - HRESULT STDMETHODCALLTYPE GetName(WCHAR* pwszName, DWORD* pcchName) override - { return parent_prof->GetName(pwszName, pcchName); } - HRESULT STDMETHODCALLTYPE GetStream(DWORD dwStreamIndex, IWMStreamConfig** ppConfig) override - { return parent_prof->GetStream(dwStreamIndex, ppConfig); } - HRESULT STDMETHODCALLTYPE GetStreamByNumber(WORD wStreamNumber, IWMStreamConfig** ppIStream) override - { - fancy_WMStreamConfig* ret = new fancy_WMStreamConfig(); - ret->owner = this; - *ppIStream = ret; - return parent_prof->GetStreamByNumber(wStreamNumber, &ret->parent); - } - HRESULT STDMETHODCALLTYPE GetStreamCount(DWORD* pcStreams) override - { return parent_prof->GetStreamCount(pcStreams); } - HRESULT STDMETHODCALLTYPE GetVersion(WMT_VERSION* pdwVersion) override - { return parent_prof->GetVersion(pdwVersion); } - HRESULT STDMETHODCALLTYPE ReconfigStream(IWMStreamConfig* pConfig) override - { return parent_prof->ReconfigStream(pConfig); } - HRESULT STDMETHODCALLTYPE RemoveMutualExclusion(IWMMutualExclusion* pME) override - { return parent_prof->RemoveMutualExclusion(pME); } - HRESULT STDMETHODCALLTYPE RemoveStream(IWMStreamConfig* pConfig) override - { return parent_prof->RemoveStream(pConfig); } - HRESULT STDMETHODCALLTYPE RemoveStreamByNumber(WORD wStreamNum) override - { return parent_prof->RemoveStreamByNumber(wStreamNum); } - HRESULT STDMETHODCALLTYPE SetDescription(const WCHAR* pwszDescription) override - { return parent_prof->SetDescription(pwszDescription); } - HRESULT STDMETHODCALLTYPE SetName(const WCHAR* pwszName) override - { return parent_prof->SetName(pwszName); } - - void set_subtype(DWORD dwOutputNum, GUID subtype) - { - DWORD idx; - parent->GetOutputNumberForStream(dwOutputNum, &idx); - CComPtr prop; - parent->GetOutputProps(idx, &prop); - char buf[256]; // needs 160 (or 90 for the audio channel), let's give it a little more - WM_MEDIA_TYPE* mt = (WM_MEDIA_TYPE*)buf; - DWORD n = sizeof(buf); - prop->GetMediaType(mt, &n); - if (mt->majortype == MEDIATYPE_Video && mt->subtype != subtype) + HRESULT STDMETHODCALLTYPE Disconnect() override + { return parent()->the_real_ones_pin->Disconnect(); } + HRESULT STDMETHODCALLTYPE ConnectedTo(IPin** pPin) override { - mt->subtype = subtype; - prop->SetMediaType(mt); - parent->SetOutputProps(idx, prop); + IPin* ret = out ? parent()->the_real_ones_pin : parent()->prev_pin; + if (!ret) + return VFW_E_NOT_CONNECTED; + ret->AddRef(); + *pPin = ret; + return S_OK; } - } -}; - -class fake_DMOObject final : public IMediaObject { -public: - class own_iunknown_t : public IUnknown { - public: - ULONG refcount = 1; - fake_DMOObject* parent() { return container_of<&fake_DMOObject::own_iunknown>(this); } - - HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void** ppvObject) override + HRESULT STDMETHODCALLTYPE ConnectionMediaType(AM_MEDIA_TYPE* pmt) override + { return E_OUTOFMEMORY; } + HRESULT STDMETHODCALLTYPE QueryPinInfo(PIN_INFO* pInfo) override { - return parent()->NonDelegatingQueryInterface(riid, ppvObject); + pInfo->pFilter = parent(); + parent()->AddRef(); + this->QueryDirection(&pInfo->dir); + wcscpy(pInfo->achName, out ? L"OUT" : L"IN"); + return S_OK; } - + HRESULT STDMETHODCALLTYPE QueryDirection(PIN_DIRECTION* pPinDir) override + { *pPinDir = out ? PINDIR_OUTPUT : PINDIR_INPUT; return S_OK; } + HRESULT STDMETHODCALLTYPE QueryId(LPWSTR* Id) override + { return E_OUTOFMEMORY; } + HRESULT STDMETHODCALLTYPE QueryAccept(const AM_MEDIA_TYPE* pmt) override + { return parent()->the_real_ones_pin->QueryAccept(pmt); } + HRESULT STDMETHODCALLTYPE EnumMediaTypes(IEnumMediaTypes** ppEnum) override + { return parent()->the_real_ones_pin->EnumMediaTypes(ppEnum); } + HRESULT STDMETHODCALLTYPE QueryInternalConnections(IPin** apPin, ULONG* nPin) override + { return E_OUTOFMEMORY; } + HRESULT STDMETHODCALLTYPE EndOfStream() override + { return parent()->the_real_ones_pin->EndOfStream(); } + HRESULT STDMETHODCALLTYPE BeginFlush() override + { return S_OK; } + HRESULT STDMETHODCALLTYPE EndFlush() override + { return S_OK; } + HRESULT STDMETHODCALLTYPE NewSegment(REFERENCE_TIME tStart, REFERENCE_TIME tStop, double dRate) override + { return S_OK; } + + // these will do something crazy if called on the out pin, but nobody will try that so who cares + HRESULT STDMETHODCALLTYPE GetAllocator(IMemAllocator** ppAllocator) override + { return parent()->the_real_ones_meminput_pin->GetAllocator(ppAllocator); } + HRESULT STDMETHODCALLTYPE NotifyAllocator(IMemAllocator* pAllocator, BOOL bReadOnly) override + { return parent()->the_real_ones_meminput_pin->NotifyAllocator(pAllocator, bReadOnly); } + HRESULT STDMETHODCALLTYPE GetAllocatorRequirements(ALLOCATOR_PROPERTIES* pProps) override + { return parent()->the_real_ones_meminput_pin->GetAllocatorRequirements(pProps); } + HRESULT STDMETHODCALLTYPE Receive(IMediaSample* pSample) override + { return parent()->the_real_ones_meminput_pin->Receive(pSample); } + HRESULT STDMETHODCALLTYPE ReceiveMultiple(IMediaSample** pSamples, LONG nSamples, LONG* nSamplesProcessed) override + { return parent()->the_real_ones_meminput_pin->ReceiveMultiple(pSamples, nSamples, nSamplesProcessed); } + HRESULT STDMETHODCALLTYPE ReceiveCanBlock() override + { return parent()->the_real_ones_meminput_pin->ReceiveCanBlock(); } + }; + + my_Pin pin_in; + my_Pin pin_out; + + class my_EnumPins final : public IEnumPins { + ULONG refcount = 1; + public: ULONG STDMETHODCALLTYPE AddRef() override { return ++refcount; } ULONG STDMETHODCALLTYPE Release() override { - uint32_t new_refcount = --refcount; + ULONG new_refcount = --refcount; if (!new_refcount) - delete parent(); + delete this; return new_refcount; } - }; - own_iunknown_t own_iunknown; - IUnknown* parent; - - AM_MEDIA_TYPE mt; - BYTE mt_buf[128]; - bool convert_rgb24_rgb32 = false; - - CComPtr my_buffer; - DWORD buf_dwFlags; - REFERENCE_TIME buf_rtTimestamp; - REFERENCE_TIME buf_rtTimelength; - - fancy_WMSyncReader* predecessor; - WORD streamnumber; - - fake_DMOObject(IUnknown* outer) : parent(outer) {} - - HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void** ppvObject) override - { return parent->QueryInterface(riid, ppvObject); } - ULONG STDMETHODCALLTYPE AddRef() override - { return parent->AddRef(); } - ULONG STDMETHODCALLTYPE Release() override - { return parent->Release(); } - - HRESULT NonDelegatingQueryInterface(REFIID riid, void** ppvObject) - { - if (riid == IID_IMediaObject) + + CComPtr parent; + size_t pos = 0; + + my_EnumPins(funny_BaseFilter* parent) { parent->AddRef(); - *ppvObject = (void*)(IMediaObject*)this; + this->parent.p = parent; + } + + HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void** ppvObject) override + { + if (riid == __uuidof(IEnumPins)) + *(IEnumPins**)ppvObject = this; + else if (riid == __uuidof(IUnknown)) + *(IUnknown**)ppvObject = this; + else + return E_NOINTERFACE; + this->AddRef(); return S_OK; } - return E_NOINTERFACE; - } + + HRESULT STDMETHODCALLTYPE Next(ULONG cPins, IPin** ppPins, ULONG* pcFetched) override + { + if (cPins != 1) + return E_UNEXPECTED; + IPin* ret; + if (pos == 0) + ret = &parent->pin_in; + else if (pos == 1) + ret = &parent->pin_out; + else + return S_FALSE; + *ppPins = ret; + ret->AddRef(); + pos++; + if (pcFetched) *pcFetched = 1; + return S_OK; + } + HRESULT STDMETHODCALLTYPE Skip(ULONG cPins) override + { return E_OUTOFMEMORY; } + HRESULT STDMETHODCALLTYPE Reset() override + { return E_OUTOFMEMORY; } + HRESULT STDMETHODCALLTYPE Clone(IEnumPins** ppEnum) override + { return E_OUTOFMEMORY; } + }; - HRESULT STDMETHODCALLTYPE AllocateStreamingResources() override + + HRESULT STDMETHODCALLTYPE GetClassID(CLSID* pClassID) override + { return the_real_one->GetClassID(pClassID); } + HRESULT STDMETHODCALLTYPE Stop() override { return S_OK; } - HRESULT STDMETHODCALLTYPE Discontinuity(DWORD dwInputStreamIndex) override + HRESULT STDMETHODCALLTYPE Pause() override { return S_OK; } - HRESULT STDMETHODCALLTYPE Flush() override - { - my_buffer = nullptr; - return S_OK; - } - HRESULT STDMETHODCALLTYPE FreeStreamingResources() override + HRESULT STDMETHODCALLTYPE Run(REFERENCE_TIME tStart) override { return S_OK; } - HRESULT STDMETHODCALLTYPE GetInputCurrentType(DWORD dwInputStreamIndex, DMO_MEDIA_TYPE* pmt) override - { return E_OUTOFMEMORY; } - HRESULT STDMETHODCALLTYPE GetInputMaxLatency(DWORD dwInputStreamIndex, REFERENCE_TIME* prtMaxLatency) override - { return E_OUTOFMEMORY; } - HRESULT STDMETHODCALLTYPE GetInputSizeInfo(DWORD dwInputStreamIndex, DWORD* pcbSize, - DWORD* pcbMaxLookahead, DWORD* pcbAlignment) override - { return E_OUTOFMEMORY; } - HRESULT STDMETHODCALLTYPE GetInputStatus(DWORD dwInputStreamIndex, DWORD* dwFlags) override - { return E_OUTOFMEMORY; } - HRESULT STDMETHODCALLTYPE GetInputStreamInfo(DWORD dwInputStreamIndex, DWORD* pdwFlags) override - { return E_OUTOFMEMORY; } - HRESULT STDMETHODCALLTYPE GetInputType(DWORD dwInputStreamIndex, DWORD dwTypeIndex, DMO_MEDIA_TYPE* pmt) override - { return E_OUTOFMEMORY; } - HRESULT STDMETHODCALLTYPE GetOutputCurrentType(DWORD dwOutputStreamIndex, DMO_MEDIA_TYPE* pmt) override - { return E_OUTOFMEMORY; } - HRESULT STDMETHODCALLTYPE GetOutputSizeInfo(DWORD dwOutputStreamIndex, DWORD* pcbSize, DWORD* pcbAlignment) override + HRESULT STDMETHODCALLTYPE GetState(DWORD dwMilliSecsTimeout, FILTER_STATE* State) override + { return the_real_one->GetState(dwMilliSecsTimeout, State); } + HRESULT STDMETHODCALLTYPE SetSyncSource(IReferenceClock* pClock) override + { return S_OK; } + HRESULT STDMETHODCALLTYPE GetSyncSource(IReferenceClock** pClock) override + { return the_real_one->GetSyncSource(pClock); } + HRESULT STDMETHODCALLTYPE EnumPins(IEnumPins** ppEnum) override { - if (dwOutputStreamIndex != 0) - return DMO_E_INVALIDSTREAMINDEX; - if (mt.formattype == FORMAT_VideoInfo || mt.formattype == FORMAT_VideoInfo2) - { - VIDEOINFOHEADER* vi = (VIDEOINFOHEADER*)mt.pbFormat; - *pcbSize = vi->bmiHeader.biBitCount * vi->bmiHeader.biWidth * vi->bmiHeader.biHeight / 8; - *pcbAlignment = 1; // Kirikiri TBufferRendererAllocator::SetProperties accepts only 1, anything else returns VFW_E_BADALIGN - } - else if (mt.formattype == FORMAT_WaveFormatEx) - { - WAVEFORMATEX* wfx = (WAVEFORMATEX*)mt.pbFormat; - *pcbSize = wfx->nAvgBytesPerSec * 4; // usual buffer size is 1 second, but let's give it some more - *pcbAlignment = 8; // float32 * two channels, should be enough - } - else - { - *pcbSize = 1024*1024; // just pick something - *pcbAlignment = 8; - } -//printf("GETOUTPUTSIZEINFO %lu -> %lu\n", dwOutputStreamIndex, *pcbSize); + *ppEnum = new my_EnumPins(this); return S_OK; } - HRESULT STDMETHODCALLTYPE GetOutputStreamInfo(DWORD dwOutputStreamIndex, DWORD* pdwFlags) override + HRESULT STDMETHODCALLTYPE FindPin(LPCWSTR Id, IPin** ppPin) override { return E_OUTOFMEMORY; } - HRESULT STDMETHODCALLTYPE GetOutputType(DWORD dwOutputStreamIndex, DWORD dwTypeIndex, DMO_MEDIA_TYPE* pmt) override + HRESULT STDMETHODCALLTYPE QueryFilterInfo(FILTER_INFO* pInfo) override { -//printf("get output type %lu\n", dwTypeIndex); - if (dwOutputStreamIndex != 0) - return DMO_E_INVALIDSTREAMINDEX; - if (dwTypeIndex >= 3 || (dwTypeIndex == 1 && mt.majortype != MEDIATYPE_Video)) - return DMO_E_NO_MORE_ITEMS; - CopyMediaType((AM_MEDIA_TYPE*)pmt, &mt); - if (pmt->majortype == MEDIATYPE_Video) - { - if (dwTypeIndex == 0) - { - pmt->subtype = MEDIASUBTYPE_YV12; - CMpegVideoCodec::update_mediatype_from_subtype((AM_MEDIA_TYPE*)pmt); - } - if (dwTypeIndex == 1) - { - pmt->subtype = MEDIASUBTYPE_RGB24; - CMpegVideoCodec::update_mediatype_from_subtype((AM_MEDIA_TYPE*)pmt); - } - if (dwTypeIndex == 2) - { - pmt->subtype = MEDIASUBTYPE_RGB32; - CMpegVideoCodec::update_mediatype_from_subtype((AM_MEDIA_TYPE*)pmt); - } - } + the_real_one->QueryFilterInfo(pInfo); + wcscpy(pInfo->achName, L"FAKE_TEXTURERENDERER_BURIKO"); // this line is just a debug aid return S_OK; } - HRESULT STDMETHODCALLTYPE GetStreamCount(DWORD* pcInputStreams, DWORD* pcOutputStreams) override + HRESULT STDMETHODCALLTYPE JoinFilterGraph(IFilterGraph* pGraph, LPCWSTR pName) override + { return S_OK; } + HRESULT STDMETHODCALLTYPE QueryVendorInfo(LPWSTR* pVendorInfo) override + { return the_real_one->QueryVendorInfo(pVendorInfo); } +}; + +class wrap_FilterGraph final : public IGraphBuilder { + ULONG refcount = 1; +public: + ULONG STDMETHODCALLTYPE AddRef() override { - *pcInputStreams = 1; - *pcOutputStreams = 1; - return S_OK; + return ++refcount; } - HRESULT STDMETHODCALLTYPE Lock(LONG bLock) override - { return E_OUTOFMEMORY; } - HRESULT STDMETHODCALLTYPE ProcessInput(DWORD dwInputStreamIndex, IMediaBuffer* pBuffer, DWORD dwFlags, - REFERENCE_TIME rtTimestamp, REFERENCE_TIME rtTimelength) override + ULONG STDMETHODCALLTYPE Release() override { -//printf("ProcessInput %p\n", this); - if (my_buffer) - return E_OUTOFMEMORY; - my_buffer = pBuffer; - buf_dwFlags = dwFlags; - buf_rtTimestamp = rtTimestamp; - buf_rtTimelength = rtTimelength; - return S_OK; + ULONG new_refcount = --refcount; + if (!new_refcount) + delete this; + return new_refcount; } - HRESULT STDMETHODCALLTYPE ProcessOutput(DWORD dwFlags, DWORD cOutputBufferCount, - DMO_OUTPUT_DATA_BUFFER* pOutputBuffers, DWORD* pdwStatus) override + + CComPtr the_real_one; + + wrap_FilterGraph(IUnknown* outer) { -//printf("ProcessOutput %p\n", this); - if (!my_buffer) - return S_FALSE; - if (cOutputBufferCount != 1) - return E_FAIL; - - BYTE* bytes; - DWORD len; - my_buffer->GetBufferAndLength(&bytes, &len); - if (convert_rgb24_rgb32) - { - pOutputBuffers[0].pBuffer->SetLength(len*4/3); - BYTE* bytes2; - DWORD len2; - pOutputBuffers[0].pBuffer->GetBufferAndLength(&bytes2, &len2); - convert_rgb24_to_rgb32(bytes2, bytes, len/3); - } - else - { - pOutputBuffers[0].pBuffer->SetLength(len); - BYTE* bytes2; - DWORD len2; - pOutputBuffers[0].pBuffer->GetBufferAndLength(&bytes2, &len2); - memcpy(bytes2, bytes, len2); - } - pOutputBuffers[0].dwStatus = buf_dwFlags & ~DMO_OUTPUT_DATA_BUFFERF_INCOMPLETE; - pOutputBuffers[0].rtTimestamp = buf_rtTimestamp; - pOutputBuffers[0].rtTimelength = buf_rtTimelength; - my_buffer = nullptr; - return S_OK; + outer->QueryInterface(&the_real_one); } - HRESULT STDMETHODCALLTYPE SetInputMaxLatency(DWORD dwInputStreamIndex, REFERENCE_TIME rtMaxLatency) override - { return E_OUTOFMEMORY; } - HRESULT STDMETHODCALLTYPE SetInputType(DWORD dwInputStreamIndex, const DMO_MEDIA_TYPE* pmt, DWORD dwFlags) override + + HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void** ppvObject) override { - if (dwInputStreamIndex != 0) - return DMO_E_INVALIDSTREAMINDEX; - if (!pmt && (dwFlags & DMO_SET_TYPEF_CLEAR)) - return S_OK; - fancy_WMSyncReader::sneaky_information sneak; - uint8_t* after_end = (uint8_t*)pmt->pbFormat + pmt->cbFormat - sizeof(sneak); - memcpy(&sneak, after_end, sizeof(sneak)); - if (sneak.magic != fancy_WMSyncReader::expected_magic) - return DMO_E_TYPE_NOT_ACCEPTED; - if (!(dwFlags & DMO_SET_TYPEF_TEST_ONLY)) - { - mt = *(AM_MEDIA_TYPE*)pmt; - mt.subtype = sneak.real_subtype; - mt.cbFormat -= sizeof(sneak); - memcpy(mt_buf, mt.pbFormat, mt.cbFormat); - mt.pbFormat = mt_buf; - - predecessor = sneak.self; - streamnumber = sneak.streamnumber; - } + if (riid == __uuidof(IGraphBuilder)) + *(IGraphBuilder**)ppvObject = this; + else if (riid == __uuidof(IUnknown)) + *(IUnknown**)ppvObject = this; + else + return the_real_one->QueryInterface(riid, ppvObject); + this->AddRef(); return S_OK; } - HRESULT STDMETHODCALLTYPE SetOutputType(DWORD dwOutputStreamIndex, const DMO_MEDIA_TYPE* pmt, DWORD dwFlags) override + + HRESULT STDMETHODCALLTYPE AddFilter(IBaseFilter* pFilter, LPCWSTR pName) override { - if (dwOutputStreamIndex != 0) - return DMO_E_INVALIDSTREAMINDEX; - if (dwFlags & DMO_SET_TYPEF_CLEAR) - return S_OK; -//if (pmt->subtype == MEDIASUBTYPE_YV12) - //printf("set subtype YV12 fl %lu\n", dwFlags); -//else if (pmt->subtype == MEDIASUBTYPE_RGB24) - //printf("set subtype RGB24 fl %lu\n", dwFlags); -//else if (pmt->subtype == MEDIASUBTYPE_RGB32) - //printf("set subtype RGB32 fl %lu\n", dwFlags); -//else if (pmt->subtype == WMMEDIASUBTYPE_PCM) - //printf("set subtype PCM fl %lu\n", dwFlags); -//else - //printf("set subtype %s fl %lu\n",guid_to_str(pmt->subtype), dwFlags); - if (pmt->subtype != mt.subtype && !(dwFlags & DMO_SET_TYPEF_TEST_ONLY)) + HRESULT hr = the_real_one->AddFilter(pFilter, pName); + // must install myself AFTER the real one, Wine's FilterGraph2_Render() tries the last-added one first + if (pName && !wcscmp(pName, L"TEXTURERENDERER_BURIKO")) { - mt.subtype = pmt->subtype; - convert_rgb24_rgb32 = false; - if (mt.subtype == MEDIASUBTYPE_RGB32) - { - convert_rgb24_rgb32 = true; - predecessor->set_subtype(streamnumber, MEDIASUBTYPE_RGB24); - } - else - predecessor->set_subtype(streamnumber, mt.subtype); - CMpegVideoCodec::update_mediatype_from_subtype(&mt); + puts("krkrwine: interjecting in front of TEXTURERENDERER_BURIKO"); + the_real_one->AddFilter(new funny_BaseFilter(pFilter), L"FAKE_TEXTURERENDERER_BURIKO"); } - return S_OK; - } -}; - -template -class ClassFactory : public com_base { -public: - HRESULT STDMETHODCALLTYPE CreateInstance(IUnknown* pUnkOuter, REFIID riid, void** ppvObject) - { -//printf("krkrwine: create %s (iface %s)\n", guid_to_str(clsid), guid_to_str(riid)); - if (pUnkOuter != nullptr) - return CLASS_E_NOAGGREGATION; - return qi_release(new T(), riid, ppvObject); + return hr; } - HRESULT STDMETHODCALLTYPE LockServer(BOOL lock) { return S_OK; } // don't care -}; - -template -class AggregatingClassFactory : public com_base { -public: - HRESULT STDMETHODCALLTYPE CreateInstance(IUnknown* pUnkOuter, REFIID riid, void** ppvObject) - { -//printf("krkrwine: create %s (iface %s)\n", guid_to_str(clsid), guid_to_str(riid)); - if (pUnkOuter == nullptr) - return VFW_E_NEED_OWNER; // wrong if this isn't a VFW object, but who cares, it's an error, good enough - if (riid != IID_IUnknown) - return E_NOINTERFACE; - T* ret = new T(pUnkOuter); - *ppvObject = (void*)(IUnknown*)&ret->own_iunknown; - return S_OK; + HRESULT STDMETHODCALLTYPE RemoveFilter(IBaseFilter* pFilter) override + { return the_real_one->RemoveFilter(pFilter); } + HRESULT STDMETHODCALLTYPE EnumFilters(IEnumFilters** ppEnum) override + { return the_real_one->EnumFilters(ppEnum); } + HRESULT STDMETHODCALLTYPE FindFilterByName(LPCWSTR pName, IBaseFilter** ppFilter) override + { return the_real_one->FindFilterByName(pName, ppFilter); } + HRESULT STDMETHODCALLTYPE ConnectDirect(IPin* ppinOut, IPin* ppinIn, const AM_MEDIA_TYPE* pmt) override + { return the_real_one->ConnectDirect(ppinOut, ppinIn, pmt); } + HRESULT STDMETHODCALLTYPE Reconnect(IPin* ppin) override + { return the_real_one->Reconnect(ppin); } + HRESULT STDMETHODCALLTYPE Disconnect(IPin* ppin) override + { return the_real_one->Disconnect(ppin); } + HRESULT STDMETHODCALLTYPE SetDefaultSyncSource() override + { return the_real_one->SetDefaultSyncSource(); } + HRESULT STDMETHODCALLTYPE Connect(IPin* ppinOut, IPin* ppinIn) override + { return the_real_one->Connect(ppinOut, ppinIn); } + HRESULT STDMETHODCALLTYPE Render(IPin* ppinOut) override + { return the_real_one->Render(ppinOut); } + HRESULT STDMETHODCALLTYPE RenderFile(LPCWSTR lpcwstrFile, LPCWSTR lpcwstrPlayList) override + { + HRESULT hr = the_real_one->RenderFile(lpcwstrFile, lpcwstrPlayList); + if (FAILED(hr) && !*lpcwstrFile) + hr = VFW_E_NOT_FOUND; + return hr; } - HRESULT STDMETHODCALLTYPE LockServer(BOOL lock) { return S_OK; } // don't care + HRESULT STDMETHODCALLTYPE AddSourceFilter(LPCWSTR lpcwstrFileName, LPCWSTR lpcwstrFilterName, IBaseFilter** ppFilter) override + { return the_real_one->AddSourceFilter(lpcwstrFileName, lpcwstrFilterName, ppFilter); } + HRESULT STDMETHODCALLTYPE SetLogFile(DWORD_PTR hFile) override + { return the_real_one->SetLogFile(hFile); } + HRESULT STDMETHODCALLTYPE Abort() override + { return the_real_one->Abort(); } + HRESULT STDMETHODCALLTYPE ShouldOperationContinue() override + { return the_real_one->ShouldOperationContinue(); } }; -typedef HRESULT STDMETHODCALLTYPE (*WMCreateSyncReader_t)(IUnknown* pUnkCert, DWORD dwRights, IWMSyncReader** ppSyncReader); -static WMCreateSyncReader_t orig_WMCreateSyncReader; -static HRESULT STDMETHODCALLTYPE myWMCreateSyncReader(IUnknown* pUnkCert, DWORD dwRights, IWMSyncReader** ppSyncReader) -{ - fancy_WMSyncReader* ret = new fancy_WMSyncReader(); - *ppSyncReader = ret; - //puts("krkrwine: creating sync reader"); - HRESULT hr = orig_WMCreateSyncReader(pUnkCert, dwRights, &ret->parent); - //puts("krkrwine: created sync reader"); - return hr; -} -static FARPROC STDMETHODCALLTYPE myGetProcAddress(HMODULE hModule, LPCSTR lpProcName) -{ - if (!strcmp(lpProcName, "WMCreateSyncReader")) - { - orig_WMCreateSyncReader = (WMCreateSyncReader_t)GetProcAddress(hModule, "WMCreateSyncReader"); - return (FARPROC)myWMCreateSyncReader; - } - return GetProcAddress(hModule, lpProcName); -} +static bool hijack_FilterGraph = false; -static void very_unsafe_init() +static void init() { static bool initialized = false; if (initialized) return; initialized = true; -#if DEBUG >= 1 -setvbuf(stdout, nullptr, _IONBF, 0); puts("krkrwine - hello world"); -#endif - // refuse to operate on Windows - auto wine_get_build_id = (const char*(*)())GetProcAddress(GetModuleHandle("ntdll.dll"), "wine_get_build_id"); - if (!wine_get_build_id) - return; - is_debian_build = (strstr(wine_get_build_id(), "Debian")); - - DWORD ignore; - - // if CLSID_CMpegVideoCodec already exists, don't install my own - CComPtr fac; - if (FAILED(CoGetClassObject(CLSID_CMpegVideoCodec, CLSCTX_INPROC_SERVER, NULL, IID_PPV_ARGS(&fac)))) + auto wine_get_version = (const char*(*)())GetProcAddress(GetModuleHandle("ntdll.dll"), "wine_get_version"); + if (!wine_get_version) { - IClassFactory* fac_mpegsplit = new ClassFactory(); - CoRegisterClassObject(CLSID_MPEG1Splitter, fac_mpegsplit, CLSCTX_INPROC_SERVER, REGCLS_MULTIPLEUSE, &ignore); - - IClassFactory* fac_mpegvideo = new ClassFactory(); - CoRegisterClassObject(CLSID_CMpegVideoCodec, fac_mpegvideo, CLSCTX_INPROC_SERVER, REGCLS_MULTIPLEUSE, &ignore); + puts("krkrwine: Don't install me on Windows!"); + return; } - - // no real way to check if this one needs to be patched, but not needed either, - // it calls the original functions and only intervenes if they misbehave - IClassFactory* fac_vmr9 = new ClassFactory(); - CoRegisterClassObject(CLSID_VideoMixingRenderer9, fac_vmr9, CLSCTX_INPROC_SERVER, REGCLS_MULTIPLEUSE, &ignore); - - // if all I needed was to swap/implement the above objects, I'd put that in install.py - // unfortunately, I also need to hijack WMCreateSyncReader, which is loaded via GetProcAddress - // I need some seriously deep shenanigans to be able to access that - - // _rmovie is just a debug aid - // use LoadLibrary, not GetProcAddress, so my hacks aren't undone if the DLL is unloaded and reloaded - HMODULE mod = LoadLibrary("_rmovie.dll"); - if (!mod) - mod = LoadLibrary("krmovie.dll"); - if (!mod) + int major_ver = strtol(wine_get_version(), nullptr, 10); + if (major_ver <= 8) + { + puts("krkrwine: your Wine is too old; use an older krkrwine or a newer Wine"); return; - - // I can't think of any real way to detect if this one needs to be monkeypatched... - uint8_t* base_addr = (uint8_t*)mod; - IMAGE_DOS_HEADER* head_dos = (IMAGE_DOS_HEADER*)base_addr; - IMAGE_NT_HEADERS* head_nt = (IMAGE_NT_HEADERS*)(base_addr + head_dos->e_lfanew); - IMAGE_DATA_DIRECTORY section_dir = head_nt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT]; - IMAGE_IMPORT_DESCRIPTOR* imports = (IMAGE_IMPORT_DESCRIPTOR*)(base_addr + section_dir.VirtualAddress); - - void* find_function = (void*)GetProcAddress; - //void* find_function = (void*)GetProcAddress(GetModuleHandle("kernel32.dll"), "GetProcAddress"); // in case your compiler sucks - void* replace_function = (void*)myGetProcAddress; - bool found_function = false; - - while (imports->Name) + } + if (major_ver == 9) { - void* * out = (void**)(base_addr + imports->FirstThunk); - while (*out) - { - if (*out == find_function) - { - // can't just *out = replace_function, import table is read only - WriteProcessMemory(GetCurrentProcess(), out, &replace_function, sizeof(replace_function), NULL); - found_function = true; - } - out++; - } - imports++; + // Kirikiri needs AvgTimePerFrame in WMV DMO output + // fixed in 9.4 https://gitlab.winehq.org/wine/wine/-/merge_requests/5240 + // it's nontrivial to check if the hack is still needed, and krkrwine is only intended for Proton, so version check is good enough + puts("krkrwine: patching CLSID_CWMVDecMediaObject"); + DWORD ignore; + IClassFactory* fac_wmv = new AggregatingClassFactory(); + CoRegisterClassObject(CLSID_CWMVDecMediaObject, fac_wmv, CLSCTX_INPROC_SERVER, REGCLS_MULTIPLEUSE, &ignore); } - - if (found_function) + if (major_ver == 9) { - // only register these if the fake WMCreateSyncReader can be injected - IClassFactory* fac_dmo = new AggregatingClassFactory(); - CoRegisterClassObject(CLSID_CWMVDecMediaObject, fac_dmo, CLSCTX_INPROC_SERVER, REGCLS_MULTIPLEUSE, &ignore); - CoRegisterClassObject(CLSID_CWMADecMediaObject, fac_dmo, CLSCTX_INPROC_SERVER, REGCLS_MULTIPLEUSE, &ignore); + // BURIKO demands IGraphBuilder::RenderFile() with empty filename to fail with VFW_E_NOT_FOUND, other errors aren't accepted + // BURIKO also demands that IGraphBuilder::Render() calls IPin::ReceiveConnection() with FORMAT_MPEGVideo or FORMAT_MPEG2Video; + // this connection will be rejected by the pin, but it takes the resolution from there and ignores the one in FORMAT_VideoInfo + // https://bugs.winehq.org/show_bug.cgi?id=56491 + // fixed in (not yet) + puts("krkrwine: patching CLSID_FilterGraph"); // those aren't wine bugs, it's workarounds for BURIKO nonsense + hijack_FilterGraph = true; } + + + + //HMODULE fakelib = LoadLibrary("Z:/home/x2/steam/steamapps/common/MakingLovers/fakelib.dll"); + //printf("krkrwine=%p\n",fakelib); + //auto* pInit = (void(*)())GetProcAddress(fakelib, "init"); + //printf("krkrwine=%p\n",pInit); + //pInit(); } #ifdef __i386__ @@ -2357,10 +825,20 @@ puts("krkrwine - hello world"); EXPORT(HRESULT, DllGetClassObject, (REFCLSID rclsid, REFIID riid, void** ppvObj)) { - very_unsafe_init(); + init(); + (void)dump_graph; + (void)guid_to_str; - // this DllGetClassObject doesn't actually implement anything, it just calls very_unsafe_init then defers to the original - return ((decltype(DllGetClassObject)*)GetProcAddress(LoadLibrary("quartz.dll"), "DllGetClassObject"))(rclsid, riid, ppvObj); + HRESULT hr = ((decltype(DllGetClassObject)*)GetProcAddress(LoadLibrary("quartz.dll"), "DllGetClassObject"))(rclsid, riid, ppvObj); + if (SUCCEEDED(hr) && hijack_FilterGraph) + { + IUnknown* fac = (IUnknown*)*ppvObj; + IUnknown* ret = new WrappingClassFactory(fac); + fac->Release(); + hr = ret->QueryInterface(riid, ppvObj); + ret->Release(); + } + return hr; } EXPORT(HRESULT, DllCanUnloadNow, ()) diff --git a/making.md b/making.md index 6791e6c..d414012 100644 --- a/making.md +++ b/making.md @@ -255,7 +255,7 @@ I don't like heuristics. I'm a thoroughbred pessimist; I keep assuming the worst But in this case, it doesn't seem like I have any choice. -I'll have to enumerate the program's windows, and check if there's exactly one visible. If yes, and this window has visible child windows, that's the target; if none, multiple, or no children, disable this functionality, to minimize the risk of breaking anything else. +I'll have to enumerate the program's windows, and check if there's exactly one visible. If yes, and this window has visible child windows, that's the target; if none, or multiple, disable this functionality, to minimize the risk of breaking anything else. A few glitches later, mostly caused by forgetting that the original PresentImage does more than just submit the swap chain, and it works. @@ -265,7 +265,7 @@ Some of it is because I screwed up the threading. This is easy to fix with some Some of it is due to reference cycles and other refcounting accidents. I don't have good debugging tools for those, but the bad ones (shotgun debugging) worked well enough. (Some filters held references to secondary interfaces on the filter graph - filters are not allowed to reference the graph, that's a reference cycle.) -Some of it a segfault somewhere during shutdown. Or, well, whatever this is a symptom of. +Some of it is a segfault somewhere during shutdown. Or, well, whatever this is a symptom of. ![image](making/segfault.png) @@ -609,7 +609,7 @@ and now everything works. And I found a bug in krkrwine.dll - it never unloads. It even explains why it doesn't break in Wine - because my custom krmovie.dll never unloads the real one. My wrapper gets unloaded and reloaded, but that does nothing. -The easiest workaround is simply LoadLibrary krmovie.dll instead of GetProcAddress, so it too remains loaded. +The easiest workaround is simply LoadLibrary krmovie.dll instead of GetModuleHandle, then leak the reference, so it too remains loaded forever. Now, let's see if that was enough... will this VN finally run correctly? @@ -626,9 +626,9 @@ Checking the Kirikiri source code reveals ...that suspicious-looking ThrowDShowE ...nope, that just tells me -致命的なエラーが発生しました。 -ファイル : custom.ks 行 : 93 -タグ : 不明 ( ← エラーの発生した前後のタグを示している場合もあります ) +致命的なエラーが発生しました。 +ファイル : custom.ks 行 : 93 +タグ : 不明 ( ← エラーの発生した前後のタグを示している場合もあります ) Cannot convert the variable type ((void) to Object) which, while good to know, isn't quite the result I was hoping for. diff --git a/reformat.py b/reformat.py index ef4a4e1..0d2da68 100755 --- a/reformat.py +++ b/reformat.py @@ -1,74 +1,57 @@ #!/usr/bin/env python3 -# This script takes a bunch of function prototypes copypasted from MSDN (or whatever it's called these days), and reformats them to valid C++. +# This script takes a bunch of function prototypes copypasted from MSDN (or whatever it's called these days), +# or function pointers copied from a Wine vtable struct, and reformats them to valid C++. # The functions will either call the same function on a parent object, or just return an error. -parent_name = "" +parent_name = "the_real_one" +debug_name = "pin" text = """ -HRESULT CheckCapabilities( - [in, out] DWORD *pCapabilities -);HRESULT ConvertTimeFormat( - [out] LONGLONG *pTarget, - [in] const GUID *pTargetFormat, - [in] LONGLONG Source, - [in] const GUID *pSourceFormat -); + HRESULT (STDMETHODCALLTYPE *GetAllocator)( + IMemInputPin *This, + IMemAllocator **ppAllocator); -HRESULT GetAvailable( - [out] LONGLONG *pEarliest, - [out] LONGLONG *pLatest -); + HRESULT (STDMETHODCALLTYPE *NotifyAllocator)( + IMemInputPin *This, + IMemAllocator *pAllocator, + BOOL bReadOnly); -HRESULT GetCapabilities( - [out] DWORD *pCapabilities -);HRESULT GetCurrentPosition( - [out] LONGLONG *pCurrent -); -HRESULT GetDuration( - [out] LONGLONG *pDuration -);HRESULT GetPositions( - [out] LONGLONG *pCurrent, - [out] LONGLONG *pStop -);HRESULT GetPreroll( - [out] LONGLONG *pllPreroll -);HRESULT GetRate( - [out] double *pdRate -);HRESULT GetStopPosition( - [out] LONGLONG *pStop -); + HRESULT (STDMETHODCALLTYPE *GetAllocatorRequirements)( + IMemInputPin *This, + ALLOCATOR_PROPERTIES *pProps); -HRESULT GetTimeFormat( - [out] GUID *pFormat -);HRESULT IsFormatSupported( - [in] const GUID *pFormat -);HRESULT IsUsingTimeFormat( - [in] const GUID *pFormat -);HRESULT QueryPreferredFormat( - [out] GUID *pFormat -); + HRESULT (STDMETHODCALLTYPE *Receive)( + IMemInputPin *This, + IMediaSample *pSample); + + HRESULT (STDMETHODCALLTYPE *ReceiveMultiple)( + IMemInputPin *This, + IMediaSample **pSamples, + LONG nSamples, + LONG *nSamplesProcessed); + + HRESULT (STDMETHODCALLTYPE *ReceiveCanBlock)( + IMemInputPin *This); -HRESULT SetPositions( - [in, out] LONGLONG *pCurrent, - [in] DWORD dwCurrentFlags, - [in, out] LONGLONG *pStop, - [in] DWORD dwStopFlags -);HRESULT SetRate( - [in] double dRate -);HRESULT SetTimeFormat( - [in] const GUID *pFormat -); """ +if debug_name: + debug_name += " " + import re for fn in text.replace("\n", "").split(";"): if not fn: continue - ret,name,args = re.match(r" *(.*) ([^ ]+)\((.*)\)", fn).groups() + fn = fn.replace("STDMETHODCALLTYPE","").replace("virtual "," ") + try: + ret,name,args = re.match(r" *(.*) \( *\*([^ ]+)\)\([^,]+This(?:,|(?=\)))(.*)\)", fn).groups() + except Exception: + ret,name,args = re.match(r" *(.*) ([^ ]+)\((.*)\)", fn).groups() args = re.sub(r"\[[^\]]+\]", "", args) args = args.split(',') for n,arg in enumerate(args): @@ -77,7 +60,7 @@ # print("------") # print(ret,name,args) if args != ['']: - args_untyped = ','.join(re.search("[A-Za-z0-9_a+]+$", a)[0] for a in args) + args_untyped = ', '.join(re.search("[A-Za-z0-9_a+]+$", a)[0] for a in args) else: args_untyped = '' # print(args_untyped) @@ -85,6 +68,9 @@ process = 'return '+parent_name+'->'+name+'('+args_untyped+');' else: process = 'return E_OUTOFMEMORY;' - fn = ret+' STDMETHODCALLTYPE '+name+'('+', '.join(args)+') override\n\t{ puts("'+name+'"); '+process+' }' + debugprint = "" + if debug_name: + debugprint = 'puts("'+debug_name+name+'"); ' + fn = ret+' STDMETHODCALLTYPE '+name+'('+', '.join(args)+') override\n\t{ '+debugprint+process+' }' fn = re.sub(" +", " ", fn).replace("( ", "(").replace(" )", ")").replace(" *", "* ").replace(" *", "* ") print(fn) diff --git a/x/nanodecode.cpp b/x/nanodecode.cpp index 867765a..e98803a 100644 --- a/x/nanodecode.cpp +++ b/x/nanodecode.cpp @@ -224,10 +224,10 @@ int main() IBaseFilter* asyncReader = make_filter(CLSID_AsyncReader); CComPtr asyncReaderFsf; require(20, asyncReader->QueryInterface(&asyncReaderFsf)); - require(21, asyncReaderFsf->Load(L"video.mpg", nullptr), "failed to open video.mpg, does the file exist?"); + require(21, asyncReaderFsf->Load(L"/home/walrus/z/wine-git/weird-offset.mpg", nullptr), "failed to open video.mpg, does the file exist?"); IBaseFilter* mpegdec = try_make_filter(CLSID_CMpegVideoCodec); - if (mpegdec) + if (mpegdec && false) { IBaseFilter* demux = chain_tail(asyncReader, CLSID_MPEG1Splitter); chain(demux, mpegdec, CLSID_VideoMixingRenderer9);