diff --git a/CMakeLists.txt b/CMakeLists.txt index c622901..1ade48c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,21 +1,18 @@ cmake_minimum_required(VERSION 3.20) project(TimeMocker CXX) -# ── vcpkg toolchain (set via: cmake -DCMAKE_TOOLCHAIN_FILE=/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).") +if(NOT WIN32) + message(FATAL_ERROR "Windows only.") endif() -add_compile_definitions(WIN32_LEAN_AND_MEAN UNICODE _UNICODE) +add_compile_definitions(WIN32_LEAN_AND_MEAN NOMINMAX UNICODE _UNICODE) -# ── Shared headers ──────────────────────────────────────────────────────────── -set(SHARED_DIR ${CMAKE_SOURCE_DIR}/Shared) +set(IMGUI_DIR "${CMAKE_SOURCE_DIR}/TimeMocker.UI/imgui") # ── TimeMocker.Hook (DLL) ───────────────────────────────────────────────────── add_library(TimeMocker.Hook SHARED @@ -23,41 +20,36 @@ add_library(TimeMocker.Hook SHARED TimeMocker.Hook/exports.cpp TimeMocker.Hook/TimeMocker.Hook.def ) -target_include_directories(TimeMocker.Hook PRIVATE ${SHARED_DIR}) +target_include_directories(TimeMocker.Hook PRIVATE Shared) 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" -) +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 +# ── ImGui static lib ────────────────────────────────────────────────────────── +add_library(imgui STATIC + ${IMGUI_DIR}/imgui.cpp + ${IMGUI_DIR}/imgui_draw.cpp + ${IMGUI_DIR}/imgui_tables.cpp + ${IMGUI_DIR}/imgui_widgets.cpp + ${IMGUI_DIR}/imgui_impl_win32.cpp + ${IMGUI_DIR}/imgui_impl_dx11.cpp +) +target_include_directories(imgui PUBLIC ${IMGUI_DIR}) + +# ── TimeMocker.UI (GUI exe) ─────────────────────────────────────────────────── +add_executable(TimeMocker.UI WIN32 + TimeMocker.UI/main.cpp 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) +target_include_directories(TimeMocker.UI PRIVATE Shared TimeMocker.Injector ${IMGUI_DIR}) +target_link_libraries(TimeMocker.UI PRIVATE imgui detours::detours d3d11 dxgi Psapi) +set_target_properties(TimeMocker.UI PROPERTIES OUTPUT_NAME "TimeMocker" + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin") -# ── 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 $ "${CMAKE_BINARY_DIR}/bin/TimeMocker.Hook.x64.dll" - COMMENT "Copying Hook DLL (x64) → bin/" -) + COMMENT "Copying Hook DLL") -install(TARGETS TimeMocker.UI TimeMocker.Hook - RUNTIME DESTINATION bin) +install(TARGETS TimeMocker.UI TimeMocker.Hook RUNTIME DESTINATION bin) diff --git a/TimeMocker.UI/TimeMocker.UI.vcxproj b/TimeMocker.UI/TimeMocker.UI.vcxproj index 562cf2a..b5b9917 100644 --- a/TimeMocker.UI/TimeMocker.UI.vcxproj +++ b/TimeMocker.UI/TimeMocker.UI.vcxproj @@ -4,92 +4,61 @@ Debug x64 Releasex64 - {C3333333-3333-3333-3333-333333333333} TimeMockerUI 10.0 - - - Application - true - v143 - Unicode + Applicationtrue + v143Unicode - Application - false - v143 - true - Unicode + Applicationfalse + v143trueUnicode - - - Level3 - true - true - stdcpp17 - - $(SolutionDir)packages\detours\include; - $(SolutionDir)Shared; - %(AdditionalIncludeDirectories) - - WIN32_LEAN_AND_MEAN;%(PreprocessorDefinitions) + Level3true + truestdcpp17 + $(ProjectDir)imgui;$(SolutionDir)packages\detours\include;$(SolutionDir)Shared;%(AdditionalIncludeDirectories) + WIN32_LEAN_AND_MEAN;NOMINMAX;%(PreprocessorDefinitions) - Console - detours.lib;%(AdditionalDependencies) + Windows + d3d11.lib;dxgi.lib;d3dcompiler.lib;detours.lib;Psapi.lib;%(AdditionalDependencies) - - - Disabled - MultiThreadedDebugDLL - - - - $(SolutionDir)packages\detours\lib\x64; - $(OutDir); - %(AdditionalLibraryDirectories) - - TimeMocker.Injector.lib;detours.lib;%(AdditionalDependencies) - + DisabledMultiThreadedDebugDLL + $(SolutionDir)packages\detours\lib\x64;%(AdditionalLibraryDirectories) - - - MaxSpeed - MultiThreadedDLL - + MaxSpeedMultiThreadedDLL - - $(SolutionDir)packages\detours\lib\x64; - $(OutDir); - %(AdditionalLibraryDirectories) - - TimeMocker.Injector.lib;detours.lib;%(AdditionalDependencies) - true - true + $(SolutionDir)packages\detours\lib\x64;%(AdditionalLibraryDirectories) + truetrue - - + + + + + + + + + - diff --git a/TimeMocker.UI/imgui/README.md b/TimeMocker.UI/imgui/README.md new file mode 100644 index 0000000..09235ee --- /dev/null +++ b/TimeMocker.UI/imgui/README.md @@ -0,0 +1,22 @@ +# imgui/ + +This directory must contain Dear ImGui source files. + +Run `scripts/setup.ps1` to download them automatically, or place them here manually +from https://github.com/ocornut/imgui (latest release). + +Required files: + imgui.h + imgui.cpp + imgui_internal.h + imgui_draw.cpp + imgui_tables.cpp + imgui_widgets.cpp + imgui_impl_win32.h + imgui_impl_win32.cpp + imgui_impl_dx11.h + imgui_impl_dx11.cpp + imconfig.h + imstb_rectpack.h + imstb_textedit.h + imstb_truetype.h diff --git a/TimeMocker.UI/main.cpp b/TimeMocker.UI/main.cpp index 64cb33d..255e33b 100644 --- a/TimeMocker.UI/main.cpp +++ b/TimeMocker.UI/main.cpp @@ -1,461 +1,639 @@ // ============================================================================= -// TimeMocker.UI — Console controller -// -// Commands: -// list — list running processes (filtered to accessible ones) -// inject — inject into process and apply current fake time -// eject — remove hook from process -// time — 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 — add auto-inject glob rule -// rule add -r — add auto-inject regex rule -// rule list — list auto-inject rules -// rule del — remove rule by index -// watch start — start auto-inject watcher (default: on startup) -// watch stop — stop auto-inject watcher -// quit / exit — exit +// TimeMocker.UI — ImGui + DirectX 11 frontend // ============================================================================= #define WIN32_LEAN_AND_MEAN +#define NOMINMAX #include #include #include -#include -#include -#include +#include +#include + +#include "imgui/imgui.h" +#include "imgui/imgui_impl_win32.h" +#include "imgui/imgui_impl_dx11.h" + #include #include -#include #include -#include -#include -#include -#include +#include +#include +#include #include "../TimeMocker.Injector/InjectionManager.h" #include "../TimeMocker.Injector/ProcessWatcher.h" +#pragma comment(lib, "d3d11.lib") #pragma comment(lib, "Psapi.lib") -// --------------------------------------------------------------------------- +// ───────────────────────────────────────────────────────────────────────────── +// Forward declarations +// ───────────────────────────────────────────────────────────────────────────── +extern IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND, UINT, WPARAM, LPARAM); +static bool CreateDeviceD3D(HWND); +static void CleanupDeviceD3D(); +static void CreateRenderTarget(); +static void CleanupRenderTarget(); +LRESULT WINAPI WndProc(HWND, UINT, WPARAM, LPARAM); + +// ───────────────────────────────────────────────────────────────────────────── +// D3D globals +// ───────────────────────────────────────────────────────────────────────────── +static ID3D11Device* g_pd3dDevice = nullptr; +static ID3D11DeviceContext* g_pd3dDeviceContext = nullptr; +static IDXGISwapChain* g_pSwapChain = nullptr; +static ID3D11RenderTargetView* g_mainRenderTargetView = nullptr; + +// ───────────────────────────────────────────────────────────────────────────── +// App state +// ───────────────────────────────────────────────────────────────────────────── +struct ProcessRow { DWORD pid; std::string name, path; bool injected; }; +struct RuleRow { std::string pattern; bool useRegex, enabled; }; +struct LogEntry { std::string ts, msg; ImVec4 color; }; + +static InjectionManager* g_injMgr = nullptr; +static ProcessWatcher* g_watcher = nullptr; + +static std::vector g_procRows; +static char g_procFilter[128] = {}; +static std::vector g_rules; +static std::deque g_log; +static std::mutex g_logMutex; +static bool g_logScrollToBottom = false; + +static int g_fakeYear = 2024, g_fakeMon = 1, g_fakeDay = 1; +static int g_fakeHour = 0, g_fakeMin = 0, g_fakeSec = 0; +static LONGLONG g_fakeUtcTicks = 0; +static bool g_timeApplied = false; + +static char g_newPattern[256] = {}; +static bool g_newUseRegex = false; + +// ───────────────────────────────────────────────────────────────────────────── +// Palette — terminal green on near-black +// ───────────────────────────────────────────────────────────────────────────── +namespace Pal { + const ImVec4 Bg = { 0.07f, 0.08f, 0.09f, 1.f }; + const ImVec4 Panel = { 0.10f, 0.11f, 0.13f, 1.f }; + const ImVec4 Border = { 0.18f, 0.22f, 0.25f, 1.f }; + const ImVec4 Accent = { 0.18f, 0.78f, 0.44f, 1.f }; + const ImVec4 AccentDim = { 0.10f, 0.48f, 0.26f, 1.f }; + const ImVec4 AccentHot = { 0.30f, 1.00f, 0.60f, 1.f }; + const ImVec4 Danger = { 0.82f, 0.22f, 0.22f, 1.f }; + const ImVec4 DangerHot = { 1.00f, 0.32f, 0.32f, 1.f }; + const ImVec4 Warning = { 0.92f, 0.68f, 0.08f, 1.f }; + const ImVec4 Muted = { 0.36f, 0.42f, 0.46f, 1.f }; + const ImVec4 Text = { 0.80f, 0.88f, 0.82f, 1.f }; + const ImVec4 TextDim = { 0.46f, 0.52f, 0.48f, 1.f }; + const ImVec4 LogInfo = { 0.50f, 0.88f, 0.62f, 1.f }; + const ImVec4 LogWarn = { 0.92f, 0.76f, 0.28f, 1.f }; + const ImVec4 LogErr = { 0.96f, 0.38f, 0.38f, 1.f }; +} + +// ───────────────────────────────────────────────────────────────────────────── // Helpers -// --------------------------------------------------------------------------- - -static void EnableAnsi() +// ───────────────────────────────────────────────────────────────────────────── +static void AppLog(const std::string& msg, ImVec4 color = Pal::LogInfo) { - HANDLE h = GetStdHandle(STD_OUTPUT_HANDLE); - DWORD mode = 0; - GetConsoleMode(h, &mode); - SetConsoleMode(h, mode | ENABLE_VIRTUAL_TERMINAL_PROCESSING); + SYSTEMTIME st; GetLocalTime(&st); + char ts[12]; sprintf_s(ts, "%02d:%02d:%02d", st.wHour, st.wMinute, st.wSecond); + std::lock_guard lk(g_logMutex); + g_log.push_back({ ts, msg, color }); + if (g_log.size() > 512) g_log.pop_front(); + g_logScrollToBottom = true; } -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) +static void WLog(const std::wstring& w) { - // 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); + char b[1024]; WideCharToMultiByte(CP_UTF8, 0, w.c_str(), -1, b, sizeof(b), nullptr, nullptr); + AppLog(b); } -// Parse "YYYY-MM-DD HH:MM:SS" → SYSTEMTIME (local) -static bool ParseDateTime(const std::string& s, SYSTEMTIME& st) +static std::string FormatDelta(LONGLONG d) { - 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(Y); - st.wMonth = static_cast(M); - st.wDay = static_cast(D); - st.wHour = static_cast(h); - st.wMinute = static_cast(m); - st.wSecond = static_cast(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); + if (!d) return "+0s"; + bool neg = d < 0; LONGLONG a = neg ? -d : d; + LONGLONG s = a/10000000LL, m=s/60; s%=60; + LONGLONG h = m/60; m%=60; LONGLONG dy=h/24; h%=24; + char buf[64]; + if (dy) sprintf_s(buf,"%s%lldd%02lldh%02lldm%02llds",neg?"-":"+",dy,h,m,s); + else if (h) sprintf_s(buf,"%s%lldh%02lldm%02llds",neg?"-":"+",h,m,s); + else if (m) sprintf_s(buf,"%s%lldm%02llds",neg?"-":"+",m,s); + else sprintf_s(buf,"%s%llds",neg?"-":"+",s); return buf; } -// --------------------------------------------------------------------------- -// Enumerate accessible processes -// --------------------------------------------------------------------------- -struct ProcInfo { DWORD pid; std::wstring name, path; }; - -static std::vector EnumProcesses(const std::wstring& filter = L"") +static void RefreshProcessList() { - std::vector result; + g_procRows.clear(); HANDLE snap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); - if (snap == INVALID_HANDLE_VALUE) return result; - + if (snap == INVALID_HANDLE_VALUE) return; 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)); - + if (!Process32FirstW(snap, &pe)) { CloseHandle(snap); return; } + do { + HANDLE h = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pe.th32ProcessID); + if (!h) continue; + wchar_t pw[MAX_PATH]={}; DWORD l=MAX_PATH; QueryFullProcessImageNameW(h,0,pw,&l); CloseHandle(h); + char n[256]={}, p[512]={}; + WideCharToMultiByte(CP_UTF8,0,pe.szExeFile,-1,n,sizeof(n),nullptr,nullptr); + WideCharToMultiByte(CP_UTF8,0,pw,-1,p,sizeof(p),nullptr,nullptr); + g_procRows.push_back({pe.th32ProcessID, n, p, g_injMgr->IsInjected(pe.th32ProcessID)}); + } 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; + std::sort(g_procRows.begin(),g_procRows.end(),[](auto&a,auto&b){return _stricmp(a.name.c_str(),b.name.c_str())<0;}); } -// --------------------------------------------------------------------------- -// Main -// --------------------------------------------------------------------------- -int main() +static void ApplyTime() { - // 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(); + SYSTEMTIME st={}; + st.wYear=(WORD)g_fakeYear; st.wMonth=(WORD)g_fakeMon; st.wDay=(WORD)g_fakeDay; + st.wHour=(WORD)g_fakeHour; st.wMinute=(WORD)g_fakeMin; st.wSecond=(WORD)g_fakeSec; + g_fakeUtcTicks = TimeUtil::LocalSystemTimeToUtcTicks(st); + g_injMgr->SetFakeTimeAll(g_fakeUtcTicks); + g_watcher->SetFakeUtcTicks(g_fakeUtcTicks); + g_timeApplied = true; + AppLog("Time applied delta=" + FormatDelta(TimeUtil::ComputeDelta(g_fakeUtcTicks))); +} - 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); +static void ResetTimeToNow() +{ + SYSTEMTIME st; GetLocalTime(&st); + g_fakeYear=st.wYear; g_fakeMon=st.wMonth; g_fakeDay=st.wDay; + g_fakeHour=st.wHour; g_fakeMin=st.wMinute; g_fakeSec=st.wSecond; + g_fakeUtcTicks = TimeUtil::RealUtcTicks(); + g_injMgr->SetFakeTimeAll(g_fakeUtcTicks); + g_watcher->SetFakeUtcTicks(g_fakeUtcTicks); + g_timeApplied = false; + AppLog("Time reset to real time", Pal::LogWarn); +} - // Check elevation +// ───────────────────────────────────────────────────────────────────────────── +// ImGui style +// ───────────────────────────────────────────────────────────────────────────── +static void ApplyTheme() +{ + ImGuiStyle& s = ImGui::GetStyle(); + s.WindowRounding=6; s.ChildRounding=4; s.FrameRounding=4; + s.PopupRounding=4; s.TabRounding=5; s.GrabRounding=4; + s.WindowBorderSize=1; s.FrameBorderSize=1; + s.ItemSpacing={8,5}; s.FramePadding={8,4}; + s.WindowPadding={12,10}; s.ScrollbarSize=10; + + ImVec4* c = s.Colors; + c[ImGuiCol_WindowBg] = Pal::Bg; + c[ImGuiCol_ChildBg] = Pal::Panel; + c[ImGuiCol_PopupBg] = {0.09f,0.10f,0.12f,0.98f}; + c[ImGuiCol_Border] = Pal::Border; + c[ImGuiCol_FrameBg] = {0.13f,0.15f,0.17f,1.f}; + c[ImGuiCol_FrameBgHovered] = {0.17f,0.20f,0.23f,1.f}; + c[ImGuiCol_FrameBgActive] = {0.10f,0.32f,0.20f,1.f}; + c[ImGuiCol_TitleBg] = {0.07f,0.08f,0.09f,1.f}; + c[ImGuiCol_TitleBgActive] = {0.07f,0.20f,0.13f,1.f}; + c[ImGuiCol_ScrollbarBg] = {0.07f,0.08f,0.09f,1.f}; + c[ImGuiCol_ScrollbarGrab] = Pal::AccentDim; + c[ImGuiCol_ScrollbarGrabHovered] = Pal::Accent; + c[ImGuiCol_ScrollbarGrabActive] = Pal::AccentHot; + c[ImGuiCol_CheckMark] = Pal::AccentHot; + c[ImGuiCol_SliderGrab] = Pal::Accent; + c[ImGuiCol_SliderGrabActive] = Pal::AccentHot; + c[ImGuiCol_Button] = {0.11f,0.28f,0.18f,1.f}; + c[ImGuiCol_ButtonHovered] = {0.15f,0.40f,0.24f,1.f}; + c[ImGuiCol_ButtonActive] = {0.18f,0.55f,0.30f,1.f}; + c[ImGuiCol_Header] = {0.09f,0.26f,0.16f,1.f}; + c[ImGuiCol_HeaderHovered] = {0.12f,0.34f,0.21f,1.f}; + c[ImGuiCol_HeaderActive] = {0.14f,0.42f,0.26f,1.f}; + c[ImGuiCol_Separator] = Pal::Border; + c[ImGuiCol_Tab] = {0.09f,0.18f,0.12f,1.f}; + c[ImGuiCol_TabHovered] = {0.13f,0.36f,0.22f,1.f}; + c[ImGuiCol_TabActive] = {0.12f,0.42f,0.26f,1.f}; + c[ImGuiCol_TabUnfocused] = {0.07f,0.12f,0.09f,1.f}; + c[ImGuiCol_TabUnfocusedActive] = {0.09f,0.24f,0.15f,1.f}; + c[ImGuiCol_Text] = Pal::Text; + c[ImGuiCol_TextDisabled] = Pal::Muted; + c[ImGuiCol_TableHeaderBg] = {0.09f,0.20f,0.13f,1.f}; + c[ImGuiCol_TableBorderStrong] = Pal::Border; + c[ImGuiCol_TableBorderLight] = {0.14f,0.16f,0.18f,1.f}; + c[ImGuiCol_TableRowBg] = {0.10f,0.11f,0.13f,1.f}; + c[ImGuiCol_TableRowBgAlt] = {0.12f,0.13f,0.15f,1.f}; + c[ImGuiCol_NavHighlight] = Pal::Accent; +} + +// ───────────────────────────────────────────────────────────────────────────── +// UI panels +// ───────────────────────────────────────────────────────────────────────────── + +static void RenderHeader() +{ + ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(0.08f,0.16f,0.11f,1.f)); + ImGui::BeginChild("##hdr", ImVec2(0,46), false); + + SYSTEMTIME real; GetLocalTime(&real); + char rb[40]; sprintf_s(rb,"REAL %04d-%02d-%02d %02d:%02d:%02d", + real.wYear,real.wMonth,real.wDay,real.wHour,real.wMinute,real.wSecond); + ImGui::SetCursorPos({12,13}); + ImGui::TextColored(Pal::Muted, "%s", rb); + + // Compute current fake local time (flowing) + LONGLONG curFake = (g_fakeUtcTicks==0) + ? TimeUtil::RealUtcTicks() + : TimeUtil::RealUtcTicks() + TimeUtil::ComputeDelta(g_fakeUtcTicks); + FILETIME ft; ULARGE_INTEGER ui; ui.QuadPart=(ULONGLONG)curFake; + ft.dwLowDateTime=ui.LowPart; ft.dwHighDateTime=ui.HighPart; + FILETIME lft; FileTimeToLocalFileTime(&ft,&lft); + SYSTEMTIME fk; FileTimeToSystemTime(&lft,&fk); + char fb[48]; sprintf_s(fb,"MOCK %04d-%02d-%02d %02d:%02d:%02d", + fk.wYear,fk.wMonth,fk.wDay,fk.wHour,fk.wMinute,fk.wSecond); + + float tw=ImGui::CalcTextSize(fb).x, ww=ImGui::GetWindowWidth(); + ImGui::SetCursorPos({(ww-tw)*.5f,13}); + ImGui::TextColored(g_timeApplied ? Pal::AccentHot : Pal::Text, "%s", fb); + + LONGLONG delta=(g_fakeUtcTicks==0)?0:TimeUtil::ComputeDelta(g_fakeUtcTicks); + std::string ds=FormatDelta(delta); + float dw2=ImGui::CalcTextSize(ds.c_str()).x; + ImGui::SetCursorPos({ww-dw2-12,13}); + ImGui::TextColored(delta==0?Pal::Muted:Pal::Warning,"%s",ds.c_str()); + + ImGui::EndChild(); + ImGui::PopStyleColor(); +} + +static void RenderTimePanel() +{ + ImGui::PushStyleColor(ImGuiCol_ChildBg,ImVec4(0.09f,0.10f,0.12f,1.f)); + ImGui::BeginChild("##tp", ImVec2(0,68), true); + ImGui::TextColored(Pal::AccentDim,"SET FAKE TIME"); ImGui::SameLine(0,18); + + auto IntW=[](const char* id, int* v, int w){ + ImGui::PushItemWidth(w); ImGui::InputInt(id,v,0,0); ImGui::PopItemWidth(); + }; + IntW("##yr",&g_fakeYear,54); ImGui::SameLine(0,3); + ImGui::TextColored(Pal::Muted,"-"); ImGui::SameLine(0,3); + IntW("##mo",&g_fakeMon,32); ImGui::SameLine(0,3); + ImGui::TextColored(Pal::Muted,"-"); ImGui::SameLine(0,3); + IntW("##dy",&g_fakeDay,32); ImGui::SameLine(0,14); + IntW("##hr",&g_fakeHour,32); ImGui::SameLine(0,3); + ImGui::TextColored(Pal::Muted,":"); ImGui::SameLine(0,3); + IntW("##mn",&g_fakeMin,32); ImGui::SameLine(0,3); + ImGui::TextColored(Pal::Muted,":"); ImGui::SameLine(0,3); + IntW("##sc",&g_fakeSec,32); + + g_fakeMon =std::clamp(g_fakeMon,1,12); g_fakeDay =std::clamp(g_fakeDay,1,31); + g_fakeHour=std::clamp(g_fakeHour,0,23);g_fakeMin =std::clamp(g_fakeMin,0,59); + g_fakeSec =std::clamp(g_fakeSec,0,59); + + ImGui::SameLine(0,16); + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.08f,0.40f,0.22f,1.f)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered,ImVec4(0.12f,0.54f,0.30f,1.f)); + ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(0.16f,0.66f,0.36f,1.f)); + if(ImGui::Button(" SET ",{54,0})) ApplyTime(); + ImGui::PopStyleColor(3); + ImGui::SameLine(0,6); + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.14f,0.16f,0.18f,1.f)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered,ImVec4(0.20f,0.23f,0.26f,1.f)); + ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(0.10f,0.32f,0.20f,1.f)); + if(ImGui::Button(" NOW ",{54,0})) ResetTimeToNow(); + ImGui::PopStyleColor(3); + + ImGui::EndChild(); + ImGui::PopStyleColor(); +} + +static void RenderProcessTab() +{ + ImGui::SetNextItemWidth(220); + ImGui::InputTextWithHint("##pf","Search processes...",g_procFilter,sizeof(g_procFilter)); + ImGui::SameLine(); + if(ImGui::Button(" Refresh ")) RefreshProcessList(); + + int ic=0; for(auto&r:g_procRows) if(r.injected) ic++; + if(ic>0){ + ImGui::SameLine(); + char b[32]; sprintf_s(b," %d injected ",ic); + ImGui::PushStyleColor(ImGuiCol_Button,ImVec4(0.08f,0.28f,0.16f,1.f)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered,ImVec4(0.08f,0.28f,0.16f,1.f)); + ImGui::PushStyleColor(ImGuiCol_ButtonActive,ImVec4(0.08f,0.28f,0.16f,1.f)); + ImGui::Button(b); ImGui::PopStyleColor(3); + } + ImGui::Spacing(); + + ImGuiTableFlags tf = ImGuiTableFlags_Borders|ImGuiTableFlags_RowBg| + ImGuiTableFlags_ScrollY|ImGuiTableFlags_SizingFixedFit| + ImGuiTableFlags_Resizable; + if(ImGui::BeginTable("##pt",4,tf,ImVec2(0,ImGui::GetContentRegionAvail().y))) { - 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) + ImGui::TableSetupScrollFreeze(0,1); + ImGui::TableSetupColumn("PID", ImGuiTableColumnFlags_WidthFixed,62); + ImGui::TableSetupColumn("Name",ImGuiTableColumnFlags_WidthFixed,165); + ImGui::TableSetupColumn("Path",ImGuiTableColumnFlags_WidthStretch); + ImGui::TableSetupColumn("Hook",ImGuiTableColumnFlags_WidthFixed,52); + ImGui::TableHeadersRow(); + + std::string filt(g_procFilter); + std::transform(filt.begin(),filt.end(),filt.begin(),::tolower); + + for(auto& row : g_procRows) { - printf("%s[WARN] Not running as Administrator. Injection into protected processes will fail.%s\n\n", C_YELLOW, C_RESET); + if(!filt.empty()){ + std::string ln=row.name, lp=row.path; + std::transform(ln.begin(),ln.end(),ln.begin(),::tolower); + std::transform(lp.begin(),lp.end(),lp.begin(),::tolower); + if(ln.find(filt)==std::string::npos && lp.find(filt)==std::string::npos) continue; + } + ImGui::TableNextRow(); + ImGui::TableSetColumnIndex(0); + ImGui::TextColored(Pal::Muted,"%lu",row.pid); + ImGui::TableSetColumnIndex(1); + ImGui::TextColored(row.injected?Pal::AccentHot:Pal::Text,"%s",row.name.c_str()); + ImGui::TableSetColumnIndex(2); + ImGui::TextColored(Pal::TextDim,"%s",row.path.c_str()); + ImGui::TableSetColumnIndex(3); + bool inj=row.injected; + char id[32]; sprintf_s(id,"##i%lu",row.pid); + if(inj){ + ImGui::PushStyleColor(ImGuiCol_FrameBg,ImVec4(0.06f,0.26f,0.14f,1.f)); + ImGui::PushStyleColor(ImGuiCol_FrameBgHovered,ImVec4(0.08f,0.32f,0.18f,1.f)); + } + if(ImGui::Checkbox(id,&inj)){ + if(inj){ + std::wstring err; + LONGLONG t=g_fakeUtcTicks==0?TimeUtil::RealUtcTicks():g_fakeUtcTicks; + if(g_injMgr->Inject(row.pid,t,&err)){ + row.injected=true; + AppLog("Injected ["+std::to_string(row.pid)+"] "+row.name); + } else { + char eb[512]; WideCharToMultiByte(CP_UTF8,0,err.c_str(),-1,eb,sizeof(eb),nullptr,nullptr); + AppLog("FAILED inject ["+std::to_string(row.pid)+"]: "+eb,Pal::LogErr); + } + } else { + g_injMgr->Eject(row.pid); + row.injected=false; + AppLog("Ejected ["+std::to_string(row.pid)+"] "+row.name,Pal::LogWarn); + } + } + if(inj) ImGui::PopStyleColor(2); + } + ImGui::EndTable(); + } +} + +static void RenderRulesTab() +{ + ImGui::SetNextItemWidth(340); + ImGui::InputTextWithHint("##np","e.g. C:\\Games\\* or ^.*\\MyApp\\.exe$",g_newPattern,sizeof(g_newPattern)); + ImGui::SameLine(); ImGui::Checkbox("Regex",&g_newUseRegex); ImGui::SameLine(); + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.08f,0.36f,0.20f,1.f)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered,ImVec4(0.12f,0.48f,0.26f,1.f)); + ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(0.16f,0.60f,0.32f,1.f)); + if(ImGui::Button(" + Add Rule ") && g_newPattern[0]){ + RuleRow r{g_newPattern,g_newUseRegex,true}; + g_rules.push_back(r); + PatternRule pr; + pr.Pattern=std::wstring(r.pattern.begin(),r.pattern.end()); + pr.UseRegex=r.useRegex; pr.Enabled=true; + g_watcher->AddRule(pr); + AppLog(std::string("Rule: ")+g_newPattern+(g_newUseRegex?" [regex]":" [glob]")); + g_newPattern[0]=0; + } + ImGui::PopStyleColor(3); + + ImGui::Spacing(); ImGui::Separator(); ImGui::Spacing(); + + ImGuiTableFlags tf=ImGuiTableFlags_Borders|ImGuiTableFlags_RowBg| + ImGuiTableFlags_ScrollY|ImGuiTableFlags_SizingFixedFit; + if(ImGui::BeginTable("##rt",4,tf,ImVec2(0,ImGui::GetContentRegionAvail().y))) + { + ImGui::TableSetupScrollFreeze(0,1); + ImGui::TableSetupColumn("#", ImGuiTableColumnFlags_WidthFixed, 28); + ImGui::TableSetupColumn("Pattern",ImGuiTableColumnFlags_WidthStretch); + ImGui::TableSetupColumn("Type", ImGuiTableColumnFlags_WidthFixed, 56); + ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthFixed, 86); + ImGui::TableHeadersRow(); + + int del=-1; + for(int i=0;i<(int)g_rules.size();i++){ + auto& r=g_rules[i]; + ImGui::TableNextRow(); + ImGui::TableSetColumnIndex(0); ImGui::TextColored(Pal::Muted,"%d",i); + ImGui::TableSetColumnIndex(1); ImGui::TextColored(r.enabled?Pal::Text:Pal::Muted,"%s",r.pattern.c_str()); + ImGui::TableSetColumnIndex(2); ImGui::TextColored(r.useRegex?Pal::Warning:Pal::AccentDim,"%s",r.useRegex?"regex":"glob"); + ImGui::TableSetColumnIndex(3); + char eid[32]; sprintf_s(eid,"##e%d",i); bool en=r.enabled; + if(ImGui::Checkbox(eid,&en)) r.enabled=en; + ImGui::SameLine(0,6); + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.30f,0.09f,0.09f,1.f)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered,ImVec4(0.48f,0.12f,0.12f,1.f)); + ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(0.62f,0.16f,0.16f,1.f)); + char did[32]; sprintf_s(did," X ##d%d",i); + if(ImGui::Button(did)) del=i; + ImGui::PopStyleColor(3); + } + if(del>=0){ + std::wstring wp(g_rules[del].pattern.begin(),g_rules[del].pattern.end()); + AppLog("Rule removed: "+g_rules[del].pattern,Pal::LogWarn); + g_watcher->RemoveRule(wp); + g_rules.erase(g_rules.begin()+del); + } + ImGui::EndTable(); + } +} + +static void RenderLogTab() +{ + if(ImGui::Button(" Clear ")){ + std::lock_guard lk(g_logMutex); + g_log.clear(); + } + ImGui::Spacing(); + ImGui::PushStyleColor(ImGuiCol_ChildBg,ImVec4(0.05f,0.06f,0.07f,1.f)); + ImGui::BeginChild("##lg",ImVec2(0,ImGui::GetContentRegionAvail().y),false,ImGuiWindowFlags_HorizontalScrollbar); + { + std::lock_guard lk(g_logMutex); + for(auto& e : g_log){ + ImGui::TextColored(Pal::Muted,"%s",e.ts.c_str()); + ImGui::SameLine(0,8); + ImGui::TextColored(e.color,"%s",e.msg.c_str()); + } + } + if(g_logScrollToBottom){ ImGui::SetScrollHereY(1.f); g_logScrollToBottom=false; } + ImGui::EndChild(); + ImGui::PopStyleColor(); +} + +// ───────────────────────────────────────────────────────────────────────────── +// Main render +// ───────────────────────────────────────────────────────────────────────────── +static void RenderUI() +{ + const ImGuiViewport* vp=ImGui::GetMainViewport(); + ImGui::SetNextWindowPos(vp->WorkPos); + ImGui::SetNextWindowSize(vp->WorkSize); + ImGuiWindowFlags wf=ImGuiWindowFlags_NoDecoration|ImGuiWindowFlags_NoMove| + ImGuiWindowFlags_NoSavedSettings|ImGuiWindowFlags_NoBringToFrontOnFocus; + ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding,0); + ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize,0); + ImGui::Begin("##root",nullptr,wf); + ImGui::PopStyleVar(2); + + // Title bar row + ImGui::TextColored(Pal::Accent,"TIMEMOCKER"); + ImGui::SameLine(0,10); + ImGui::TextColored(Pal::Muted,"// Win32 API hook via MS Detours // DirectX 11 UI"); + + ImGui::Spacing(); + RenderHeader(); + ImGui::Spacing(); + RenderTimePanel(); + ImGui::Spacing(); + + if(ImGui::BeginTabBar("##tabs")){ + if(ImGui::BeginTabItem(" Processes ")) { ImGui::Spacing(); RenderProcessTab(); ImGui::EndTabItem(); } + if(ImGui::BeginTabItem(" Auto-Inject ")) { ImGui::Spacing(); RenderRulesTab(); ImGui::EndTabItem(); } + if(ImGui::BeginTabItem(" Log ")) { ImGui::Spacing(); RenderLogTab(); ImGui::EndTabItem(); } + ImGui::EndTabBar(); + } + ImGui::End(); +} + +// ───────────────────────────────────────────────────────────────────────────── +// WinMain +// ───────────────────────────────────────────────────────────────────────────── +int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE, LPSTR, int) +{ + // Elevation check + { + HANDLE hTok; OpenProcessToken(GetCurrentProcess(),TOKEN_QUERY,&hTok); + TOKEN_ELEVATION e{}; DWORD sz=sizeof(e); + GetTokenInformation(hTok,TokenElevation,&e,sz,&sz); CloseHandle(hTok); + if(!e.TokenIsElevated){ + MessageBoxA(nullptr,"TimeMocker requires Administrator.\nRestarting elevated...","Elevation Required",MB_ICONWARNING|MB_OK); + wchar_t p[MAX_PATH]; GetModuleFileNameW(nullptr,p,MAX_PATH); + ShellExecuteW(nullptr,L"runas",p,nullptr,nullptr,SW_SHOWNORMAL); + return 0; } } - InjectionManager mgr; - mgr.OnLog = [](const std::wstring& msg) { Log(msg.c_str()); }; + WNDCLASSEXW wc{}; + wc.cbSize=sizeof(wc); wc.style=CS_CLASSDC; wc.lpfnWndProc=WndProc; + wc.hInstance=hInstance; wc.hCursor=LoadCursor(nullptr,IDC_ARROW); + wc.lpszClassName=L"TimeMockerWnd"; + RegisterClassExW(&wc); - 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); + HWND hwnd=CreateWindowExW(0,L"TimeMockerWnd",L"TimeMocker",WS_OVERLAPPEDWINDOW, + CW_USEDEFAULT,CW_USEDEFAULT,1280,780,nullptr,nullptr,hInstance,nullptr); + + if(!CreateDeviceD3D(hwnd)){ CleanupDeviceD3D(); return 1; } + ShowWindow(hwnd,SW_SHOWDEFAULT); UpdateWindow(hwnd); + + IMGUI_CHECKVERSION(); + ImGui::CreateContext(); + ImGuiIO& io=ImGui::GetIO(); + io.ConfigFlags|=ImGuiConfigFlags_NavEnableKeyboard; + io.IniFilename=nullptr; + ApplyTheme(); + ImGui_ImplWin32_Init(hwnd); + ImGui_ImplDX11_Init(g_pd3dDevice,g_pd3dDeviceContext); + + g_injMgr = new InjectionManager(); + g_watcher = new ProcessWatcher(*g_injMgr); + g_injMgr->OnLog = WLog; + g_watcher->OnLog = WLog; + g_watcher->OnAutoInjected=[](DWORD pid,const std::wstring& name,const std::wstring&){ + char b[256]; WideCharToMultiByte(CP_UTF8,0,name.c_str(),-1,b,sizeof(b),nullptr,nullptr); + AppLog(std::string("[AutoInject] [")+std::to_string(pid)+"] "+b, Pal::AccentHot); }; - LONGLONG fakeUtcTicks = TimeUtil::RealUtcTicks(); // start at real time (delta = 0) - std::vector ruleList; // local copy for display + ResetTimeToNow(); + RefreshProcessList(); + g_watcher->Start(1500); + AppLog("TimeMocker started. Watcher active."); - 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 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); + bool done=false; + while(!done){ + MSG msg; + while(PeekMessage(&msg,nullptr,0,0,PM_REMOVE)){ + TranslateMessage(&msg); DispatchMessage(&msg); + if(msg.message==WM_QUIT) done=true; } - if (tokens.empty()) continue; + if(done) break; - std::string cmd = tokens[0]; - std::transform(cmd.begin(), cmd.end(), cmd.begin(), ::tolower); + ImGui_ImplDX11_NewFrame(); + ImGui_ImplWin32_NewFrame(); + ImGui::NewFrame(); + RenderUI(); + ImGui::Render(); - // ---- quit ----------------------------------------------------------- - if (cmd == "quit" || cmd == "exit") break; - - // ---- help ----------------------------------------------------------- - else if (cmd == "help") - { - printf( - " %slist%s [filter] list running processes\n" - " %sinject%s inject hook DLL into process\n" - " %seject%s 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 add glob auto-inject rule\n" - " %srule add%s -r add regex auto-inject rule\n" - " %srule list%s list rules\n" - " %srule del%s 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 %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 %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] \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 \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); - } + const float cc[4]={0.07f,0.08f,0.09f,1.f}; + g_pd3dDeviceContext->OMSetRenderTargets(1,&g_mainRenderTargetView,nullptr); + g_pd3dDeviceContext->ClearRenderTargetView(g_mainRenderTargetView,cc); + ImGui_ImplDX11_RenderDrawData(ImGui::GetDrawData()); + g_pSwapChain->Present(1,0); } - watcher.Stop(); - printf("\nGoodbye.\n"); + g_watcher->Stop(); + delete g_watcher; delete g_injMgr; + ImGui_ImplDX11_Shutdown(); + ImGui_ImplWin32_Shutdown(); + ImGui::DestroyContext(); + CleanupDeviceD3D(); + DestroyWindow(hwnd); + UnregisterClassW(wc.lpszClassName,hInstance); return 0; } + +// ───────────────────────────────────────────────────────────────────────────── +// D3D boilerplate +// ───────────────────────────────────────────────────────────────────────────── +static bool CreateDeviceD3D(HWND hWnd) +{ + DXGI_SWAP_CHAIN_DESC sd{}; + sd.BufferCount=2; sd.BufferDesc.Format=DXGI_FORMAT_R8G8B8A8_UNORM; + sd.Flags=DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH; + sd.BufferUsage=DXGI_USAGE_RENDER_TARGET_OUTPUT; + sd.OutputWindow=hWnd; sd.SampleDesc.Count=1; sd.Windowed=TRUE; + sd.SwapEffect=DXGI_SWAP_EFFECT_DISCARD; + D3D_FEATURE_LEVEL fl; const D3D_FEATURE_LEVEL fls[]={D3D_FEATURE_LEVEL_11_0,D3D_FEATURE_LEVEL_10_0}; + HRESULT hr=D3D11CreateDeviceAndSwapChain(nullptr,D3D_DRIVER_TYPE_HARDWARE,nullptr,0,fls,2, + D3D11_SDK_VERSION,&sd,&g_pSwapChain,&g_pd3dDevice,&fl,&g_pd3dDeviceContext); + if(hr==DXGI_ERROR_UNSUPPORTED) + hr=D3D11CreateDeviceAndSwapChain(nullptr,D3D_DRIVER_TYPE_WARP,nullptr,0,fls,2, + D3D11_SDK_VERSION,&sd,&g_pSwapChain,&g_pd3dDevice,&fl,&g_pd3dDeviceContext); + if(FAILED(hr)) return false; + CreateRenderTarget(); return true; +} +static void CleanupDeviceD3D(){ + CleanupRenderTarget(); + if(g_pSwapChain){g_pSwapChain->Release();g_pSwapChain=nullptr;} + if(g_pd3dDeviceContext){g_pd3dDeviceContext->Release();g_pd3dDeviceContext=nullptr;} + if(g_pd3dDevice){g_pd3dDevice->Release();g_pd3dDevice=nullptr;} +} +static void CreateRenderTarget(){ + ID3D11Texture2D* bb; + g_pSwapChain->GetBuffer(0,IID_PPV_ARGS(&bb)); + g_pd3dDevice->CreateRenderTargetView(bb,nullptr,&g_mainRenderTargetView); + bb->Release(); +} +static void CleanupRenderTarget(){ + if(g_mainRenderTargetView){g_mainRenderTargetView->Release();g_mainRenderTargetView=nullptr;} +} + +LRESULT WINAPI WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ + if(ImGui_ImplWin32_WndProcHandler(hWnd,msg,wParam,lParam)) return true; + switch(msg){ + case WM_SIZE: + if(g_pd3dDevice && wParam!=SIZE_MINIMIZED){ + CleanupRenderTarget(); + g_pSwapChain->ResizeBuffers(0,LOWORD(lParam),HIWORD(lParam),DXGI_FORMAT_UNKNOWN,0); + CreateRenderTarget(); + } + return 0; + case WM_SYSCOMMAND: + if((wParam&0xfff0)==SC_KEYMENU) return 0; + break; + case WM_DESTROY: + PostQuitMessage(0); return 0; + } + return DefWindowProcW(hWnd,msg,wParam,lParam); +} diff --git a/scripts/setup.ps1 b/scripts/setup.ps1 index 028d6a2..2309b17 100644 --- a/scripts/setup.ps1 +++ b/scripts/setup.ps1 @@ -1,64 +1,99 @@ # ============================================================================= -# setup.ps1 — Bootstrap MS Detours for TimeMockerCpp +# setup.ps1 — Bootstrap MS Detours + Dear ImGui for TimeMockerCpp # -# Run once before opening the solution in Visual Studio: +# Run once before opening the solution: # 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" } +$repoRoot = Split-Path $PSScriptRoot -Parent +$vcpkgRoot = if ($env:VCPKG_ROOT) { $env:VCPKG_ROOT } else { Join-Path $repoRoot "vcpkg" } +$pkgDir = Join-Path $repoRoot "packages\detours" +$imguiDir = Join-Path $repoRoot "TimeMocker.UI\imgui" -# ── 1. Ensure vcpkg ────────────────────────────────────────────────────────── +# ── 1. vcpkg ───────────────────────────────────────────────────────────────── if (!(Test-Path (Join-Path $vcpkgRoot "vcpkg.exe"))) { - Write-Host "Cloning vcpkg into $vcpkgRoot ..." -ForegroundColor Cyan + Write-Host "Cloning vcpkg..." -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 -} +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 +# ── 2. Install Detours ──────────────────────────────────────────────────────── +Write-Host "Installing detours:x64-windows..." -ForegroundColor Cyan & $vcpkg install "detours:x64-windows" - -Write-Host "Installing detours:x86-windows ..." -ForegroundColor Cyan +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 +# ── 3. Copy Detours headers + libs ─────────────────────────────────────────── +$incDst = Join-Path $pkgDir "include" New-Item -ItemType Directory -Force -Path $incDst | Out-Null -Copy-Item -Path (Join-Path $incSrc "detours.h") -Destination $incDst -Force +Copy-Item -Path (Join-Path $vcpkgRoot "installed\x64-windows\include\detours.h") ` + -Destination $incDst -Force -foreach ($triplet in @("x64", "x86")) +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 + Copy-Item -Path (Join-Path $vcpkgRoot "installed\$triplet-windows\lib\detours.lib") ` + -Destination (Join-Path $libDst "detours.lib") -Force + Write-Host " Detours $triplet copied" -ForegroundColor Green +} + +# ── 4. Download Dear ImGui ──────────────────────────────────────────────────── +Write-Host "" +Write-Host "Fetching Dear ImGui (latest release)..." -ForegroundColor Cyan +New-Item -ItemType Directory -Force -Path $imguiDir | Out-Null + +# Use the GitHub API to find the latest release tag +$release = Invoke-RestMethod "https://api.github.com/repos/ocornut/imgui/releases/latest" +$tag = $release.tag_name +Write-Host " Tag: $tag" -ForegroundColor Green + +$baseUrl = "https://raw.githubusercontent.com/ocornut/imgui/$tag" + +$coreFiles = @( + "imgui.h", "imgui.cpp", + "imgui_internal.h", + "imgui_draw.cpp", + "imgui_tables.cpp", + "imgui_widgets.cpp", + "imconfig.h", + "imstb_rectpack.h", + "imstb_textedit.h", + "imstb_truetype.h" +) + +$backendFiles = @( + "imgui_impl_win32.h", "imgui_impl_win32.cpp", + "imgui_impl_dx11.h", "imgui_impl_dx11.cpp" +) + +foreach ($f in $coreFiles) +{ + $url = "$baseUrl/$f" + $dest = Join-Path $imguiDir $f + Write-Host " Downloading $f" -NoNewline + Invoke-WebRequest -Uri $url -OutFile $dest -UseBasicParsing + Write-Host " ✓" -ForegroundColor Green +} + +foreach ($f in $backendFiles) +{ + $url = "$baseUrl/backends/$f" + $dest = Join-Path $imguiDir $f + Write-Host " Downloading $f" -NoNewline + Invoke-WebRequest -Uri $url -OutFile $dest -UseBasicParsing + Write-Host " ✓" -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 +Write-Host "═══════════════════════════════════════════════════" -ForegroundColor Cyan +Write-Host " Setup complete!" -ForegroundColor Green +Write-Host " Open TimeMocker.sln in Visual Studio 2022" -ForegroundColor Green +Write-Host " Build: Release | x64" -ForegroundColor Green +Write-Host "═══════════════════════════════════════════════════" -ForegroundColor Cyan