diff --git a/TimeMocker.Hook/InjectionEntryPoint.cs b/TimeMocker.Hook/InjectionEntryPoint.cs index 98e3f84..b02c283 100644 --- a/TimeMocker.Hook/InjectionEntryPoint.cs +++ b/TimeMocker.Hook/InjectionEntryPoint.cs @@ -14,7 +14,7 @@ namespace TimeMocker.Hook [StructLayout(LayoutKind.Sequential)] public struct MockTimeInfo { - public long FakeUtcTicks; // DateTime ticks (UTC) + public long DeltaTicks; // Offset to add to DateTime.UtcNow.Ticks (can be negative) public int Enabled; // 1 = mock active, 0 = passthrough } @@ -108,9 +108,12 @@ namespace TimeMocker.Hook private DateTime GetFakeUtc() { var info = ReadMockInfo(); - return info.Enabled == 1 - ? new DateTime(info.FakeUtcTicks, DateTimeKind.Utc) - : DateTime.UtcNow; + if (info.Enabled == 1) + { + // Return real UTC time plus the stored delta offset + return new DateTime(DateTime.UtcNow.Ticks + info.DeltaTicks, DateTimeKind.Utc); + } + return DateTime.UtcNow; } private void InstallHooks() diff --git a/TimeMocker.UI/Core/InjectionManager.cs b/TimeMocker.UI/Core/InjectionManager.cs index 545f7be..7b4189e 100644 --- a/TimeMocker.UI/Core/InjectionManager.cs +++ b/TimeMocker.UI/Core/InjectionManager.cs @@ -44,7 +44,7 @@ namespace TimeMocker.UI.Core try { // Write disabled state initially so hook passes through - entry.Shm.Write(new MockTimeInfo { Enabled = 0, FakeUtcTicks = DateTime.UtcNow.Ticks }); + entry.Shm.Write(new MockTimeInfo { Enabled = 0, DeltaTicks = 0 }); RemoteHooking.Inject( process.Id, @@ -74,9 +74,20 @@ namespace TimeMocker.UI.Core { if (!_injected.TryGetValue(processId, out var entry)) return; + long deltaTicks; + if (enabled) + { + // Calculate delta: (desired fake time) - (current real UTC time) + deltaTicks = fakeUtc.Ticks - DateTime.UtcNow.Ticks; + } + else + { + deltaTicks = 0; + } + entry.Shm.Write(new MockTimeInfo { - FakeUtcTicks = fakeUtc.Ticks, + DeltaTicks = deltaTicks, Enabled = enabled ? 1 : 0 }); } @@ -100,7 +111,7 @@ namespace TimeMocker.UI.Core public void Eject(int processId) { if (!_injected.TryGetValue(processId, out var entry)) return; - entry.Shm.Write(new MockTimeInfo { Enabled = 0 }); // disable mock first + entry.Shm.Write(new MockTimeInfo { Enabled = 0, DeltaTicks = 0 }); // disable mock first entry.Shm.Dispose(); _injected.Remove(processId); Log($"Ejected from [{processId}] {entry.ProcessName}"); diff --git a/TimeMocker.UI/Core/SharedMemoryManager.cs b/TimeMocker.UI/Core/SharedMemoryManager.cs index 1d35d60..b7b0384 100644 --- a/TimeMocker.UI/Core/SharedMemoryManager.cs +++ b/TimeMocker.UI/Core/SharedMemoryManager.cs @@ -46,7 +46,7 @@ namespace TimeMocker.UI.Core [StructLayout(LayoutKind.Sequential)] public struct MockTimeInfo { - public long FakeUtcTicks; - public int Enabled; + public long DeltaTicks; // Offset to add to DateTime.UtcNow.Ticks (can be negative) + public int Enabled; // 1 = mock active, 0 = passthrough } } \ No newline at end of file diff --git a/TimeMocker.UI/Forms/MainForm.cs b/TimeMocker.UI/Forms/MainForm.cs index a41951b..0afdb6d 100644 --- a/TimeMocker.UI/Forms/MainForm.cs +++ b/TimeMocker.UI/Forms/MainForm.cs @@ -25,7 +25,6 @@ namespace TimeMocker.UI.Forms "timemocker-config.json"); public bool AutoInjectEnabled { get; set; } = true; - public bool AutoAdvanceEnabled { get; set; } = true; public List Patterns { get; set; } = new List(); public void Save() @@ -70,7 +69,6 @@ namespace TimeMocker.UI.Forms private CheckBox chkMockEnabled; private Button btnApply; private Label lblPreview; - private CheckBox chkAutoAdvance; // -- Patterns tab private DataGridView dgvPatterns; @@ -83,11 +81,6 @@ namespace TimeMocker.UI.Forms private RichTextBox rtbLog; private Button btnClearLog; - // Time advancing - private Timer _advanceTimer; - private DateTime _fakeTimeBase; - private DateTime _advanceStartReal; - public MainForm() { _config = AppConfig.Load(); @@ -112,7 +105,6 @@ namespace TimeMocker.UI.Forms // Load config settings chkWatcherEnabled.Checked = _config.AutoInjectEnabled; - chkAutoAdvance.Checked = _config.AutoAdvanceEnabled; foreach (var pattern in _config.Patterns) { _watcher.AddRule(new PatternRule @@ -194,21 +186,10 @@ namespace TimeMocker.UI.Forms ApplyTime(); }; - chkAutoAdvance = new CheckBox - { - Text = "Auto-advance time", - ForeColor = Color.FromArgb(220, 220, 220), - Width = 140, - Height = 30, - Margin = new Padding(8, 12, 4, 0), - Checked = true - }; - chkAutoAdvance.CheckedChanged += ToggleAutoAdvance; - lblPreview = new Label { AutoSize = false, - Width = 260, + Width = 300, Height = 20, ForeColor = Color.FromArgb(180, 180, 180), Font = new Font("Segoe UI", 8.5f, FontStyle.Italic), @@ -217,8 +198,7 @@ namespace TimeMocker.UI.Forms timeFlow.Controls.AddRange(new Control[] { - chkMockEnabled, dtpDate, dtpTime, btnApply, btnSetNow, - chkAutoAdvance, lblPreview + chkMockEnabled, dtpDate, dtpTime, btnApply, btnSetNow, lblPreview }); grpTime.Controls.Add(timeFlow); @@ -247,10 +227,6 @@ namespace TimeMocker.UI.Forms Controls.Add(tabMain); Controls.Add(grpTime); - - // advance timer - _advanceTimer = new Timer { Interval = 1000 }; - _advanceTimer.Tick += AdvanceTick; } // ===================================================================== @@ -573,52 +549,28 @@ namespace TimeMocker.UI.Forms _watcher.FakeUtc = dt; _watcher.MockEnabled = chkMockEnabled.Checked; - if (chkAutoAdvance.Checked) - { - _fakeTimeBase = GetFakeTime(); - _advanceStartReal = DateTime.Now; - } - UpdateTimePreview(); } private void UpdateTimePreview() { var dt = GetFakeTime(); - lblPreview.Text = chkMockEnabled.Checked - ? $"Fake: {dt:yyyy-MM-dd HH:mm:ss} (local)" - : "Pass-through (real time)"; - } - - private void ToggleAutoAdvance(object sender, EventArgs e) - { - if (chkAutoAdvance.Checked) + if (chkMockEnabled.Checked) { - _fakeTimeBase = GetFakeTime(); - _advanceStartReal = DateTime.Now; - _advanceTimer.Start(); + // Calculate what the current offset is + var realNow = DateTime.Now; + var delta = dt - realNow; + var deltaStr = delta.TotalSeconds >= 0 + ? $"+{delta.TotalSeconds:F0}s" + : $"{delta.TotalSeconds:F0}s"; + lblPreview.Text = $"Fake: {dt:yyyy-MM-dd HH:mm:ss} (local, offset {deltaStr})"; } else { - _advanceTimer.Stop(); + lblPreview.Text = "Pass-through (real time)"; } } - private void AdvanceTick(object sender, EventArgs e) - { - if (!chkMockEnabled.Checked) return; - var elapsed = DateTime.Now - _advanceStartReal; - var advanced = _fakeTimeBase + elapsed; - // Silently update dtpDate/dtpTime without triggering ValueChanged loop - dtpDate.ValueChanged -= (s, ev) => UpdateTimePreview(); - dtpTime.ValueChanged -= (s, ev) => UpdateTimePreview(); - dtpDate.Value = advanced; - dtpTime.Value = advanced; - dtpDate.ValueChanged += (s, ev) => UpdateTimePreview(); - dtpTime.ValueChanged += (s, ev) => UpdateTimePreview(); - ApplyTime(); - } - // ===================================================================== // Helpers // ===================================================================== @@ -738,7 +690,6 @@ namespace TimeMocker.UI.Forms { // Save config _config.AutoInjectEnabled = chkWatcherEnabled.Checked; - _config.AutoAdvanceEnabled = chkAutoAdvance.Checked; _config.Patterns.Clear(); foreach (DataGridViewRow row in dgvPatterns.Rows) {