diff options
Diffstat (limited to 'tools/native-frame-capture/NativeFrameCapture.cs')
| -rw-r--r-- | tools/native-frame-capture/NativeFrameCapture.cs | 324 |
1 files changed, 310 insertions, 14 deletions
diff --git a/tools/native-frame-capture/NativeFrameCapture.cs b/tools/native-frame-capture/NativeFrameCapture.cs index 6371ae6..9721049 100644 --- a/tools/native-frame-capture/NativeFrameCapture.cs +++ b/tools/native-frame-capture/NativeFrameCapture.cs @@ -49,6 +49,8 @@ internal static partial class NativeFrameCapture private const string Iron3dName = "iron3d.dll"; private const uint RenderGameRva = 0x13BD0; private const uint ProjectionSnapshotRva = 0x13CE4; + private const uint GeometryProjectionRva = 0x1D110; + private const uint SkyRenderRva = 0x48110; private const uint RenderReturnRva = 0x13D7B; private const uint AtmospherePhaseRva = 0x421DC; private const uint FogConsumerRva = 0x2F230; @@ -101,6 +103,10 @@ internal static partial class NativeFrameCapture public uint ProjectionGeneration; public uint ProjectionThreadId; public ProjectionReadback Projection; + public ProjectionReadback GeometryProjection; + public uint GeometryProjectionGeneration; + public uint GeometryProjectionThreadId; + public SkyUvReadback SkyUv; public uint CameraWorldGameTimeWord; public bool CameraWorldGameTimeWordReadable; public uint WorldGameTimeWord; @@ -129,6 +135,11 @@ internal static partial class NativeFrameCapture public string NativeFogStatus; } + private sealed class SkyUvReadback + { + public uint Object, Origin, RawClock, ElapsedMilliseconds, Generation, ThreadId; + } + private sealed class AtmosphereReadback { public uint SampleThreadId; @@ -201,6 +212,8 @@ internal static partial class NativeFrameCapture private sealed class SelectedCameraState { public SelectedCameraInput Input; + public int PrecedingFramesRemaining; + public int PrecedingFramesCompleted; public uint CandidateCameraPointer; public uint CandidateThreadId; public uint CandidateGeneration; @@ -226,6 +239,47 @@ internal static partial class NativeFrameCapture public uint OriginalParentPointer; } + private static SelectedCameraState NextSelectedWarmupFrame(SelectedCameraState previous) + { + if (previous.PrecedingFramesRemaining <= 0 || !previous.RestoreVerified + || !previous.Restored || !previous.MatrixIntactAtReturn + || !previous.ProjectionMatchesInput || previous.PixelAttributionInvalidated) + throw new InvalidOperationException("Cannot advance a selected camera before a verified frame and exact restoration."); + return new SelectedCameraState + { + Input = previous.Input, + PrecedingFramesRemaining = previous.PrecedingFramesRemaining - 1, + PrecedingFramesCompleted = previous.PrecedingFramesCompleted + 1, + CandidateCameraPointer = previous.CameraPointer, + CandidateThreadId = previous.ThreadId, + CandidateGeneration = previous.Generation, + CandidateProjectionVerified = true + }; + } + + private static void SelfCheckSelectedCameraWarmup() + { + SelectedCameraState first = new SelectedCameraState + { + PrecedingFramesRemaining = 2, CameraPointer = 123, ThreadId = 11, Generation = 8, + Restored = true, RestoreVerified = true, MatrixIntactAtReturn = true, + ProjectionMatchesInput = true + }; + SelectedCameraState second = NextSelectedWarmupFrame(first); + if (second.PrecedingFramesRemaining != 1 || second.PrecedingFramesCompleted != 1 + || second.CandidateCameraPointer != 123 || second.CandidateThreadId != 11 + || second.CandidateGeneration != 8 || second.Applied || second.RestoreVerified) + throw new InvalidOperationException("Selected camera warmup transition self-check failed."); + bool unrestoredRejected = false; + try { NextSelectedWarmupFrame(second); } + catch (InvalidOperationException) { unrestoredRejected = true; } + if (!unrestoredRejected) throw new InvalidOperationException("Unrestored warmup frame was accepted."); + second.Restored = second.RestoreVerified = second.MatrixIntactAtReturn = second.ProjectionMatchesInput = true; + SelectedCameraState final = NextSelectedWarmupFrame(second); + if (final.PrecedingFramesRemaining != 0 || final.PrecedingFramesCompleted != 2) + throw new InvalidOperationException("Selected camera final frame self-check failed."); + } + private sealed class BoundaryTraceProgress { public bool BeginSceneSeen; @@ -695,6 +749,104 @@ internal static partial class NativeFrameCapture return match; } + private static bool GeometryProjectionMatchesFrame(FrameSnapshot frame) + { + ProjectionReadback geometry = frame.GeometryProjection; + ProjectionReadback boundary = frame.Projection; + if (geometry == null || boundary == null || !geometry.Verified || !boundary.Verified || geometry.Mode == 0 + || frame.GeometryProjectionGeneration == 0 + || frame.GeometryProjectionGeneration != frame.CameraGeneration + || frame.GeometryProjectionGeneration != frame.ProjectionGeneration + || frame.GeometryProjectionThreadId != frame.CameraThreadId + || frame.GeometryProjectionThreadId != frame.ProjectionThreadId + || geometry.Renderer != boundary.Renderer || geometry.Far != boundary.Far + || geometry.Fov != boundary.Fov) return false; + for (int i = 0; i < 4; i++) + if (geometry.Viewport[i] != boundary.Viewport[i]) return false; + return true; + } + + private static void AppendGeometryProjectionJson(StringBuilder json, FrameSnapshot frame) + { + if (!GeometryProjectionMatchesFrame(frame)) + { + json.AppendLine(" \"geometry_projection\": null,"); + return; + } + ProjectionReadback p = frame.GeometryProjection; + json.AppendLine(" \"geometry_projection\": {"); + json.AppendLine(" \"verified\": true,"); + json.AppendLine(" \"sample_boundary\": \"Terrain+0x1D110, before indexed geometry draw\","); + json.AppendLine(" \"terrain_module_sha256\": " + JsonString(ExpectedSha256[TerrainName]) + ","); + json.AppendLine(" \"frame_generation\": " + frame.GeometryProjectionGeneration + ","); + json.AppendLine(" \"thread_id\": " + frame.GeometryProjectionThreadId + ","); + json.AppendLine(" \"viewport\": [" + String.Join(", ", p.Viewport) + "],"); + json.AppendLine(" \"near_plane\": " + p.Near.ToString("R", CultureInfo.InvariantCulture) + ","); + json.AppendLine(" \"far_plane\": " + p.Far.ToString("R", CultureInfo.InvariantCulture) + ","); + json.AppendLine(" \"field_of_view_radians\": " + p.Fov.ToString("R", CultureInfo.InvariantCulture)); + json.AppendLine(" },"); + } + + private static SkyUvReadback CaptureSkyUv(IntPtr process, uint terrainBase, uint worldBase, + byte[] context, FrameSnapshot frame, uint threadId) + { + uint obj = BitConverter.ToUInt32(context, ContextEcxOffset); + uint stack = BitConverter.ToUInt32(context, ContextEspOffset); + if (frame.Camera == null || frame.CameraThreadId != threadId || frame.CameraGeneration == 0 + || ReadU32Exact(process, stack + 4) != frame.Camera.Camera + || ReadU32Exact(process, obj) != unchecked(terrainBase + 0x65F98)) return null; + uint origin = ReadU32Exact(process, obj + 8); + uint rawClock = ReadU32Exact(process, unchecked(worldBase + 0x32A38)); + uint elapsed = unchecked(rawClock - origin); + float[] expected = new float[] { + (elapsed % 300000) * 3.3333333e-6f, (elapsed % 300000) * 3.3333333e-6f, + (elapsed % 100000) * 1e-5f, (elapsed % 100000) * 1e-5f, + (elapsed % 50000) * 2e-5f, 0 }; + uint[] pointers = new uint[] { ReadU32Exact(process, obj + 0x48), + ReadU32Exact(process, obj + 0x40), ReadU32Exact(process, obj + 0x44) }; + for (int i = 0; i < expected.Length; i++) + { + float actual = BitConverter.ToSingle(BitConverter.GetBytes( + ReadU32Exact(process, pointers[i / 2] + (uint)(i % 2) * 4)), 0); + if (Single.IsNaN(actual) || Single.IsInfinity(actual) + || Math.Abs(actual - expected[i]) > 1e-6f) + { + Log("SKY_UV_CLOCK_REJECTED zenith coordinate does not match the current world clock."); + return null; + } + } + Log("SKY_UV_CLOCK generation=" + frame.CameraGeneration + " tid=" + threadId + + " raw=" + rawClock + " origin=" + origin + " elapsed_ms=" + elapsed); + return new SkyUvReadback { Object = obj, Origin = origin, RawClock = rawClock, + ElapsedMilliseconds = elapsed, Generation = frame.CameraGeneration, ThreadId = threadId }; + } + + private static bool SkyUvMatchesFrame(FrameSnapshot frame) + { + SkyUvReadback sky = frame.SkyUv; + return sky != null && sky.Generation == frame.CameraGeneration + && sky.Generation == frame.ProjectionGeneration && sky.ThreadId == frame.CameraThreadId + && sky.ThreadId == frame.ProjectionThreadId && frame.WorldGameTimeWordReadable + && sky.RawClock == frame.WorldGameTimeWord && frame.CameraWorldGameTimeWordReadable + && sky.RawClock == frame.CameraWorldGameTimeWord; + } + + private static void AppendSkyUvJson(StringBuilder json, FrameSnapshot frame) + { + if (!SkyUvMatchesFrame(frame)) { json.AppendLine(" \"sky_uv_clock\": null,"); return; } + SkyUvReadback sky = frame.SkyUv; + json.AppendLine(" \"sky_uv_clock\": {"); + json.AppendLine(" \"verified\": true,"); + json.AppendLine(" \"sample_boundary\": \"Terrain+0x48110, sky render\","); + json.AppendLine(" \"terrain_module_sha256\": " + JsonString(ExpectedSha256[TerrainName]) + ","); + json.AppendLine(" \"raw_clock\": " + sky.RawClock + ","); + json.AppendLine(" \"origin_raw_clock\": " + sky.Origin + ","); + json.AppendLine(" \"frame_generation\": " + sky.Generation + ","); + json.AppendLine(" \"thread_id\": " + sky.ThreadId + ","); + json.AppendLine(" \"elapsed_milliseconds\": " + sky.ElapsedMilliseconds); + json.AppendLine(" },"); + } + private static bool VerifyAtmospherePhaseBoundary(IntPtr process, uint terrainBase, out byte firstByte, out string evidence) { @@ -1236,6 +1388,7 @@ internal static partial class NativeFrameCapture return CanArmPresentForFrame(frame, threadId) && (!frame.SelectedCameraRequested || (frame.SelectedCamera != null && frame.SelectedCamera.Applied && frame.SelectedCamera.ProjectionMatchesInput + && frame.SelectedCamera.PrecedingFramesRemaining == 0 && frame.SelectedCamera.CandidateProjectionVerified && frame.SelectedCamera.Restored && frame.SelectedCamera.RestoreVerified && frame.SelectedCamera.MatrixIntactAtReturn @@ -1276,6 +1429,7 @@ internal static partial class NativeFrameCapture && frame.CameraThreadId == threadId && frame.ProjectionThreadId == threadId && (!frame.SelectedCameraRequested || (frame.SelectedCamera != null && frame.SelectedCamera.Applied && frame.SelectedCamera.ProjectionMatchesInput + && frame.SelectedCamera.PrecedingFramesRemaining == 0 && frame.SelectedCamera.CandidateProjectionVerified && !frame.SelectedCamera.PixelAttributionInvalidated && SelectedMatrixMatchesProjection(frame))); @@ -1843,11 +1997,14 @@ internal static partial class NativeFrameCapture json.AppendLine(" \"far_plane\": " + projection.Far.ToString("R", CultureInfo.InvariantCulture) + ","); json.AppendLine(" \"field_of_view_radians\": " + projection.Fov.ToString("R", CultureInfo.InvariantCulture) + ","); json.AppendLine(" \"projection_mode_byte\": " + projection.Mode.ToString(CultureInfo.InvariantCulture) + ","); + AppendGeometryProjectionJson(json, frame); + AppendSkyUvJson(json, frame); if (frame.SelectedCamera == null) json.AppendLine(" \"selected_camera\": null,"); else { SelectedCameraState selected = frame.SelectedCamera; json.AppendLine(" \"selected_camera\": {"); + json.AppendLine(" \"preceding_selected_world_frames\": " + selected.PrecedingFramesCompleted + ","); json.AppendLine(" \"input_json\": " + JsonString(selected.Input.Path) + ","); json.AppendLine(" \"input_kind\": " + JsonString(selected.Input.Synthetic ? "synthetic-camera-plan" : "native-capture") + ","); @@ -1949,6 +2106,9 @@ internal static partial class NativeFrameCapture { if (IntPtr.Size != 4) throw new InvalidOperationException("This helper must be compiled and run as x86."); SelfCheckCameraInput(); + SelfCheckSelectedCameraWarmup(); + SelfCheckGeometryProjection(); + SelfCheckSkyUv(); SelfCheckNativeFogPairing(); SelfCheckRenderInvocationGates(); SelfCheckDebugEventHandleOwnership(); @@ -2088,6 +2248,10 @@ internal static partial class NativeFrameCapture bool wrongCameraThreadRejected = !IsMatchingPerspectiveFrame(matched, 12); bool emptyCameraFrameRejected = !IsMatchingPerspectiveFrame(new FrameSnapshot(), 11); bool selectedFrameValid = IsMatchingPerspectiveFrame(selectedMatched, 11); + selectedMatched.SelectedCamera.PrecedingFramesRemaining = 1; + bool selectedWarmupRejected = !CanArmPresentForFrame(selectedMatched, 11) + && !IsMatchingPerspectiveFrame(selectedMatched, 11); + selectedMatched.SelectedCamera.PrecedingFramesRemaining = 0; bool selectedUnrestoredRejected = !IsMatchingPerspectiveFrame(selectedUnrestored, 11); bool selectedUnrestoredCaptureEligible = CanArmPresentForFrame(selectedUnrestored, 11); bool selectedIntervenedCaptureRejected = !CanArmPresentForFrame(selectedIntervened, 11); @@ -2189,7 +2353,7 @@ internal static partial class NativeFrameCapture || !atmosphereFrameValid || !atmosphereClockMismatchRejected || !cameraFrameValid || !wrongCameraThreadRejected || !emptyCameraFrameRejected - || !selectedFrameValid || !selectedUnrestoredRejected || !selectedUnrestoredCaptureEligible + || !selectedFrameValid || !selectedWarmupRejected || !selectedUnrestoredRejected || !selectedUnrestoredCaptureEligible || !selectedIntervenedCaptureRejected || !selectedIntervenedRejected || !selectedChangedMatrixRejected || !selectedBoundaryReadAllowed || !boundaryReadRejectsUnselected @@ -2255,6 +2419,60 @@ internal static partial class NativeFrameCapture return SelfCheckReadback(); } + private static void SelfCheckSkyUv() + { + FrameSnapshot frame = new FrameSnapshot { CameraGeneration = 7, ProjectionGeneration = 7, + CameraThreadId = 11, ProjectionThreadId = 11, WorldGameTimeWordReadable = true, + CameraWorldGameTimeWordReadable = true, CameraWorldGameTimeWord = 265584022, + WorldGameTimeWord = 265584022, + SkyUv = new SkyUvReadback { Generation = 7, ThreadId = 11, RawClock = 265584022, + Origin = 509280060, ElapsedMilliseconds = unchecked(265584022U - 509280060U) } }; + StringBuilder json = new StringBuilder(); + AppendSkyUvJson(json, frame); + if (!SkyUvMatchesFrame(frame) || json.ToString().IndexOf("4051271258", StringComparison.Ordinal) < 0) + throw new InvalidOperationException("Verified sky UV wrapping clock was rejected."); + frame.SkyUv.ThreadId++; + if (SkyUvMatchesFrame(frame)) throw new InvalidOperationException("Sky UV thread mismatch accepted."); + frame.SkyUv.ThreadId--; + frame.SkyUv.Generation++; + if (SkyUvMatchesFrame(frame)) throw new InvalidOperationException("Sky UV generation mismatch accepted."); + frame.SkyUv.Generation--; + frame.SkyUv.RawClock++; + json.Length = 0; + AppendSkyUvJson(json, frame); + if (SkyUvMatchesFrame(frame) || json.ToString().IndexOf("null", StringComparison.Ordinal) < 0) + throw new InvalidOperationException("Sky UV clock mismatch accepted."); + Console.WriteLine("self-check: sky UV clock requires matching thread/generation/world clock and preserves u32 wrap OK"); + } + + private static void SelfCheckGeometryProjection() + { + FrameSnapshot frame = new FrameSnapshot { + CameraGeneration = 7, ProjectionGeneration = 7, GeometryProjectionGeneration = 7, + CameraThreadId = 11, ProjectionThreadId = 11, GeometryProjectionThreadId = 11, + Projection = new ProjectionReadback { Renderer = 100, Viewport = new int[] { 0, 0, 1280, 1024 }, + Near = 5, Far = 700, Fov = 1.3f, Mode = 1, Verified = true }, + GeometryProjection = new ProjectionReadback { Renderer = 100, Viewport = new int[] { 0, 0, 1280, 1024 }, + Near = 0.5f, Far = 700, Fov = 1.3f, Mode = 1, Verified = true } + }; + StringBuilder json = new StringBuilder("{\n"); + AppendGeometryProjectionJson(json, frame); + json.Append("\"end\":true}"); + Dictionary<string, object> parsed = new System.Web.Script.Serialization.JavaScriptSerializer() + .Deserialize<Dictionary<string, object>>(json.ToString()); + Dictionary<string, object> projection = (Dictionary<string, object>)parsed["geometry_projection"]; + if (Convert.ToSingle(projection["near_plane"]) != 0.5f) + throw new InvalidOperationException("Geometry projection must preserve draw near, not restored camera near."); + frame.GeometryProjectionThreadId++; + if (GeometryProjectionMatchesFrame(frame)) throw new InvalidOperationException("Geometry projection thread mismatch accepted."); + frame.GeometryProjectionThreadId--; + frame.GeometryProjectionGeneration--; + if (GeometryProjectionMatchesFrame(frame)) throw new InvalidOperationException("Geometry projection generation mismatch accepted."); + frame.GeometryProjectionGeneration++; + frame.GeometryProjection.Viewport[2]--; + if (GeometryProjectionMatchesFrame(frame)) throw new InvalidOperationException("Geometry projection viewport mismatch accepted."); + } + private static void SelfCheckNativeFogPairing() { if (FogConsumerRva != 0x2F230 || FogConsumerVtableRva != 0x6527C @@ -2894,6 +3112,8 @@ internal static partial class NativeFrameCapture string terrainModuleSha256 = null; BreakpointInfo cameraBreakpoint = null; BreakpointInfo projectionBreakpoint = null; + BreakpointInfo geometryProjectionBreakpoint = null; + BreakpointInfo skyUvBreakpoint = null; BreakpointInfo returnBreakpoint = null; BreakpointInfo atmosphereBreakpoint = null; BreakpointInfo beginSceneBreakpoint = null; @@ -2919,7 +3139,8 @@ internal static partial class NativeFrameCapture bool projectionSelectorMode = boundaryTrace || boundaryCapture; frame.SelectedCameraRequested = selectedCameraInput != null; SelectedCameraState selectedCamera = selectedCameraInput == null ? null - : new SelectedCameraState { Input = selectedCameraInput }; + : new SelectedCameraState { Input = selectedCameraInput, + PrecedingFramesRemaining = boundaryCapture ? 0 : 2 }; try { if (identityProcess == null || identityProcess.HasExited @@ -3076,6 +3297,21 @@ internal static partial class NativeFrameCapture throw new InvalidOperationException("Loaded Terrain hash mismatch: " + hash); terrainBase = imageBase; terrainModuleSha256 = hash; + byte[] geometryPrefix = new byte[] { 0x81, 0xEC, 0xBC, 0, 0, 0, 0x53, 0x55, 0x8B, 0xE9 }; + byte[] geometryCode = new byte[geometryPrefix.Length]; + if (!ReadExact(process, unchecked(terrainBase + GeometryProjectionRva), geometryCode) + || !ByteArraysEqual(geometryCode, geometryPrefix)) + throw new InvalidOperationException("Terrain geometry projection boundary signature failed."); + geometryProjectionBreakpoint = ArmBreakpoint(process, "Terrain.geometry-projection", + unchecked(terrainBase + GeometryProjectionRva), geometryPrefix[0]); + byte[] skyPrefix = new byte[] { 0x6A, 0xFF, 0x68, 0, 0, 0, 0, 0x64, 0xA1, 0, 0, 0, 0 }; + Array.Copy(BitConverter.GetBytes(unchecked(terrainBase + 0x637E6)), 0, skyPrefix, 3, 4); + byte[] skyCode = new byte[skyPrefix.Length]; + if (!ReadExact(process, unchecked(terrainBase + SkyRenderRva), skyCode) + || !ByteArraysEqual(skyCode, skyPrefix)) + throw new InvalidOperationException("Terrain sky render boundary signature failed."); + skyUvBreakpoint = ArmBreakpoint(process, "Terrain.sky-uv-clock", + unchecked(terrainBase + SkyRenderRva), skyPrefix[0]); byte atmosphereByte; string atmosphereEvidence; if (!VerifyAtmospherePhaseBoundary(process, terrainBase, out atmosphereByte, out atmosphereEvidence)) @@ -3330,6 +3566,12 @@ internal static partial class NativeFrameCapture Log("CAMERA_SETTER_REGION_FREED phase=Restore"); else Log("CAMERA_SETTER_REGION_FREE_FAILED phase=Restore error=" + Marshal.GetLastWin32Error()); cameraSetter = null; + if (!stopAfterAttempt && selectedCamera.PrecedingFramesRemaining > 0) + { + selectedCamera = NextSelectedWarmupFrame(selectedCamera); + Log("SELECTED_CAMERA_WARMUP_COMPLETE completed=" + selectedCamera.PrecedingFramesCompleted + + " remaining=" + selectedCamera.PrecedingFramesRemaining); + } } } } @@ -3535,6 +3777,12 @@ internal static partial class NativeFrameCapture hitBreakpoint = cameraBreakpoint; else if (projectionBreakpoint != null && projectionBreakpoint.Armed && exceptionAddress == projectionBreakpoint.Address) hitBreakpoint = projectionBreakpoint; + else if (geometryProjectionBreakpoint != null && geometryProjectionBreakpoint.Armed + && exceptionAddress == geometryProjectionBreakpoint.Address) + hitBreakpoint = geometryProjectionBreakpoint; + else if (skyUvBreakpoint != null && skyUvBreakpoint.Armed + && exceptionAddress == skyUvBreakpoint.Address) + hitBreakpoint = skyUvBreakpoint; else if (returnBreakpoint != null && returnBreakpoint.Armed && exceptionAddress == returnBreakpoint.Address) hitBreakpoint = returnBreakpoint; else if (atmosphereBreakpoint != null && atmosphereBreakpoint.Armed && exceptionAddress == atmosphereBreakpoint.Address) @@ -3608,6 +3856,16 @@ internal static partial class NativeFrameCapture && !RestoreBreakpointWhileStopped(process, fogConsumerBreakpoint)) throw new InvalidOperationException("Could not disarm the previous generation's native fog boundary."); frame.CameraGeneration++; + frame.GeometryProjection = null; + frame.SkyUv = null; + if (skyUvBreakpoint != null && !skyUvBreakpoint.Armed) + skyUvBreakpoint = ArmBreakpoint(process, "Terrain.sky-uv-clock", + skyUvBreakpoint.Address, skyUvBreakpoint.OriginalByte); + frame.GeometryProjectionGeneration = 0; + frame.GeometryProjectionThreadId = 0; + if (geometryProjectionBreakpoint != null && !geometryProjectionBreakpoint.Armed) + geometryProjectionBreakpoint = ArmBreakpoint(process, "Terrain.geometry-projection", + geometryProjectionBreakpoint.Address, geometryProjectionBreakpoint.OriginalByte); ResetNativeFogForGeneration(frame); frame.CameraThreadId = eventTid; frame.CameraWorldGameTimeWordReadable = false; @@ -3696,6 +3954,31 @@ internal static partial class NativeFrameCapture frame, eventTid, ref fogConsumerBreakpoint); } } + else if (hitBreakpoint == skyUvBreakpoint) + { + if (frame.SkyUv == null && world3dBase != 0) + frame.SkyUv = CaptureSkyUv(process, terrainBase, world3dBase, + stoppedContext, frame, eventTid); + } + else if (hitBreakpoint == geometryProjectionBreakpoint) + { + uint esp = BitConverter.ToUInt32(stoppedContext, ContextEspOffset); + if (frame.Camera != null && frame.CameraThreadId == eventTid && ngiBase != 0 + && ReadU32Exact(process, esp + 8) == frame.Camera.Camera + 0x60 + && ReadU32Exact(process, esp + 4) == ReadU32Exact(process, ngiBase + NgiRendererGlobalRva)) + { + ProjectionReadback p = ReadProjection(process, ngiBase); + if (p.Verified && p.Mode != 0) + { + frame.GeometryProjection = p; + frame.GeometryProjectionGeneration = frame.CameraGeneration; + frame.GeometryProjectionThreadId = eventTid; + Log("GEOMETRY_PROJECTION generation=" + frame.CameraGeneration + " tid=" + eventTid + + " near=" + p.Near.ToString("R", CultureInfo.InvariantCulture) + + " far=" + p.Far.ToString("R", CultureInfo.InvariantCulture)); + } + } + } else if (hitBreakpoint == projectionBreakpoint) { string candidateProjectionEvidence = "candidate preconditions not met"; @@ -3870,7 +4153,8 @@ internal static partial class NativeFrameCapture && targetProjectionMatchesInput) + " rawWorldTime=0x" + frame.WorldGameTimeWord.ToString("X8")); if (cameraWordsCurrent && frame.Projection.Verified && frame.Projection.Mode != 0 - && targetProjectionMatchesInput && ngiBase != 0 && presentBoundaryVerified) + && targetProjectionMatchesInput && ngiBase != 0 && presentBoundaryVerified + && (selectedCamera == null || selectedCamera.PrecedingFramesRemaining == 0)) EnsurePresentBreakpoint(process, ngiBase, frame, eventTid, presentOriginalByte, ref presentBreakpoint); } @@ -3919,7 +4203,7 @@ internal static partial class NativeFrameCapture { remote = StartRemoteSurfaceProbe(process, eventTid, ngiBase, returnBreakpoint, frame, - new BreakpointInfo[] { cameraBreakpoint, projectionBreakpoint, + new BreakpointInfo[] { cameraBreakpoint, projectionBreakpoint, geometryProjectionBreakpoint, skyUvBreakpoint, returnBreakpoint, atmosphereBreakpoint, beginSceneBreakpoint, fogConsumerBreakpoint, endSceneBreakpoint, presentBreakpoint, @@ -3975,7 +4259,7 @@ internal static partial class NativeFrameCapture } cameraSetter = StartCameraSetter(process, eventTid, terrainBase, selectedCamera.CameraPointer, selectedCamera.OriginalMatrixBytes, - stoppedContext, returnBreakpoint, false, CameraSetterPhase.Restore, + stoppedContext, returnBreakpoint, selectedCamera.PrecedingFramesRemaining > 0, CameraSetterPhase.Restore, selectedCamera.EntryEsp, selectedCamera.ReturnAddress); Log("SELECTED_CAMERA_RESTORE_STARTED generation=" + selectedCamera.Generation + " camera=0x" + selectedCamera.CameraPointer.ToString("X8") @@ -4067,7 +4351,7 @@ internal static partial class NativeFrameCapture { remote = StartRemoteSurfaceProbe(process, eventTid, ngiBase, endSceneBreakpoint, frame, - new BreakpointInfo[] { cameraBreakpoint, projectionBreakpoint, + new BreakpointInfo[] { cameraBreakpoint, projectionBreakpoint, geometryProjectionBreakpoint, skyUvBreakpoint, returnBreakpoint, atmosphereBreakpoint, beginSceneBreakpoint, endSceneBreakpoint, fogConsumerBreakpoint, presentBreakpoint, @@ -4115,7 +4399,7 @@ internal static partial class NativeFrameCapture { remote = StartRemoteBoundaryReadback(process, eventTid, ngiBase, endSceneBreakpoint, frame, outputPath, - new BreakpointInfo[] { cameraBreakpoint, projectionBreakpoint, + new BreakpointInfo[] { cameraBreakpoint, projectionBreakpoint, geometryProjectionBreakpoint, skyUvBreakpoint, returnBreakpoint, atmosphereBreakpoint, beginSceneBreakpoint, endSceneBreakpoint, fogConsumerBreakpoint, presentBreakpoint, @@ -4333,6 +4617,10 @@ internal static partial class NativeFrameCapture if (hitBreakpoint == fogConsumerBreakpoint) rearmAfterStep = !frame.NativeFogSamplingStopped && !frame.NativeFogSampleLimitExceeded; + if (hitBreakpoint == geometryProjectionBreakpoint) + rearmAfterStep = frame.GeometryProjection == null; + if (hitBreakpoint == skyUvBreakpoint) + rearmAfterStep = frame.SkyUv == null; BeginSingleStep(process, eventTid, hitBreakpoint, stoppedContext, out pendingStep, rearmAfterStep); Log("SINGLE_STEP_STARTED boundary=" + hitBreakpoint.Name + " tid=" + eventTid); @@ -4405,7 +4693,7 @@ internal static partial class NativeFrameCapture captureFailure = captureFailure ?? "No matching native camera/projection/present frame completed before the bounded timeout."; Log("NO_FRAME_CAPTURE bounded timeout at stopped debug event"); if (!RestoreArmedBreakpointsWhileStopped(process, - cameraBreakpoint, projectionBreakpoint, returnBreakpoint, atmosphereBreakpoint, + cameraBreakpoint, projectionBreakpoint, geometryProjectionBreakpoint, skyUvBreakpoint, returnBreakpoint, atmosphereBreakpoint, beginSceneBreakpoint, endSceneBreakpoint, fogConsumerBreakpoint, presentBreakpoint, presentCompletionBreakpoint, missionBreakpoint)) @@ -4425,6 +4713,8 @@ internal static partial class NativeFrameCapture && !fatal && !processExited && (cameraBreakpoint != null && cameraBreakpoint.Armed || projectionBreakpoint != null && projectionBreakpoint.Armed + || geometryProjectionBreakpoint != null && geometryProjectionBreakpoint.Armed + || skyUvBreakpoint != null && skyUvBreakpoint.Armed || returnBreakpoint != null && returnBreakpoint.Armed || atmosphereBreakpoint != null && atmosphereBreakpoint.Armed || beginSceneBreakpoint != null && beginSceneBreakpoint.Armed @@ -4435,7 +4725,7 @@ internal static partial class NativeFrameCapture || missionBreakpoint != null && missionBreakpoint.Armed)) { if (!RestoreArmedBreakpointsWhileStopped(process, - cameraBreakpoint, projectionBreakpoint, returnBreakpoint, atmosphereBreakpoint, + cameraBreakpoint, projectionBreakpoint, geometryProjectionBreakpoint, skyUvBreakpoint, returnBreakpoint, atmosphereBreakpoint, beginSceneBreakpoint, endSceneBreakpoint, fogConsumerBreakpoint, presentBreakpoint, presentCompletionBreakpoint, missionBreakpoint)) @@ -4484,6 +4774,8 @@ internal static partial class NativeFrameCapture || cameraMutationUnrestored || eventOutstanding || cameraBreakpoint != null && cameraBreakpoint.Armed || projectionBreakpoint != null && projectionBreakpoint.Armed + || geometryProjectionBreakpoint != null && geometryProjectionBreakpoint.Armed + || skyUvBreakpoint != null && skyUvBreakpoint.Armed || returnBreakpoint != null && returnBreakpoint.Armed || atmosphereBreakpoint != null && atmosphereBreakpoint.Armed || beginSceneBreakpoint != null && beginSceneBreakpoint.Armed @@ -4513,7 +4805,7 @@ internal static partial class NativeFrameCapture if (DrainFrameTerminationEvents(process, eventBuffer, processId, ref eventOutstanding, ref outstandingPid, ref outstandingTid, ref outstandingCode, ref outstandingContinueStatus, ref outstandingFileHandleClosed, - new BreakpointInfo[] { cameraBreakpoint, projectionBreakpoint, returnBreakpoint, + new BreakpointInfo[] { cameraBreakpoint, projectionBreakpoint, geometryProjectionBreakpoint, skyUvBreakpoint, returnBreakpoint, atmosphereBreakpoint, beginSceneBreakpoint, endSceneBreakpoint, fogConsumerBreakpoint, presentBreakpoint, presentCompletionBreakpoint, missionBreakpoint }, out drainEvidence)) @@ -4582,7 +4874,7 @@ internal static partial class NativeFrameCapture if (DrainFrameTerminationEvents(process, eventBuffer, processId, ref eventOutstanding, ref outstandingPid, ref outstandingTid, ref outstandingCode, ref outstandingContinueStatus, ref outstandingFileHandleClosed, - new BreakpointInfo[] { cameraBreakpoint, projectionBreakpoint, returnBreakpoint, + new BreakpointInfo[] { cameraBreakpoint, projectionBreakpoint, geometryProjectionBreakpoint, skyUvBreakpoint, returnBreakpoint, atmosphereBreakpoint, beginSceneBreakpoint, endSceneBreakpoint, fogConsumerBreakpoint, presentBreakpoint, presentCompletionBreakpoint, missionBreakpoint }, out drainEvidence)) @@ -4620,7 +4912,7 @@ internal static partial class NativeFrameCapture else Log("PROCESS_STATE_AFTER_DETACH unreadable error=" + Marshal.GetLastWin32Error()); } breakpointsRestored = processExited || AreBreakpointsRestored(cameraBreakpoint, - projectionBreakpoint, returnBreakpoint, atmosphereBreakpoint, beginSceneBreakpoint, + projectionBreakpoint, geometryProjectionBreakpoint, skyUvBreakpoint, returnBreakpoint, atmosphereBreakpoint, beginSceneBreakpoint, endSceneBreakpoint, fogConsumerBreakpoint, presentBreakpoint, presentCompletionBreakpoint, missionBreakpoint); Log("FINAL_BREAKPOINT_STATE restored=" + breakpointsRestored + " processExited=" + processExited); @@ -4752,7 +5044,7 @@ internal static partial class NativeFrameCapture } private static bool RestoreArmedBreakpointsWhileStopped(IntPtr process, - BreakpointInfo cameraBreakpoint, BreakpointInfo projectionBreakpoint, BreakpointInfo returnBreakpoint, + BreakpointInfo cameraBreakpoint, BreakpointInfo projectionBreakpoint, BreakpointInfo geometryProjectionBreakpoint, BreakpointInfo skyUvBreakpoint, BreakpointInfo returnBreakpoint, BreakpointInfo atmosphereBreakpoint, BreakpointInfo beginSceneBreakpoint, BreakpointInfo endSceneBreakpoint, BreakpointInfo fogConsumerBreakpoint, BreakpointInfo presentBreakpoint, BreakpointInfo presentCompletionBreakpoint, @@ -4760,6 +5052,8 @@ internal static partial class NativeFrameCapture { return RestoreBreakpointWhileStopped(process, cameraBreakpoint) && RestoreBreakpointWhileStopped(process, projectionBreakpoint) + && RestoreBreakpointWhileStopped(process, geometryProjectionBreakpoint) + && RestoreBreakpointWhileStopped(process, skyUvBreakpoint) && RestoreBreakpointWhileStopped(process, returnBreakpoint) && RestoreBreakpointWhileStopped(process, atmosphereBreakpoint) && RestoreBreakpointWhileStopped(process, beginSceneBreakpoint) @@ -4771,7 +5065,7 @@ internal static partial class NativeFrameCapture } private static bool AreBreakpointsRestored(BreakpointInfo cameraBreakpoint, - BreakpointInfo projectionBreakpoint, BreakpointInfo returnBreakpoint, + BreakpointInfo projectionBreakpoint, BreakpointInfo geometryProjectionBreakpoint, BreakpointInfo skyUvBreakpoint, BreakpointInfo returnBreakpoint, BreakpointInfo atmosphereBreakpoint, BreakpointInfo beginSceneBreakpoint, BreakpointInfo endSceneBreakpoint, BreakpointInfo fogConsumerBreakpoint, BreakpointInfo presentBreakpoint, BreakpointInfo presentCompletionBreakpoint, @@ -4779,6 +5073,8 @@ internal static partial class NativeFrameCapture { return (cameraBreakpoint == null || !cameraBreakpoint.Armed) && (projectionBreakpoint == null || !projectionBreakpoint.Armed) + && (geometryProjectionBreakpoint == null || !geometryProjectionBreakpoint.Armed) + && (skyUvBreakpoint == null || !skyUvBreakpoint.Armed) && (returnBreakpoint == null || !returnBreakpoint.Armed) && (atmosphereBreakpoint == null || !atmosphereBreakpoint.Armed) && (beginSceneBreakpoint == null || !beginSceneBreakpoint.Armed) |
