369 lines
11 KiB
Lua
369 lines
11 KiB
Lua
-- This file is part of mimgui project
|
|
-- Licensed under the MIT License
|
|
-- Copyright (c) 2018, FYP <https://github.com/THE-FYP>
|
|
|
|
assert(getMoonloaderVersion() >= 025)
|
|
|
|
local imgui = require 'mimgui.imgui'
|
|
local DX9 = require 'mimgui.dx9'
|
|
local ffi = require 'ffi'
|
|
local winmsg = require 'windows.message'
|
|
local memory = require 'memory'
|
|
local mimgui = {}
|
|
|
|
local renderer = nil
|
|
local subscriptionsInitialize = {}
|
|
local subscriptionsNewFrame = {}
|
|
local eventsRegistered = false
|
|
local active = false
|
|
local cursorActive = false
|
|
local playerLocked = false
|
|
local iniFilePath = nil
|
|
local defaultGlyphRanges = nil
|
|
|
|
setmetatable(mimgui, {__index = imgui, __newindex = function(t, k, v)
|
|
if imgui[k] then
|
|
print('[mimgui] Warning! Overwriting existing key "'..k..'"!')
|
|
end
|
|
rawset(t, k, v)
|
|
end})
|
|
|
|
-- background "Shift" triggering fix
|
|
memory.fill(0x00531155, 0x90, 5, true)
|
|
|
|
local function ShowCursor(show)
|
|
if show then
|
|
showCursor(true)
|
|
elseif cursorActive then
|
|
showCursor(false)
|
|
end
|
|
cursorActive = show
|
|
end
|
|
|
|
local function LockPlayer(lock)
|
|
if lock then
|
|
lockPlayerControl(true)
|
|
elseif playerLocked then
|
|
lockPlayerControl(false)
|
|
end
|
|
playerLocked = lock
|
|
end
|
|
|
|
-- MoonLoader v.027
|
|
if not isCursorActive then
|
|
isCursorActive = function() return cursorActive end
|
|
end
|
|
|
|
local function InitializeRenderer()
|
|
-- init renderer
|
|
local hwnd = ffi.cast('HWND', readMemory(0x00C8CF88, 4, false))
|
|
local d3ddevice = ffi.cast('LPDIRECT3DDEVICE9', getD3DDevicePtr())
|
|
renderer = assert(DX9.new(d3ddevice, hwnd))
|
|
renderer:SwitchContext()
|
|
|
|
-- configure imgui
|
|
imgui.GetIO().ImeWindowHandle = nil -- default causes crash. TODO: why?
|
|
imgui.GetIO().LogFilename = nil
|
|
local confdir = getWorkingDirectory() .. [[\config\mimgui\]]
|
|
if not doesDirectoryExist(confdir) then
|
|
createDirectory(confdir)
|
|
end
|
|
iniFilePath = ffi.new('char[260]', confdir .. script.this.filename .. '.ini')
|
|
imgui.GetIO().IniFilename = iniFilePath
|
|
|
|
-- change font
|
|
local fontFile = getFolderPath(0x14) .. '\\trebucbd.ttf'
|
|
assert(doesFileExist(fontFile), '[mimgui] Font "' .. fontFile .. '" doesn\'t exist!')
|
|
local builder = imgui.ImFontGlyphRangesBuilder()
|
|
builder:AddRanges(imgui.GetIO().Fonts:GetGlyphRangesCyrillic())
|
|
builder:AddText([[‚„…†‡€‰‹‘’“”•–—™›№]])
|
|
defaultGlyphRanges = imgui.ImVector_ImWchar()
|
|
builder:BuildRanges(defaultGlyphRanges)
|
|
imgui.GetIO().Fonts:AddFontFromFileTTF(fontFile, 14, nil, defaultGlyphRanges[0].Data)
|
|
|
|
-- invoke initializers
|
|
for _, cb in ipairs(subscriptionsInitialize) do
|
|
cb()
|
|
end
|
|
end
|
|
|
|
local function RegisterEvents()
|
|
addEventHandler('onD3DPresent', function()
|
|
if active then
|
|
if not renderer then
|
|
InitializeRenderer()
|
|
end
|
|
if renderer and not renderer.lost then
|
|
renderer:SwitchContext()
|
|
for _, sub in ipairs(subscriptionsNewFrame) do
|
|
if sub._render and sub._before then
|
|
sub:_before()
|
|
end
|
|
end
|
|
renderer:NewFrame()
|
|
local hideCursor = true
|
|
for _, sub in ipairs(subscriptionsNewFrame) do
|
|
if sub._render then
|
|
sub:_draw()
|
|
hideCursor = hideCursor and sub.HideCursor
|
|
end
|
|
end
|
|
if hideCursor and not isCursorActive() then
|
|
imgui.SetMouseCursor(imgui.lib.ImGuiMouseCursor_None)
|
|
end
|
|
renderer:EndFrame()
|
|
end
|
|
end
|
|
end)
|
|
|
|
local keyState = {}
|
|
local WM_MOUSEHWHEEL = 0x020E
|
|
local mouseMsgs = {
|
|
[WM_MOUSEHWHEEL]=true,
|
|
[winmsg.WM_LBUTTONDOWN]=true,
|
|
[winmsg.WM_LBUTTONDBLCLK]=true,
|
|
[winmsg.WM_RBUTTONDOWN]=true,
|
|
[winmsg.WM_RBUTTONDBLCLK]=true,
|
|
[winmsg.WM_MBUTTONDOWN]=true,
|
|
[winmsg.WM_MBUTTONDBLCLK]=true,
|
|
[winmsg.WM_LBUTTONUP]=true,
|
|
[winmsg.WM_RBUTTONUP]=true,
|
|
[winmsg.WM_MBUTTONUP]=true,
|
|
[winmsg.WM_MOUSEWHEEL]=true,
|
|
[winmsg.WM_SETCURSOR]=true
|
|
}
|
|
local keyboardMsgs = {
|
|
[winmsg.WM_KEYDOWN]=true,
|
|
[winmsg.WM_SYSKEYDOWN]=true,
|
|
[winmsg.WM_KEYUP]=true,
|
|
[winmsg.WM_SYSKEYUP]=true,
|
|
[winmsg.WM_CHAR]=true
|
|
}
|
|
addEventHandler('onWindowMessage', function(msg, wparam, lparam)
|
|
if not renderer then
|
|
return
|
|
end
|
|
|
|
if not mimgui.DisableInput then
|
|
local keyboard = keyboardMsgs[msg]
|
|
local mouse = mouseMsgs[msg]
|
|
if active and (keyboard or mouse) then
|
|
renderer:SwitchContext()
|
|
local io = imgui.GetIO()
|
|
renderer:WindowMessage(msg, wparam, lparam)
|
|
if (keyboard and io.WantCaptureKeyboard) or (mouse and io.WantCaptureMouse) then
|
|
if msg == winmsg.WM_KEYDOWN or msg == winmsg.WM_SYSKEYDOWN then
|
|
keyState[wparam] = false
|
|
consumeWindowMessage(true, true, true)
|
|
elseif msg == winmsg.WM_KEYUP or msg == winmsg.WM_SYSKEYUP then
|
|
if not keyState[wparam] then
|
|
consumeWindowMessage(true, true, true)
|
|
end
|
|
else
|
|
consumeWindowMessage(true, true, true)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- save key states to prevent key sticking
|
|
if msg == winmsg.WM_KILLFOCUS then
|
|
keyState = {}
|
|
elseif wparam < 256 then
|
|
if msg == winmsg.WM_KEYDOWN or msg == winmsg.WM_SYSKEYDOWN then
|
|
keyState[wparam] = true
|
|
elseif msg == winmsg.WM_KEYUP or msg == winmsg.WM_SYSKEYUP then
|
|
keyState[wparam] = false
|
|
end
|
|
end
|
|
end)
|
|
|
|
addEventHandler('onD3DDeviceLost', function()
|
|
if renderer and not renderer.lost then
|
|
renderer:InvalidateDeviceObjects()
|
|
renderer.lost = true
|
|
end
|
|
end)
|
|
|
|
addEventHandler('onD3DDeviceReset', function()
|
|
if renderer then
|
|
renderer.lost = false
|
|
end
|
|
end)
|
|
|
|
addEventHandler('onScriptTerminate', function(scr)
|
|
if scr == script.this then
|
|
ShowCursor(false)
|
|
LockPlayer(false)
|
|
end
|
|
end)
|
|
|
|
local updaterThread = lua_thread.create(function()
|
|
while true do
|
|
wait(0)
|
|
local activate, hideCursor, lockPlayer = false, true, false
|
|
if #subscriptionsNewFrame > 0 then
|
|
for i, sub in ipairs(subscriptionsNewFrame) do
|
|
if type(sub.Condition) == 'function' then
|
|
sub._render = sub.Condition()
|
|
else
|
|
sub._render = sub.Condition and true
|
|
end
|
|
if sub._render then
|
|
hideCursor = hideCursor and sub.HideCursor
|
|
lockPlayer = lockPlayer or sub.LockPlayer
|
|
end
|
|
activate = activate or sub._render
|
|
end
|
|
end
|
|
active = activate
|
|
ShowCursor(active and not hideCursor)
|
|
LockPlayer(active and lockPlayer)
|
|
end
|
|
end)
|
|
updaterThread.work_in_pause = true
|
|
end
|
|
|
|
local function Unsubscribe(t, sub)
|
|
for i, v in ipairs(t) do
|
|
if v == sub then
|
|
table.remove(t, i)
|
|
return
|
|
end
|
|
end
|
|
end
|
|
|
|
local function ImGuiEnum(name)
|
|
return setmetatable({__name = name}, {__index = function(t, k)
|
|
return imgui.lib[t.__name .. k]
|
|
end})
|
|
end
|
|
|
|
--- API ---
|
|
mimgui._VERSION = '1.7.0'
|
|
mimgui.DisableInput = false
|
|
|
|
mimgui.ComboFlags = ImGuiEnum('ImGuiComboFlags_')
|
|
mimgui.Dir = ImGuiEnum('ImGuiDir_')
|
|
mimgui.ColorEditFlags = ImGuiEnum('ImGuiColorEditFlags_')
|
|
mimgui.Col = ImGuiEnum('ImGuiCol_')
|
|
mimgui.WindowFlags = ImGuiEnum('ImGuiWindowFlags_')
|
|
mimgui.NavInput = ImGuiEnum('ImGuiNavInput_')
|
|
mimgui.FocusedFlags = ImGuiEnum('ImGuiFocusedFlags_')
|
|
mimgui.Cond = ImGuiEnum('ImGuiCond_')
|
|
mimgui.BackendFlags = ImGuiEnum('ImGuiBackendFlags_')
|
|
mimgui.TreeNodeFlags = ImGuiEnum('ImGuiTreeNodeFlags_')
|
|
mimgui.StyleVar = ImGuiEnum('ImGuiStyleVar_')
|
|
mimgui.DrawCornerFlags = ImGuiEnum('ImDrawCornerFlags_')
|
|
mimgui.DragDropFlags = ImGuiEnum('ImGuiDragDropFlags_')
|
|
mimgui.SelectableFlags = ImGuiEnum('ImGuiSelectableFlags_')
|
|
mimgui.InputTextFlags = ImGuiEnum('ImGuiInputTextFlags_')
|
|
mimgui.MouseCursor = ImGuiEnum('ImGuiMouseCursor_')
|
|
mimgui.FontAtlasFlags = ImGuiEnum('ImFontAtlasFlags_')
|
|
mimgui.HoveredFlags = ImGuiEnum('ImGuiHoveredFlags_')
|
|
mimgui.ConfigFlags = ImGuiEnum('ImGuiConfigFlags_')
|
|
mimgui.DrawListFlags = ImGuiEnum('ImDrawListFlags_')
|
|
mimgui.DataType = ImGuiEnum('ImGuiDataType_')
|
|
mimgui.Key = ImGuiEnum('ImGuiKey_')
|
|
|
|
function mimgui.OnInitialize(cb)
|
|
assert(type(cb) == 'function')
|
|
table.insert(subscriptionsInitialize, cb)
|
|
return {Unsubscribe = function() Unsubscribe(subscriptionsInitialize, cb) end}
|
|
end
|
|
|
|
function mimgui.OnFrame(cond, cbBeforeFrame, cbDraw)
|
|
assert(type(cond) == 'function')
|
|
assert(type(cbBeforeFrame) == 'function')
|
|
if cbDraw then assert(type(cbDraw) == 'function') end
|
|
if not eventsRegistered then
|
|
RegisterEvents()
|
|
eventsRegistered = true
|
|
end
|
|
local sub = {
|
|
Condition = cond,
|
|
LockPlayer = false,
|
|
HideCursor = false,
|
|
_before = cbDraw and cbBeforeFrame or nil,
|
|
_draw = cbDraw or cbBeforeFrame,
|
|
_render = false,
|
|
}
|
|
function sub:Unsubscribe()
|
|
Unsubscribe(subscriptionsNewFrame, self)
|
|
end
|
|
function sub:IsActive()
|
|
return self._render
|
|
end
|
|
table.insert(subscriptionsNewFrame, sub)
|
|
return sub
|
|
end
|
|
|
|
function mimgui.SwitchContext()
|
|
return renderer:SwitchContext()
|
|
end
|
|
|
|
function mimgui.CreateTextureFromFile(path)
|
|
return renderer:CreateTextureFromFile(path)
|
|
end
|
|
|
|
function mimgui.CreateTextureFromFileInMemory(src, size)
|
|
return renderer:CreateTextureFromFileInMemory(src, size)
|
|
end
|
|
|
|
function mimgui.ReleaseTexture(tex)
|
|
return renderer:ReleaseTexture(tex)
|
|
end
|
|
|
|
function mimgui.CreateFontsTexture()
|
|
return renderer:CreateFontsTexture()
|
|
end
|
|
|
|
function mimgui.InvalidateFontsTexture()
|
|
return renderer:InvalidateFontsTexture()
|
|
end
|
|
|
|
function mimgui.GetRenderer()
|
|
return renderer
|
|
end
|
|
|
|
function mimgui.IsInitialized()
|
|
return renderer ~= nil
|
|
end
|
|
|
|
function mimgui.StrCopy(dst, src, len)
|
|
if len or tostring(ffi.typeof(dst)):find('*', 1, true) then
|
|
ffi.copy(dst, src, len)
|
|
else
|
|
len = math.min(ffi.sizeof(dst) - 1, #src)
|
|
ffi.copy(dst, src, len)
|
|
dst[len] = 0
|
|
end
|
|
end
|
|
|
|
local new = {}
|
|
setmetatable(new, {
|
|
__index = function(self, key)
|
|
local basetype = ffi.typeof(key)
|
|
local mt = {
|
|
__index = function(self, sz)
|
|
return setmetatable({type = ffi.typeof('$[$]', self.type, sz)}, getmetatable(self))
|
|
end,
|
|
__call = function(self, ...)
|
|
return self.type(...)
|
|
end
|
|
}
|
|
return setmetatable({type = ffi.typeof('$[1]', basetype), basetype = basetype}, {
|
|
__index = function(self, sz)
|
|
return setmetatable({type = ffi.typeof('$[$]', self.basetype, sz)}, mt)
|
|
end,
|
|
__call = mt.__call
|
|
})
|
|
end,
|
|
__call = function(self, t, ...)
|
|
return ffi.new(t, ...)
|
|
end
|
|
})
|
|
mimgui.new = new
|
|
|
|
return mimgui
|