Page created by RedstoneShell (LuaNT author and main developer)
Another Operation System for OpenComputers. Yet, he don't have own Floppy. Possibly first OS with GUI that can run at Tier 2 computers, also perfectly run at Tier 3. Can start at 2x Tier 1 Memory (384KB), but for stable work recomended 2x Tier 2 Memory (768KB). Also can run at 1MB HDD Disk, or 512KB Floppy.
For install you need computer with installed OpenOS and installed Internet Card. Before installation, place in HDD slot second Hard Disk Drive, for economy place 1MB, but you can place other. After installing HDD, run in console “pastebin get -f Kv80QkZG luantinstall.lua”, after successful installation of script, enter “luantinstall”. After that follow steps, and at disk selection enter number of second HDD, if you don't know what HDD you install, just select number of HDD that don't named “openos” or “tmpfs”.
OR IF YOU NEED MANY LuaNT's, install standard Floppy Disk and select it. Now at Desktop you can open menu of My Pc and click Install To... for install at network of computers!
After installation in Reboot option enter NO, and shutdown computer. After shutdowning, remove HDD with OpenOS, and left only LuaNT disk.
Now start computer, if you made this correctly at monitor you see something this
Don't worry if after “BCD: Parsing Boot Configuration Data entries…”, system don't make anything, she just initialize without Debug Log at screen.
After seeing Log On menu, press Enter, and enter in password field: “admin123”. After Enter, if you enter correctly, you see desktop.
from 14.09.2026 you can switch Tier 2 Compatiblity Mode, here's how it changes screen:
And without Tier 2 CM:
If you initialize LuaNT 4.0 at Tier 3 computer, everything is okay.
But if you have some Tier 2 computer (“bucket”), some colors can be weird… for that, click at Start, and select Color Setup…, now click at 4 bit colors, click OK, and reboot to see changes. For better GUI's at Tier 2, also click in WinLogOn in left-top corner button named T2 Mode, after clicking computer reboot in Tier 2 Compatible Mode.
Yes, LuaNT 4.0, making for Powerful Tier 3 computers, but from 4.0.1.3 she start some adaptation for Tier 2 computers, for 4.0.1.3 only maked Color Setup…
Now return to work. At desktop you can see My PC, SMS, Minesweeper, Internet Explorer, Setup Mgr and Notepad. If you need to customize your LuaNT copy, open Notepad and use Ctrl+O to open file, enter file to open, write data,and click to X at right-top corner of Notepad and Save file. For other customizations, open SMS, select need package, press Enter, read description, press Enter for install, and run.
Also by open menu at My PC, you can see Install To… that unclickable at HDD. and Properties, by clicking at Properties, opens this window:
LuaNT is a modular OS, that means, she doesn't have “everything from box”, you install needed modules from SMS, or use standard 490KB LuaNT from installer.
Read more about OS here:
Getting Started Manuals and FAQ's
Some note: If you need to start DHCP, check if you have 1MB+ RAM, or he don't launch!
Interesting fact! You can launch LuaNT 4.0 from Floppy!
From 14.09.2026, you can use program nfs.lua(Network FileSystem), install by SMS > Wireless HDD.
For start, create Server/Computer with OpenOS, insert disk with OpenOS, and disk that need to make Wireless, or connect external RAID, he identical. Now install NetHDD Server→"pastebin get -f zNjJJFZ3 nethdd_serv.lua", and enter "nethdd_serv". If you start it at first time, he displays HDD's list to select HDD's to make it Wireless, enter number of needed disk and press ENTER. After that server makes this disk Wireless.
Now at LuaNT open Start menu > Run…, and enter “nfs.lua” and press ENTER. Wait some time and in window click “[SEARCH]”, wait some time, and click enter at needed server (TIP: edit nethdd_serv.lua to change server name from “\\\\MYSERVER\\disk” to needed). Now after ENTER, open new windows, by clicking any letter at keyboard, you set letter of Wireless disk, by press “[OK]” you mount wireless disk in system. Now you can open Start menu > File Explorer, and select new disk that named for example: “\\MYSERVER\disk”.
Now after mounting, every programs and LuaNT see this disk as normal disk in your computer. WARNING!: After reboot, connection to disk RESET, after that you need to repeat all actions to Re-mount Wireless back in LuaNT!
For example you can watch this video about launching and setup Wireless HDD
From 15.09.2026, you can use program L2ctrl.lua(Relay Controller), install by SMS > L2Control. For start, place near Relay and connect to computer with LuaNT, now open Start menu > Run… and enter “L2ctrl” and press ENTER, in menu you can switch Repeater mode, for wireless Relay switch strenght.
For Lossmeter button, create computer with OpenOS, place Network Card, run “pastebin get -f VebVX21x lossmeter.lua” and start “lossmeter”.
After connect OpenOS computer to Relay by Cable, now check if in computer with LuaNT installed Network Card, after check click button Lossmeter, wait some time, and after that at screen you see % of Loss, transmited/received packets and lossed packets. Heres menu:
Loss for Relay without anything:
Loss for Relay with Tier 2 CPU only:
Loss for Relay with Tier 3 CPU only:
from 26.09.2026 you can use “eToken” in LuaNT, for make 2FA WinLogOn.
Before that PLEASE, check if you have external Disk Drive for PC or internal Disk Drive item for Server Rack.
First, you need to make your eToken, for that install SMS > eTokenSetup. After click Start > Run…, enter etoken and ENTER. At opened menu click [ Create Token ]:
Now in new menu enter PIN-code for token, WARNING!: Please remember the entered PIN-code in menu, to Logon in system!
After entering PIN, press ENTER, and close program.
Now install SMS > eTokenLogOn, after that, reboot PC, now you can see new WinLogOn, for start, place in external Disk Driver your eToken floppy, press ENTER in WinLogOn, after enter PIN-code that you enter in eTokenSetup. After entering PIN-code, you see normal Logon menu.
New LogOn menu:
PIN Code:
Now, if you developer, that customize system or just script maker, make sure, this API is very different that MineOS or OpenOS, heres everything that you can use:
DbgPrint(msg:text)
Save message in log file “/ntbootdd.log” message, also with _G.DebugMode=true, displays message at screen
_G.DbgPrintToFile:boolean
_G.DispatcherActive:boolean
WARNING!: Changing of this varible, immedantly shutdown LuaNT multi-task, and active thread before "coroutine.yield()" collect every proccessor time!. Switch for disabling multi-task, use at own risk!
_G.Drives:table
filesystem's. Format {["C:"="\Device\<name><number>\Partition<number>"], ... }. Reading of this Kernel Table, is safe. But if you need to change values in this table, don't manipulate with registered Devices, you can add your RAM Disks, or other…, if you need to change exists Device in table, change values at own risk! Removing disks from table, makes OS lost every information about this Drive (you cannot find removed letter anymore in File Explorer, attempt to make something with this letter return error), before computer restart.
_G.InThreadResume:boolean
true, core skip update of current thread. Change at own risk.
_G.Mm.NonPagedPool:table
_G.Drives["<disk_letter>:"] here to get link to disk filesystem component object. You can add here everything, but change exists value at own risk. Changing of critical structures can cause a BSOD. If you get from this table, filesystem for disk, you can check if this disk is Wireless, Wireless HDD have identical filesystem but with addtive parameters is: "_isNetwork:boolean" - true if HDD is Remote, don't exists in normal filesystem component of HDD's, "_server" - name of this disk for every Clients with nfs.lua, set only at server that provide connection to disk over Wireless Network Card
_G.NTTC:integer
_G.ThreadGC:table
"Enabled:boolean" - If true thread with many errors, can be downed by priority. "Interval:integer" - Interval, if Counter⇒Interval, kernel start GC to find dead tasks to clean, "Counter:integer" - You know what im do. Changing of this parameters only change intervals to preventing memory leaks. Making this table nil, cause immedantly BSOD if core try to check dead threads.
_G.PspActiveProcessList:table
LdrLoadDll(path:string):table|nil, string
require, for LuaNT. Returns library as first argument, addtive have second return val, is a error, if first args is nil, second is error
KeDelayExecutionThread(seconds:integer)
seconds.
PsCreateSystemThread(path:string, name:string, priorityClass:integer, token:table):table|boolean
path, register it in Multitask with name received at name parameter. Also, for priorityClass use this table: HIGH_PRIORITY = 31, REALTIME_PRIORITY = 16, NORMAL_PRIORITY = 8, IDLE_PRIORITY = 0 or make nil for automatic priority (NORMALPRIORITY). If you hate token just make it nil, for register thread as SYSTEM, for other receive in parameter this structure: { name = "name_of_token", group = "token_group" }. Returns false if thread don't created, returns Thread, if created.
PsTerminateThread(pid:integer):boolean
true if removed, false if error.
PsSetPriority(pid:integer, newPriority:integer):boolean
priority of selected Thread by this PID. in newPriority set number from PsCreateSystemThread priority table. Returns true if success, false if error.
KiCleanDeadThreads():integer
PerformSystemShutdown()
_G.HAL.HalQueryRealTimeClock(PTIME_FIELDS:table)
local tabl={}, after function, table contains Year, Month, Day, Hour, Minute, Second, Milliseconds, Weekday.
GDI.PatBlt(hdc:table, x:int, y:int, w:int, h:int, dwRop:int):boolean
gdi32.lua. In hdc write GDI.GetDC(0). dwRop is operation that GDI32+ need to make, here list: GDI.PATCOPY paint destination rectangle by Brush Color, GDI.BLACKNESS/WHITENESS makes color of rectangle 0x000000/0xFFFFFF, GDI.PATINVERT makes color of rectangle a inverted from Bursh Color.
GDI.CreateSolidBrush(clr:int): table
PatBlt, in clr you receive 0xRRGGBB. Return object use in SelectObject
GDI.SelectObject(hdc_:table, hObj:table): tabke
GDI.GetDC(0). in hObj receive needed object. Returns integer, for brush, he returns old brush
GDI.SetTextColor(hdc:table, color:integer): integer
GDI.GetDC(0). in color receive needed color in 0xRRGGBB. Returns previous color.
GDI.TextOut(hdc:table, x:integer, y:integer, text:string)
GDI.GetDC(0).
wininet.HttpGet(url:string):boolean, string
wininet.lua. Returns in first arg true at success, false at error. In second args returns error, or content.
winprint.PrintText(text:string, alignment:string, color:integer):boolean, string
winprint.lua. WARNING!: For working, need add-on OpenPrinter, and connected Printer to computer!
In text receive needed text for print, separate by \r\n. In alignment receive “center”, “left” or “right. In color 0xRRGGBB. Returns true on success, false on error and error text in next arg.
_G.RpcSs.RpcServerRegisterIf(interfaceName:string, methodsTable:table):boolean
interfaceName enter name of your interface, in second args receive table, example {a=function(a,b) return a+b end}
_G.RpcSs.RpcCliExecute(interfaceName:string, methodName:string, ...):boolean, string|object
Call method from registered interface. In ... you receive arguments for function. Returns true on success, false on error. At true in second args returns function result, at false in second args text of error.
_G.RpcSs.RpcServerUnregisterIf(interfaceName:string):boolean
true on success, false on error.
esent.JetInit():boolean
esent.lua. Initialize instance (64 pages x 4096 bytes=256KB)
esent.SetCatalog(catalog:table):boolean, string
JetInit but before JetOpenDatabase. Returns true if success, false if instance don't initialized.
esent.JetBeginSession():integer
sessionId for using in other functions.
esent.JetOpenDatabase(sesid:integer, path:string):boolean, string
sssionId from JetBeginSession, in second arg you receive path to create/load DB file. Returns true at success, false on error, error text in second return arg.
esent.JetPrepareUpdate(sesid:integer, tableName:string, prepType:string):boolean, string
JetBeginSession, in second arg you receive name of table, WARNING!: The table name must exist in the Catalog., in third arg you receive operation for page: "Insert", "Update" or "Delete". Returns true if success, false if error.
esent.JetSetColumn(sesid:integer, columnName:string, value:object):boolean, string
WARNING!: Before call, check if before this you calll JetPrepareUpdate. In first arg you receive result of JetBeginSession. In second arg name of column that write in page buffer. In third arg you receive value that set in column. Returns true if success, false if error.
esent.JetUpdate(sesid:integer):boolean
WARNING!: Function locked at DHCP Tables..
esent.JetBeginTransaction(sesid:integer):boolean, string
savepoint with snapshot of every pages. In first args you receive result of JetBeginSession. Returns true if success, false if error.
esent.JetCommitTransaction(sesid:integer, grbit:object);boolean, string
checkpoint. In first args you receive result of JetBeginSession, second arg unused. Returns true if success, false if error.
esent.JetRollback(sesid:integer):boolean, string
savepoint, use at own risk, every data after last savepoint remove. In first args you receive result of JetBeginSession. Returns true if success, false if error.
esent.JetOpenTable(sesid:integer, tableName:string):boolean|integer, string
JetBeginSession. In second arg you receive name of table WARNING!: The table name must exist in the Catalog.. Returns cursorId on success, false if error.
esent.JetMove(cursorId:integer, direction:string|integer):boolean
JetOpenTable. In second arg you receive command for cursor: "Next" or 1 move cursor to next column, "Prev" or -1 moves cursor to previous column, "First"/"Last" move cursor to first/last column. Return true on success move, false if cursor at start/end of page.
esent.JetRetrieveColumn(sesid:integer, cursorId:integer, columnName:string):nil|string
JetBeginSession, in second arg you receive ID of opened cursor by JetOpenTable. In third arg you receive name of column in page. Returns value of this column, nil if error.
esent.JetCloseTable(cursorId:integer):boolean
JetOpenTable. Returns true if success, false on error.
esent.QueryRecords(tableName:string):table
WARNING!: The table name must exist in the Catalog.
esent.DeleteRecord(tableName:string, keyColumn:string, keyValue:object):boolean
keyValue. In first arg you receive name of table WARNING!: The table name must exist in the Catalog., in second args you receive column name in page, in third arg you receive value, if value equal to value in column, record deletes. Returns true if success, false on error.
esent.JetCloseDatabase(sesid:integer, path:string):boolean, string
sssionId from JetBeginSession, in second arg you receive path to DB file. Returns true if success, false on error.
esent.JetEndSession(sesid:integer):boolean
sssionId from JetBeginSession. Returns true if success, false if error.
esent.JetTerm():boolean
true if success, false if error.
_G.IoConnectInterrupt(ServiceRoutine:function):string|nil
interrupt handler in kernel. In first arg you enter function that parse interrupt, here signature: ServiceRoutine(struct:table, sig:string, addr:string, arg1:object, arg2:object, arg3:object, arg4:object), ignore first param, sig is name of event (“key_down”, “touch”, …), arg1..4 is arguments of specified event (See Also: Signals). Returns uniquie id of handler in format IRQ%08X for example: IRQ3EF3DEE9, you need to save this code, for disconnect your handler later. Returns nil if ServiceRoutine is not a function. Since: 4.0.1.4
_G.IoDisconnectInterrupt(InterruptObject:string):boolean
interrupt handler by his ID. In first arg you receive saved IRQ%08X from IoConnectInterrupt. Returns true if handler found and deleted. false if don't found of first args is not string. Since: 4.0.1.4
_G.KeInitializeEvent(Event:table[, Type:integer, State:boolean])
event object. If first arg you receive table that init as event, if second arg you receive event type (0=NotificationEvent - every waiting wake up, 1=SynchronizationEvent - only one waiting wake up), in third arg you receive initial state (true - signaled, false - not signaled). Don't return's anything, only fill empty table at input by values. Since: 4.0.1.4
_G.KeSetEvent(Event:table[, Increment:integer, Wait:boolean]):boolean
signaled state and wake up everyone that waiting. In first arg you receive table filled by KeInitializeEvent. Returns true if event set, false if input is nil. Since: 4.0.1.4
_G.ExInitializeWorkItem(WorkItem:table, Routine:function, Context:object)
Work Item for subsequent queuing. In first arg you receive empty table for filling, in second arg you receive function that call at Work Item processing, singnature: Routine(Context), if third arg you receive anything that received in Routine. Since: 4.0.1.4
_G.ExQueueWorkItem(WorkItem:table, QueueType:integer):boolean
Work Item in system query for executing. In first arg you receive initialized WorkItem by ExInitializeWorkItem. Second arg don't make anything. Return true if Work Item added in query, false if Work Item invalid or in query. Since: 4.0.1.4
_G.ExDequeueWorkItem():table|nil
WARNING!: Calling of this function external can cause unstable work of PnP and other drivers/programs that use System Query!. Calling inside of kernel main loop, get second Work Item from query, delete and return 1st element in query. Returns table with second Work Item from query, nil if System Query don't exists or nil. Since: 4.0.1.4Example of worker with JetBlue
local esent, err = LdrLoadDll("Windows/System32/esent.lua") if not esent then DbgPrint("TEST: Cannot load esent.lua: " .. tostring(err)) return end esent.JetInit() esent.SetCatalog({ ["MyTable"] = { columns = { "ID", "Name", "Value" }, rootPage = 3 } }) DbgPrint("TEST: Catalog replaced with MyTable (rootPage=3)") local sesid = esent.JetBeginSession() DbgPrint("TEST: Session created, sesid=" .. tostring(sesid)) local dbPath = "/mydb.dat" esent.JetOpenDatabase(sesid, dbPath) DbgPrint("TEST: DB open: " .. dbPath) esent.JetPrepareUpdate(sesid, "MyTable", "Insert") esent.JetSetColumn(sesid, "ID", "1") esent.JetSetColumn(sesid, "Name", "TestRecord") esent.JetSetColumn(sesid, "Value", "Hello LuaNT") esent.JetUpdate(sesid) DbgPrint("TEST: Record added to MyTable") local records = esent.QueryRecords("MyTable") DbgPrint("TEST: Records in MyTable: " .. tostring(#records)) for i = 1, #records do DbgPrint("TEST: Record " .. i .. ": " .. records[i]) end esent.JetCloseDatabase(sesid, dbPath) DbgPrint("TEST: DB closed") esent.JetEndSession(sesid) esent.JetTerm()
Copy this code, and write in Windows/test.lua. After that open Start menu > Run… and enter test.lua. If you make everything correctly, computer make some noise 4-7s. And in logs you can see:
[1970-01-18 23:00:14] RUN: Starting Windows/test.lua [1970-01-18 23:00:18] KE: Created Thread 'test' at Priority 8 [1970-01-18 23:04:08] ESENT: JetInit - Instance initialized with 64 pages (256.0 KB) [1970-01-18 23:04:08] TEST: Catalog replaced with MyTable (rootPage=3) [1970-01-18 23:04:12] ESENT: JetBeginSession - Session 1 created [1970-01-18 23:04:12] TEST: Session created, sesid=1 [1970-01-18 23:04:15] ESENT: JetOpenDatabase - New database will be created at /mydb.dat [1970-01-18 23:04:15] ESENT: JetOpenDatabase - Database '/mydb.dat' opened [1970-01-18 23:04:19] TEST: DB open: /mydb.dat [1970-01-18 23:04:19] ESENT: JetPrepareUpdate - Prepared Insert for table MyTable [1970-01-18 23:04:22] ESENT: JetUpdate - Inserted record into MyTable [1970-01-18 23:04:22] TEST: Record added to MyTable [1970-01-18 23:04:26] TEST: Records in MyTable: 1 [1970-01-18 23:04:26] TEST: Record 1: ID=1;Name=TestRecord;Value=Hello LuaNT; [1970-01-18 23:05:45] ESENT: JetCloseDatabase - Wrote 64 pages to /mydb.dat [1970-01-18 23:05:49] TEST: DB closed [1970-01-18 23:05:49] ESENT: JetEndSession - Session 1 ended [1970-01-18 23:05:52] ESENT: JetTerm - Instance terminated [1970-01-18 23:05:52] KE: Thread test (PID: 5) finished normally. [1970-01-18 23:09:43] KE: Thread test (PID: 5) has finished normally. [1970-01-18 23:09:43] KE: GC - Cleaning dead thread test (PID: 5) [1970-01-18 23:09:46] MM: Freeing resources for Thread: test [1970-01-18 23:09:46] KE: GC cleaned 1 dead threads
You can see result by downloading ESEDBView from SMS, open he by Start menu > Run… and enter “esedbview.lua”, press ENTER. Follow instructions, and you can se this
Also you can try this GDI32+ example:
local gdi32, err = _G.KRNL_GDI32 or LdrLoadDll("Windows/System32/gdi32.lua") if not gdi32 then DbgPrint("GDI_TEST: Cannot load gdi32.lua: " .. tostring(err)) return end local hdc = gdi32.GetDC(0) if not hdc then DbgPrint("GDI_TEST: GetDC(0) return nil") return end local screenW = _G.HAL.w local screenH = _G.HAL.h local boxW = 20 local boxH = 10 local boxX = math.floor((screenW - boxW) / 2) local boxY = math.floor((screenH - boxH) / 2) local redBrush = gdi32.CreateSolidBrush(0xFF0000) gdi32.SelectObject(hdc, redBrush) gdi32.PatBlt(hdc, boxX, boxY, boxW, boxH, gdi32.PATCOPY) local whiteBrush = gdi32.CreateSolidBrush(0xFFFFFF) gdi32.SelectObject(hdc, whiteBrush) gdi32.PatBlt(hdc, boxX, boxY, boxW, 1, gdi32.PATCOPY) gdi32.PatBlt(hdc, boxX, boxY + boxH - 1, boxW, 1, gdi32.PATCOPY) gdi32.PatBlt(hdc, boxX, boxY, 1, boxH, gdi32.PATCOPY) gdi32.PatBlt(hdc, boxX + boxW - 1, boxY, 1, boxH, gdi32.PATCOPY) gdi32.SetTextColor(hdc, 0x000000) gdi32.SetBkColor(hdc, 0xFF0000) local label = "GDI+" local textX = boxX + math.floor((boxW - #label) / 2) local textY = boxY + math.floor(boxH / 2) gdi32.TextOut(hdc, textX, textY, label) while true do local signal = { computer.pullSignal(1) } local event = signal[1] if event == "key_down" or event == "touch" then break end end
After start he draw this
Also you can try new 4.0.1.4 features by this example driver:
local demo = {} demo.InterruptObject = nil function demo.OnInterrupt(struct, sig, addr, arg1, arg2, arg3, arg4) if sig == "key_down" then _G.DbgPrint("[DemoDriver] key_down: char=" .. tostring(arg1) .. " code=" .. tostring(arg2)) elseif sig == "touch" then _G.DbgPrint("[DemoDriver] touch: x=" .. tostring(arg1) .. " y=" .. tostring(arg2)) elseif sig == "component_added" then _G.DbgPrint("[DemoDriver] component_added: addr=" .. tostring(addr)) elseif sig == "component_removed" then _G.DbgPrint("[DemoDriver] component_removed: addr=" .. tostring(addr)) end end function demo.DriverEntry() _G.DbgPrint("[DemoDriver] Loading...") demo.InterruptObject = _G.IoConnectInterrupt(demo.OnInterrupt) if demo.InterruptObject then _G.DbgPrint("[DemoDriver] Registered interrupt at \\Structures\\" .. demo.InterruptObject) else _G.DbgPrint("[DemoDriver] FAILED to register interrupt") return nil end if _G.RegisterShutdownDriver then _G.RegisterShutdownDriver("demodriver", demo) end _G.DbgPrint("[DemoDriver] Loaded successfully") return demo end function demo.DriverUnload() _G.DbgPrint("[DemoDriver] Unloading...") if demo.InterruptObject and _G.IoDisconnectInterrupt then _G.IoDisconnectInterrupt(demo.InterruptObject) _G.DbgPrint("[DemoDriver] Disconnected interrupt " .. demo.InterruptObject) demo.InterruptObject = nil end _G.DbgPrint("[DemoDriver] Unloaded") return true end return demo.DriverEntry()
After start, if remove everything from log, you can see that:
[1970-01-31 00:39:25] ntoskrnl: Loading driver \SystemRoot\System32\drivers\demo.lua [1970-01-31 00:39:28] [DemoDriver] Loading... [1970-01-31 00:39:32] IO: IoConnectInterrupt - Registered handler at \Structures\IRQ3374CDC2 [1970-01-31 00:39:32] [DemoDriver] Registered interrupt at \Structures\IRQ3374CDC2 [1970-01-31 00:39:36] [DemoDriver] Loaded successfully [1970-01-31 00:39:36] ntoskrnl: Driver demo loaded successfully ... [1970-01-31 00:54:10] [DemoDriver] key_down: char=100 code=32 [1970-01-31 00:54:46] [DemoDriver] key_down: char=109 code=50 [1970-01-31 00:55:04] [DemoDriver] key_down: char=105 code=23 [1970-01-31 00:55:22] [DemoDriver] key_down: char=110 code=49 [1970-01-31 00:55:55] [DemoDriver] key_down: char=49 code=2 [1970-01-31 00:56:13] [DemoDriver] key_down: char=50 code=3 [1970-01-31 00:56:27] [DemoDriver] key_down: char=51 code=4 ... [1970-01-31 00:58:30] [DemoDriver] key_down: char=13 code=28 [1970-01-31 00:59:56] [DemoDriver] touch: x=71 y=20 [1970-01-31 01:01:55] [DemoDriver] touch: x=56 y=30 ... [1970-01-31 01:04:04] [DemoDriver] touch: x=4 y=48 ... [1970-01-31 01:04:19] [DemoDriver] touch: x=4 y=48 ... [1970-01-31 01:05:42] [DemoDriver] touch: x=10 y=41 ...
Also you can combine Io with Ex:
local demo = {} demo.InterruptObject = nil demo.WorkItem = {} demo.Counter = 0 local function ProcessSignal(Context) local sig = Context.sig local arg1 = Context.arg1 local arg2 = Context.arg2 demo.Counter = demo.Counter + 1 _G.DbgPrint(string.format( "[DemoWorkItem] #%d Processing signal: sig=%s arg1=%s arg2=%s", demo.Counter, tostring(sig), tostring(arg1), tostring(arg2) )) if sig == "key_down" then _G.DbgPrint("[DemoWorkItem] Key pressed: " .. tostring(arg1)) elseif sig == "touch" then _G.DbgPrint("[DemoWorkItem] Touch at: " .. tostring(arg1) .. "," .. tostring(arg2)) elseif sig == "component_added" then _G.DbgPrint("[DemoWorkItem] Component arrived") elseif sig == "component_removed" then _G.DbgPrint("[DemoWorkItem] Component removed") end Context.sig = nil Context.arg1 = nil Context.arg2 = nil end function demo.OnInterrupt(struct, sig, addr, arg1, arg2, arg3, arg4) if sig ~= "key_down" and sig ~= "touch" and sig ~= "component_added" and sig ~= "component_removed" then return end demo.Context.sig = sig demo.Context.arg1 = arg1 demo.Context.arg2 = arg2 local ok = _G.ExQueueWorkItem(demo.WorkItem, 1) if not ok then _G.DbgPrint("[DemoWorkItem] Failed to queue WorkItem (already in queue?)") end end function demo.DriverEntry() _G.DbgPrint("[DemoWorkItem] Loading...") demo.Context = { sig = nil, arg1 = nil, arg2 = nil } _G.ExInitializeWorkItem(demo.WorkItem, ProcessSignal, demo.Context) _G.DbgPrint("[DemoWorkItem] WorkItem initialized") demo.InterruptObject = _G.IoConnectInterrupt(demo.OnInterrupt) if demo.InterruptObject then _G.DbgPrint("[DemoWorkItem] Interrupt registered at \\Structures\\" .. demo.InterruptObject) else _G.DbgPrint("[DemoWorkItem] FAILED to register interrupt") return nil end if _G.RegisterShutdownDriver then _G.RegisterShutdownDriver("demoworkitem", demo) end _G.DbgPrint("[DemoWorkItem] Loaded successfully") return demo end function demo.DriverUnload() _G.DbgPrint("[DemoWorkItem] Unloading...") if demo.InterruptObject and _G.IoDisconnectInterrupt then _G.IoDisconnectInterrupt(demo.InterruptObject) demo.InterruptObject = nil end demo.WorkItem = {} demo.Context = nil _G.DbgPrint("[DemoWorkItem] Unloaded") return true end return demo.DriverEntry()
After starting you can see that, if try to login in WinLogon (ENTER→a→d→m→i→n→1→2→3→ENTER):
[1970-01-31 01:38:02] ntoskrnl: Loading driver \SystemRoot\System32\drivers\demo.lua [1970-01-31 01:38:06] [DemoWorkItem] Loading... [1970-01-31 01:38:09] [DemoWorkItem] WorkItem initialized [1970-01-31 01:38:09] IO: IoConnectInterrupt - Registered handler at \Structures\IRQ7E13ACB9 [1970-01-31 01:38:13] [DemoWorkItem] Interrupt registered at \Structures\IRQ7E13ACB9 [1970-01-31 01:38:13] [DemoWorkItem] Loaded successfully [1970-01-31 01:38:16] ntoskrnl: Driver demo loaded successfully ... [1970-01-31 01:51:39] IO: ExQueueWorkItem - WorkItem queued (type=1) [1970-01-31 01:51:39] [DemoWorkItem] #1 Processing signal: sig=key_down arg1=13 arg2=28 [1970-01-31 01:51:46] [DemoWorkItem] Key pressed: 13 [1970-01-31 01:53:34] IO: ExQueueWorkItem - WorkItem queued (type=1) [1970-01-31 01:53:34] [DemoWorkItem] #2 Processing signal: sig=key_down arg1=97 arg2=30 [1970-01-31 01:53:38] [DemoWorkItem] Key pressed: 97 [1970-01-31 01:54:54] IO: ExQueueWorkItem - WorkItem queued (type=1) [1970-01-31 01:54:54] [DemoWorkItem] #3 Processing signal: sig=key_down arg1=100 arg2=32 [1970-01-31 01:54:57] [DemoWorkItem] Key pressed: 100 [1970-01-31 01:56:02] IO: ExQueueWorkItem - WorkItem queued (type=1) [1970-01-31 01:56:02] [DemoWorkItem] #4 Processing signal: sig=key_down arg1=109 arg2=50 [1970-01-31 01:56:06] [DemoWorkItem] Key pressed: 109 [1970-01-31 01:56:56] IO: ExQueueWorkItem - WorkItem queued (type=1) [1970-01-31 01:56:56] [DemoWorkItem] #5 Processing signal: sig=key_down arg1=105 arg2=23 [1970-01-31 01:57:00] [DemoWorkItem] Key pressed: 105 [1970-01-31 01:57:32] IO: ExQueueWorkItem - WorkItem queued (type=1) [1970-01-31 01:57:32] [DemoWorkItem] #6 Processing signal: sig=key_down arg1=110 arg2=49 [1970-01-31 01:57:36] [DemoWorkItem] Key pressed: 110 [1970-01-31 01:58:44] IO: ExQueueWorkItem - WorkItem queued (type=1) [1970-01-31 01:58:44] [DemoWorkItem] #7 Processing signal: sig=key_down arg1=49 arg2=2 [1970-01-31 01:58:48] [DemoWorkItem] Key pressed: 49 [1970-01-31 01:59:27] IO: ExQueueWorkItem - WorkItem queued (type=1) [1970-01-31 01:59:27] [DemoWorkItem] #8 Processing signal: sig=key_down arg1=50 arg2=3 [1970-01-31 01:59:31] [DemoWorkItem] Key pressed: 50 [1970-01-31 02:00:18] IO: ExQueueWorkItem - WorkItem queued (type=1) [1970-01-31 02:00:18] [DemoWorkItem] #9 Processing signal: sig=key_down arg1=51 arg2=4 [1970-01-31 02:00:21] [DemoWorkItem] Key pressed: 51 [1970-01-31 02:01:22] WINLOGON: Auth success, initializing Desktop for user: Administrator [1970-01-31 02:01:22] RPC: Interface 'IServiceControlManager' successfully registered. [1970-01-31 02:03:14] IO: ExQueueWorkItem - WorkItem queued (type=1) [1970-01-31 02:03:14] [DemoWorkItem] #10 Processing signal: sig=key_down arg1=13 arg2=28 [1970-01-31 02:03:18] [DemoWorkItem] Key pressed: 13