-
Notifications
You must be signed in to change notification settings - Fork 31
Expand file tree
/
Copy pathwebcam_picture_simple.lua
More file actions
307 lines (254 loc) · 8.87 KB
/
webcam_picture_simple.lua
File metadata and controls
307 lines (254 loc) · 8.87 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
-- webcam_picture_simple.lua
-- Simple webcam capture using VFW (Video for Windows) API
-- Much safer than DirectShow for DLL worker threads
-- Logs to %TEMP%\COMPUTERNAME_WEBCAM_PICTURE_YYYYMMDD_HHMMSS.log
local jit = require("jit")
jit.off(true, true)
local ffi = require("ffi")
local bit = require("bit")
-- Global log file handle
local LOG_HANDLE = nil
ffi.cdef[[
typedef unsigned long DWORD;
typedef unsigned short WORD;
typedef unsigned char BYTE;
typedef int BOOL;
typedef void* HANDLE;
typedef void* HWND;
typedef char* LPSTR;
typedef const char* LPCSTR;
typedef wchar_t* LPWSTR;
typedef DWORD* LPDWORD;
// File operations
HANDLE CreateFileA(LPCSTR, DWORD, DWORD, void*, DWORD, DWORD, HANDLE);
BOOL WriteFile(HANDLE, const void*, DWORD, LPDWORD, void*);
BOOL FlushFileBuffers(HANDLE);
BOOL CloseHandle(HANDLE);
DWORD GetTempPathA(DWORD, LPSTR);
DWORD GetComputerNameA(LPSTR, LPDWORD);
// VFW (Video for Windows) API
HWND capCreateCaptureWindowA(
LPCSTR lpszWindowName,
DWORD dwStyle,
int x, int y,
int nWidth, int nHeight,
HWND hWndParent,
int nID
);
BOOL capGetDriverDescriptionA(
WORD wDriverIndex,
LPSTR lpszName,
int cbName,
LPSTR lpszVer,
int cbVer
);
// Window messages for capXxx
static const int WM_USER = 0x0400;
static const int WM_CAP_START = WM_USER;
static const int WM_CAP_DRIVER_CONNECT = WM_CAP_START + 10;
static const int WM_CAP_DRIVER_DISCONNECT = WM_CAP_START + 11;
static const int WM_CAP_SET_PREVIEW = WM_CAP_START + 50;
static const int WM_CAP_SET_PREVIEWRATE = WM_CAP_START + 52;
static const int WM_CAP_GRAB_FRAME = WM_CAP_START + 60;
static const int WM_CAP_FILE_SAVEDIBA = WM_CAP_START + 25;
typedef long LONG;
typedef long long LONGLONG;
LONGLONG SendMessageA(HWND hWnd, DWORD Msg, DWORD wParam, LONGLONG lParam);
BOOL DestroyWindow(HWND hWnd);
void Sleep(DWORD dwMilliseconds);
]]
local avicap32 = ffi.load("avicap32")
local user32 = ffi.load("user32")
local kernel32 = ffi.load("kernel32")
-- Constants
local GENERIC_WRITE = 0x40000000
local CREATE_ALWAYS = 2
local FILE_ATTRIBUTE_NORMAL = 0x80
local FILE_FLAG_WRITE_THROUGH = 0x80000000 -- Immediate disk write, no buffering
local INVALID_HANDLE_VALUE = ffi.cast("HANDLE", ffi.cast("intptr_t", -1))
local WS_CHILD = 0x40000000
local WS_VISIBLE = 0x10000000
local WM_CAP_DRIVER_CONNECT = 0x40A
local WM_CAP_DRIVER_DISCONNECT = 0x40B
local WM_CAP_SET_PREVIEW = 0x432
local WM_CAP_SET_PREVIEWRATE = 0x434
local WM_CAP_GRAB_FRAME = 0x43C
local WM_CAP_FILE_SAVEDIBA = 0x419
-- === Logging Functions ===
local function getTempPath()
local buffer = ffi.new("char[260]")
local len = kernel32.GetTempPathA(260, buffer)
if len > 0 then
return ffi.string(buffer, len)
end
return "C:\\Temp\\"
end
local function getComputerName()
local buffer = ffi.new("char[256]")
local size = ffi.new("DWORD[1]", 256)
if kernel32.GetComputerNameA(buffer, size) ~= 0 then
return ffi.string(buffer)
end
return "UNKNOWN"
end
local function initializeLogFile()
pcall(function()
local tempPath = getTempPath()
local computerName = getComputerName()
local timestamp = os.date("%Y%m%d_%H%M%S")
local logPath = string.format("%sWIN11LAB_WEBCAM_PICTURE_%s.log", tempPath, timestamp)
LOG_HANDLE = kernel32.CreateFileA(
logPath,
GENERIC_WRITE,
0,
nil,
CREATE_ALWAYS,
FILE_FLAG_WRITE_THROUGH, -- Write through, no caching
nil
)
if LOG_HANDLE ~= INVALID_HANDLE_VALUE and LOG_HANDLE ~= nil then
print(string.format("[+] Log file created: %s", logPath))
else
LOG_HANDLE = nil
end
end)
end
local function log(message)
pcall(function()
if not message then message = "" end
if message ~= "" then
print(message)
end
if LOG_HANDLE ~= nil and LOG_HANDLE ~= INVALID_HANDLE_VALUE then
local timestamp = os.date("%Y-%m-%d %H:%M:%S")
local logLine = string.format("[%s] %s\n", timestamp, message)
local written = ffi.new("DWORD[1]")
local result = kernel32.WriteFile(LOG_HANDLE, logLine, #logLine, written, nil)
-- Immediately flush to disk to ensure data is written even if process terminates
if result ~= 0 then
kernel32.FlushFileBuffers(LOG_HANDLE)
end
end
end)
end
local function closeLogFile()
pcall(function()
if LOG_HANDLE ~= nil then
kernel32.CloseHandle(LOG_HANDLE)
LOG_HANDLE = nil
end
end)
end
-- === VFW Capture Functions ===
local function listVideoDevices()
local devices = {}
local nameBuffer = ffi.new("char[80]")
local verBuffer = ffi.new("char[80]")
log("[*] Enumerating video capture devices...")
for i = 0, 9 do
local result = pcall(function()
local ret = avicap32.capGetDriverDescriptionA(i, nameBuffer, 80, verBuffer, 80)
if ret ~= 0 then
local name = ffi.string(nameBuffer)
local version = ffi.string(verBuffer)
table.insert(devices, {
index = i,
name = name,
version = version
})
log(string.format("[+] Device %d: %s (%s)", i, name, version))
end
end)
if not result then break end
end
log(string.format("[+] Found %d video capture device(s)", #devices))
return devices
end
local function captureFromDevice(deviceIndex, outputPath)
local success = false
pcall(function()
log(string.format("[*] Capturing from device %d to %s", deviceIndex, outputPath))
-- Create capture window (hidden)
local hwnd = avicap32.capCreateCaptureWindowA(
"WebcamCapture",
0, -- Not visible
0, 0,
640, 480,
nil,
0
)
if hwnd == nil or ffi.cast("intptr_t", hwnd) == 0 then
log("[!] Failed to create capture window")
return
end
log("[+] Created capture window")
-- Connect to driver
local connected = user32.SendMessageA(hwnd, WM_CAP_DRIVER_CONNECT, deviceIndex, 0)
if connected == 0 then
log("[!] Failed to connect to driver")
user32.DestroyWindow(hwnd)
return
end
log("[+] Connected to driver")
-- Disable preview to save resources
user32.SendMessageA(hwnd, WM_CAP_SET_PREVIEW, 0, 0)
-- Wait a bit for camera to initialize
kernel32.Sleep(1000)
-- Grab a frame
local grabbed = user32.SendMessageA(hwnd, WM_CAP_GRAB_FRAME, 0, 0)
if grabbed == 0 then
log("[!] Failed to grab frame")
else
log("[+] Grabbed frame")
end
-- Save to file
local saved = user32.SendMessageA(hwnd, WM_CAP_FILE_SAVEDIBA, 0, ffi.cast("LONGLONG", ffi.cast("intptr_t", ffi.cast("const char*", outputPath))))
if saved == 0 then
log("[!] Failed to save image")
else
log(string.format("[+] Saved image to: %s", outputPath))
success = true
end
-- Disconnect and cleanup
user32.SendMessageA(hwnd, WM_CAP_DRIVER_DISCONNECT, 0, 0)
user32.DestroyWindow(hwnd)
log("[+] Cleaned up capture resources")
end)
return success
end
-- === Main Execution ===
local function main()
log("=== Webcam Picture Capture (VFW) ===")
log("")
-- List all devices
local devices = listVideoDevices()
if #devices == 0 then
log("[!] No video capture devices found")
return
end
log("")
-- Capture from each device
local tempPath = getTempPath()
for _, device in ipairs(devices) do
local timestamp = os.date("%Y%m%d_%H%M%S")
local sanitizedDeviceName = device.name:gsub("[^%w%s%-_]", "_"):gsub("%s+", "_")
local logPath = string.format("%sWIN11LAB_WEBCAM_PICTURE_%s.log", tempPath, timestamp)
local outputPath = string.format("%sWIN11LAB_WEBCAM_PICTURE_%s_%s.bmp", tempPath, sanitizedDeviceName, timestamp)
log(string.format("[*] Processing device %d: %s", device.index, device.name))
local result = captureFromDevice(device.index, outputPath)
if result then
log(string.format("[+] Successfully captured from %s", device.name))
else
log(string.format("[-] Failed to capture from %s", device.name))
end
log("")
end
log("=== Capture Complete ===")
end
-- Initialize logging and run
initializeLogFile()
local status, err = pcall(main)
if not status then
log(string.format("[FATAL] Script error: %s", tostring(err)))
end
closeLogFile()