Compare commits

...

85 Commits

Author SHA1 Message Date
neru 392dd8a68e fix: add additional checking 2026-01-10 02:33:18 -03:00
neru 9264c819ee style: var naming, misc changes 2026-01-10 01:58:19 -03:00
neru 1d4c1ca900 feat: add Supermarket Together script 2026-01-10 01:50:36 -03:00
neru e817d874c0 fix: leftover colon 2025-05-13 00:43:33 -03:00
neru 28c92e78c2 fix: usage conditional check when printing 2025-05-13 00:43:06 -03:00
neru c430904fa6 style: consistent casing 2025-05-13 00:42:34 -03:00
neru 60db9abb31 style: consistent casing 2025-05-13 00:42:05 -03:00
neru 16b1f474c4 feat: add help command functionality 2025-05-13 00:41:20 -03:00
neru 8c46690f52 fix: no formatting on usage 2025-05-13 00:38:05 -03:00
neru 55abc526bf style: help message formatting 2025-05-13 00:37:30 -03:00
neru 41a79d80a5 feat: add usage 2025-05-13 00:36:57 -03:00
neru 166fd3efc3 style: format help messages differently 2025-05-13 00:33:54 -03:00
neru a8d4d01378 feat: add command descriptions and help cmd 2025-05-13 00:32:10 -03:00
neru e47713e0c3 style: format 2025-05-08 20:42:12 -03:00
neru b85f7e29f2 fix: bad formatting 2025-05-08 20:40:06 -03:00
neru d1900f8327 fix: bad starting index 2025-05-08 20:39:40 -03:00
neru 6486731698 fix: bad index 2025-05-08 20:39:10 -03:00
neru d72ca4a727 fix: handle invocation when no turtles are available 2025-05-08 20:36:20 -03:00
neru 3b0555abd0 feat: add turtles command 2025-05-08 20:35:21 -03:00
neru 0162a776f8 feat: add init message to confirm execution 2025-05-08 20:32:11 -03:00
neru e22242c4e7 chore: misc changes and refactor 2025-05-08 20:31:42 -03:00
neru 7ca69cf0a0 docs: add to-do 2025-05-08 13:53:47 -03:00
neru 1ee6e9c0b8 fix: move logOffset checks 2025-05-08 13:51:59 -03:00
neru 973abbbfed fix: return when logs are empty 2025-05-08 13:49:59 -03:00
neru 45c499b6de fix: reset offset on clear 2025-05-08 13:49:04 -03:00
neru 9563262457 feat: add clear command 2025-05-08 13:48:27 -03:00
neru 17cc45f5c3 style: change init msg 2025-05-08 13:41:35 -03:00
neru 11aa0db67b feat: move scan to separate command 2025-05-08 13:41:06 -03:00
neru ad4289910b fix: stop cursor blinking on exit 2025-05-08 13:39:17 -03:00
neru ad17715e4a feat: clear screen on exit 2025-05-08 13:38:40 -03:00
neru 868663d33f feat: log cmd args on execution 2025-05-08 13:38:29 -03:00
neru e8c68ffded fix: simplify regex pattern 2025-05-08 13:37:11 -03:00
neru 5aea3bb618 docs: add to-do entry 2025-05-08 13:33:31 -03:00
neru 6fbb54c2cd feat: add commands 2025-05-08 13:33:24 -03:00
neru 283ee5cb71 docs: add to-do list 2025-05-08 13:31:18 -03:00
neru 9da79947d3 style: remove unused empty function 2025-05-08 13:22:47 -03:00
neru c5ab37664b feat: add monitor support 2025-05-08 13:20:22 -03:00
neru 77da21f1e7 feat: add cursor blinking 2025-05-08 13:13:58 -03:00
neru b6b35cfd2f style: formatting 2025-05-08 13:12:58 -03:00
neru 31d333333c feat: handle backspace on command line 2025-05-08 13:12:41 -03:00
neru 3c7836c513 docs: add logs to-do comment 2025-05-08 13:10:15 -03:00
neru 588f56075c fix: index calculation 2025-05-08 13:08:07 -03:00
neru 67b1e7b7d9 style: change colors 2025-05-08 13:03:52 -03:00
neru 709b7fb992 style: format 2025-05-08 13:03:35 -03:00
neru 0465b2de5b style: make cmd bar gray 2025-05-08 13:03:23 -03:00
neru 7305789194 fix: clearing before setting color 2025-05-08 13:03:16 -03:00
neru dd117a7ca2 fix: wrong cursor pos 2025-05-08 13:02:22 -03:00
neru cd2ff71b20 fix: wrong log start height 2025-05-08 13:02:02 -03:00
neru 3c235a7513 feat: begin implementing console commands 2025-05-08 13:01:36 -03:00
neru f29770e3f0 feat: move command bar to top 2025-05-08 13:01:24 -03:00
neru 63c3e4e273 feat: add glfw key codes 2025-05-08 13:01:10 -03:00
neru 0d7e6eb8e5 style: formatting 2025-05-08 01:58:29 -03:00
neru 08607c6497 style: change string formatting 2025-05-08 01:39:45 -03:00
neru 6d454821f9 style: change status bar col 2025-05-08 01:38:50 -03:00
neru 99b4fec189 test: remove test msg 2025-05-08 01:31:29 -03:00
neru 5022f71398 fix: prefix whitespace 2025-05-08 01:30:57 -03:00
neru 55314b33b2 fix: variable name 2025-05-08 01:29:02 -03:00
neru a6ce7c8b48 fix: iterate in reverse 2025-05-08 01:27:57 -03:00
neru 4638994190 fix: misc changes 2025-05-08 01:25:34 -03:00
neru 2f22573319 fix: wrong place for space 2025-05-08 01:18:29 -03:00
neru 42f8d24b76 fix: sort loop 2025-05-08 01:17:54 -03:00
neru aaa63e5293 fix: prefix spaces 2025-05-08 01:17:09 -03:00
neru 091811ad3f fix: wrong idxstuff 2025-05-08 01:09:08 -03:00
neru b0975519d1 test: add wrap test str 2025-05-08 01:07:33 -03:00
neru cc2988dbc8 fix: correct prefix space 2025-05-08 01:07:02 -03:00
neru 3bcce0f50f fix: bad clear 2025-05-08 01:06:10 -03:00
neru e29b4e9190 refactor: rewrite text wrapping 2025-05-08 00:57:44 -03:00
neru a00403549a fix: properly restore color 2025-05-08 00:55:39 -03:00
neru f426fb4f3f fix: draw text 2025-05-08 00:30:35 -03:00
neru 2002158be8 fix: return type 2025-05-08 00:30:00 -03:00
neru 8236be1fff feat: wrap lines on logs 2025-05-08 00:29:02 -03:00
neru 84637cb3dd feat: add status bar 2025-05-07 23:45:45 -03:00
neru ed9a3ec614 fix: old variable name 2025-05-07 23:40:05 -03:00
neru b022e1f470 fix: typo, again!!!!!!! 2025-05-07 23:39:42 -03:00
neru 853de03a3f fix: typo 2025-05-07 23:39:04 -03:00
neru 471204380d fix: typo 2025-05-07 23:38:41 -03:00
neru 3675c46279 style: move log function 2025-05-07 23:38:09 -03:00
neru 74e6730c10 fix: typo 2025-05-07 13:12:09 -03:00
neru cddf13d5cc test: remove test message 2025-05-06 20:29:35 -03:00
neru 0c3e147795 test: test code 2025-05-06 20:27:13 -03:00
neru ff540b4e6e fix: wrong cursor position 2025-05-06 20:26:22 -03:00
neru 6fb01064cd fix: sort functions 2025-05-06 20:25:45 -03:00
neru a8c673abf5 test: remove test logs 2025-05-06 20:23:48 -03:00
neru df6681c544 fix: missing variable name 2025-05-06 20:23:12 -03:00
neru 48c5b6d7be fix: remove unnecessary key system 2025-05-06 20:22:53 -03:00
3 changed files with 643 additions and 111 deletions
+42 -12
View File
@@ -162,27 +162,38 @@ end
declareCommandHandler("ABORT", handleAbort) declareCommandHandler("ABORT", handleAbort)
--[[ --[[
main event handling
]] ]]
function pollAndProcessMessages() local eventListeners = {}
local eventType, side, channel, replyChannel, message, dist = os.pullEvent("modem_message") local function registerEventListener(name, listener)
if not eventListeners[name] then eventListeners[name] = {} end
table.insert(eventListeners[name], listener)
end
-- ignore messages not from host, HOST_CHANNEL and REPLY_CHANNEL will get assigned after receiving SCAN on channel 0 local function dispatchEvents(event, ...)
local eventTbl = eventListeners[event]
if not eventTbl then return end
for i = 1, #eventTbl, 1 do
eventTbl[i](...)
end
end
local function handleMessages(side, channel, replyChannel, message, dist)
if TURTLE_CHANNEL and channel ~= TURTLE_CHANNEL then return end if TURTLE_CHANNEL and channel ~= TURTLE_CHANNEL then return end
-- message validation -- msg validation
if type(message) ~= "table" then return end if type(message) ~= "table" then return end
if not message.header or message.header ~= MSG_HEADER then return end if not message.header or message.header ~= MSG_HEADER then return end
if not message.data then message.data = {} end if not message.data then message.data = {} end
-- command validation -- cmd validation
if not message.name then if not message.name then
log.warning("received nameless message") log.warning("received nameless command?")
return return
end end
if not commands[message.name] then if not commands[message.name] then
log.error("unhandled message (%s)", message.name) log.error("unhandled command (%s)", message.name)
return return
end end
@@ -194,14 +205,33 @@ function pollAndProcessMessages()
end end
end end
cmd.handler(message.data) -- cmd execution
local success, err = pcall(cmd.handler, message.data, message.id)
if not success then
log.error("error occurred while executing command %s: %s", message.name, err)
return
end
-- message handling
log.info("processed command (%s)", message.name) log.info("processed command (%s)", message.name)
end end
registerEventListener("modem_message", handleMessages)
log.general("tutel client init") --[[
event polling
]]
function pollEvents()
local eventData = { os.pullEvent() }
local eventName = eventData[1]
table.remove(eventData, 1)
dispatchEvents(eventName, table.unpack(eventData))
end
--[[
main loop
]]
log.info("init")
while true do while true do
pollAndProcessMessages() pollEvents()
end end
+331 -96
View File
@@ -10,7 +10,15 @@
]] ]]
--[[ --[[
network variables TO-DO List:
* Add argument / funcion type checks on callback styled register functions
* Make log screen scrolling work based on drawn lines not log indexes
* Rename network commands to messages, to avoid confusion with console commands
* Periodically check if turtle connections are valid
]]
--[[
general variables
]] ]]
local HOST_CHANNEL = 1337 local HOST_CHANNEL = 1337
local TURTLE_CHANNEL_BASE = 1338 local TURTLE_CHANNEL_BASE = 1338
@@ -20,6 +28,59 @@ local MSG_HEADER = 0x747574656C
local modem = peripheral.find("modem") or error("No modems found", 0) local modem = peripheral.find("modem") or error("No modems found", 0)
modem.open(HOST_CHANNEL) modem.open(HOST_CHANNEL)
local monitor = peripheral.find("monitor")
local scr = monitor or term
local shouldRun = true
--[[
helper functions
]]
function wrapText(text, maxWidth)
if not text then return {} end
if maxWidth <= 0 then return { text } end
local lines = {}
local currentLine = ""
for word, newline in text:gmatch("([^%s\n]*)(%s*\n?)") do
if newline:find("\n") then
if currentLine ~= "" then
table.insert(lines, currentLine)
currentLine = ""
end
if word ~= "" then
table.insert(lines, word)
else
table.insert(lines, "")
end
elseif #currentLine + #word <= maxWidth then
if currentLine == "" then
currentLine = word
else
currentLine = currentLine .. " " .. word
end
else
if currentLine ~= "" then
table.insert(lines, currentLine)
currentLine = word
else
while #word > maxWidth do
table.insert(lines, word:sub(1, maxWidth))
word = word:sub(maxWidth + 1)
end
currentLine = word
end
end
end
if currentLine ~= "" then
table.insert(lines, currentLine)
end
return lines
end
--[[ --[[
logging logging
]] ]]
@@ -110,7 +171,7 @@ end
function handleScan(keys, id) function handleScan(keys, id)
local turt = registerTurtle({ keys.positionX, keys.positionY, keys.positionZ }, keys.fuel, id) local turt = registerTurtle({ keys.positionX, keys.positionY, keys.positionZ }, keys.fuel, id)
log.info("Received turtle info (pos: %d, %d, %d - fuel: %d - channel: %d - id: %d)", turt.position[1], log.info("Received turtle info (pos: %d, %d, %d | fuel: %d | channel: %d | id: %d)", turt.position[1],
turt.position[2], turt.position[3], turt.fuel, turt.channel, id) turt.position[2], turt.position[3], turt.fuel, turt.channel, id)
sendToAll("REGISTER", { ["id"] = id, ["turtleChannel"] = turt.channel }) sendToAll("REGISTER", { ["id"] = id, ["turtleChannel"] = turt.channel })
@@ -152,19 +213,19 @@ function handleMessages(side, channel, replyChannel, message, dist)
-- cmd validation -- cmd validation
if not message.name then if not message.name then
log.warning("received nameless command?") log.warning("Received nameless command?")
return return
end end
if not commands[message.name] then if not commands[message.name] then
log.error("unhandled command (%s)", message.name) log.error("Unhandled command (%s)", message.name)
return return
end end
local cmd = commands[message.name] local cmd = commands[message.name]
for i, v in pairs(cmd.keys) do for i, v in pairs(cmd.keys) do
if type(message.data[i]) ~= v and v ~= "optional" then if type(message.data[i]) ~= v and v ~= "optional" then
log.error("command %s requires key %s of type %s (was %s)", message.name, i, v, type(message[i])) log.error("Command %s requires key %s of type %s (was %s)", message.name, i, v, type(message[i]))
return return
end end
end end
@@ -172,125 +233,300 @@ function handleMessages(side, channel, replyChannel, message, dist)
-- cmd execution -- cmd execution
local success, err = pcall(cmd.handler, message.data, message.id) local success, err = pcall(cmd.handler, message.data, message.id)
if not success then if not success then
log.error("error occurred while executing command %s: %s", message.name, err) log.error("Error occurred while executing command %s: %s", message.name, err)
end end
log.info("processed command (%s)", message.name) log.info("Processed command (%s)", message.name)
end end
registerEventListener("modem_message", handleMessages) registerEventListener("modem_message", handleMessages)
local logOffset = 0
-- to-do: add key handlers
--[[ --[[
key handling key handling
]] ]]
local keyListeners = {} local GLFW_KEY_DOWN = 264
function registerKeyListener(id, listener) local GLFW_KEY_UP = 265
if not keyListeners[id] then keyListeners[id] = {} end local GLFW_KEY_ENTER = 257
table.insert(keyListeners[id], listener) local GLFW_KEY_BACKSPACE = 259
end local logOffset = 0
function dispatchKey(key, isHeld) function scrollLogs(key, isHeld)
local keyTbl = eventListeners[key] if key == GLFW_KEY_DOWN then
if not keyTbl then return end logOffset = logOffset - 1
elseif key == GLFW_KEY_UP then
for i = 1, #keyTbl, 1 do logOffset = logOffset + 1
keyTbl[i](isHeld)
end end
end end
registerEventListener("key", dispatchKey)
function scrollLogsUp(isHeld) registerEventListener("key", scrollLogs)
logOffset = logOffset - 1
--[[
console
]]
local consoleCommands = {}
local commandBuffer = ""
function registerConsoleCommand(name, handler, desc, usage)
local cmd = {
["name"] = name,
["handler"] = handler,
["desc"] = desc or "",
["usage"] = usage or nil
}
consoleCommands[name] = cmd
end
function processCommand()
local cmd
local args = {}
for word in commandBuffer:gmatch("%S+") do
if not cmd then
cmd = word
else
table.insert(args, word)
end
end
if consoleCommands[cmd] then
log.info("> %s %s", cmd, table.concat(args, " "))
consoleCommands[cmd].handler(args)
else
log.warning("Unknown command: %s", cmd)
end
commandBuffer = ""
end
function handleConsoleInput(char)
commandBuffer = commandBuffer .. char
end
registerEventListener("char", handleConsoleInput)
function handleConsoleKeys(key)
if key == GLFW_KEY_ENTER and commandBuffer:len() > 0 then
processCommand()
elseif key == GLFW_KEY_BACKSPACE then
local len = commandBuffer:len()
if len >= 1 then
commandBuffer = commandBuffer:sub(0, len - 1)
end
end
end
registerEventListener("key", handleConsoleKeys)
--[[
console commands
]]
function echoCommand(args)
log.info(table.concat(args, " "))
end
registerConsoleCommand("echo", echoCommand, "Prints your message", "echo (string)")
function exitCommand(args)
shouldRun = false
end
registerConsoleCommand("exit", exitCommand, "Exits the program")
function scanCommand(args)
log.info("Broadcasting SCAN")
sendToAll("SCAN", { ["hostChannel"] = HOST_CHANNEL })
end
registerConsoleCommand("scan", scanCommand, "Sends out a scan command to pair unlinked turtles")
function clearCommand(args)
log.buffer = {}
end
registerConsoleCommand("clear", clearCommand, "Clears the console")
function turtlesCommand(args)
if #turtles == 0 then
log.info("No turtles registered")
return
end
for i = 1, #turtles, 1 do
local turt = turtles[i]
local turtleMessage = ("Coords: %d, %d, %d - fuel: %d - channel: %d - id: %d - status: %s"):format(
turt.position[1],
turt.position[2], turt.position[3], turt.fuel, turt.channel, turt.id, turt.status)
log.info(turtleMessage)
end
end
registerConsoleCommand("turtles", turtlesCommand, "Lists all the linked turtles")
function helpCommand(args)
if #args == 0 then
log.info("----- commands -----")
for _, cmd in pairs(consoleCommands) do
log.info("%s - %s", cmd.name, cmd.desc)
end
else
local cmdName = args[1]
local cmd = consoleCommands[cmdName]
if not cmdName then
log.error("Unable to find command: %s", cmdName)
return
end
log.info("---- %s ----", cmdName)
log.info(cmd.desc)
if cmd.usage then
log.info("Usage: %s", cmd.usage)
end
end
end
registerConsoleCommand("help", helpCommand, "Prints out all the console commands or usage of a specified command",
"help or help (command name)")
--[[
drawing functions
]]
function drawLogs(x0, y0, x1, y1)
local w = x1 - x0
local h = y1 - y0
if #log.buffer == 0 then return end
if logOffset < 0 then logOffset = 0 end if logOffset < 0 then logOffset = 0 end
end if logOffset >= #log.buffer then logOffset = #log.buffer - 1 end
registerKeyListener(265, scrollLogsUp) if #log.buffer == 0 then logOffset = 0 end
function scrollLogsDown(isHeld) local drawnLines = 0
logOffset = logOffset + 1 for i = #log.buffer - logOffset, 1, -1 do
if logOffset >= #log.buffer then logOffset = #log.buffer - 1 end if drawnLines > h then break end
end
registerKeyListener(264, scrollLogsUp)
local entry = log.buffer[i]
local format = logTypes[entry.type]
--[[ local prevColourBg = scr.getBackgroundColour()
main local prevColourFg = scr.getTextColour()
]]
log.general("tutel host init")
-- transmit scan local prefix = ("[%s]"):format(entry.type)
-- log.info("broadcasting SCAN") local prefixLength = #prefix
local maxMessageWidth = math.max(1, w - prefixLength)
--[[ local wrappedMessage = wrapText(tostring(entry.message), maxMessageWidth)
screen drawing
]]
function updateScreen()
local w, h = term.getSize()
local function drawTextCentered(text) local messageBG, messageFG
local x = (w / 2) - (text:len() / 2) if format.AffectContent then
local _, curY = term.getCursorPos() messageBG = format.BG
term.setCursorPos(x, curY) messageFG = format.FG
term.write(text) else
end messageBG = prevColourBg
messageFG = prevColourFg
end
-- reset state and clear screen for lineIdx = #wrappedMessage, 1, -1 do
term.setBackgroundColor(colors.black) if drawnLines > h then break end
term.clear()
-- -- title bar local line = wrappedMessage[lineIdx]
term.setCursorPos(1, 1)
term.setBackgroundColor(colors.lightGray) scr.setCursorPos(1, y1 - drawnLines)
term.setTextColour(colors.black)
term.clearLine() if lineIdx == 1 then
drawTextCentered("tutel host controller") scr.setBackgroundColor(format.BG)
scr.setTextColor(format.FG)
term.setBackgroundColor(colors.black) scr.write(prefix)
term.setTextColour(colors.white)
-- logs
local function drawLogs(startY, endY)
local drawnLines = 0
for i = #log.buffer - logOffset, 1, -1 do
if drawnLines > endY - startY then break end
local entry = log.buffer[i]
local format = logTypes[entry.type]
term.setCursorPos(0, endY - drawnLines)
local prevColourBg = term.getBackgroundColour()
local prevColourFg = term.getTextColour()
term.setBackgroundColour(format["BG"])
term.setTextColour(format["FG"])
term.write(("[%s]"):format(entry.type))
if format["AffectContent"] then
term.write((" %s\n"):format(entry.message))
term.setBackgroundColour(prevColourBg)
term.setTextColour(prevColourFg)
else else
term.setBackgroundColour(prevColourBg) scr.setBackgroundColor(messageBG)
term.setTextColour(prevColourFg) scr.write((" "):rep(prefixLength))
term.write((" %s\n"):format(entry.message)) end
scr.setBackgroundColor(messageBG)
scr.setTextColor(messageFG)
scr.write(" ")
scr.write(line)
local remaining = w - (prefixLength + #line)
if remaining > 0 then
scr.write((" "):rep(remaining))
end end
drawnLines = drawnLines + 1 drawnLines = drawnLines + 1
end end
scr.setBackgroundColor(prevColourBg)
scr.setTextColor(prevColourFg)
end
end
function drawStatusBar(y)
scr.setCursorPos(1, y)
scr.setBackgroundColor(colors.gray)
scr.setTextColor(colors.white)
scr.clearLine()
local str = ("Turtles: %d"):format(#turtles)
scr.write(str)
end
function drawCommandBar(w, y)
scr.setCursorPos(1, y)
scr.setBackgroundColor(colors.gray)
scr.setTextColor(colors.white)
scr.clearLine()
scr.write("$ ")
local startingIndex = commandBuffer:len() - w + 4
if startingIndex < 0 then
startingIndex = 0
end end
drawLogs(2, h - 1) scr.write(commandBuffer:sub(startingIndex))
scr.setCursorBlink(true)
end end
--[[ --[[
event polling main loop functions
]] ]]
function updateScreen()
local w, h = scr.getSize()
local function drawTextCentered(text)
local x = (w / 2) - (text:len() / 2)
local _, curY = scr.getCursorPos()
scr.setCursorPos(x, curY)
scr.write(text)
end
-- reset state and clear screen
scr.setBackgroundColor(colors.black)
scr.clear()
-- -- title bar
scr.setCursorPos(1, 1)
scr.setBackgroundColor(colors.lightGray)
scr.setTextColour(colors.black)
scr.clearLine()
drawTextCentered("tutel host controller")
scr.setBackgroundColor(colors.black)
scr.setTextColour(colors.white)
drawLogs(1, 2, w, h - 1)
drawStatusBar(2)
drawCommandBar(w, h)
end
function pollEvents() function pollEvents()
local eventData = { os.pullEvent() } local eventData = { os.pullEvent() }
@@ -301,15 +537,14 @@ function pollEvents()
end end
--[[ --[[
main loop main
]] ]]
for i=0, 15, 1 do log.general("Init")
log.info("some log %d", i)
log.warning("some warning %d", i)
end
sendToAll("SCAN", { ["hostChannel"] = HOST_CHANNEL }) while shouldRun do
while true do
updateScreen() updateScreen()
pollEvents() pollEvents()
end end
scr.clear()
scr.setCursorBlink(false)
+267
View File
@@ -0,0 +1,267 @@
if (mono_enumDomains() == nil) then LaunchMonoDataCollector() end
local is64Bit = targetIs64Bit()
local ARRAY_SIZE_OFF = is64Bit and 0x18 or 0x10
local ARRAY_DATA_OFF = is64Bit and 0x20 or 0x14
local PTR_SIZE = is64Bit and 8 or 4
local monoTypeToVartypeLookup = {
[0x02] = vtByte, -- BOOLEAN
[0x03] = vtWord, -- CHAR
[0x04] = vtByte, -- I1
[0x05] = vtByte, -- U1
[0x06] = vtWord, -- I2
[0x07] = vtWord, -- U2
[0x08] = vtDword, -- I4 (Int32)
[0x09] = vtDword, -- U4
[0x0a] = vtQword, -- I8
[0x0b] = vtQword, -- U8
[0x0c] = vtSingle, -- R4
[0x0d] = vtDouble, -- R8
[0x0e] = vtString, -- STRING
[0x0f] = vtPointer, -- PTR
[0x12] = vtPointer, -- CLASS
[0x14] = vtPointer -- ARRAY
}
--[[
generic helper functions
]]
function printf(fmt, ...) print(fmt:format(...)) end
function splitByComma(str)
if str == nil or str == "" then return {} end
local result = {}
for word in string.gmatch(str, "([^,]+)") do table.insert(result, word) end
return result
end
--[[
mono helper functions
]]
function dumpFields(class)
print("class fields:")
for i, v in pairs(mono_class_enumFields(class)) do
printf("\tname: %s - static: %s", v.name, tostring(v.isStatic))
end
end
function getFieldFromName(class, name, shouldBeStatic)
local fields = mono_class_enumFields(class)
if not fields then return nil end
for i, v in pairs(fields) do
if v.isStatic == (shouldBeStatic == true) and v.name == name then
return v
end
end
print(("could not find field: %s"):format(name))
return nil
end
--[[
mono class wrappers
]]
function MonoArray(addr)
if not addr or addr == 0 then error("attempted to instantiate MonoArray with invalid addr") end
local classId, className = mono_object_getClass(addr)
if not className then error("address did not point to valid object") end
if className:sub(-2) ~= "[]" then error("address did not point to array") end
local array = {
_address = addr,
getLength = function(self)
return readInteger(self._address + ARRAY_SIZE_OFF)
end,
getElement = function(self, idx)
if idx < 0 or idx >= self:getLength() then
error("invalid index")
end
return
readPointer(self._address + ARRAY_DATA_OFF + (idx * PTR_SIZE))
end
}
setmetatable(array, {
__tostring = function() return "Mono array instance" end,
__index = function(self, k)
local idx = tonumber(k)
if idx ~= nil then return self:getElement(idx) end
return rawget(self, k)
end
})
return array
end
local stringClass = mono_findClass("System", "String")
local string_Length = getFieldFromName(stringClass, "_stringLength")
local string_FirstChar = getFieldFromName(stringClass, "_firstChar")
function MonoStringReader(addr)
if not addr or addr == 0 then return nil end
local classId, className = mono_object_getClass(addr)
if className ~= "String" then error("address did not point to string") end
local str = {
_address = addr,
getLength = function(self)
return readInteger(self._address + string_Length.offset)
end,
getValue = function(self)
return readString(self._address + string_FirstChar.offset,
self:getLength() * 2, true)
end
}
setmetatable(str, {
__tostring = function(self) return self:getValue() end,
__index = str
})
return str
end
function MonoFunctionHelper(fnName, fnId, isStatic)
local fn = {
_name = fnName,
_id = fnId,
_signatures = {},
_static = isStatic
}
if type(fnId) == "table" then
for i, v in ipairs(fnId) do
local sig = mono_method_getSignature(v)
table.insert(fn._signatures, {id = v, params = splitByComma(sig)})
end
end
setmetatable(fn, {
__call = function(self, ...)
local args = {...}
local methodArgs = args
local instancePtr = nil
if not self._static then
if #args == 0 then
error("Instance method " .. self._name ..
" requires instance pointer")
end
instancePtr = table.remove(methodArgs, 1)
end
local activeId = nil
if type(self._id) == "table" then
for _, candidate in ipairs(self._signatures) do
if #candidate.params == #methodArgs then
activeId = candidate.id
break
end
end
if not activeId then
error("No overload for " .. self._name ..
" matches arg count " .. #methodArgs)
end
else
activeId = self._id
end
local paramsInfo = mono_method_get_parameters(activeId)
local monoArgs = {}
for i = 1, #methodArgs do
local pType = paramsInfo.parameters[i].type
table.insert(monoArgs, {
type = monoTypeToVartypeLookup[pType] or vtPointer,
value = methodArgs[i]
})
end
return mono_invoke_method(mono_enumDomains()[1], activeId,
instancePtr, monoArgs)
end,
__tostring = function(self) return "MonoFunction: " .. self._name end
})
return fn
end
function MonoClass(namespace, name)
local classId = mono_findClass(namespace, name)
if not classId then error("Could not find class " .. name) end
local domain = mono_enumDomains()[1]
local staticAddr = mono_class_getStaticFieldAddress(domain, classId)
local methodsRaw = mono_class_enumMethods(classId)
local methodsSorted = {}
for _, v in pairs(methodsRaw) do
if methodsSorted[v.name] then
if type(methodsSorted[v.name]) ~= "table" then
methodsSorted[v.name] = {methodsSorted[v.name]}
end
table.insert(methodsSorted[v.name], v.method)
else
methodsSorted[v.name] = v.method
end
end
local methodsWrapped = {}
for mName, mId in pairs(methodsSorted) do
local checkId = type(mId) == "table" and mId[1] or mId
local flags = mono_method_getFlags(checkId)
local isStatic = bAnd(flags, 0x0010) ~= 0
methodsWrapped[mName] = MonoFunctionHelper(mName, mId, isStatic)
end
local classObj = {
_id = classId,
_name = name,
_ns = namespace,
_fieldInfo = {},
_staticAddr = staticAddr,
_methods = methodsWrapped,
getStaticFieldOffset = function(self, name)
local field = getFieldFromName(self._id, name, true)
return field and field.offset or nil
end,
getStaticField = function(self, offset)
return readPointer(self._staticAddr + offset)
end,
getMethod = function(self, methodName)
local m = self._methods[methodName]
if not m then
error("Method " .. methodName .. " not found!")
end
return m
end
}
return setmetatable(classObj, {
__index = function(self, key)
return self._methods[key] or rawget(self, key)
end
})
end
local typeClass = MonoClass("System", "Type")
function Type(typeName)
local typeStr = mono_new_string(typeName)
local getType = typeClass:getMethod("GetType")
local typeRet = getType(typeStr)
print(type(typeRet), typeRet)
end
--[[
game
]]
local GameDataClass = MonoClass("", "GameData")
local instanceOffset = GameDataClass:getStaticFieldOffset("Instance")
printf("GameData class id: %s", tostring(GameDataClass))
printf("GameData.Instance field offset: %x", instanceOffset or 0)
local gameDataInstance = GameDataClass:getStaticField(instanceOffset)
printf("GameData: %x", gameDataInstance or 0)
local alterFunds = GameDataClass:getMethod("CmdAlterFunds")
printf("GameData.CmdAlterFunds: %x", alterFunds._id or 0)
alterFunds(gameDataInstance, 13371337)