chore: init first version

This commit is contained in:
2026-02-27 17:30:37 +07:00
parent 7a3f634726
commit 1b24468759
16 changed files with 2159 additions and 43 deletions
+15 -41
View File
@@ -1,41 +1,15 @@
# Prerequisites
*.d
# Compiled Object files
*.slo
*.lo
*.o
*.obj
# Precompiled Headers
*.gch
*.pch
# Linker files
*.ilk
# Debugger Files
*.pdb
# Compiled Dynamic libraries
*.so
*.dylib
*.dll
# Fortran module files
*.mod
*.smod
# Compiled Static libraries
*.lai
*.la
*.a
*.lib
# Executables
*.exe
*.out
*.app
# debug information files
*.dwo
.vs/
x64/
x86/
Debug/
Release/
*.user
*.aps
*.suo
*.db
*.opendb
vcpkg/
packages/detours/lib/
packages/detours/include/
build/
out/
+63
View File
@@ -0,0 +1,63 @@
cmake_minimum_required(VERSION 3.20)
project(TimeMocker CXX)
# ── vcpkg toolchain (set via: cmake -DCMAKE_TOOLCHAIN_FILE=<vcpkg>/scripts/buildsystems/vcpkg.cmake)
find_package(detours CONFIG REQUIRED)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# Require Windows
if (NOT WIN32)
message(FATAL_ERROR "TimeMocker is Windows-only (uses Win32 API and MS Detours).")
endif()
add_compile_definitions(WIN32_LEAN_AND_MEAN UNICODE _UNICODE)
# ── Shared headers ────────────────────────────────────────────────────────────
set(SHARED_DIR ${CMAKE_SOURCE_DIR}/Shared)
# ── TimeMocker.Hook (DLL) ─────────────────────────────────────────────────────
add_library(TimeMocker.Hook SHARED
TimeMocker.Hook/dllmain.cpp
TimeMocker.Hook/exports.cpp
TimeMocker.Hook/TimeMocker.Hook.def
)
target_include_directories(TimeMocker.Hook PRIVATE ${SHARED_DIR})
target_link_libraries(TimeMocker.Hook PRIVATE detours::detours)
set_target_properties(TimeMocker.Hook PROPERTIES
OUTPUT_NAME "TimeMocker.Hook"
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin"
)
# ── TimeMocker.Injector (static lib) ─────────────────────────────────────────
add_library(TimeMocker.Injector STATIC
TimeMocker.Injector/InjectionManager.cpp
)
target_include_directories(TimeMocker.Injector
PUBLIC ${CMAKE_SOURCE_DIR}/TimeMocker.Injector
PRIVATE ${SHARED_DIR}
)
target_link_libraries(TimeMocker.Injector PUBLIC detours::detours Psapi)
# ── TimeMocker.UI (console exe) ───────────────────────────────────────────────
add_executable(TimeMocker.UI
TimeMocker.UI/main.cpp
)
target_include_directories(TimeMocker.UI PRIVATE ${SHARED_DIR})
target_link_libraries(TimeMocker.UI PRIVATE TimeMocker.Injector detours::detours)
set_target_properties(TimeMocker.UI PROPERTIES
OUTPUT_NAME "TimeMocker"
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin"
)
# Copy hook DLLs next to the UI executable after build
add_custom_command(TARGET TimeMocker.UI POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different
$<TARGET_FILE:TimeMocker.Hook>
"${CMAKE_BINARY_DIR}/bin/TimeMocker.Hook.x64.dll"
COMMENT "Copying Hook DLL (x64) → bin/"
)
install(TARGETS TimeMocker.UI TimeMocker.Hook
RUNTIME DESTINATION bin)
+238 -2
View File
@@ -1,2 +1,238 @@
# time-mocker-cpp
[time-mocker](https://github.com/tiennm99/time-mocker) but written in C++
# TimeMocker — C++ / MS Detours Edition
A Windows tool that injects fake time into running processes by hooking Win32 time APIs using **Microsoft Detours**.
Port/rewrite of the original C# / EasyHook version with identical IPC design but in native C++.
---
## Architecture
```
TimeMocker.sln
├── Shared/
│ └── MockTimeInfo.h ─ Named MMF layout (shared between UI and Hook)
├── TimeMocker.Hook/ ─ DLL injected into target processes
│ ├── dllmain.cpp ─ DllMain: opens MMF, installs/removes Detours hooks
│ └── exports.cpp ─ Sentinel export for version check
├── TimeMocker.Injector/ ─ Static library: injection + IPC management
│ ├── InjectionManager.h/.cpp ─ LoadLibrary remote-thread injection + SharedMemoryHandle
│ └── ProcessWatcher.h ─ Background thread: auto-inject via glob/regex rules
└── TimeMocker.UI/
└── main.cpp ─ Interactive console controller
```
---
## Hooked APIs
| API | DLL |
|-----|-----|
| `GetSystemTime` | kernel32 |
| `GetLocalTime` | kernel32 |
| `GetSystemTimeAsFileTime` | kernel32 |
| `GetSystemTimePreciseAsFileTime` | kernel32 |
| `NtQuerySystemTime` | ntdll |
All hooks read `DeltaTicks` from the named MMF on each call and return
`real_utc_filetime_ticks + DeltaTicks`. When `DeltaTicks == 0` the real
time passes through unchanged.
---
## IPC Design
```
Named Memory-Mapped File: TimeMocker_<PID>
Size: 8 bytes
[0..7] DeltaTicks (LONGLONG, 100-ns units, signed)
= fakeUtcTicks - realUtcTicks at the moment the user clicks "Set"
```
- The UI creates the MMF **before** injection so the hook can open it in `DllMain`.
- Updates are written as atomic `InterlockedExchange64` — no locks on the hot path.
- The hook reads with a single volatile 8-byte load (~4 ns, no syscall).
- When the UI closes the MMF (on eject/exit), the hook's next read returns `0`,
silently falling back to real time.
---
## How Detours Works Here
```
Before injection:
kernel32!GetSystemTime → [real implementation]
After injection:
kernel32!GetSystemTime → Hook_GetSystemTime (our function)
→ Real_GetSystemTime (trampoline, via DetourAttach)
```
`DetourAttach` patches the first 514 bytes of the real function with a
`JMP` to our hook, and saves the overwritten bytes + a JMP back in a
trampoline so we can call the original.
---
## Requirements
- **Windows 10/11 x64**
- **Visual Studio 2022** (v143 toolset) or **CMake 3.20+**
- **Microsoft Detours** (installed via `scripts/setup.ps1` → vcpkg)
- Must run as **Administrator** (cross-process injection requires elevated privileges)
---
## Setup & Build
### Option A — Visual Studio 2022
```powershell
# 1. Install Detours via vcpkg (one-time)
powershell -ExecutionPolicy Bypass -File scripts\setup.ps1
# 2. Open solution
start TimeMocker.sln
# 3. Build → Release | x64
```
Output:
```
x64\Release\TimeMocker.exe ← controller
x64\Release\TimeMocker.Hook.x64.dll ← hook DLL (must be next to .exe)
x64\Release\TimeMocker.Hook.x86.dll ← hook DLL for 32-bit targets
```
### Option B — CMake + vcpkg
```powershell
# Install vcpkg + detours
git clone https://github.com/microsoft/vcpkg vcpkg
.\vcpkg\bootstrap-vcpkg.bat -disableMetrics
.\vcpkg\vcpkg install detours:x64-windows detours:x86-windows
# Configure + build
cmake -B build -DCMAKE_TOOLCHAIN_FILE=vcpkg/scripts/buildsystems/vcpkg.cmake -A x64
cmake --build build --config Release
```
---
## Usage (Console Controller)
```
timemocker> help
list [filter] list running processes
inject <pid> inject hook DLL into process
eject <pid> remove hook from process
time YYYY-MM-DD HH:MM:SS set fake time (local)
time now reset to real time
status show injected processes + delta
rule add <pattern> add glob auto-inject rule
rule add -r <pattern> add regex auto-inject rule
rule list list rules
rule del <index> remove rule by index
watch start / stop control auto-inject watcher
quit exit
```
### Examples
```
# List all processes with "notepad" in name/path
timemocker> list notepad
# Inject into Notepad (PID 12345) and set time to Jan 1 2020
timemocker> time 2020-01-01 00:00:00
timemocker> inject 12345
# Change time while injected (takes effect immediately)
timemocker> time 2025-12-31 23:59:00
# Auto-inject any process matching a glob
timemocker> rule add C:\Games\MyGame\*
# Auto-inject chrome.exe by name
timemocker> rule add *chrome*
# Auto-inject using regex
timemocker> rule add -r ^.*\\MyApp\.exe$
# Reset to real time and eject
timemocker> time now
timemocker> eject 12345
```
---
## Project Details
### DLL Injection Method
Uses the classic **LoadLibrary remote thread** technique:
1. `OpenProcess` with `PROCESS_CREATE_THREAD | PROCESS_VM_*` rights
2. `VirtualAllocEx` → write DLL path string into target's memory
3. `CreateRemoteThread(LoadLibraryW, dllPath)` → loads the DLL in-process
4. `WaitForSingleObject` → confirm load completed
5. On `DLL_PROCESS_ATTACH` the hook's `DllMain` opens the MMF and installs Detours
For **new processes** (before they start), `DetourCreateProcessWithDllEx()` can also
be used — it's included in the Detours SDK and injects before the first instruction
runs, before any DRM/anti-cheat initialises.
### 32-bit vs 64-bit
- **64-bit target**: `TimeMocker.Hook.x64.dll` is injected
- **32-bit target** (WOW64): `TimeMocker.Hook.x86.dll` is injected
- Both Hook DLLs are identical source; the platform is selected at build time
- The UI (x64) determines which DLL to use by calling `IsWow64Process`
### Auto-Inject Watcher
`ProcessWatcher` runs a background thread that polls `CreateToolhelp32Snapshot`
every 1.5 seconds. Any process whose full path or `.exe` name matches an enabled
rule is automatically injected with the current fake time delta.
Previously-seen PIDs are tracked in a `HashSet` so they're only matched once,
even if the process restarts with the same PID (very rare on Windows).
### Limitations
| Limitation | Notes |
|-----------|-------|
| **64-bit injector only** | The UI is x64-only; a separate x86 injector would be needed for 32-bit-only scenarios |
| **Anti-cheat / protected processes** | EAC, BattlEye, and system PPL processes block `OpenProcess` |
| **QueryPerformanceCounter** | Not affected — it reads a hardware MSR; EasyHook/Detours cannot intercept kernel MSR reads |
| **GetTickCount / timeGetTime** | Not hooked in this version — easy to add following the same pattern |
| **Hot eject** | DLL stays loaded but hooks are removed on unload (not forcible without `FreeLibrary` in a remote thread) |
---
## Adding a New Hook
1. Declare the real function pointer in `dllmain.cpp`:
```cpp
static DWORD (WINAPI* Real_GetTickCount)() = GetTickCount;
```
2. Write the hook function:
```cpp
static DWORD WINAPI Hook_GetTickCount()
{
// Convert fake UTC ticks to milliseconds since boot (approximate)
return static_cast<DWORD>(GetFakeUtcTicks() / 10000);
}
```
3. Add `DetourAttach` / `DetourDetach` calls in `InstallHooks` / `RemoveHooks`.
---
## License
Apache 2.0 (same as the original C# project).
+30
View File
@@ -0,0 +1,30 @@
#pragma once
// =============================================================================
// TimeMocker Shared IPC
// Named Memory-Mapped File layout shared between UI and Hook DLL.
// One MMF per injected process, named: TimeMocker_<PID>
// =============================================================================
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
static const wchar_t* MMF_PREFIX = L"TimeMocker_";
static const DWORD MMF_SIZE = sizeof(LONGLONG); // 8 bytes: DeltaTicks (Int64)
// The only field: signed tick offset to add to QueryUnbiasedInterruptTime / FILETIME.
// A tick = 100 nanoseconds (same unit as FILETIME / SYSTEMTIME internals).
// DeltaTicks == 0 → real time (pass-through)
// DeltaTicks > 0 → future (clock is ahead)
// DeltaTicks < 0 → past (clock is behind)
#pragma pack(push, 1)
struct MockTimeInfo
{
LONGLONG DeltaTicks; // Offset to add to the real UTC FILETIME ticks
};
#pragma pack(pop)
// Helper: build the MMF name for a given PID
inline void GetMmfName(DWORD pid, wchar_t* buf, size_t cchBuf)
{
swprintf_s(buf, cchBuf, L"%ls%lu", MMF_PREFIX, pid);
}
+7
View File
@@ -0,0 +1,7 @@
LIBRARY TimeMocker.Hook
EXPORTS
; No public exports required Detours helper process detection uses
; an internal mechanism. We export a sentinel for the injector to
; verify the DLL loaded correctly.
TimeMockerHookVersion @1
+140
View File
@@ -0,0 +1,140 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|Win32">
<Configuration>Debug</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|Win32">
<Configuration>Release</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<ProjectGuid>{A1111111-1111-1111-1111-111111111111}</ProjectGuid>
<Keyword>Win32Proj</Keyword>
<RootNamespace>TimeMockerHook</RootNamespace>
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<!-- ── Common compiler settings ── -->
<ItemDefinitionGroup>
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<SDLCheck>true</SDLCheck>
<ConformanceMode>true</ConformanceMode>
<!-- Include Detours via vcpkg or a local copy -->
<AdditionalIncludeDirectories>$(SolutionDir)packages\detours\include;$(SolutionDir)Shared;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PreprocessorDefinitions>WIN32_LEAN_AND_MEAN;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
<Link>
<!-- Link Detours and runtime imports -->
<AdditionalDependencies>detours.lib;%(AdditionalDependencies)</AdditionalDependencies>
<ModuleDefinitionFile>TimeMocker.Hook.def</ModuleDefinitionFile>
</Link>
</ItemDefinitionGroup>
<!-- ── Debug x86 ── -->
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<ClCompile>
<Optimization>Disabled</Optimization>
<PreprocessorDefinitions>_DEBUG;TIMEMOCKER_HOOK_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
</ClCompile>
<Link>
<AdditionalLibraryDirectories>$(SolutionDir)packages\detours\lib\x86;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
</Link>
</ItemDefinitionGroup>
<!-- ── Release x86 ── -->
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<ClCompile>
<Optimization>MaxSpeed</Optimization>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<PreprocessorDefinitions>NDEBUG;TIMEMOCKER_HOOK_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
</ClCompile>
<Link>
<AdditionalLibraryDirectories>$(SolutionDir)packages\detours\lib\x86;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
</Link>
</ItemDefinitionGroup>
<!-- ── Debug x64 ── -->
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<ClCompile>
<Optimization>Disabled</Optimization>
<PreprocessorDefinitions>_DEBUG;TIMEMOCKER_HOOK_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
</ClCompile>
<Link>
<AdditionalLibraryDirectories>$(SolutionDir)packages\detours\lib\x64;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
</Link>
</ItemDefinitionGroup>
<!-- ── Release x64 ── -->
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<ClCompile>
<Optimization>MaxSpeed</Optimization>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<PreprocessorDefinitions>NDEBUG;TIMEMOCKER_HOOK_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
</ClCompile>
<Link>
<AdditionalLibraryDirectories>$(SolutionDir)packages\detours\lib\x64;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="dllmain.cpp" />
<ClCompile Include="exports.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\Shared\MockTimeInfo.h" />
<None Include="TimeMocker.Hook.def" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
</Project>
+311
View File
@@ -0,0 +1,311 @@
// =============================================================================
// TimeMocker.Hook — MS Detours-based time hooking DLL
//
// Injected into a target process by TimeMocker.Injector.
// Opens a named Memory-Mapped File created by the UI:
// Name: TimeMocker_<PID>
// Data: MockTimeInfo { LONGLONG DeltaTicks }
//
// Hooks 5 Win32 time APIs:
// kernel32!GetSystemTime
// kernel32!GetLocalTime
// kernel32!GetSystemTimeAsFileTime
// kernel32!GetSystemTimePreciseAsFileTime
// ntdll!NtQuerySystemTime
//
// The delta (DeltaTicks) is added to the real UTC FILETIME on every call.
// When DeltaTicks == 0 the real time passes through unchanged.
// =============================================================================
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#include <detours.h>
#include <cstdio>
#include <cstdint>
#include "../Shared/MockTimeInfo.h"
// ---------------------------------------------------------------------------
// Module-level state
// ---------------------------------------------------------------------------
static HANDLE g_hMapFile = nullptr;
static LPVOID g_pView = nullptr;
static wchar_t g_logPath[MAX_PATH];
// ---------------------------------------------------------------------------
// Logging (writes to %TEMP%\TimeMocker.Hook.log)
// ---------------------------------------------------------------------------
static void Log(const char* fmt, ...)
{
FILE* f = nullptr;
if (_wfopen_s(&f, g_logPath, L"a") == 0 && f)
{
SYSTEMTIME st;
GetLocalTime(&st); // NOTE: intentional real time call before hooks install
fprintf(f, "[%02d:%02d:%02d] ", st.wHour, st.wMinute, st.wSecond);
va_list va;
va_start(va, fmt);
vfprintf(f, fmt, va);
va_end(va);
fprintf(f, "\n");
fclose(f);
}
}
// ---------------------------------------------------------------------------
// Read the delta from shared memory (inline, hot path)
// ---------------------------------------------------------------------------
static inline LONGLONG ReadDeltaTicks()
{
if (!g_pView) return 0LL;
// Atomic-friendly: LONGLONG is 8-byte aligned, single read is atomic on x86/x64
return *reinterpret_cast<volatile LONGLONG*>(g_pView);
}
// ---------------------------------------------------------------------------
// FILETIME helpers
// ---------------------------------------------------------------------------
static const LONGLONG FILETIME_EPOCH_BIAS = 116444736000000000LL; // Jan 1, 1601 → Jan 1, 1970
// Convert a FILETIME (100-ns ticks since Jan 1, 1601 UTC) to a SYSTEMTIME UTC
static void FileTimeToSystemTimeLocal(LONGLONG ticks, SYSTEMTIME* pSt, bool localTime)
{
FILETIME ft;
ft.dwLowDateTime = static_cast<DWORD>(ticks & 0xFFFFFFFF);
ft.dwHighDateTime = static_cast<DWORD>(ticks >> 32);
if (localTime)
{
FILETIME localFt;
FileTimeToLocalFileTime(&ft, &localFt);
FileTimeToSystemTime(&localFt, pSt);
}
else
{
FileTimeToSystemTime(&ft, pSt);
}
}
// Get real UTC as 100-ns ticks (FILETIME ticks)
static inline LONGLONG GetRealUtcTicks()
{
FILETIME ft;
// Call the REAL GetSystemTimeAsFileTime (trampoline, set up after DetourAttach)
// We use a raw read of the real function pointer stored in the trampoline.
// To avoid recursion during hook installation we use a flag.
ULARGE_INTEGER ui;
GetSystemTimeAsFileTime(&ft); // will be redirected after hooks are live; see note below
ui.LowPart = ft.dwLowDateTime;
ui.HighPart = ft.dwHighDateTime;
return static_cast<LONGLONG>(ui.QuadPart);
}
// ---------------------------------------------------------------------------
// Original function pointers (Detours trampolines)
// ---------------------------------------------------------------------------
static void (WINAPI* Real_GetSystemTime)(LPSYSTEMTIME) = GetSystemTime;
static void (WINAPI* Real_GetLocalTime)(LPSYSTEMTIME) = GetLocalTime;
static void (WINAPI* Real_GetSystemTimeAsFileTime)(LPFILETIME) = GetSystemTimeAsFileTime;
static void (WINAPI* Real_GetSystemTimePreciseAsFileTime)(LPFILETIME) = GetSystemTimePreciseAsFileTime;
typedef NTSTATUS (NTAPI* NtQuerySystemTimeFn)(PLARGE_INTEGER SystemTime);
static NtQuerySystemTimeFn Real_NtQuerySystemTime = nullptr;
// ---------------------------------------------------------------------------
// Helper: get fake UTC ticks
// ---------------------------------------------------------------------------
static LONGLONG GetFakeUtcTicks()
{
// Get real time via the trampoline (not the hook)
FILETIME ft;
Real_GetSystemTimeAsFileTime(&ft);
ULARGE_INTEGER ui;
ui.LowPart = ft.dwLowDateTime;
ui.HighPart = ft.dwHighDateTime;
return static_cast<LONGLONG>(ui.QuadPart) + ReadDeltaTicks();
}
// ---------------------------------------------------------------------------
// Hook implementations
// ---------------------------------------------------------------------------
static void WINAPI Hook_GetSystemTime(LPSYSTEMTIME lpSystemTime)
{
if (!lpSystemTime) return;
LONGLONG ticks = GetFakeUtcTicks();
FileTimeToSystemTimeLocal(ticks, lpSystemTime, false);
}
static void WINAPI Hook_GetLocalTime(LPSYSTEMTIME lpLocalTime)
{
if (!lpLocalTime) return;
LONGLONG ticks = GetFakeUtcTicks();
FileTimeToSystemTimeLocal(ticks, lpLocalTime, true);
}
static void WINAPI Hook_GetSystemTimeAsFileTime(LPFILETIME lpFileTime)
{
if (!lpFileTime) return;
LONGLONG ticks = GetFakeUtcTicks();
lpFileTime->dwLowDateTime = static_cast<DWORD>(ticks & 0xFFFFFFFF);
lpFileTime->dwHighDateTime = static_cast<DWORD>(ticks >> 32);
}
static void WINAPI Hook_GetSystemTimePreciseAsFileTime(LPFILETIME lpFileTime)
{
// Same as GetSystemTimeAsFileTime — we can't improve precision beyond real time
Hook_GetSystemTimeAsFileTime(lpFileTime);
}
static NTSTATUS NTAPI Hook_NtQuerySystemTime(PLARGE_INTEGER SystemTime)
{
if (!SystemTime) return STATUS_ACCESS_VIOLATION;
SystemTime->QuadPart = GetFakeUtcTicks();
return STATUS_SUCCESS;
}
// ---------------------------------------------------------------------------
// Install / remove hooks
// ---------------------------------------------------------------------------
static bool InstallHooks()
{
// Resolve NtQuerySystemTime from ntdll
HMODULE hNtdll = GetModuleHandleW(L"ntdll.dll");
if (!hNtdll)
{
Log("ERROR: ntdll.dll not found");
return false;
}
Real_NtQuerySystemTime = reinterpret_cast<NtQuerySystemTimeFn>(
GetProcAddress(hNtdll, "NtQuerySystemTime"));
if (!Real_NtQuerySystemTime)
{
Log("ERROR: NtQuerySystemTime not found in ntdll");
return false;
}
DetourTransactionBegin();
DetourUpdateThread(GetCurrentThread());
DetourAttach(reinterpret_cast<PVOID*>(&Real_GetSystemTime),
reinterpret_cast<PVOID>(Hook_GetSystemTime));
DetourAttach(reinterpret_cast<PVOID*>(&Real_GetLocalTime),
reinterpret_cast<PVOID>(Hook_GetLocalTime));
DetourAttach(reinterpret_cast<PVOID*>(&Real_GetSystemTimeAsFileTime),
reinterpret_cast<PVOID>(Hook_GetSystemTimeAsFileTime));
DetourAttach(reinterpret_cast<PVOID*>(&Real_GetSystemTimePreciseAsFileTime),
reinterpret_cast<PVOID>(Hook_GetSystemTimePreciseAsFileTime));
DetourAttach(reinterpret_cast<PVOID*>(&Real_NtQuerySystemTime),
reinterpret_cast<PVOID>(Hook_NtQuerySystemTime));
LONG err = DetourTransactionCommit();
if (err != NO_ERROR)
{
Log("ERROR: DetourTransactionCommit failed: %ld", err);
return false;
}
Log("Hooks installed successfully (delta=%lld ticks)", ReadDeltaTicks());
return true;
}
static void RemoveHooks()
{
DetourTransactionBegin();
DetourUpdateThread(GetCurrentThread());
DetourDetach(reinterpret_cast<PVOID*>(&Real_GetSystemTime),
reinterpret_cast<PVOID>(Hook_GetSystemTime));
DetourDetach(reinterpret_cast<PVOID*>(&Real_GetLocalTime),
reinterpret_cast<PVOID>(Hook_GetLocalTime));
DetourDetach(reinterpret_cast<PVOID*>(&Real_GetSystemTimeAsFileTime),
reinterpret_cast<PVOID>(Hook_GetSystemTimeAsFileTime));
DetourDetach(reinterpret_cast<PVOID*>(&Real_GetSystemTimePreciseAsFileTime),
reinterpret_cast<PVOID>(Hook_GetSystemTimePreciseAsFileTime));
if (Real_NtQuerySystemTime)
DetourDetach(reinterpret_cast<PVOID*>(&Real_NtQuerySystemTime),
reinterpret_cast<PVOID>(Hook_NtQuerySystemTime));
DetourTransactionCommit();
Log("Hooks removed");
}
// ---------------------------------------------------------------------------
// Open the shared memory created by the UI
// ---------------------------------------------------------------------------
static bool OpenSharedMemory(DWORD pid)
{
wchar_t mmfName[64];
GetMmfName(pid, mmfName, _countof(mmfName));
g_hMapFile = OpenFileMappingW(FILE_MAP_READ | FILE_MAP_WRITE, FALSE, mmfName);
if (!g_hMapFile)
{
Log("ERROR: OpenFileMapping('%ls') failed: %lu", mmfName, GetLastError());
return false;
}
g_pView = MapViewOfFile(g_hMapFile, FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, MMF_SIZE);
if (!g_pView)
{
Log("ERROR: MapViewOfFile failed: %lu", GetLastError());
CloseHandle(g_hMapFile);
g_hMapFile = nullptr;
return false;
}
Log("Shared memory '%ls' opened (delta=%lld)", mmfName, ReadDeltaTicks());
return true;
}
static void CloseSharedMemory()
{
if (g_pView) { UnmapViewOfFile(g_pView); g_pView = nullptr; }
if (g_hMapFile) { CloseHandle(g_hMapFile); g_hMapFile = nullptr; }
}
// ---------------------------------------------------------------------------
// DllMain
// ---------------------------------------------------------------------------
BOOL WINAPI DllMain(HINSTANCE /*hInst*/, DWORD reason, LPVOID /*reserved*/)
{
if (DetourIsHelperProcess()) return TRUE;
switch (reason)
{
case DLL_PROCESS_ATTACH:
{
// Build log path: %TEMP%\TimeMocker.Hook.log
wchar_t tempDir[MAX_PATH];
GetTempPathW(MAX_PATH, tempDir);
swprintf_s(g_logPath, _countof(g_logPath), L"%lsTimeMocker.Hook.log", tempDir);
DWORD pid = GetCurrentProcessId();
Log("DLL_PROCESS_ATTACH pid=%lu", pid);
if (!OpenSharedMemory(pid))
{
// UI hasn't created the MMF yet — this is fatal for injection
return FALSE;
}
DisableThreadLibraryCalls(/*hInst*/ GetModuleHandleW(nullptr));
if (!InstallHooks())
{
CloseSharedMemory();
return FALSE;
}
break;
}
case DLL_PROCESS_DETACH:
RemoveHooks();
CloseSharedMemory();
Log("DLL_PROCESS_DETACH");
break;
}
return TRUE;
}
+7
View File
@@ -0,0 +1,7 @@
#include <Windows.h>
// Exported sentinel — lets the injector verify the DLL was built correctly
extern "C" __declspec(dllexport) DWORD TimeMockerHookVersion()
{
return 0x0001'0000; // v1.0
}
+318
View File
@@ -0,0 +1,318 @@
// =============================================================================
// TimeMocker.Injector — InjectionManager.cpp
//
// Uses the classic LoadLibrary remote-thread injection technique:
// 1. Open the target process with sufficient rights
// 2. Write the DLL path into the target's address space
// 3. Create a remote thread that calls LoadLibraryW
//
// For new processes, DetourCreateProcessWithDllEx() can alternatively be used
// (see TimeMocker.Injector.CLI for an example).
// =============================================================================
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#include <TlHelp32.h>
#include <Psapi.h>
#include <detours.h>
#include <cstdio>
#include <cassert>
#include <cwchar>
#include <memory>
#include <sstream>
#include "InjectionManager.h"
#pragma comment(lib, "Psapi.lib")
// ============================================================================
// SharedMemoryHandle
// ============================================================================
SharedMemoryHandle::SharedMemoryHandle(DWORD pid)
{
wchar_t buf[64];
GetMmfName(pid, buf, _countof(buf));
m_name = buf;
m_hMap = CreateFileMappingW(
INVALID_HANDLE_VALUE, nullptr,
PAGE_READWRITE, 0, MMF_SIZE,
m_name.c_str());
if (!m_hMap) return;
m_pView = MapViewOfFile(m_hMap, FILE_MAP_ALL_ACCESS, 0, 0, MMF_SIZE);
if (!m_pView)
{
CloseHandle(m_hMap);
m_hMap = nullptr;
}
else
{
// Zero-initialise (DeltaTicks = 0 → real time)
ZeroMemory(m_pView, MMF_SIZE);
}
}
SharedMemoryHandle::~SharedMemoryHandle()
{
if (m_pView) UnmapViewOfFile(m_pView);
if (m_hMap) CloseHandle(m_hMap);
}
void SharedMemoryHandle::Write(const MockTimeInfo& info)
{
if (!m_pView) return;
// Atomic write on aligned 8-byte address on x64/x86
InterlockedExchange64(reinterpret_cast<LONGLONG*>(m_pView), info.DeltaTicks);
}
// ============================================================================
// TimeUtil
// ============================================================================
LONGLONG TimeUtil::RealUtcTicks()
{
FILETIME ft;
GetSystemTimeAsFileTime(&ft);
ULARGE_INTEGER ui;
ui.LowPart = ft.dwLowDateTime;
ui.HighPart = ft.dwHighDateTime;
return static_cast<LONGLONG>(ui.QuadPart);
}
LONGLONG TimeUtil::LocalSystemTimeToUtcTicks(const SYSTEMTIME& st)
{
FILETIME localFt, utcFt;
SystemTimeToFileTime(&st, &localFt);
LocalFileTimeToFileTime(&localFt, &utcFt);
ULARGE_INTEGER ui;
ui.LowPart = utcFt.dwLowDateTime;
ui.HighPart = utcFt.dwHighDateTime;
return static_cast<LONGLONG>(ui.QuadPart);
}
LONGLONG TimeUtil::ComputeDelta(LONGLONG fakeUtcTicks)
{
return fakeUtcTicks - RealUtcTicks();
}
// ============================================================================
// InjectionManager helpers
// ============================================================================
void InjectionManager::Log(const wchar_t* fmt, ...) const
{
if (!OnLog) return;
wchar_t buf[1024];
va_list va;
va_start(va, fmt);
vswprintf_s(buf, _countof(buf), fmt, va);
va_end(va);
OnLog(buf);
}
std::wstring InjectionManager::ResolveHookDll(bool x64) const
{
// Prefer explicit directory; fall back to the injector's own directory
std::wstring dir = m_hookDllDir;
if (dir.empty())
{
wchar_t exe[MAX_PATH];
GetModuleFileNameW(nullptr, exe, MAX_PATH);
wchar_t* slash = wcsrchr(exe, L'\\');
if (slash) { *(slash + 1) = L'\0'; dir = exe; }
}
return dir + (x64 ? L"TimeMocker.Hook.x64.dll" : L"TimeMocker.Hook.x86.dll");
}
bool InjectionManager::IsProcess64Bit(HANDLE hProcess)
{
BOOL wow64 = FALSE;
IsWow64Process(hProcess, &wow64);
// If we are 64-bit and the target is NOT WOW64, target is 64-bit
#ifdef _WIN64
return !wow64;
#else
return false; // 32-bit injector can only inject x86
#endif
}
// ============================================================================
// InjectionManager
// ============================================================================
InjectionManager::InjectionManager(const std::wstring& hookDllDir)
: m_hookDllDir(hookDllDir)
{
}
InjectionManager::~InjectionManager()
{
std::lock_guard<std::mutex> lk(m_mutex);
for (auto& kv : m_injected)
delete kv.second;
m_injected.clear();
}
bool InjectionManager::Inject(DWORD pid, LONGLONG fakeUtcTicks, std::wstring* pError)
{
std::lock_guard<std::mutex> lk(m_mutex);
if (m_injected.count(pid))
{
// Already injected — just update time
m_injected[pid]->Shm->Write({ TimeUtil::ComputeDelta(fakeUtcTicks) });
return true;
}
// ---- Open target process -----------------------------------------------
HANDLE hProcess = OpenProcess(
PROCESS_CREATE_THREAD | PROCESS_QUERY_INFORMATION |
PROCESS_VM_OPERATION | PROCESS_VM_WRITE | PROCESS_VM_READ,
FALSE, pid);
if (!hProcess)
{
std::wstring err = L"OpenProcess failed: " + std::to_wstring(GetLastError());
Log(L"[Inject] %ls", err.c_str());
if (pError) *pError = err;
return false;
}
bool is64 = IsProcess64Bit(hProcess);
std::wstring dllPath = ResolveHookDll(is64);
// ---- Create shared memory (must exist BEFORE DLL is loaded) -------------
auto* entry = new InjectedProcessInfo();
entry->Pid = pid;
entry->Shm = new SharedMemoryHandle(pid);
if (!entry->Shm->IsValid())
{
delete entry;
CloseHandle(hProcess);
std::wstring err = L"CreateFileMapping failed: " + std::to_wstring(GetLastError());
Log(L"[Inject] %ls", err.c_str());
if (pError) *pError = err;
return false;
}
// Write initial delta
entry->Shm->Write({ TimeUtil::ComputeDelta(fakeUtcTicks) });
// ---- Collect process name / path ----------------------------------------
wchar_t pathBuf[MAX_PATH] = {};
DWORD pathLen = MAX_PATH;
QueryFullProcessImageNameW(hProcess, 0, pathBuf, &pathLen);
entry->ProcessPath = pathBuf;
const wchar_t* slash = wcsrchr(pathBuf, L'\\');
entry->ProcessName = slash ? (slash + 1) : pathBuf;
// ---- Inject the DLL via LoadLibraryW remote thread ----------------------
SIZE_T dllPathBytes = (dllPath.size() + 1) * sizeof(wchar_t);
LPVOID remoteStr = VirtualAllocEx(hProcess, nullptr, dllPathBytes,
MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
if (!remoteStr)
{
delete entry;
CloseHandle(hProcess);
std::wstring err = L"VirtualAllocEx failed: " + std::to_wstring(GetLastError());
Log(L"[Inject] %ls", err.c_str());
if (pError) *pError = err;
return false;
}
WriteProcessMemory(hProcess, remoteStr, dllPath.c_str(), dllPathBytes, nullptr);
HMODULE hKernel = GetModuleHandleW(L"kernel32.dll");
FARPROC pLoadLib = GetProcAddress(hKernel, "LoadLibraryW");
HANDLE hThread = CreateRemoteThread(
hProcess, nullptr, 0,
reinterpret_cast<LPTHREAD_START_ROUTINE>(pLoadLib),
remoteStr, 0, nullptr);
if (!hThread)
{
VirtualFreeEx(hProcess, remoteStr, 0, MEM_RELEASE);
delete entry;
CloseHandle(hProcess);
std::wstring err = L"CreateRemoteThread failed: " + std::to_wstring(GetLastError());
Log(L"[Inject] %ls", err.c_str());
if (pError) *pError = err;
return false;
}
// Wait for LoadLibraryW to return (give it 5 seconds)
WaitForSingleObject(hThread, 5000);
// Check the return value (hModule loaded)
DWORD exitCode = 0;
GetExitCodeThread(hThread, &exitCode);
CloseHandle(hThread);
VirtualFreeEx(hProcess, remoteStr, 0, MEM_RELEASE);
CloseHandle(hProcess);
if (!exitCode)
{
delete entry;
std::wstring err = L"LoadLibraryW in target returned NULL — DLL load failed";
Log(L"[Inject] %ls", err.c_str());
if (pError) *pError = err;
return false;
}
m_injected[pid] = entry;
Log(L"[Inject] pid=%lu '%ls' injected ('%ls')", pid, entry->ProcessName.c_str(), dllPath.c_str());
return true;
}
bool InjectionManager::SetFakeTime(DWORD pid, LONGLONG fakeUtcTicks)
{
std::lock_guard<std::mutex> lk(m_mutex);
auto it = m_injected.find(pid);
if (it == m_injected.end()) return false;
it->second->Shm->Write({ TimeUtil::ComputeDelta(fakeUtcTicks) });
return true;
}
void InjectionManager::SetFakeTimeAll(LONGLONG fakeUtcTicks)
{
std::lock_guard<std::mutex> lk(m_mutex);
LONGLONG delta = TimeUtil::ComputeDelta(fakeUtcTicks);
for (auto& kv : m_injected)
kv.second->Shm->Write({ delta });
}
bool InjectionManager::Eject(DWORD pid)
{
std::lock_guard<std::mutex> lk(m_mutex);
auto it = m_injected.find(pid);
if (it == m_injected.end()) return false;
// Zero out the delta so the hook passes through real time before we unmap
it->second->Shm->Write({ 0LL });
Sleep(50); // let any in-flight hook calls complete
delete it->second;
m_injected.erase(it);
Log(L"[Eject] pid=%lu ejected (shared memory closed)", pid);
return true;
}
bool InjectionManager::IsInjected(DWORD pid) const
{
std::lock_guard<std::mutex> lk(m_mutex);
return m_injected.count(pid) != 0;
}
void InjectionManager::ForEach(std::function<void(const InjectedProcessInfo&)> fn) const
{
std::lock_guard<std::mutex> lk(m_mutex);
for (auto& kv : m_injected)
fn(*kv.second);
}
+110
View File
@@ -0,0 +1,110 @@
#pragma once
// =============================================================================
// TimeMocker.Injector — inject/eject Hook DLL + manage shared memory
//
// Usage:
// InjectionManager mgr;
// mgr.Inject(pid, fakeTimeUtc); // inject and set time
// mgr.SetFakeTime(pid, fakeUtc); // update time while injected
// mgr.Eject(pid); // detach hook
// =============================================================================
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#include <string>
#include <unordered_map>
#include <functional>
#include <mutex>
#include "../Shared/MockTimeInfo.h"
// ---------------------------------------------------------------------------
// SharedMemoryHandle
// Wraps one named MMF per injected process.
// ---------------------------------------------------------------------------
class SharedMemoryHandle
{
public:
explicit SharedMemoryHandle(DWORD pid);
~SharedMemoryHandle();
SharedMemoryHandle(const SharedMemoryHandle&) = delete;
SharedMemoryHandle& operator=(const SharedMemoryHandle&) = delete;
bool IsValid() const { return m_pView != nullptr; }
void Write(const MockTimeInfo& info);
const std::wstring& Name() const { return m_name; }
private:
std::wstring m_name;
HANDLE m_hMap = nullptr;
LPVOID m_pView = nullptr;
};
// ---------------------------------------------------------------------------
// InjectedProcessInfo
// ---------------------------------------------------------------------------
struct InjectedProcessInfo
{
DWORD Pid = 0;
std::wstring ProcessName;
std::wstring ProcessPath;
SharedMemoryHandle* Shm = nullptr;
};
// ---------------------------------------------------------------------------
// InjectionManager
// ---------------------------------------------------------------------------
class InjectionManager
{
public:
explicit InjectionManager(const std::wstring& hookDllDir = L"");
~InjectionManager();
// Inject hook DLL into process and set initial fake time (UTC FILETIME ticks)
bool Inject(DWORD pid, LONGLONG fakeUtcTicks, std::wstring* pError = nullptr);
// Update fake time for an already-injected process
bool SetFakeTime(DWORD pid, LONGLONG fakeUtcTicks);
// Set fake time for all injected processes
void SetFakeTimeAll(LONGLONG fakeUtcTicks);
// Remove hook from process (best-effort — DLL stays loaded but hooks removed on next call)
bool Eject(DWORD pid);
bool IsInjected(DWORD pid) const;
// Callback for log messages
std::function<void(const std::wstring&)> OnLog;
// Iterate injected processes
void ForEach(std::function<void(const InjectedProcessInfo&)> fn) const;
private:
std::wstring ResolveHookDll(bool x64) const;
static bool IsProcess64Bit(HANDLE hProcess);
static LONGLONG RealUtcTicks();
static LONGLONG ToFiletimeDelta(LONGLONG fakeUtcTicks);
void Log(const wchar_t* fmt, ...) const;
mutable std::mutex m_mutex;
std::unordered_map<DWORD, InjectedProcessInfo*> m_injected;
std::wstring m_hookDllDir; // directory where Hook DLLs live
};
// ---------------------------------------------------------------------------
// Utility: convert a DateTime-style local SYSTEMTIME to UTC FILETIME ticks
// (helper for callers that work with wall-clock time)
// ---------------------------------------------------------------------------
namespace TimeUtil
{
// Get the current real UTC as FILETIME ticks (100-ns units since Jan 1, 1601)
LONGLONG RealUtcTicks();
// Convert a local SYSTEMTIME to UTC FILETIME ticks
LONGLONG LocalSystemTimeToUtcTicks(const SYSTEMTIME& st);
// Build a DeltaTicks value: how many ticks ahead/behind of real time
LONGLONG ComputeDelta(LONGLONG fakeUtcTicks);
}
+188
View File
@@ -0,0 +1,188 @@
#pragma once
// =============================================================================
// ProcessWatcher — polls running processes and auto-injects those matching
// a set of glob/regex patterns.
// =============================================================================
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#include <TlHelp32.h>
#include <string>
#include <vector>
#include <unordered_set>
#include <functional>
#include <thread>
#include <atomic>
#include <mutex>
#include <regex>
#include <algorithm>
#include "InjectionManager.h"
struct PatternRule
{
std::wstring Pattern;
bool UseRegex = false;
bool Enabled = true;
bool IsMatch(const std::wstring& path) const
{
if (path.empty()) return false;
std::wstring regexStr;
if (UseRegex)
{
regexStr = Pattern;
}
else
{
regexStr = L"^";
for (wchar_t c : Pattern)
{
switch (c)
{
case L'*': regexStr += L".*"; break;
case L'?': regexStr += L'.'; break;
case L'.': regexStr += L"\\."; break;
case L'\\': regexStr += L"\\\\"; break;
default: regexStr += c; break;
}
}
regexStr += L'$';
}
try
{
std::wregex re(regexStr, std::regex_constants::icase);
return std::regex_match(path, re) || std::regex_search(path, re);
}
catch (...) { return false; }
}
};
class ProcessWatcher
{
public:
explicit ProcessWatcher(InjectionManager& mgr)
: m_mgr(mgr)
{
m_fakeUtcTicks = TimeUtil::RealUtcTicks();
}
~ProcessWatcher() { Stop(); }
void AddRule(PatternRule rule)
{
std::lock_guard<std::mutex> lk(m_rulesMutex);
m_rules.push_back(std::move(rule));
}
void RemoveRule(const std::wstring& pattern)
{
std::lock_guard<std::mutex> lk(m_rulesMutex);
m_rules.erase(
std::remove_if(m_rules.begin(), m_rules.end(),
[&](const PatternRule& r){ return r.Pattern == pattern; }),
m_rules.end());
}
void ClearRules()
{
std::lock_guard<std::mutex> lk(m_rulesMutex);
m_rules.clear();
}
void SetFakeUtcTicks(LONGLONG ticks) { m_fakeUtcTicks.store(ticks); }
void Start(DWORD pollIntervalMs = 1500)
{
if (m_running.exchange(true)) return;
m_thread = std::thread([this, pollIntervalMs]()
{
while (m_running.load())
{
Scan();
for (DWORD e = 0; m_running.load() && e < pollIntervalMs; e += 100)
Sleep(100);
}
});
}
void Stop()
{
if (!m_running.exchange(false)) return;
if (m_thread.joinable()) m_thread.join();
}
// Callbacks
std::function<void(DWORD pid, const std::wstring& name, const std::wstring& path)> OnAutoInjected;
std::function<void(const std::wstring&)> OnLog;
private:
void Scan()
{
HANDLE snap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (snap == INVALID_HANDLE_VALUE) return;
PROCESSENTRY32W pe; pe.dwSize = sizeof(pe);
if (!Process32FirstW(snap, &pe)) { CloseHandle(snap); return; }
std::lock_guard<std::mutex> ruleLk(m_rulesMutex);
do
{
DWORD pid = pe.th32ProcessID;
{ std::lock_guard<std::mutex> lk(m_seenMutex); if (m_seenPids.count(pid)) continue; }
if (m_mgr.IsInjected(pid)) continue;
HANDLE hProc = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pid);
std::wstring fullPath;
if (hProc)
{
wchar_t buf[MAX_PATH] = {}; DWORD len = MAX_PATH;
QueryFullProcessImageNameW(hProc, 0, buf, &len);
fullPath = buf;
CloseHandle(hProc);
}
std::wstring procName = pe.szExeFile;
for (auto& rule : m_rules)
{
if (!rule.Enabled) continue;
if (!rule.IsMatch(fullPath) && !rule.IsMatch(procName)) continue;
{ std::lock_guard<std::mutex> lk(m_seenMutex); m_seenPids.insert(pid); }
std::wstring err;
if (m_mgr.Inject(pid, m_fakeUtcTicks.load(), &err))
{
DoLog(L"[AutoInject] '%ls' → [%lu] %ls", rule.Pattern.c_str(), pid, procName.c_str());
if (OnAutoInjected) OnAutoInjected(pid, procName, fullPath);
}
else
{
DoLog(L"[AutoInject] FAIL [%lu] %ls: %ls", pid, procName.c_str(), err.c_str());
}
break;
}
} while (Process32NextW(snap, &pe));
CloseHandle(snap);
}
void DoLog(const wchar_t* fmt, ...) const
{
if (!OnLog) return;
wchar_t buf[1024]; va_list va; va_start(va, fmt);
vswprintf_s(buf, _countof(buf), fmt, va); va_end(va);
OnLog(buf);
}
InjectionManager& m_mgr;
mutable std::mutex m_rulesMutex;
std::vector<PatternRule> m_rules;
std::mutex m_seenMutex;
std::unordered_set<DWORD> m_seenPids;
std::atomic<LONGLONG> m_fakeUtcTicks{ 0 };
std::atomic<bool> m_running{ false };
std::thread m_thread;
};
@@ -0,0 +1,71 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|Win32"> <Configuration>Debug</Configuration> <Platform>Win32</Platform> </ProjectConfiguration>
<ProjectConfiguration Include="Release|Win32"><Configuration>Release</Configuration><Platform>Win32</Platform> </ProjectConfiguration>
<ProjectConfiguration Include="Debug|x64"> <Configuration>Debug</Configuration> <Platform>x64</Platform> </ProjectConfiguration>
<ProjectConfiguration Include="Release|x64"> <Configuration>Release</Configuration><Platform>x64</Platform> </ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<ProjectGuid>{B2222222-2222-2222-2222-222222222222}</ProjectGuid>
<RootNamespace>TimeMockerInjector</RootNamespace>
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
<ConfigurationType>StaticLibrary</ConfigurationType><UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset><CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
<ConfigurationType>StaticLibrary</ConfigurationType><UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset><WholeProgramOptimization>true</WholeProgramOptimization><CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
<ConfigurationType>StaticLibrary</ConfigurationType><UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset><CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
<ConfigurationType>StaticLibrary</ConfigurationType><UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset><WholeProgramOptimization>true</WholeProgramOptimization><CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ItemDefinitionGroup>
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<SDLCheck>true</SDLCheck>
<ConformanceMode>true</ConformanceMode>
<LanguageStandard>stdcpp17</LanguageStandard>
<AdditionalIncludeDirectories>$(SolutionDir)packages\detours\include;$(SolutionDir)Shared;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PreprocessorDefinitions>WIN32_LEAN_AND_MEAN;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<ClCompile><Optimization>Disabled</Optimization><RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary></ClCompile>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<ClCompile><Optimization>MaxSpeed</Optimization><RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary></ClCompile>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<ClCompile><Optimization>Disabled</Optimization><RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary></ClCompile>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<ClCompile><Optimization>MaxSpeed</Optimization><RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary></ClCompile>
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="InjectionManager.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="InjectionManager.h" />
<ClInclude Include="ProcessWatcher.h" />
<ClInclude Include="..\Shared\MockTimeInfo.h" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
</Project>
+95
View File
@@ -0,0 +1,95 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|x64"> <Configuration>Debug</Configuration> <Platform>x64</Platform> </ProjectConfiguration>
<ProjectConfiguration Include="Release|x64"><Configuration>Release</Configuration><Platform>x64</Platform> </ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<ProjectGuid>{C3333333-3333-3333-3333-333333333333}</ProjectGuid>
<RootNamespace>TimeMockerUI</RootNamespace>
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ItemDefinitionGroup>
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<SDLCheck>true</SDLCheck>
<ConformanceMode>true</ConformanceMode>
<LanguageStandard>stdcpp17</LanguageStandard>
<AdditionalIncludeDirectories>
$(SolutionDir)packages\detours\include;
$(SolutionDir)Shared;
%(AdditionalIncludeDirectories)
</AdditionalIncludeDirectories>
<PreprocessorDefinitions>WIN32_LEAN_AND_MEAN;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<AdditionalDependencies>detours.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<ClCompile>
<Optimization>Disabled</Optimization>
<RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
</ClCompile>
<Link>
<AdditionalLibraryDirectories>
$(SolutionDir)packages\detours\lib\x64;
$(OutDir);
%(AdditionalLibraryDirectories)
</AdditionalLibraryDirectories>
<AdditionalDependencies>TimeMocker.Injector.lib;detours.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<ClCompile>
<Optimization>MaxSpeed</Optimization>
<RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
</ClCompile>
<Link>
<AdditionalLibraryDirectories>
$(SolutionDir)packages\detours\lib\x64;
$(OutDir);
%(AdditionalLibraryDirectories)
</AdditionalLibraryDirectories>
<AdditionalDependencies>TimeMocker.Injector.lib;detours.lib;%(AdditionalDependencies)</AdditionalDependencies>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="main.cpp" />
<!-- Compile injector source directly (alternative to linking static lib) -->
<ClCompile Include="..\TimeMocker.Injector\InjectionManager.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\TimeMocker.Injector\InjectionManager.h" />
<ClInclude Include="..\TimeMocker.Injector\ProcessWatcher.h" />
<ClInclude Include="..\Shared\MockTimeInfo.h" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
</Project>
+461
View File
@@ -0,0 +1,461 @@
// =============================================================================
// TimeMocker.UI — Console controller
//
// Commands:
// list — list running processes (filtered to accessible ones)
// inject <pid> — inject into process and apply current fake time
// eject <pid> — remove hook from process
// time <datetime> — set fake time e.g. time "2024-06-15 14:30:00"
// time now — reset fake time to real time
// status — show injected processes and current time offset
// rule add <glob> — add auto-inject glob rule
// rule add -r <rx> — add auto-inject regex rule
// rule list — list auto-inject rules
// rule del <n> — remove rule by index
// watch start — start auto-inject watcher (default: on startup)
// watch stop — stop auto-inject watcher
// quit / exit — exit
// =============================================================================
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#include <TlHelp32.h>
#include <Psapi.h>
#include <cstdio>
#include <cstdlib>
#include <cwchar>
#include <string>
#include <vector>
#include <sstream>
#include <algorithm>
#include <iostream>
#include <iomanip>
#include <io.h>
#include <fcntl.h>
#include "../TimeMocker.Injector/InjectionManager.h"
#include "../TimeMocker.Injector/ProcessWatcher.h"
#pragma comment(lib, "Psapi.lib")
// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------
static void EnableAnsi()
{
HANDLE h = GetStdHandle(STD_OUTPUT_HANDLE);
DWORD mode = 0;
GetConsoleMode(h, &mode);
SetConsoleMode(h, mode | ENABLE_VIRTUAL_TERMINAL_PROCESSING);
}
static const char* C_RESET = "\033[0m";
static const char* C_CYAN = "\033[36m";
static const char* C_GREEN = "\033[32m";
static const char* C_YELLOW = "\033[33m";
static const char* C_RED = "\033[31m";
static const char* C_GRAY = "\033[90m";
static void Log(const wchar_t* msg)
{
// Convert wide to UTF-8 for console output
char buf[1024];
WideCharToMultiByte(CP_UTF8, 0, msg, -1, buf, sizeof(buf), nullptr, nullptr);
printf("%s[LOG]%s %s\n", C_GRAY, C_RESET, buf);
}
// Parse "YYYY-MM-DD HH:MM:SS" → SYSTEMTIME (local)
static bool ParseDateTime(const std::string& s, SYSTEMTIME& st)
{
memset(&st, 0, sizeof(st));
int Y, M, D, h, m, sec;
if (sscanf_s(s.c_str(), "%d-%d-%d %d:%d:%d", &Y, &M, &D, &h, &m, &sec) == 6 ||
sscanf_s(s.c_str(), "%d/%d/%d %d:%d:%d", &Y, &M, &D, &h, &m, &sec) == 6)
{
st.wYear = static_cast<WORD>(Y);
st.wMonth = static_cast<WORD>(M);
st.wDay = static_cast<WORD>(D);
st.wHour = static_cast<WORD>(h);
st.wMinute = static_cast<WORD>(m);
st.wSecond = static_cast<WORD>(sec);
return true;
}
return false;
}
static std::string FormatDelta(LONGLONG deltaTicks)
{
// deltaTicks: 100-ns units
bool neg = deltaTicks < 0;
LONGLONG abs = neg ? -deltaTicks : deltaTicks;
LONGLONG secs = abs / 10000000LL;
LONGLONG mins = secs / 60; secs %= 60;
LONGLONG hours = mins / 60; mins %= 60;
LONGLONG days = hours / 24; hours %= 24;
char buf[128] = {};
if (days) sprintf_s(buf, "%s%lldd%02lldh%02lldm%02llds", neg?"-":"+", days, hours, mins, secs);
else if (hours) sprintf_s(buf, "%s%lldh%02lldm%02llds", neg?"-":"+", hours, mins, secs);
else if (mins) sprintf_s(buf, "%s%lldm%02llds", neg?"-":"+", mins, secs);
else sprintf_s(buf, "%s%llds", neg?"-":"+", secs);
return buf;
}
// ---------------------------------------------------------------------------
// Enumerate accessible processes
// ---------------------------------------------------------------------------
struct ProcInfo { DWORD pid; std::wstring name, path; };
static std::vector<ProcInfo> EnumProcesses(const std::wstring& filter = L"")
{
std::vector<ProcInfo> result;
HANDLE snap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (snap == INVALID_HANDLE_VALUE) return result;
PROCESSENTRY32W pe; pe.dwSize = sizeof(pe);
if (!Process32FirstW(snap, &pe)) { CloseHandle(snap); return result; }
do
{
HANDLE hProc = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pe.th32ProcessID);
if (!hProc) continue;
wchar_t pathBuf[MAX_PATH] = {}; DWORD len = MAX_PATH;
QueryFullProcessImageNameW(hProc, 0, pathBuf, &len);
CloseHandle(hProc);
ProcInfo info;
info.pid = pe.th32ProcessID;
info.name = pe.szExeFile;
info.path = pathBuf;
if (!filter.empty())
{
auto contains = [&](const std::wstring& hay, const std::wstring& needle)
{
std::wstring lh = hay, ln = needle;
std::transform(lh.begin(), lh.end(), lh.begin(), ::towlower);
std::transform(ln.begin(), ln.end(), ln.begin(), ::towlower);
return lh.find(ln) != std::wstring::npos;
};
if (!contains(info.name, filter) && !contains(info.path, filter)) continue;
}
result.push_back(info);
} while (Process32NextW(snap, &pe));
CloseHandle(snap);
std::sort(result.begin(), result.end(), [](const ProcInfo& a, const ProcInfo& b)
{
return _wcsicmp(a.name.c_str(), b.name.c_str()) < 0;
});
return result;
}
// ---------------------------------------------------------------------------
// Main
// ---------------------------------------------------------------------------
int main()
{
// Switch console to UTF-8
SetConsoleOutputCP(CP_UTF8);
_setmode(_fileno(stdout), _O_U8TEXT); // redirect wide to UTF-8
_setmode(_fileno(stdout), _O_TEXT); // reset (we'll use printf)
EnableAnsi();
printf("%s╔══════════════════════════════════════════════╗%s\n", C_CYAN, C_RESET);
printf("%s║ TimeMocker — C++ / MS Detours ║%s\n", C_CYAN, C_RESET);
printf("%s╚══════════════════════════════════════════════╝%s\n", C_CYAN, C_RESET);
printf("Type %shelp%s for command list.\n\n", C_YELLOW, C_RESET);
// Check elevation
{
HANDLE hToken;
OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken);
TOKEN_ELEVATION elev;
DWORD sz = sizeof(elev);
GetTokenInformation(hToken, TokenElevation, &elev, sz, &sz);
CloseHandle(hToken);
if (!elev.TokenIsElevated)
{
printf("%s[WARN] Not running as Administrator. Injection into protected processes will fail.%s\n\n", C_YELLOW, C_RESET);
}
}
InjectionManager mgr;
mgr.OnLog = [](const std::wstring& msg) { Log(msg.c_str()); };
ProcessWatcher watcher(mgr);
watcher.OnLog = [](const std::wstring& msg) { Log(msg.c_str()); };
watcher.OnAutoInjected = [&](DWORD pid, const std::wstring& name, const std::wstring& /*path*/)
{
char buf[256]; WideCharToMultiByte(CP_UTF8, 0, name.c_str(), -1, buf, sizeof(buf), nullptr, nullptr);
printf("%s[AutoInject]%s [%lu] %s\n", C_GREEN, C_RESET, pid, buf);
};
LONGLONG fakeUtcTicks = TimeUtil::RealUtcTicks(); // start at real time (delta = 0)
std::vector<PatternRule> ruleList; // local copy for display
watcher.SetFakeUtcTicks(fakeUtcTicks);
watcher.Start(1500);
printf("%s[Watcher]%s Auto-inject watcher started.\n\n", C_GREEN, C_RESET);
std::string line;
while (true)
{
printf("%stimemocker>%s ", C_CYAN, C_RESET);
fflush(stdout);
if (!std::getline(std::cin, line)) break;
// Tokenize
std::istringstream iss(line);
std::vector<std::string> tokens;
{
std::string tok;
// Handle quoted tokens
bool inQ = false; std::string cur;
for (char c : line)
{
if (c == '"') { inQ = !inQ; }
else if (c == ' ' && !inQ && !cur.empty()) { tokens.push_back(cur); cur.clear(); }
else { cur += c; }
}
if (!cur.empty()) tokens.push_back(cur);
}
if (tokens.empty()) continue;
std::string cmd = tokens[0];
std::transform(cmd.begin(), cmd.end(), cmd.begin(), ::tolower);
// ---- quit -----------------------------------------------------------
if (cmd == "quit" || cmd == "exit") break;
// ---- help -----------------------------------------------------------
else if (cmd == "help")
{
printf(
" %slist%s [filter] list running processes\n"
" %sinject%s <pid> inject hook DLL into process\n"
" %seject%s <pid> remove hook from process\n"
" %stime%s YYYY-MM-DD HH:MM:SS set fake time (local)\n"
" %stime%s now reset to real time\n"
" %sstatus%s show injected processes + delta\n"
" %srule add%s <pattern> add glob auto-inject rule\n"
" %srule add%s -r <pattern> add regex auto-inject rule\n"
" %srule list%s list rules\n"
" %srule del%s <index> remove rule by index\n"
" %swatch start%s / %sstop%s control auto-inject watcher\n"
" %squit%s exit\n",
C_YELLOW,C_RESET, C_YELLOW,C_RESET, C_YELLOW,C_RESET,
C_YELLOW,C_RESET, C_YELLOW,C_RESET, C_YELLOW,C_RESET,
C_YELLOW,C_RESET, C_YELLOW,C_RESET, C_YELLOW,C_RESET,
C_YELLOW,C_RESET, C_YELLOW,C_RESET, C_YELLOW,C_RESET,
C_YELLOW,C_RESET, C_YELLOW,C_RESET);
}
// ---- list -----------------------------------------------------------
else if (cmd == "list")
{
std::wstring filter;
if (tokens.size() > 1)
{
std::string fs = tokens[1];
filter = std::wstring(fs.begin(), fs.end());
}
auto procs = EnumProcesses(filter);
printf("%s%-8s %-28s %s%s\n", C_GRAY, "PID", "Name", "Path", C_RESET);
for (auto& p : procs)
{
char name[128] = {}, path[512] = {};
WideCharToMultiByte(CP_UTF8, 0, p.name.c_str(), -1, name, sizeof(name), nullptr, nullptr);
WideCharToMultiByte(CP_UTF8, 0, p.path.c_str(), -1, path, sizeof(path), nullptr, nullptr);
bool inj = mgr.IsInjected(p.pid);
printf("%-8lu %-28s %s%s\n", p.pid, name, path, inj ? "" : "");
}
printf(" %lu processes shown\n", (DWORD)procs.size());
}
// ---- inject ---------------------------------------------------------
else if (cmd == "inject")
{
if (tokens.size() < 2) { printf("%sUsage: inject <pid>%s\n", C_RED, C_RESET); continue; }
DWORD pid = (DWORD)atoul(tokens[1].c_str());
std::wstring err;
if (mgr.Inject(pid, fakeUtcTicks, &err))
{
auto delta = TimeUtil::ComputeDelta(fakeUtcTicks);
printf("%s[OK]%s Injected pid=%lu delta=%s\n",
C_GREEN, C_RESET, pid, FormatDelta(delta).c_str());
}
else
{
char ebuf[512]; WideCharToMultiByte(CP_UTF8,0,err.c_str(),-1,ebuf,sizeof(ebuf),nullptr,nullptr);
printf("%s[FAIL]%s %s\n", C_RED, C_RESET, ebuf);
}
}
// ---- eject ----------------------------------------------------------
else if (cmd == "eject")
{
if (tokens.size() < 2) { printf("%sUsage: eject <pid>%s\n", C_RED, C_RESET); continue; }
DWORD pid = (DWORD)atoul(tokens[1].c_str());
if (mgr.Eject(pid))
printf("%s[OK]%s Ejected pid=%lu\n", C_GREEN, C_RESET, pid);
else
printf("%s[FAIL]%s pid=%lu not injected\n", C_RED, C_RESET, pid);
}
// ---- time -----------------------------------------------------------
else if (cmd == "time")
{
if (tokens.size() < 2) { printf("%sUsage: time YYYY-MM-DD HH:MM:SS | time now%s\n", C_RED, C_RESET); continue; }
std::string arg = tokens[1];
if (tokens.size() >= 3) arg += " " + tokens[2]; // join date and time
if (arg == "now")
{
fakeUtcTicks = TimeUtil::RealUtcTicks();
}
else
{
SYSTEMTIME st;
if (!ParseDateTime(arg, st))
{
printf("%sInvalid format. Use: YYYY-MM-DD HH:MM:SS%s\n", C_RED, C_RESET);
continue;
}
fakeUtcTicks = TimeUtil::LocalSystemTimeToUtcTicks(st);
}
mgr.SetFakeTimeAll(fakeUtcTicks);
watcher.SetFakeUtcTicks(fakeUtcTicks);
auto delta = TimeUtil::ComputeDelta(fakeUtcTicks);
// Display local time
SYSTEMTIME disp;
FILETIME ft;
ULARGE_INTEGER ui; ui.QuadPart = (ULONGLONG)fakeUtcTicks;
ft.dwLowDateTime = ui.LowPart; ft.dwHighDateTime = ui.HighPart;
FILETIME lft; FileTimeToLocalFileTime(&ft, &lft);
FileTimeToSystemTime(&lft, &disp);
printf("%s[Time]%s Fake time set to %04d-%02d-%02d %02d:%02d:%02d (local) delta=%s\n",
C_GREEN, C_RESET,
disp.wYear, disp.wMonth, disp.wDay,
disp.wHour, disp.wMinute, disp.wSecond,
FormatDelta(delta).c_str());
}
// ---- status ---------------------------------------------------------
else if (cmd == "status")
{
LONGLONG delta = TimeUtil::ComputeDelta(fakeUtcTicks);
// Show local fake time
FILETIME ft; ULARGE_INTEGER ui; ui.QuadPart = (ULONGLONG)fakeUtcTicks;
ft.dwLowDateTime = ui.LowPart; ft.dwHighDateTime = ui.HighPart;
FILETIME lft; FileTimeToLocalFileTime(&ft, &lft);
SYSTEMTIME st; FileTimeToSystemTime(&lft, &st);
printf("Fake time : %04d-%02d-%02d %02d:%02d:%02d (local) delta=%s\n",
st.wYear, st.wMonth, st.wDay,
st.wHour, st.wMinute, st.wSecond,
FormatDelta(delta).c_str());
printf("Injected processes:\n");
bool any = false;
mgr.ForEach([&](const InjectedProcessInfo& p)
{
any = true;
char name[256] = {};
WideCharToMultiByte(CP_UTF8, 0, p.ProcessName.c_str(), -1, name, sizeof(name), nullptr, nullptr);
printf(" %s[%lu]%s %s\n", C_GREEN, p.Pid, C_RESET, name);
});
if (!any) printf(" (none)\n");
}
// ---- rule -----------------------------------------------------------
else if (cmd == "rule")
{
if (tokens.size() < 2) { printf("Subcommands: add, list, del\n"); continue; }
std::string sub = tokens[1];
std::transform(sub.begin(), sub.end(), sub.begin(), ::tolower);
if (sub == "list")
{
if (ruleList.empty()) { printf(" (no rules)\n"); continue; }
for (size_t i = 0; i < ruleList.size(); i++)
{
char pat[512]; WideCharToMultiByte(CP_UTF8, 0, ruleList[i].Pattern.c_str(), -1, pat, sizeof(pat), nullptr, nullptr);
printf(" [%zu] %s%s%s (%s)\n",
i, C_YELLOW, pat, C_RESET,
ruleList[i].UseRegex ? "regex" : "glob");
}
}
else if (sub == "add")
{
if (tokens.size() < 3) { printf("Usage: rule add [-r] <pattern>\n"); continue; }
bool isRegex = false;
std::string patStr;
if (tokens[2] == "-r" || tokens[2] == "--regex")
{
isRegex = true;
if (tokens.size() < 4) { printf("Missing pattern after -r\n"); continue; }
patStr = tokens[3];
}
else
{
patStr = tokens[2];
}
PatternRule rule;
rule.Pattern = std::wstring(patStr.begin(), patStr.end());
rule.UseRegex = isRegex;
rule.Enabled = true;
watcher.AddRule(rule);
ruleList.push_back(rule);
printf("%s[OK]%s Rule added: '%s' (%s)\n",
C_GREEN, C_RESET, patStr.c_str(), isRegex ? "regex" : "glob");
}
else if (sub == "del")
{
if (tokens.size() < 3) { printf("Usage: rule del <index>\n"); continue; }
size_t idx = (size_t)atoi(tokens[2].c_str());
if (idx >= ruleList.size()) { printf("%sIndex out of range%s\n", C_RED, C_RESET); continue; }
watcher.RemoveRule(ruleList[idx].Pattern);
ruleList.erase(ruleList.begin() + idx);
printf("%s[OK]%s Rule removed\n", C_GREEN, C_RESET);
}
else
{
printf("Unknown rule subcommand: %s\n", sub.c_str());
}
}
// ---- watch ----------------------------------------------------------
else if (cmd == "watch")
{
if (tokens.size() < 2) { printf("Usage: watch start|stop\n"); continue; }
std::string sub = tokens[1];
if (sub == "start") { watcher.Start(); printf("%s[Watcher]%s Started\n", C_GREEN, C_RESET); }
else if (sub == "stop") { watcher.Stop(); printf("%s[Watcher]%s Stopped\n", C_YELLOW, C_RESET); }
}
else
{
printf("%sUnknown command: %s%s (type help)\n", C_RED, cmd.c_str(), C_RESET);
}
}
watcher.Stop();
printf("\nGoodbye.\n");
return 0;
}
+41
View File
@@ -0,0 +1,41 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.11.35222.181
MinimumVisualStudioVersion = 10.0.40219.1
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "TimeMocker.Hook", "TimeMocker.Hook\TimeMocker.Hook.vcxproj", "{A1111111-1111-1111-1111-111111111111}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "TimeMocker.Injector", "TimeMocker.Injector\TimeMocker.Injector.vcxproj", "{B2222222-2222-2222-2222-222222222222}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "TimeMocker.UI", "TimeMocker.UI\TimeMocker.UI.vcxproj", "{C3333333-3333-3333-3333-333333333333}"
ProjectSection(ProjectDependencies) = postProject
{A1111111-1111-1111-1111-111111111111} = {A1111111-1111-1111-1111-111111111111}
{B2222222-2222-2222-2222-222222222222} = {B2222222-2222-2222-2222-222222222222}
EndProjectSection
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|x64 = Debug|x64
Release|x64 = Release|x64
Debug|x86 = Debug|x86
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{A1111111-1111-1111-1111-111111111111}.Debug|x64.ActiveCfg = Debug|x64
{A1111111-1111-1111-1111-111111111111}.Debug|x64.Build.0 = Debug|x64
{A1111111-1111-1111-1111-111111111111}.Release|x64.ActiveCfg = Release|x64
{A1111111-1111-1111-1111-111111111111}.Release|x64.Build.0 = Release|x64
{A1111111-1111-1111-1111-111111111111}.Debug|x86.ActiveCfg = Debug|Win32
{A1111111-1111-1111-1111-111111111111}.Debug|x86.Build.0 = Debug|Win32
{A1111111-1111-1111-1111-111111111111}.Release|x86.ActiveCfg = Release|Win32
{A1111111-1111-1111-1111-111111111111}.Release|x86.Build.0 = Release|Win32
{B2222222-2222-2222-2222-222222222222}.Debug|x64.ActiveCfg = Debug|x64
{B2222222-2222-2222-2222-222222222222}.Debug|x64.Build.0 = Debug|x64
{B2222222-2222-2222-2222-222222222222}.Release|x64.ActiveCfg = Release|x64
{B2222222-2222-2222-2222-222222222222}.Release|x64.Build.0 = Release|x64
{C3333333-3333-3333-3333-333333333333}.Debug|x64.ActiveCfg = Debug|x64
{C3333333-3333-3333-3333-333333333333}.Debug|x64.Build.0 = Debug|x64
{C3333333-3333-3333-3333-333333333333}.Release|x64.ActiveCfg = Release|x64
{C3333333-3333-3333-3333-333333333333}.Release|x64.Build.0 = Release|x64
EndGlobalSection
EndGlobal
+64
View File
@@ -0,0 +1,64 @@
# =============================================================================
# setup.ps1 — Bootstrap MS Detours for TimeMockerCpp
#
# Run once before opening the solution in Visual Studio:
# powershell -ExecutionPolicy Bypass -File scripts\setup.ps1
#
# What it does:
# 1. Clones / updates vcpkg (if not already present at $env:VCPKG_ROOT or ./vcpkg)
# 2. Installs detours:x64-windows and detours:x86-windows
# 3. Copies the resulting headers + libs into packages\detours\
# so the vcxproj files can find them without requiring vcpkg integration.
# =============================================================================
$ErrorActionPreference = "Stop"
$scriptDir = $PSScriptRoot
$repoRoot = Split-Path $scriptDir -Parent
$pkgDir = Join-Path $repoRoot "packages\detours"
$vcpkgRoot = if ($env:VCPKG_ROOT) { $env:VCPKG_ROOT } else { Join-Path $repoRoot "vcpkg" }
# ── 1. Ensure vcpkg ──────────────────────────────────────────────────────────
if (!(Test-Path (Join-Path $vcpkgRoot "vcpkg.exe")))
{
Write-Host "Cloning vcpkg into $vcpkgRoot ..." -ForegroundColor Cyan
git clone https://github.com/microsoft/vcpkg.git $vcpkgRoot
& (Join-Path $vcpkgRoot "bootstrap-vcpkg.bat") -disableMetrics
}
else
{
Write-Host "vcpkg found at $vcpkgRoot" -ForegroundColor Green
}
$vcpkg = Join-Path $vcpkgRoot "vcpkg.exe"
# ── 2. Install Detours ───────────────────────────────────────────────────────
Write-Host "Installing detours:x64-windows ..." -ForegroundColor Cyan
& $vcpkg install "detours:x64-windows"
Write-Host "Installing detours:x86-windows ..." -ForegroundColor Cyan
& $vcpkg install "detours:x86-windows"
# ── 3. Copy headers + libs into packages\detours\ ───────────────────────────
$x64installed = Join-Path $vcpkgRoot "installed\x64-windows"
$x86installed = Join-Path $vcpkgRoot "installed\x86-windows"
$incSrc = Join-Path $x64installed "include"
$incDst = Join-Path $pkgDir "include"
Write-Host "Copying headers → $incDst" -ForegroundColor Cyan
New-Item -ItemType Directory -Force -Path $incDst | Out-Null
Copy-Item -Path (Join-Path $incSrc "detours.h") -Destination $incDst -Force
foreach ($triplet in @("x64", "x86"))
{
$libSrc = Join-Path (Join-Path $vcpkgRoot "installed\$triplet-windows") "lib\detours.lib"
$libDst = Join-Path $pkgDir "lib\$triplet"
New-Item -ItemType Directory -Force -Path $libDst | Out-Null
Copy-Item -Path $libSrc -Destination (Join-Path $libDst "detours.lib") -Force
Write-Host "Copied $triplet detours.lib → $libDst" -ForegroundColor Green
}
Write-Host ""
Write-Host "Setup complete! Open TimeMocker.sln in Visual Studio 2022." -ForegroundColor Green
Write-Host "Build configuration: Debug|x64 or Release|x64" -ForegroundColor Green