aboutsummaryrefslogtreecommitdiff
path: root/tools/native-frame-capture/NativeFrameCapture.cs
diff options
context:
space:
mode:
Diffstat (limited to 'tools/native-frame-capture/NativeFrameCapture.cs')
-rw-r--r--tools/native-frame-capture/NativeFrameCapture.cs324
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)