Workspace-wide refactor that fixes correctness, safety, and concurrency issues across the three crates while preserving the public CLI/UX surface. core: - Split SharedDelta into SharedDeltaReader / SharedDeltaWriter so the access-mode (FILE_MAP_READ vs ALL_ACCESS) is encoded in the type. - Use AtomicI64::from_ptr on the page-aligned mapped view instead of a raw *mut AtomicI64 cast. - Detect ERROR_ALREADY_EXISTS via CreateOutcome and surface it. - Move MMF name to the Global\ namespace so cross-session injection is no longer silently scoped to the controller's session. - Add tick-math round-trip tests (i64 + SYSTEMTIME boundary cases). hook: - install_hook! macro collapses the five hook installs to a table; per-hook failures are collected into InstallReport instead of aborting mid-chain and leaving a partial state armed. - Unify fake_filetime helpers behind a single trampoline-parameterised fn. - Fix GetLocalTime: previously returned UTC; now goes FILETIME(UTC) -> SYSTEMTIME -> SystemTimeToTzSpecificLocalTime so DST is resolved against the source date, matching real GetLocalTime. - Replace thread::spawn from DllMain with raw CreateThread and DisableThreadLibraryCalls(hinst) to avoid loader-lock deadlocks; close the returned thread handle to plug a per-injection kernel leak. - Propagate the real NtQuerySystemTime NTSTATUS instead of always returning STATUS_SUCCESS. - Return STATUS_ACCESS_VIOLATION on null out-pointer. - Pipe InstallReport + MMF-open failures to OutputDebugStringW for DbgView visibility in the target process. ui: - New win32_process_info module: pe_machine reads up to 64 KiB so PEs with large e_lfanew values parse cleanly; query_full_image_name and is_native_x64 (IsWow64Process2) gate inject against PID reuse and WoW64 / non-AMD64 targets. - Drop for InjectionManager zeroes every injected process's delta so targets return to real time on UI exit. - inject() reorders checks so the system-process guard runs against the filename derived from the live image path, not the stale watcher snapshot; failures are pushed to the ring-buffer log so auto-inject loops are no longer silent. - Refuse to inject critical Windows processes (csrss, smss, lsass, services, svchost, MsMpEng, ...) explicitly. - Switch the log from unbounded Vec to a VecDeque ring buffer (cap 1000). - Rename eject -> disable to match what it actually does (zero delta, keep DLL loaded). - Unicode-aware paths_equivalent via to_lowercase comparison so non-ASCII case differences don't yield false-positive PID-reuse errors. - app.rs date/time picker: switch to DragValue so mid-typing keystrokes don't rewrite the date; clamp year 1970-2200; checked arithmetic on unix_micros -> FILETIME so far-future inputs don't silently overflow; surface DST-gap status to the user; "Now" auto-applies. - Add 51 unit tests across mmf, PE parser, system-process guards, time math; expose helpers via pub(crate) for test access. - Drop unused serde_json and thiserror deps. Total: 56/56 tests passing, cargo clippy clean, cargo build --release produces time_mocker_ui.exe and time_mocker_hook.dll.
TimeMocker
A Windows tool that injects fake time into running processes by hooking Win32 time APIs. Written in Rust.
Status: Active development. This is the canonical implementation. Language-specific ports are available in C# (EasyHook) and C++ (MS Detours).
Architecture
time-mocker-rs/
├── crates/
│ ├── time-mocker-core/ — shared types (MockTimeInfo) + named MMF helper + tick conversions
│ ├── time-mocker-hook/ — cdylib injected into target processes; hooks 5 time APIs via retour
│ └── time-mocker-ui/ — egui controller binary; injects via dll-syringe, writes delta per PID
Hooked APIs
| API | DLL |
|---|---|
GetSystemTime |
kernel32 |
GetLocalTime |
kernel32 |
GetSystemTimeAsFileTime |
kernel32 |
GetSystemTimePreciseAsFileTime |
kernel32 |
NtQuerySystemTime |
ntdll |
IPC Design
Named Memory-Mapped File per injected process:
Name: TimeMocker_<PID>
Size: 8 bytes
[0..7] DeltaTicks (i64 — 100-ns units, added to the real FILETIME)
The hook reads the delta on every time API call and returns real_filetime + delta. The controller writes the delta whenever the user picks a new fake time.
Tick epoch difference vs the C# version: The C# version stores a delta against
DateTime.UtcNow.Ticks(epoch 0001-01-01 UTC). The Rust version stores a delta in raw FILETIME units (epoch 1601-01-01 UTC). The two IPC contracts are not interoperable — the Rust UI and Rust hook DLL only talk to each other.
Build
# Nightly Rust (required by retour for inline x64 detours)
# A `rust-toolchain.toml` at the repo root pins the channel automatically.
cargo build --release
# Outputs:
# target/release/time_mocker_ui.exe
# target/release/time_mocker_hook.dll (must be next to the UI exe)
Requirements
- Windows 10/11 x64
- Rust nightly (pinned via
rust-toolchain.toml) - Must run as Administrator (UAC manifest embedded)
License
Apache-2.0 — see LICENSE.
Related
- time-mocker-csharp — C# / EasyHook implementation
- time-mocker-cpp — C++ / MS Detours implementation