411 lines
14 KiB
Lua
411 lines
14 KiB
Lua
------------------------------------------------------------------------------------------------------------
|
|
local ffi = require( "ffi" )
|
|
|
|
local tinsert = table.insert
|
|
|
|
local kernel32 = ffi.load( "kernel32.dll" )
|
|
local user32 = ffi.load( "user32.dll" )
|
|
local comdlg32 = ffi.load( "Comdlg32.dll" )
|
|
local gdi32 = ffi.load( "gdi32.dll" )
|
|
local shell32 = ffi.load( "shell32.dll" )
|
|
local ole32 = ffi.load( "ole32.dll" )
|
|
local shlwapi = ffi.load( "shlwapi.dll" )
|
|
|
|
local Colors = {
|
|
-- Color constants.
|
|
rgbRed = 0x000000FF,
|
|
rgbGreen = 0x0000FF00,
|
|
rgbBlue = 0x00FF0000,
|
|
rgbBlack = 0x00000000,
|
|
rgbWhite = 0x00FFFFFF,
|
|
}
|
|
|
|
ffi.cdef[[
|
|
|
|
typedef unsigned long DWORD;
|
|
typedef int BOOL;
|
|
typedef const char* LPCSTR;
|
|
typedef unsigned int HANDLE;
|
|
typedef unsigned int HWND;
|
|
typedef unsigned long *ULONG_PTR;
|
|
typedef long LPARAM;
|
|
typedef int (*WNDENUMPROC)(HWND hwnd, LPARAM lParam);
|
|
|
|
typedef struct enumData {
|
|
uint64_t lparam[1];
|
|
const char * title;
|
|
} enumData;
|
|
|
|
typedef struct tagPROCESSENTRY32 {
|
|
DWORD dwSize;
|
|
DWORD cntUsage;
|
|
DWORD th32ProcessID;
|
|
ULONG_PTR th32DefaultHeapID;
|
|
DWORD th32ModuleID;
|
|
DWORD cntThreads;
|
|
DWORD th32ParentProcessID;
|
|
long pcPriClassBase;
|
|
DWORD dwFlags;
|
|
char szExeFile[260];
|
|
} PROCESSENTRY32;
|
|
|
|
typedef struct { long left; long top; long right; long bottom; } RECT;
|
|
|
|
HANDLE CreateToolhelp32Snapshot(DWORD dwFlags, DWORD th32ProcessID);
|
|
BOOL Process32First(HANDLE hSnapshot, PROCESSENTRY32 *lppe);
|
|
BOOL Process32Next(HANDLE hSnapshot, PROCESSENTRY32 *lppe);
|
|
BOOL CloseHandle(HANDLE hObject);
|
|
|
|
BOOL EnumWindows(WNDENUMPROC lpEnumFunc, LPARAM lParam);
|
|
DWORD GetWindowThreadProcessId(HWND hWnd, DWORD *lpdwProcessId);
|
|
BOOL IsWindowVisible(HWND hWnd);
|
|
|
|
DWORD WaitForInputIdle(HANDLE hProcess, DWORD dwMilliseconds);
|
|
HWND GetForegroundWindow(void);
|
|
|
|
int GetSystemMetrics(int nIndex);
|
|
int AdjustWindowRect(RECT* lpRect, unsigned long dwStyle, int bMenu);
|
|
long GetWindowLongA(unsigned int hWnd, int nIndex);
|
|
BOOL SystemParametersInfoW(unsigned int uiAction, unsigned int uiParam, RECT* pvParam, unsigned int fWinIni);
|
|
|
|
BOOL SetWindowPos(unsigned int hWnd, void* hWndInsertAfter, int X, int Y, int cx, int cy, unsigned int uFlags);
|
|
|
|
void Sleep(uint32_t ms);
|
|
]]
|
|
|
|
require("ffi.windows")
|
|
|
|
-- -----------------------------------------------------------------------------------
|
|
local TH32CS_SNAPPROCESS = 0x00000002
|
|
local process_parents = {}
|
|
local process_childs = {}
|
|
|
|
local function GetAllProcesses( )
|
|
-- Setup Toolhelp snapshot
|
|
local snapshot = kernel32.CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0)
|
|
if snapshot == -1 then return end
|
|
|
|
local entry = ffi.new("PROCESSENTRY32[1]")
|
|
entry[0].dwSize = ffi.sizeof("PROCESSENTRY32")
|
|
|
|
local found = false
|
|
local first_entry = kernel32.Process32First(snapshot, entry)
|
|
if first_entry ~= 0 then
|
|
repeat
|
|
process_parents[entry[0].th32ParentProcessID] = process_parents[entry[0].th32ParentProcessID] or {}
|
|
tinsert(process_parents[entry[0].th32ParentProcessID], entry[0].th32ProcessID)
|
|
process_childs[entry[0].th32ProcessID] = entry[0].th32ParentProcessID
|
|
print(entry[0].th32ProcessID, entry[0].th32ParentProcessID)
|
|
until kernel32.Process32Next(snapshot, entry) == 0
|
|
end
|
|
|
|
kernel32.CloseHandle(snapshot)
|
|
end
|
|
|
|
-- -----------------------------------------------------------------------------------
|
|
|
|
function CheckChildren( win_pid, target_pid )
|
|
|
|
if( process_parents[target_pid] ) then
|
|
for i, v in ipairs(process_parents[target_pid]) do
|
|
print(v, win_pid, target_pid)
|
|
if v == win_pid then return true end
|
|
end
|
|
end
|
|
return false
|
|
end
|
|
|
|
-- -----------------------------------------------------------------------------------
|
|
|
|
local function FindWindowByProcId( proc_handle, proc_id, timeout_ms )
|
|
|
|
timeout_ms = timeout_ms or 5000
|
|
|
|
-- Wait until process is idle (i.e., its message queue is waiting)
|
|
local wait_result = ffi.C.Sleep(timeout_ms)
|
|
|
|
-- Get the currently active (foreground) window
|
|
local hwnd = user32.GetForegroundWindow()
|
|
if hwnd == nil then
|
|
print("No foreground window found.")
|
|
return nil
|
|
end
|
|
|
|
-- Check if it belongs to our target process
|
|
local pid_ptr = ffi.new("DWORD[1]")
|
|
user32.GetWindowThreadProcessId(hwnd, pid_ptr)
|
|
return hwnd
|
|
end
|
|
|
|
-- -----------------------------------------------------------------------------------
|
|
|
|
local function AssocQ( outstr, bsize )
|
|
local ftype = ffi.cast("char *", ffi.string("http"))
|
|
local verb = ffi.cast("char *", ffi.string("open"))
|
|
local hr = shlwapi.AssocQueryStringA(user32.ASSOCF_NONE, user32.ASSOCSTR_EXECUTABLE, ftype, verb, outstr, bsize)
|
|
return outstr, bsize
|
|
end
|
|
|
|
-- -----------------------------------------------------------------------------------
|
|
|
|
local function getDefaultBrowser()
|
|
local bsize = ffi.new("uint32_t[1]")
|
|
AssocQ( nil, bsize)
|
|
local outstr = ffi.new("char[?]", bsize[0])
|
|
local hr = AssocQ( outstr, bsize)
|
|
return outstr, bsize
|
|
end
|
|
|
|
-- -----------------------------------------------------------------------------------
|
|
-- To be added to UTILS
|
|
local function execute(commandLine, currentDirectory)
|
|
local si = ffi.new("STARTUPINFOA[1]")
|
|
si[0].cb = ffi.sizeof(si[0])
|
|
-- si[0].dwFlags = ffi.C.STARTF_USESTDHANDLES
|
|
local pi = ffi.new("PROCESS_INFORMATION[1]")
|
|
local hproc = ffi.C.CreateProcessA(nil, commandLine, nil, nil, 1, 0, nil, currentDirectory, si, pi) ~= 0
|
|
-- Short delay to make sure the process has at least begun.
|
|
ffi.C.Sleep(200)
|
|
return pi[0].dwProcessId, pi[0].dwThreadId, pi
|
|
end
|
|
|
|
-- -----------------------------------------------------------------------------------
|
|
-- To be added to UTILS
|
|
local function shellexecute(hwnd, commandline, directory)
|
|
|
|
local cmdstr = ffi.new("char[?]", #commandline + 1)
|
|
ffi.copy(cmdstr, ffi.string(commandline))
|
|
|
|
local si = ffi.new("SHELLEXECUTEINFOA[1]")
|
|
si[0].cbSize = ffi.sizeof(si[0])
|
|
si[0].fMask = user32.SEE_MASK_NOCLOSEPROCESS
|
|
si[0].hwnd = 0
|
|
si[0].lpVerb = nil
|
|
si[0].lpFile = ffi.cast("char *", cmdstr)
|
|
si[0].lpParameters = nil
|
|
si[0].lpDirectory = nil
|
|
-- si[0].hInstApp = kernel32.GetModuleHandleA(nil)
|
|
si[0].nShow = user32.SW_SHOW
|
|
local res = shell32.ShellExecuteExA(si)
|
|
print(string.format("Error: 0x%02x", kernel32.GetLastError()))
|
|
print("result:", res)
|
|
-- Short delay to make sure the process has at least begun.
|
|
ffi.C.Sleep(200)
|
|
return si[0].hProcess, si[0].hwnd, si
|
|
end
|
|
|
|
------------------------------------------------------------------------------------------------------------
|
|
local reg = nil
|
|
|
|
function CreateWindow(name, px, py, wide, high, createtype, wndproc, exstyle, parent, menu)
|
|
|
|
local hparent = parent or 0
|
|
local hmenu = menu or 0
|
|
|
|
local hInstance = kernel32.GetModuleHandleA(nil)
|
|
local CLASS_NAME = 'TestWindowClass'
|
|
|
|
if (reg == nil ) then
|
|
|
|
local classstruct = {}
|
|
classstruct.cbSize = ffi.sizeof( "WNDCLASSEXA" )
|
|
classstruct.style = bit.bor(user32.CS_HREDRAW, user32.CS_VREDRAW)
|
|
|
|
classstruct.lpfnWndProc = wndproc
|
|
|
|
|
|
classstruct.cbClsExtra = 0
|
|
classstruct.cbWndExtra = 0
|
|
classstruct.hInstance = hInstance
|
|
classstruct.hIcon = user32.LoadIconA(0, idi.APPLICATION)
|
|
classstruct.hCursor = user32.LoadCursorA(0, idc.ARROW)
|
|
classstruct.hbrBackground = nil
|
|
classstruct.lpszMenuName = nil
|
|
classstruct.lpszClassName = CLASS_NAME
|
|
classstruct.hIconSm = 0
|
|
|
|
local wndclass = ffi.new( "WNDCLASSEXA", classstruct )
|
|
if(reg == nil) then
|
|
reg = user32.RegisterClassExA( wndclass )
|
|
|
|
if (reg == 0) then
|
|
error('error #' .. kernel32.GetLastError())
|
|
end
|
|
end
|
|
end
|
|
|
|
exstyle = exstyle or 0
|
|
|
|
local hwnd = user32.CreateWindowExA( exstyle, CLASS_NAME, name, createtype, px, py, wide, high, hparent, hmenu, hInstance, nil)
|
|
if (hwnd == 0) then
|
|
error 'unable to create window'
|
|
end
|
|
|
|
user32.ShowWindow(hwnd, user32.SW_SHOW)
|
|
print("[HWND] ", hwnd)
|
|
return hwnd
|
|
end
|
|
|
|
local function SetTransparentBlend( hwnd, colorref, byte, style )
|
|
user32.SetLayeredWindowAttributes(hwnd, colorref, byte, style )
|
|
end
|
|
|
|
------------------------------------------------------------------------------------------------------------
|
|
-- To be added to UTILS
|
|
local function RemoveWindowBorders(hwnd)
|
|
|
|
local style = user32.GetWindowLongA(hwnd, user32.GWL_STYLE)
|
|
|
|
local minimalStyle = bit.bnot(user32.WS_NOBORDERS)
|
|
style = bit.band( style, minimalStyle )
|
|
-- lExStyle = bit.bor(lExStyle, minimalStyle)
|
|
user32.SetWindowLongA(hwnd, user32.GWL_STYLE, style)
|
|
end
|
|
|
|
------------------------------------------------------------------------------------------------------------
|
|
-- To be added to UTILS
|
|
local pid = ffi.new("DWORD[1]")
|
|
local buffer = ffi.new("char[256]")
|
|
|
|
local function EnumWindowsProc( hwnd, lparam )
|
|
|
|
local processData = ffi.cast("enumData *", lparam)
|
|
|
|
-- local style = user32.GetWindowLongA(hwnd, user32.GWL_STYLE)
|
|
|
|
if(processData[0].title == nil) then
|
|
pid[0] = user32.GetWindowThreadProcessId(hwnd, nil)
|
|
if(pid[0] == processData[0].lparam[0]) then
|
|
|
|
ffi.fill(buffer, 0, 256)
|
|
user32.GetWindowTextA(hwnd, buffer, 255)
|
|
local titlestr = ffi.string(buffer)
|
|
print(string.format("PROC MATCH: %s %d", titlestr, pid[0]))
|
|
-- Window title?
|
|
processData[0].lparam[0] = ffi.cast("uint64_t", hwnd)
|
|
return false
|
|
end
|
|
else
|
|
ffi.fill(buffer, 0, 256)
|
|
user32.GetWindowTextA(hwnd, buffer, 255)
|
|
local titlestr = ffi.string(buffer)
|
|
if(string.len(titlestr) > 5) then
|
|
local wintitlestr = ffi.string(processData[0].title)
|
|
titlestr = string.gsub(titlestr, "%-", "_")
|
|
wintitlestr = string.gsub(wintitlestr, "%-", "_")
|
|
print(string.format("HWND MATCH: %s %s", titlestr, wintitlestr))
|
|
if(string.match(titlestr, wintitlestr)) then
|
|
processData[0].lparam[0] = ffi.cast("uint64_t", hwnd)
|
|
return false
|
|
end
|
|
end
|
|
end
|
|
|
|
return true
|
|
end
|
|
|
|
------------------------------------------------------------------------------------------------------------
|
|
|
|
local function GetHwndFromProcess( pid, title )
|
|
local processdata = ffi.new("enumData[1]")
|
|
processdata[0].lparam[0] = pid
|
|
if(title) then
|
|
processdata[0].title = ffi.cast("const char *", ffi.string(title))
|
|
else
|
|
processdata[0].title = nil
|
|
end
|
|
user32.EnumWindows( EnumWindowsProc, ffi.cast("LPARAM", processdata))
|
|
return ffi.cast("HWND", processdata[0].lparam[0])
|
|
end
|
|
|
|
------------------------------------------------------------------------------------------------------------
|
|
|
|
local function GetFile( filepath )
|
|
local data = nil
|
|
local dfile = io.open(filepath, "r")
|
|
if(dfile) then
|
|
data = dfile:read("*a")
|
|
dfile:close()
|
|
end
|
|
return data
|
|
end
|
|
|
|
------------------------------------------------------------------------------------------------------------
|
|
|
|
function WindowsFileSelect()
|
|
|
|
end
|
|
|
|
------------------------------------------------------------------------------------------------------------
|
|
|
|
function WindowsFolderSelect()
|
|
local ofn = ffi.new("OPENFILENAME")
|
|
ofn.lStructSize = ffi.sizeof(ofn)
|
|
ofn.lpstrFile = ffi.new( "char[512]" )
|
|
ofn.lpstrFilter = ffi.new( "char[19]", "All\0*.*\0Text\0*.TXT\0")
|
|
ofn.lpstrInitialDir = ffi.new( "char[3]", "C:/")
|
|
comdlg32.GetOpenFileNameA( ofn )
|
|
|
|
print("Selected folder: ", ffi.string(ofn.lpstrFile ), ffi.string(ofn.lpstrFilter ))
|
|
end
|
|
|
|
------------------------------------------------------------------------------------------------------------
|
|
-- Windows Hook tests - not really working well Only seems to work on Mainhwnd, I think
|
|
-- this is because the main windows belongs to this process.
|
|
local msgtbl = {}
|
|
|
|
local function MsgFilter(code, wParam, lParam)
|
|
print(code,wParam,lParam)
|
|
if(code < 0) then
|
|
return 0
|
|
end
|
|
|
|
local msg = ffi.cast("MSG *", lParam)
|
|
--print( unityhwnd, msg[0].message, msg[0].wparam, msg[0].lparam)
|
|
|
|
if(msg[0].message == user32.WM_MOUSEMOVE or msg[0].message == user32.WM_NCMOUSEMOVE) then
|
|
user32.PostMessageA( unityhwnd, msg[0].message, msg[0].wparam, msg[0].lparam)
|
|
end
|
|
table.insert( msgtbl, {hwnd=unityhwnd, msg=code, wparam=wParam, lparam=lParam})
|
|
end
|
|
|
|
local pid = user32.GetWindowThreadProcessId( wvhwnd or 0, nil )
|
|
local hhookSysMsg = user32.SetWindowsHookExA( user32.WH_CALLWNDPROC, MsgFilter, 0, pid)
|
|
|
|
|
|
local SWP_NOZORDER = 0x0004
|
|
local SWP_NOACTIVATE = 0x0010
|
|
local SWP_ASYNCWINDOWPOS = 0x4000
|
|
local SWP_SHOWWINDOW = 0x0040
|
|
|
|
local function SetWindowPos( hwnd, x, y, width, height )
|
|
|
|
user32.SetWindowPos(hwnd, nil, x, y, width, height, bit.bor(SWP_NOZORDER, SWP_ASYNCWINDOWPOS, SWP_NOACTIVATE))
|
|
end
|
|
|
|
------------------------------------------------------------------------------------------------------------
|
|
|
|
return {
|
|
CreateWindow = CreateWindow,
|
|
SetWindowPos = SetWindowPos,
|
|
|
|
SetTransparentBlend = SetTransparentBlend,
|
|
GetHwndFromProcess = GetHwndFromProcess,
|
|
RemoveWindowBorders = RemoveWindowBorders,
|
|
WindowsFileSelect = WindowsFileSelect,
|
|
WindowsFolderSelect = WindowsFolderSelect,
|
|
|
|
execute = execute,
|
|
shellexecute = shellexecute,
|
|
Sleep = ffi.C.Sleep,
|
|
GetFile = GetFile,
|
|
|
|
FindWindowByProcId = FindWindowByProcId,
|
|
GetAllProcesses = GetAllProcesses,
|
|
getDefaultBrowser = getDefaultBrowser,
|
|
|
|
kernel = kernel32,
|
|
user = user32,
|
|
gdi = gdi32,
|
|
comdlg = comdlg32,
|
|
} |