From 40ae508ceb1c7e35e9ce891f2d9e30a8d15b222a Mon Sep 17 00:00:00 2001 From: tiennm99 Date: Wed, 25 Feb 2026 16:10:15 +0700 Subject: [PATCH] chore: init claude.md --- CLAUDE.md | 141 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 141 insertions(+) create mode 100644 CLAUDE.md diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..14cd0b0 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,141 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +TimeMocker is a Windows application that injects fake time into running processes by hooking Win32 time APIs. It consists of a WinForms UI controller and an EasyHook-based DLL that gets injected into target processes. + +## Build Commands + +```bash +# Build the solution (x64 only) +dotnet build TimeMocker.sln -c Release -p:Platform=x64 + +# Build Debug configuration +dotnet build TimeMocker.sln -c Debug -p:Platform=x64 + +# Clean and rebuild +dotnet clean TimeMocker.sln && dotnet build TimeMocker.sln -c Release -p:Platform=x64 +``` + +**Important**: This is an x64-only project. The solution only has x64 platform configurations. Building for x86 or AnyCPU will fail. + +**Output locations**: +- UI exe: `TimeMocker.UI/bin/x64/Release/net48/TimeMocker.exe` +- Hook DLL: `TimeMocker.UI/bin/x64/Release/net48/TimeMocker.Hook.dll` + +The hook DLL must be next to the UI exe (automatically copied via ProjectReference). + +## Architecture + +``` +TimeMocker.sln +├── TimeMocker.UI/ — WinForms controller (requires Admin elevation) +│ ├── Forms/MainForm.cs — Main UI with process selection, time picker, pattern manager +│ ├── Core/InjectionManager.cs — EasyHook-based injector, manages injected processes +│ ├── Core/ProcessWatcher.cs — Background scanner for auto-inject rules (poll-based) +│ └── Core/SharedMemoryManager.cs — Named MMF for IPC with hook DLL +│ +└── TimeMocker.Hook/ — DLL injected into target processes + └── InjectionEntryPoint.cs — Hooks 5 Win32 time APIs via EasyHook +``` + +### IPC Design + +The fake time is stored in a **named Memory-Mapped File** (one per injected process): + +- Name format: `TimeMocker_` +- Size: 12 bytes (`Marshal.SizeOf()`) +- Layout: + - `[0..7]` FakeUtcTicks (Int64 — DateTime.Ticks) + - `[8..11]` Enabled (Int32 — 0=passthrough, 1=mock) + +The hook reads this on every time API call (~50 ns read, no syscall). The UI writes via `SharedMemoryManager.Write()`. + +### Hooked APIs + +| API | DLL | +|-----|-----| +| `GetSystemTime` | kernel32 | +| `GetLocalTime` | kernel32 | +| `GetSystemTimeAsFileTime` | kernel32 | +| `GetSystemTimePreciseAsFileTime` | kernel32 | +| `NtQuerySystemTime` | ntdll | + +All hooked functions share the same `GetFakeUtc()` logic which reads from shared memory and either returns the fake time or real `DateTime.UtcNow` depending on the Enabled flag. + +### Auto-Inject Pattern Matching + +The `ProcessWatcher` supports both glob and regex patterns for matching process paths or names: + +- Glob: `C:\Games\MyGame\*` or `*chrome*` (converted to regex internally) +- Regex: `^.*\\MyApp\.exe$` + +Patterns are matched against both the full process path (`MainModule.FileName`) and process name (`ProcessName`). + +## Key Components + +### InjectionManager + +- Manages the dictionary of injected processes (`Dictionary`) +- Uses `RemoteHooking.Inject()` to inject `TimeMocker.Hook.dll` into target processes +- Creates a `SharedMemoryManager` instance per process for IPC +- Handles eject by disabling the mock (setting Enabled=0) before disposing shared memory + +### SharedMemoryManager + +- Creates a named memory-mapped file using `MemoryMappedFile.CreateOrOpen()` +- `MockTimeInfo` struct is blittable (sequential layout) for direct memory write +- Must be disposed when process is ejected + +### ProcessWatcher + +- Polls running processes every ~1.5 seconds (configurable) +- Tracks seen process IDs to avoid duplicate injections +- Auto-injects when a process matches any enabled rule +- Uses current `FakeUtc` and `MockEnabled` settings at injection time + +### InjectionEntryPoint (Hook DLL) + +- Implements `IEntryPoint` from EasyHook +- `Run()` method opens the named MMF, installs hooks, and keeps alive with a sleep loop +- Uses `LocalHook.Create()` with thread ACL set to exclude thread 0 (all threads) +- Hook delegates read the shared memory on each call (hot path optimized) +- Logs errors to `%TEMP%\TimeMocker.Hook.log` + +## Development Notes + +- **Target Framework**: .NET Framework 4.8 (pre-installed on Windows 10+) +- **Unsafe Code**: Both projects use `AllowUnsafeBlocks=true` for memory-mapped file operations +- **Dependencies**: EasyHook 2.7.7030.0 (available via NuGet) +- **Language Version**: C# 8 + +### Limitations + +- **64-bit only**: 32-bit processes require a separate 32-bit hook DLL build +- **No hot-unload support**: EasyHook doesn't fully support DLL ejection; disable mock instead +- **Protected processes**: Anti-cheat services (Epic, BattlEye, etc.) will block injection +- **QueryPerformanceCounter**: Not affected (hardware register, cannot be hooked via EasyHook) + +### Time Epoch + +FILETIME epoch is January 1, 1601 (UTC). Hooked functions returning FILETIME convert from DateTime.Ticks using: +```csharp +var epoch = new DateTime(1601, 1, 1, 0, 0, 0, DateTimeKind.Utc); +long fileTimeTicks = (fakeUtc - epoch).Ticks; +``` + +## Common Tasks + +### Add a new time API hook +1. Add the hook delegate in `InjectionEntryPoint.cs` with `[UnmanagedFunctionPointer(CallingConvention.StdCall)]` +2. Add a `LocalHook` field for the hook handle +3. Create the hook in `InstallHooks()` using `LocalHook.GetProcAddress(dllName, functionName)` +4. Implement the hook function that calls `GetFakeUtc()` and returns the appropriate format + +### Modify auto-inject polling interval +Pass a different interval to `ProcessWatcher.Start(int pollIntervalMs)` when enabling the watcher in `MainForm.cs`. + +### Debug hook DLL +The hook writes errors to `%TEMP%\TimeMocker.Hook.log`. For more detailed debugging, you may need to attach a debugger to the target process after injection.