using System; using System.Collections.Generic; using System.Diagnostics; using System.Drawing; using System.IO; using System.Linq; using System.Text.Json; using System.Windows.Forms; using TimeMocker.UI.Core; namespace TimeMocker.UI.Forms { public class PatternRuleDto { public string Pattern { get; set; } public bool UseRegex { get; set; } public bool Enabled { get; set; } = true; } public class AppConfig { private static readonly string ConfigPath = Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "TimeMocker", "timemocker-config.json"); public bool AutoInjectEnabled { get; set; } = true; public List Patterns { get; set; } = new List(); public void Save() { var dir = Path.GetDirectoryName(ConfigPath); if (!string.IsNullOrEmpty(dir) && !Directory.Exists(dir)) { Directory.CreateDirectory(dir); } File.WriteAllText(ConfigPath, JsonSerializer.Serialize(this, new JsonSerializerOptions { WriteIndented = true })); } public static AppConfig Load() { return File.Exists(ConfigPath) ? JsonSerializer.Deserialize(File.ReadAllText(ConfigPath)) ?? new AppConfig() : new AppConfig(); } } public partial class MainForm : Form { private readonly AppConfig _config; private InjectionManager _injMgr; private ProcessWatcher _watcher; // Controls private TabControl tabMain; private TabPage tabProcesses, tabPatterns, tabLog; // -- Process tab private DataGridView dgvProcesses; private Button btnRefresh, btnInject, btnEject; private TextBox txtProcSearch; private Label lblProcSearch; // -- Time panel (shared) private GroupBox grpTime; private DateTimePicker dtpDate; private DateTimePicker dtpTime; private Button btnApply; private Label lblPreview; // -- Patterns tab private DataGridView dgvPatterns; private Button btnAddPattern, btnRemovePattern; private CheckBox chkWatcherEnabled; private TextBox txtNewPattern; private RadioButton rdoGlob, rdoRegex; // -- Log tab private RichTextBox rtbLog; private Button btnClearLog; public MainForm() { _config = AppConfig.Load(); Text = "TimeMocker – Process Time Injection"; Size = new Size(900, 680); MinimumSize = new Size(750, 560); StartPosition = FormStartPosition.CenterScreen; Font = new Font("Segoe UI", 9f); BackColor = Color.FromArgb(45, 52, 64); ForeColor = Color.FromArgb(200, 200, 200); _injMgr = new InjectionManager(); _watcher = new ProcessWatcher(_injMgr); _injMgr.LogMessage += AppendLog; _watcher.LogMessage += AppendLog; _watcher.ProcessAutoInjected += entry => BeginInvoke((Action)(() => RefreshInjectedTab())); BuildUI(); // Load config settings chkWatcherEnabled.Checked = _config.AutoInjectEnabled; foreach (var pattern in _config.Patterns) { _watcher.AddRule(new PatternRule { Pattern = pattern.Pattern, UseRegex = pattern.UseRegex, Enabled = pattern.Enabled }); dgvPatterns.Rows.Add(pattern.Pattern, pattern.UseRegex ? "Regex" : "Glob", pattern.Enabled); } RefreshProcessList(); UpdateTimePreview(); ApplyTime(); // Initialize with current time } // ===================================================================== // UI Builder // ===================================================================== private void BuildUI() { // ---- Shared time panel (top) ------------------------------------ grpTime = new GroupBox { Text = "Mock Time Settings", Dock = DockStyle.Top, Height = 110, ForeColor = Color.FromArgb(100, 160, 220), Padding = new Padding(8) }; var timeFlow = new FlowLayoutPanel { Dock = DockStyle.Fill, FlowDirection = FlowDirection.LeftToRight, WrapContents = false, AutoSize = false }; dtpDate = new DateTimePicker { Format = DateTimePickerFormat.Short, Width = 120, Height = 26, Value = DateTime.Now, Margin = new Padding(4, 10, 4, 0) }; dtpDate.ValueChanged += (s, e) => UpdateTimePreview(); dtpTime = new DateTimePicker { Format = DateTimePickerFormat.Time, ShowUpDown = true, Width = 100, Height = 26, Value = DateTime.Now, Margin = new Padding(4, 10, 4, 0) }; dtpTime.ValueChanged += (s, e) => UpdateTimePreview(); btnApply = MakeButton("Apply to All", 100, Color.FromArgb(70, 140, 200)); btnApply.Margin = new Padding(8, 10, 4, 0); btnApply.Click += (s, e) => ApplyTime(); var btnSetNow = MakeButton("Set to Now", 90, Color.FromArgb(100, 110, 120)); btnSetNow.Margin = new Padding(4, 10, 4, 0); btnSetNow.Click += (s, e) => { dtpDate.Value = dtpTime.Value = DateTime.Now; ApplyTime(); }; lblPreview = new Label { AutoSize = false, Width = 300, Height = 20, ForeColor = Color.FromArgb(140, 150, 160), Font = new Font("Segoe UI", 8.5f, FontStyle.Italic), Margin = new Padding(4, 14, 0, 0) }; timeFlow.Controls.AddRange(new Control[] { dtpDate, dtpTime, btnApply, btnSetNow, lblPreview }); grpTime.Controls.Add(timeFlow); // ---- Tabs ------------------------------------------------------- tabMain = new TabControl { Dock = DockStyle.Fill, DrawMode = TabDrawMode.OwnerDrawFixed, SizeMode = TabSizeMode.Fixed, ItemSize = new Size(120, 28) }; tabMain.DrawItem += DrawTab; tabProcesses = new TabPage("Processes"); tabPatterns = new TabPage("Auto-Inject Rules"); tabLog = new TabPage("Log"); StyleTab(tabProcesses); StyleTab(tabPatterns); StyleTab(tabLog); BuildProcessTab(); BuildPatternsTab(); BuildLogTab(); tabMain.TabPages.AddRange(new[] { tabProcesses, tabPatterns, tabLog }); Controls.Add(tabMain); Controls.Add(grpTime); } // ===================================================================== // Process Tab // ===================================================================== private void BuildProcessTab() { var panel = new Panel { Dock = DockStyle.Fill }; // Top toolbar var toolbar = new FlowLayoutPanel { Dock = DockStyle.Top, Height = 40, Padding = new Padding(4) }; lblProcSearch = new Label { Text = "Search:", AutoSize = true, Margin = new Padding(4, 8, 2, 0) }; txtProcSearch = new TextBox { Width = 180, Margin = new Padding(0, 6, 8, 0) }; txtProcSearch.TextChanged += (s, e) => FilterProcessList(); btnRefresh = MakeButton("⟳ Refresh", 90, Color.FromArgb(100, 110, 120)); btnRefresh.Margin = new Padding(0, 6, 4, 0); btnRefresh.Click += (s, e) => RefreshProcessList(); btnInject = MakeButton("Inject →", 90, Color.FromArgb(80, 160, 100)); btnInject.Margin = new Padding(0, 6, 4, 0); btnInject.Click += OnInjectClick; btnEject = MakeButton("✕ Eject", 80, Color.FromArgb(190, 90, 90)); btnEject.Margin = new Padding(0, 6, 4, 0); btnEject.Click += OnEjectClick; toolbar.Controls.AddRange(new Control[] { lblProcSearch, txtProcSearch, btnRefresh, btnInject, btnEject }); toolbar.BackColor = Color.FromArgb(55, 62, 74); // Grid – split: available processes | injected processes var split = new SplitContainer { Dock = DockStyle.Fill, Orientation = Orientation.Horizontal, SplitterDistance = 300, SplitterWidth = 5, BackColor = Color.FromArgb(65, 72, 84) }; // Top: available var lblAvail = MakeSectionLabel("Running Processes"); dgvProcesses = MakeGrid(); dgvProcesses.Columns.AddRange( Col("PID", 50), Col("Name", 160), Col("Path", 380)); var topPanel = new Panel { Dock = DockStyle.Fill }; topPanel.Controls.Add(dgvProcesses); topPanel.Controls.Add(lblAvail); // Bottom: injected var lblInj = MakeSectionLabel("Injected Processes"); var dgvInjected = MakeGrid(); dgvInjected.Tag = "injected"; dgvInjected.Columns.AddRange( Col("PID", 50), Col("Name", 160), Col("Path", 380), Col("Status", 80)); var botPanel = new Panel { Dock = DockStyle.Fill }; botPanel.Controls.Add(dgvInjected); botPanel.Controls.Add(lblInj); split.Panel1.Controls.Add(topPanel); split.Panel2.Controls.Add(botPanel); panel.Controls.Add(split); panel.Controls.Add(toolbar); // store for refresh _dgvInjected = dgvInjected; tabProcesses.Controls.Add(panel); } private DataGridView _dgvInjected; private List _allRows = new List(); private class ProcessRow { public int Id; public string Name, Path; } private void RefreshProcessList() { _allRows.Clear(); foreach (var p in Process.GetProcesses().OrderBy(x => x.ProcessName)) { var path = ""; try { path = p.MainModule?.FileName ?? ""; } catch { } _allRows.Add(new ProcessRow { Id = p.Id, Name = p.ProcessName, Path = path }); } FilterProcessList(); RefreshInjectedTab(); } private void FilterProcessList() { var q = txtProcSearch.Text.Trim().ToLower(); dgvProcesses.Rows.Clear(); foreach (var r in _allRows) { if (q.Length > 0 && !r.Name.ToLower().Contains(q) && !r.Path.ToLower().Contains(q)) continue; dgvProcesses.Rows.Add(r.Id, r.Name, r.Path); } } private void RefreshInjectedTab() { if (_dgvInjected == null) return; _dgvInjected.Rows.Clear(); foreach (var e in _injMgr.InjectedProcesses) _dgvInjected.Rows.Add(e.ProcessId, e.ProcessName, e.ProcessPath, e.IsInjected ? "Active" : "Pending"); } private void OnInjectClick(object sender, EventArgs e) { var selected = GetSelectedProcessId(dgvProcesses); if (selected == null) { ShowInfo("Select a process first."); return; } try { var p = Process.GetProcessById(selected.Value); _injMgr.Inject(p); var dt = GetFakeTime(); _injMgr.SetFakeTime(p.Id, dt.ToUniversalTime(), enabled: true); RefreshInjectedTab(); AppendLog($"Manually injected into [{p.Id}] {p.ProcessName}"); } catch (Exception ex) { MessageBox.Show($"Injection failed:\n{ex.Message}", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); } } private void OnEjectClick(object sender, EventArgs e) { var selected = GetSelectedProcessId(_dgvInjected); if (selected == null) { ShowInfo("Select an injected process first."); return; } _injMgr.Eject(selected.Value); RefreshInjectedTab(); } // ===================================================================== // Patterns Tab // ===================================================================== private void BuildPatternsTab() { var panel = new Panel { Dock = DockStyle.Fill }; var toolbar = new FlowLayoutPanel { Dock = DockStyle.Top, Height = 80, Padding = new Padding(4), BackColor = Color.FromArgb(55, 62, 74) }; // Pattern input row var lblNew = new Label { Text = "Pattern:", AutoSize = true, Margin = new Padding(4, 12, 4, 0) }; txtNewPattern = new TextBox { Width = 280, Margin = new Padding(0, 10, 4, 0), Text = "e.g. C:\\Games\\MyGame\\* or ^.*chrome.*$" }; rdoGlob = new RadioButton { Text = "Glob", Checked = true, AutoSize = true, Margin = new Padding(4, 12, 4, 0), ForeColor = Color.FromArgb(200, 200, 200) }; rdoRegex = new RadioButton { Text = "Regex", AutoSize = true, Margin = new Padding(4, 12, 4, 0), ForeColor = Color.FromArgb(200, 200, 200) }; btnAddPattern = MakeButton("+ Add Rule", 100, Color.FromArgb(80, 160, 100)); btnAddPattern.Margin = new Padding(8, 8, 4, 0); btnAddPattern.Click += OnAddPattern; btnRemovePattern = MakeButton("✕ Remove", 90, Color.FromArgb(190, 90, 90)); btnRemovePattern.Margin = new Padding(4, 8, 4, 0); btnRemovePattern.Click += OnRemovePattern; chkWatcherEnabled = new CheckBox { Text = "Enable Auto-Inject Watcher", ForeColor = Color.FromArgb(120, 190, 120), AutoSize = true, Margin = new Padding(16, 12, 4, 0), Checked = true }; chkWatcherEnabled.CheckedChanged += OnWatcherToggle; toolbar.Controls.AddRange(new Control[] { lblNew, txtNewPattern, rdoGlob, rdoRegex, btnAddPattern, btnRemovePattern, chkWatcherEnabled }); var lblSection = MakeSectionLabel("Auto-Inject Rules (process path or name must match)"); dgvPatterns = MakeGrid(); dgvPatterns.Dock = DockStyle.Fill; dgvPatterns.Columns.AddRange( Col("Pattern", 320), Col("Type", 60), BoolCol("Enabled")); panel.Controls.Add(dgvPatterns); panel.Controls.Add(lblSection); panel.Controls.Add(toolbar); tabPatterns.Controls.Add(panel); } private void OnAddPattern(object sender, EventArgs e) { var pat = txtNewPattern.Text.Trim(); if (string.IsNullOrEmpty(pat)) return; var rule = new PatternRule { Pattern = pat, UseRegex = rdoRegex.Checked, Enabled = true }; _watcher.AddRule(rule); dgvPatterns.Rows.Add(pat, rule.UseRegex ? "Regex" : "Glob", true); txtNewPattern.Clear(); } private void OnRemovePattern(object sender, EventArgs e) { if (dgvPatterns.SelectedRows.Count == 0) return; var idx = dgvPatterns.SelectedRows[0].Index; var pat = dgvPatterns.Rows[idx].Cells[0].Value?.ToString(); _watcher.ClearRules(); dgvPatterns.Rows.RemoveAt(idx); // Re-add remaining foreach (DataGridViewRow row in dgvPatterns.Rows) _watcher.AddRule(new PatternRule { Pattern = row.Cells[0].Value?.ToString() ?? "", UseRegex = row.Cells[1].Value?.ToString() == "Regex", Enabled = (bool)(row.Cells[2].Value ?? true) }); } private void OnWatcherToggle(object sender, EventArgs e) { if (chkWatcherEnabled.Checked) { _watcher.FakeUtc = GetFakeTime().ToUniversalTime(); _watcher.MockEnabled = true; _watcher.Start(); AppendLog("Process watcher started."); } else { _watcher.Stop(); AppendLog("Process watcher stopped."); } } // ===================================================================== // Log Tab // ===================================================================== private void BuildLogTab() { rtbLog = new RichTextBox { Dock = DockStyle.Fill, BackColor = Color.FromArgb(30, 35, 42), ForeColor = Color.FromArgb(170, 210, 150), Font = new Font("Consolas", 9f), ReadOnly = true, ScrollBars = RichTextBoxScrollBars.Vertical }; btnClearLog = MakeButton("Clear", 70, Color.FromArgb(100, 110, 120)); btnClearLog.Dock = DockStyle.Bottom; btnClearLog.Click += (s, e) => rtbLog.Clear(); tabLog.Controls.Add(rtbLog); tabLog.Controls.Add(btnClearLog); } // ===================================================================== // Time Logic // ===================================================================== private DateTime GetFakeTime() { return dtpDate.Value.Date + dtpTime.Value.TimeOfDay; } private void ApplyTime() { var dt = GetFakeTime().ToUniversalTime(); _injMgr.SetFakeTimeAll(dt, enabled: true); _watcher.FakeUtc = dt; _watcher.MockEnabled = true; UpdateTimePreview(); } private void UpdateTimePreview() { var dt = GetFakeTime(); // 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})"; } // ===================================================================== // Helpers // ===================================================================== private void AppendLog(string msg) { if (rtbLog == null) return; if (rtbLog.InvokeRequired) { rtbLog.BeginInvoke((Action)(() => AppendLog(msg))); return; } rtbLog.AppendText($"[{DateTime.Now:HH:mm:ss}] {msg}\n"); rtbLog.ScrollToCaret(); } private int? GetSelectedProcessId(DataGridView dgv) { if (dgv.SelectedRows.Count == 0) return null; var cell = dgv.SelectedRows[0].Cells[0].Value; return cell is int i ? i : (int?)null; } private static DataGridView MakeGrid() { var g = new DataGridView { Dock = DockStyle.Fill, ReadOnly = true, AllowUserToAddRows = false, AllowUserToDeleteRows = false, SelectionMode = DataGridViewSelectionMode.FullRowSelect, MultiSelect = false, BackgroundColor = Color.FromArgb(52, 58, 68), ForeColor = Color.FromArgb(175, 180, 185), GridColor = Color.FromArgb(85, 92, 102), BorderStyle = BorderStyle.None, RowHeadersVisible = false, AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.None, ColumnHeadersHeight = 28 }; g.EnableHeadersVisualStyles = false; g.ColumnHeadersDefaultCellStyle.BackColor = Color.FromArgb(72, 78, 88); g.ColumnHeadersDefaultCellStyle.ForeColor = Color.FromArgb(160, 185, 210); g.DefaultCellStyle.SelectionBackColor = Color.FromArgb(98, 138, 178); g.AlternatingRowsDefaultCellStyle.BackColor = Color.FromArgb(58, 64, 74); return g; } private static DataGridViewTextBoxColumn Col(string name, int w) { return new DataGridViewTextBoxColumn { HeaderText = name, Width = w, SortMode = DataGridViewColumnSortMode.NotSortable }; } private static DataGridViewCheckBoxColumn BoolCol(string name) { return new DataGridViewCheckBoxColumn { HeaderText = name, Width = 65 }; } private static Button MakeButton(string text, int w, Color bg) { return new Button { Text = text, Width = w, Height = 26, BackColor = bg, ForeColor = Color.White, FlatStyle = FlatStyle.Flat, Cursor = Cursors.Hand }; } private static Label MakeSectionLabel(string text) { return new Label { Text = text, Dock = DockStyle.Top, Height = 22, ForeColor = Color.FromArgb(150, 180, 220), Font = new Font("Segoe UI", 8.5f, FontStyle.Bold), Padding = new Padding(4, 2, 0, 0), BackColor = Color.FromArgb(50, 57, 69) }; } private static void StyleTab(TabPage tab) { tab.BackColor = Color.FromArgb(45, 52, 64); tab.ForeColor = Color.FromArgb(200, 200, 200); } private void DrawTab(object sender, DrawItemEventArgs e) { var tab = (TabControl)sender; var page = tab.TabPages[e.Index]; var rect = e.Bounds; var selected = e.Index == tab.SelectedIndex; using var bg = new SolidBrush(selected ? Color.FromArgb(80, 130, 180) : Color.FromArgb(60, 68, 80)); e.Graphics.FillRectangle(bg, rect); var sf = new StringFormat { Alignment = StringAlignment.Center, LineAlignment = StringAlignment.Center }; using var fg = new SolidBrush(selected ? Color.White : Color.FromArgb(170, 180, 190)); e.Graphics.DrawString(page.Text, Font, fg, rect, sf); } private static void ShowInfo(string msg) { MessageBox.Show(msg, "Info", MessageBoxButtons.OK, MessageBoxIcon.Information); } protected override void OnFormClosing(FormClosingEventArgs e) { // Save config _config.AutoInjectEnabled = chkWatcherEnabled.Checked; _config.Patterns.Clear(); foreach (DataGridViewRow row in dgvPatterns.Rows) { _config.Patterns.Add(new PatternRuleDto { Pattern = row.Cells[0].Value?.ToString() ?? "", UseRegex = row.Cells[1].Value?.ToString() == "Regex", Enabled = (bool)(row.Cells[2].Value ?? true) }); } _config.Save(); _watcher.Stop(); _injMgr.Dispose(); base.OnFormClosing(e); } } }