justinstenning / Direct3DHook

DirectX Capture and Overlays by using Direct3D API hooks
http://spazzarama.com/2011/03/14/c-screen-capture-and-overlays-for-direct3d-9-10-and-11-using-api-hooks
MIT License
580 stars 178 forks source link

PresentHook Exception when trying to Record Screens from Rise of the Tomb Raider #43

Closed ForestJay closed 7 years ago

ForestJay commented 8 years ago

Hello,

First, thank you so much for creating this tool! Without it I don't know how I'd capture screenshots from Direct3D games.

Most games (like Portal 2) work fine using the capture.dll straight from a clone of master (ie: I made no changes to the Direct3DHook code). When I try to get screenshots from Rise of the Tomb Raider the following exception occurs:

DXHookD3D11: PresentHook: Request Start

DXHookD3D11: PresentHook: Exeception: SharpDX.SharpDXException: SharpDX.SharpDXException: HRESULT: [0x80070057], Module: [General], ApiCode: [E_INVALIDARG/Invalid Arguments], Message: The parameter is incorrect.

at SharpDX.Result.CheckError() at SharpDX.Direct3D11.Device.CreateTexture2D(Texture2DDescription& descRef, DataBox[] initialDataRef, Texture2D texture2DOut) at SharpDX.Direct3D11.Texture2D..ctor(Device device, Texture2DDescription description) at Capture.Hook.DXHookD3D11.EnsureResources(Device device, Texture2DDescription description, Rectangle captureRegion, ScreenshotRequest request) in C:\Users\fores\git\Direct3DHook\Capture\Hook\DXHookD3D11.cs:line 280 at Capture.Hook.DXHookD3D11.PresentHook(IntPtr swapChainPtr, Int32 syncInterval, PresentFlags flags) in C:\Users\fores\git\Direct3DHook\Capture\Hook\DXHookD3D11.cs:line 355

When I load the symbols for capture.dll the debugger never stops at the code listed in this exception. For reference, I have DirectX12 installed and am running Windows 10. I've tried the game with DirectX 12 enabled and disabled.

This could very well be an issue with my code, but that exception doesn't tell me what argument is wrong or why it is wrong.

Here is my calling code:

