Files
2026-02-27 17:31:56 +07:00

640 lines
30 KiB
C++

// =============================================================================
// TimeMocker.UI — ImGui + DirectX 11 frontend
// =============================================================================
#define WIN32_LEAN_AND_MEAN
#define NOMINMAX
#include <Windows.h>
#include <TlHelp32.h>
#include <Psapi.h>
#include <d3d11.h>
#include <shellapi.h>
#include "imgui/imgui.h"
#include "imgui/imgui_impl_win32.h"
#include "imgui/imgui_impl_dx11.h"
#include <string>
#include <vector>
#include <algorithm>
#include <mutex>
#include <deque>
#include <cstdio>
#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<ProcessRow> g_procRows;
static char g_procFilter[128] = {};
static std::vector<RuleRow> g_rules;
static std::deque<LogEntry> 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 AppLog(const std::string& msg, ImVec4 color = Pal::LogInfo)
{
SYSTEMTIME st; GetLocalTime(&st);
char ts[12]; sprintf_s(ts, "%02d:%02d:%02d", st.wHour, st.wMinute, st.wSecond);
std::lock_guard<std::mutex> lk(g_logMutex);
g_log.push_back({ ts, msg, color });
if (g_log.size() > 512) g_log.pop_front();
g_logScrollToBottom = true;
}
static void WLog(const std::wstring& w)
{
char b[1024]; WideCharToMultiByte(CP_UTF8, 0, w.c_str(), -1, b, sizeof(b), nullptr, nullptr);
AppLog(b);
}
static std::string FormatDelta(LONGLONG d)
{
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;
}
static void RefreshProcessList()
{
g_procRows.clear();
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; }
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(g_procRows.begin(),g_procRows.end(),[](auto&a,auto&b){return _stricmp(a.name.c_str(),b.name.c_str())<0;});
}
static void ApplyTime()
{
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)));
}
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);
}
// ─────────────────────────────────────────────────────────────────────────────
// 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)))
{
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)
{
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<std::mutex> 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<std::mutex> 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;
}
}
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);
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);
};
ResetTimeToNow();
RefreshProcessList();
g_watcher->Start(1500);
AppLog("TimeMocker started. Watcher active.");
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(done) break;
ImGui_ImplDX11_NewFrame();
ImGui_ImplWin32_NewFrame();
ImGui::NewFrame();
RenderUI();
ImGui::Render();
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);
}
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);
}