-
Notifications
You must be signed in to change notification settings - Fork 4
Mod Tools
Mod tools are similar to scripts except they can create GUIs. Accessible via the Mod Tools sub menu in the Tools menu. Loaded mod tools show in the submenu, and any opened files are reloaded on startup and appear in that submenu.
Mod tools support all the functions that scripts support (e.g. engine.readnumber() and jrpc.callvoidat())
-- These 4 functions return a table that represent the GUI element.
gui.create_dockpanel(lastChildFill : boolean) -> table
gui.create_stackpanel(isVertical : boolean) -> table
gui.create_gridpanel() -> table
gui.create_button(text : string) -> table
gui.create_text(text : string) -> table
-- Sets the GUI's root panel to draw
gui.set_root_panel(panel : table)
-- Blocks until there's pending messages queued on the lua thread, then processes them, and returns.
-- For example, when the user presses a button, it dispatches a click event to the lua thread,
-- then this method will process that press event (which starts an internal loop until the click
-- release event is sent, and this inner loop calls the equivalent of pump.try_run_messages())
pump.run_messages()
-- Processes any pending messages queued on the lua thread, or returns if the queue is empty.
-- This is the same as `pump.run_messages()` except doesn't block if the queue is empty.
-- Note, this may block anyway (e.g. pressing a button which has a holding function set)
pump.try_run_messages()These are not in release builds yet.
-- Creates a timer that invokes the callback function at the specified interval (in seconds). The interval is also the delay for invoking the callback for the first time (aka due time)
gui.create_timer(interval : number, callback : function) -> TimerObject
-- Destroys the timer returned by create_timer, which stops the callback from being invoked.
gui.destroy_timer(timer : TimerObject)These are instance methods, so the invisible first parameter is the element itself
-- Horizontally aligns this element relative to the parent. Can be "stretch", "left", "center" or "right"
set_align_h(align : string)
-- Vertically aligns this element relative to the parent. Can be "stretch", "top", "center" or "bottom"
set_align_v(align : string)Stack, Dock and Grid panels contain these functions. These are instance methods, so the invisible first parameter is the panel itself
-- Returns the index of the element within the panel. The index is zero-based, so 0 is the first element. If the element was not found, -1 is returned.
index_of(element : table) -> number
-- Removes an element from the panel at the given index
remove_at(index : number)All column/row parameters pass column first and row next, so that it behaves like X and Y coordinates. These are instance methods, so the invisible first parameter is the panel itself
-- Note: Row and column indices are zero-based and cannot be negative
-- RowSpan and ColumnSpan must be >= 1
add(column : number, row : number, element : table)
add(column : number, row : number, columnSpan : number, rowSpan : number, element : table)-- Adds a row with the given format for the pixel height. E.g. "25", "3*", "Auto"
add_row(layout: string)
-- Adds a column with the given format for the pixel width. E.g. "25", "3*", "Auto"
add_column(layout: string)
-- Removes the row at the given index. The index is zero-based, so 0 is the first row.
remove_row(index : number)
-- Removes the column at the given index. The index is zero-based, so 0 is the first column.
remove_column(index : number)These are instance methods, so the invisible first parameter is the panel itself
-- Adds an element docked to either "left", "top", "right" or "bottom"
add(dock : string, element : table)
-- Adds an element docked to the left. Ideally only use for the last child when lastChildFill is true
add(element : table)These are instance methods, so the invisible first parameter is the panel itself
-- Adds an element
add(element : table)These are instance methods, so the invisible first parameter is the button itself.
The callback function is passed the Button table (as in, the table that you called one of the functions with) as the first parameter.
-- Sets the function that runs when the user clicks then releases their mouse on the button
set_press_function(callback : function(self : table))
-- Sets the function that is called in a loop while the user has left-click pressed.
-- The 'delta' parameter is the amount of time since the callback was last called, in seconds.
-- Remarks about the callback's return value: When no value is available or it returns false, the loop continues. When true, it breaks the loop (as if the user released their LMB). Return nothing or false to truly loop until the user stops pressing.
set_holding_function(callback : function(self : table, delta : number) -> boolean?)
-- Sets the button's text
set_text(text : string)The holding function is provided the delta time between each callback invocation. On the first run, it will be very very small.
You should use this if you need to, for example, increase a float on the console at a fixed rate (e.g. 25.0 per second).
Example: engine.writenumber(addr, "float", 25.0 * delta)
In the demo script at the bottom of the page, the text increments by 3000 per second on a modern PC. This is because setting the text requires posting a message to the UI dispatcher to set the text, because the caller is the lua thread. If you only used lua code and no network operations, it would run as fast as the lua virtual machine can execute, which is significantly faster than 3000 times per second.
Warning
Do not create your own loop inside of this, because the outer loop will pump any queued messages automatically. If you need a loop, call pump.try_run_messages() often
These are instance methods, so the invisible first parameter is the text block itself
-- Sets the text of the text block
set_text(text : string)This is a demo of UI layout
press_btn_counter = 0
holding_btn_counter = 0
function CreateTestDock(side)
-- create a dock panel that won't fill the last child control,
-- since we want a left and right dock
local dock = gui.create_dockpanel(false)
-- create a horizontal stack panel
local bottom_spL = gui.create_stackpanel(true)
bottom_spL.add(bottom_spL, gui.create_button(side .. " left btn 1"))
bottom_spL.add(bottom_spL, gui.create_button(side .. " left btn 2"))
bottom_spL.add(bottom_spL, gui.create_button(side .. " left btn 3"))
-- create a horizontal stack panel
local bottom_spR = gui.create_stackpanel(true)
bottom_spR.add(bottom_spR, gui.create_button(side .. " right btn 1"))
bottom_spR.add(bottom_spR, gui.create_button(side .. " right btn 2"))
bottom_spR.add(bottom_spR, gui.create_button(side .. " right btn 3"))
-- add panels to the dock
dock.add(dock, "left", bottom_spL)
dock.add(dock, "right", bottom_spR)
return dock
end
-- create our root panel, which will fill the last child (tmpstack)
-- to the window contents (inbetween the top and bottom docks ofc)
local root = gui.create_dockpanel(true)
root.add(root, "top", CreateTestDock("Top "))
root.add(root, "bottom", CreateTestDock("Bottom "))
-- create a vertical stack panel
local tmpstack = gui.create_stackpanel(true)
tmpstack.add(tmpstack, gui.create_text("Hello!"))
local tmpbutton1 = gui.create_button("Click to incr per press")
tmpbutton1.set_press_function(tmpbutton1, function ()
press_btn_counter = press_btn_counter + 1
tmpbutton1.set_text(tmpbutton1, press_btn_counter)
end)
tmpstack.add(tmpstack, tmpbutton1)
local tmpbutton2 = gui.create_button("Click to incr while holding")
tmpbutton2.set_holding_function(tmpbutton2, function (delta)
holding_btn_counter = holding_btn_counter + 1
tmpbutton2.set_text(tmpbutton2, holding_btn_counter)
end)
tmpstack.add(tmpstack, tmpbutton2)
root.add(root, tmpstack)
-- assign root panel to the GUI
gui.set_root_panel(root)
-- the important part. In order for button clicks to work,
-- pump.run_messages() must be called often
while true do
pump.run_messages()
end
Here is a small script to move the player around using 4 buttons:
local tb_x = gui.create_text("Pos X: ???");
local tb_y = gui.create_text("Pos Y: ???");
local grid = gui.create_gridpanel()
grid:add_row("40")
grid:add_row("40")
grid:add_row("40")
grid:add_column("40")
grid:add_column("40")
grid:add_column("40")
local btn_fwd = gui.create_button("+Y");
local btn_lft = gui.create_button("-X");
local btn_rht = gui.create_button("+X");
local btn_bck = gui.create_button("-Y");
btn_fwd:set_holding_function(function (delta)
local num = engine.readnumber(0x830CBFA0, "float") + (100 * delta)
engine.writenumber(0x830CBFA0, "float", num)
tb_y:set_text("Pos Y: " .. tostring(num))
end)
btn_lft:set_holding_function(function (delta)
local num = engine.readnumber(0x830CBF9C, "float") - (100 * delta)
engine.writenumber(0x830CBF9C, "float", num)
tb_x:set_text("Pos X: " .. tostring(num))
end)
btn_rht:set_holding_function(function (delta)
local num = engine.readnumber(0x830CBF9C, "float") + (100 * delta)
engine.writenumber(0x830CBF9C, "float", num)
tb_x:set_text("Pos X: " .. tostring(num))
end)
btn_bck:set_holding_function(function (delta)
local num = engine.readnumber(0x830CBFA0, "float") - (100 * delta)
engine.writenumber(0x830CBFA0, "float", num)
tb_y:set_text("Pos Y: " .. tostring(num))
end)
grid:add(1, 0, btn_fwd)
grid:add(0, 1, btn_lft)
grid:add(2, 1, btn_rht)
grid:add(1, 2, btn_bck)
local stack = gui.create_stackpanel(true)
stack:add(grid)
stack:add(tb_x)
stack:add(tb_y)
-- assign root panel to the GUI
gui.set_root_panel(stack)
-- the important part. In order for button clicks to work,
-- pump.run_messages() must be called often
while true do
pump.run_messages()
end
function GetTemperature(sensorType)
if sensorType < 0 or sensorType > 3 then
error("Sensor type must be 0, 1, 2, or 3")
end
local addr_HalSendSMCMessage = jrpc.getprocaddress("xboxkrnl.exe", 0x29)
-- XamSwapDiscPatchAddress + 0x3000
local addr_XamBuffer = jrpc.getprocaddress("xam.xex", 0xA29) + 12288
local smcMsg = "7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0"
jrpc.callat("int", addr_HalSendSMCMessage, {"byte[]", smcMsg}, {"int", addr_XamBuffer})
local data = engine.readbytes(addr_XamBuffer, 12)
local lo = data[sensorType * 2 + 2]
local hi = data[sensorType * 2 + 3]
return bit32.bor(lo, bit32.lshift(hi, 8)) / 256.0
end
s_CpuLabel = gui.create_text("CPU: ? c")
s_GpuLabel = gui.create_text("GPU: ? c")
s_MemLabel = gui.create_text("MEMORY: ? c")
s_BrdLabel = gui.create_text("CHASSIS: ? c")
function UpdateTemps()
s_CpuLabel:set_text("CPU: " .. GetTemperature(0) .. "c")
s_GpuLabel:set_text("GPU: " .. GetTemperature(1) .. "c")
s_MemLabel:set_text("MEMORY: " .. GetTemperature(2) .. "c")
s_BrdLabel:set_text("CHASSIS: " .. GetTemperature(3) .. "c")
end
local stack = gui.create_stackpanel(true)
stack:add(s_CpuLabel)
stack:add(s_GpuLabel)
stack:add(s_MemLabel)
stack:add(s_BrdLabel)
gui.set_root_panel(stack)
gui.create_timer(1, UpdateTemps)
while (true) do
pump.run_messages()
end
-
Home
- Connect to a console
- Scanning Options
- Scan results & Saved Address Table
- Remote Commands
- Memory Dump
- Tools
- Preferences/App Settings
-
API
- Making a custom connection
- Busy Tokens
- Models, ViewStates, MVP & Binding
- Plugins
- Config Pages
- Brushes and Icons
- Data Manager, Context Data and Data Keys
- Commands and Shortcuts
- Context Menus
- Windows and Dialogs