` using System; using System.Diagnostics; using System.Threading; using System.Drawing; using Capture.Hook; using Capture.Interface; using Capture; using System.IO;

namespace GamePlayTest
{
    class Direct3DVideo : Video
    {
        private const int MAX_HOOK_RETRY = 20;
        private int processId = 0;
        private Process process;
        private CaptureProcess captureProcess;
        private int frame = 0;
        private Size? resize = null;
        private OutputDirectory outputDirectory;
        private static Stopwatch stopWatch;

        /// <summary>
        /// Generates the Videos by gathering frames and processing via FFMPEG.
        /// </summary>
        public override void RecordScreenTillGameEnd(string exe, OutputDirectory outputDirectoryArg, CustomMessageBox alertBox, Thread workerThreadArg)
        {
            stopWatch = null;
            workerThread = workerThreadArg;
            outputDirectory = outputDirectoryArg;
            int retry = 45;
            bool found = false;

            do
            {
                if (WinProcess.Count(exe) <= 0) retry--;
                else found = true;
            } while (retry > 0 && !found);

            AttachProcess(exe);
            stopWatch = Stopwatch.StartNew(); //creates and start the instance of Stopwatch
            RequestD3DScreenShot();

            workerThread.Join();

            alertBox.Show();
        }

        public override bool IsStarted()
        {
            return (stopWatch != null);
        }

        void RequestD3DScreenShot()
        {
            captureProcess.CaptureInterface.BeginGetScreenshot(new Rectangle(0, 0, 0, 0), new TimeSpan(0, 0, 2), 
                Callback, resize, (ImageFormat)Enum.Parse(typeof(ImageFormat), "Bitmap"));
        }

        private void AttachProcess(string exe)
        {
            int retry = MAX_HOOK_RETRY;
            while (captureProcess == null && --retry > 0)
            {
                Thread.Sleep(100);
                Process[] processes = Process.GetProcessesByName(Path.GetFileNameWithoutExtension(exe));
                foreach (Process currProcess in processes)
                {
                    // Simply attach to the first one found.

                    // If the process doesn't have a mainwindowhandle yet, skip it (we need to be able to get the hwnd to set foreground etc)
                    if (currProcess.MainWindowHandle == IntPtr.Zero || HookManager.IsHooked(currProcess.Id))
                    {
                        continue;
                    }

                    Direct3DVersion direct3DVersion = Direct3DVersion.AutoDetect;

                    CaptureConfig cc = new CaptureConfig()
                    {
                        Direct3DVersion = direct3DVersion,
                        ShowOverlay = false
                    };

                    processId = currProcess.Id;
                    process = currProcess;

                    var captureInterface = new CaptureInterface();
                    captureInterface.RemoteMessage += new MessageReceivedEvent(CaptureInterface_RemoteMessage);
                    captureProcess = new CaptureProcess(process, cc, captureInterface);

                    break;
                }
                Thread.Sleep(10);
            }

            if (captureProcess == null)
            {
                ShowUser.Exception("No DirectX executable found matching: '" + exe + "' !  Is this a DirectX game?");
                Environment.Exit(0);
            }
        }

        /// <summary>
        /// The callback for when the screenshot has been taken
        /// </summary>
        /// <param name="clientPID"></param>
        /// <param name="status"></param>
        /// <param name="screenshotResponse"></param>
        void Callback(IAsyncResult result)
        {
            try
            {
                using (Screenshot screenshot = captureProcess.CaptureInterface.EndGetScreenshot(result))
                {
                    if (screenshot != null && screenshot.Data != null)
                    {
                        if (image != null)
                        {
                            image.Dispose();
                        }

                        image = screenshot.ToBitmap();
                        if (resize == null)
                        {
                            resize = new Size(image.Width / SIZE_MODIFIER, image.Height / SIZE_MODIFIER);
                        }
                        else
                        {
                            AppendTime();
                            image.Save(outputDirectory.pathToArtifacts + "\\tempGameScreenshot" + frame.ToString().PadLeft(5, '0') + ".png",
                                System.Drawing.Imaging.ImageFormat.Png);
                            frame++;
                        }
                    }

                    if (workerThread.IsAlive)
                    {
                        while (frame >= ExpectedFrames(stopWatch.ElapsedMilliseconds))
                        {
                            Thread.Sleep(SLEEP_INTERVAL);
                        }

                        Thread t = new Thread(new ThreadStart(RequestD3DScreenShot));
                        t.Start();
                    }
                    else
                    {
                        Logger.log.Info("Total frames: " + frame + " Expected frames: " + (ExpectedFrames(stopWatch.ElapsedMilliseconds) - 1));
                        Logger.log.Info("Done getting shots from D3D.");
                    }
                }
            }
            catch (System.Runtime.Remoting.RemotingException ex)
            {
                Logger.log.Debug(ex.Message);
            }
        }

        private void AppendTime()
        {
            using (Graphics graphics = Graphics.FromImage(image))
            {
                using (Font arialFont = new Font("Arial", 10))
                {
                    graphics.DrawString(Controller.time.CurrentTime(), arialFont, Brushes.DarkRed, new PointF(0f, 0f));
                }
            }
        }

        public override void StopRecording(CustomMessageBox alertBox)
        {
            int cnt = 1;
            Thread ffmpgSave = LaunchSave();

            do
            {
                alertBox.descriptionLabel.Text = "Please wait " + new string('.', cnt++ % 10);
                alertBox.UpdateDescription();
                Thread.Sleep(Controller.THREAD_SLEEP_TIME);
            } while (ffmpgSave.IsAlive);
        }

        public Thread LaunchSave()
        {
            Thread workerThread = new Thread(this.Save);
            workerThread.Start();
            Logger.log.Info("Starting monitor game thread...");
            return workerThread;
        }

        private void Save()
        {
            if (resize == null)
            {
                return;
            }

            string arg = "-framerate " + VID_FRAME_FPS + " -i " + outputDirectory.pathToArtifacts +
                "\\tempGameScreenshot%05d.png -c:v libx264 -pix_fmt yuv420p -preset ultrafast -s " + 
                resize.Value.Width + "x" + resize.Value.Height + CODEC_CMDS + "-y \"" + outputDirectory.pathToVideo;

            Logger.log.Debug("Launching FFMPEG with arguments:" + arg);
            Process proc = new Process
            {
                StartInfo = new ProcessStartInfo
                {
                    FileName = "ffmpeg",
                    Arguments = arg,
                    UseShellExecute = false,
                    RedirectStandardOutput = true,
                    CreateNoWindow = true,
                    RedirectStandardError = true
                }
            };
            proc.Start();

            // May want to remove this ffmpeg info from the console in the future.
            while (!proc.StandardError.EndOfStream)
            {
                Logger.log.Error(proc.StandardError.ReadLine());
            }
#if DEBUG
            while (!proc.StandardOutput.EndOfStream)
            {
                Logger.log.Debug(proc.StandardOutput.ReadLine());
            }
#endif
            var dir = new DirectoryInfo(outputDirectory.pathToArtifacts);

            foreach (var file in dir.EnumerateFiles("tempGameScreenshot*.png"))
            {
                file.Delete();
            }
        }

        /// <summary>
        /// Display messages from the target process
        /// </summary>
        /// <param name="message"></param>
        private void CaptureInterface_RemoteMessage(MessageReceivedEventArgs message)
        {
            Logger.log.Info(message);
        }
    }
}

`

Any help would be greatly appreciated. Thank you!

justinstenning commented 7 years ago

Sorry for the delay.

Try attaching the debugger to the target application, then inject / hook etc.. and then see where the problem arises.

Unfortunately that error is fairly unhelpful (it is a DirectX error), and will generally mean that some part of the call has incompatible parameters, either with the DirectX device or other elements (e.g. trying to use a texture format that is not available for that particular device due to the way it was created by the game).

ForestJay commented 7 years ago

Thank you! I've moved onto another project but might come back to this in the future.

Misiu commented 7 years ago

@ForestJay what project are You using? Could You please post link to it? Thanks.

ForestJay commented 7 years ago

Unfortunately, it is in a private repository. I might be able to share the specific code but we are keeping the repo private for now.

On Nov 2, 2016 12:40 AM, "Tomek" notifications@github.com wrote:

@ForestJay https://github.com/ForestJay what project are You using? Could You please post link to it? Thanks.

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/spazzarama/Direct3DHook/issues/43#issuecomment-257793444, or mute the thread https://github.com/notifications/unsubscribe-auth/AHeitfO-MMMxwIB4hZmHrwmcruCesWLSks5q6D6JgaJpZM4IzOWq .

Misiu commented 7 years ago

@ForestJay I'm building ambilight clone, so I'll be more than happy to have it working with newest games. If You can share code responsible for grabbing (recording) screen that would be awesome!