├── .gitignore ├── Archive ├── FindStaticFields.LUA └── menuitems.lua ├── Build ├── Build.md ├── DevTableScript.lua ├── build.lua └── output │ ├── .gitignore │ └── .placeholder ├── CheatEngineMonoHelper.CT ├── Dev.CT ├── EndlessSpace.CT ├── ExceptionAutoSave_CheatEngineMonoHelper.CT ├── LICENSE ├── LuaForm └── formMonoClass.lua ├── Mono └── Classes.md ├── MonoHelper.CEA ├── Pages ├── _config.yml └── index.md ├── README.md ├── Scripts ├── Enum.lua └── StaticField.lua ├── autorun ├── monohelper.lua └── monoscript.lua ├── docs ├── Building.md ├── Class.png ├── CryingSuns │ ├── Battle.md │ ├── Cheats │ │ └── Weapons-QuickDeploy.md │ ├── DeployableWeapon.md │ ├── Dock.md │ ├── Enums.md │ ├── Events.md │ ├── Expedition.md │ ├── README.md │ └── Statics.md ├── General.md ├── Index.html ├── MonoStructures.md ├── Releases │ ├── MonoHelper.CT │ ├── index.md │ ├── monohelper-1.0.0.lua │ ├── monohelper-1.1.0.lua │ └── monohelper.lua ├── Search.png ├── SelectImage.png ├── TODO.md ├── Tutorials │ ├── 7DaysToDie │ │ ├── dropOneILSpy.png │ │ └── index.md │ ├── Raft │ │ ├── PlayerStatsFields.png │ │ ├── PlayerStatsMethods.png │ │ ├── Raft.CT │ │ ├── SearchHealth.png │ │ ├── SearchOxygen.png │ │ ├── Stat_Oxygen.png │ │ └── index.md │ └── Youtube7DaysToDie │ │ ├── index.md │ │ └── script.md ├── _config.yml └── index.md ├── monoscript.lua └── src ├── forms ├── formMonoClass.FRM ├── formMonoImage.FRM ├── formMonoSearch.FRM └── lfm │ ├── README.md │ ├── formMonoClass.LFM │ ├── formMonoImage.LFM │ └── formMonoSearch.LFM ├── lua ├── bootstrap.lua ├── forms │ ├── formClass.lua │ ├── formSearch.lua │ └── formSelectImage.lua ├── generators │ ├── index.lua │ └── original_hook.lua ├── mono.lua ├── mono │ ├── MonoClass.lua │ ├── MonoField.lua │ ├── MonoImage.lua │ └── MonoMethod.lua ├── monomenu.lua ├── temp │ ├── README.md │ ├── monopopups.lua │ └── notes.lua └── util.lua └── samples ├── CreateMemoryRecord.lua ├── Dev.CT.lua ├── Popups.lua └── old_util.lua /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | -------------------------------------------------------------------------------- /Archive/FindStaticFields.LUA: -------------------------------------------------------------------------------- 1 | staticFields = {} 2 | for i,c in ipairs(mi.classes) do 3 | for j,f in ipairs(c.staticFields) do 4 | table.insert(staticFields, f) 5 | end 6 | end 7 | table.sort(staticFields) 8 | 9 | local temp = staticFields 10 | staticFields = {} 11 | for i,f in ipairs(temp) do 12 | if f.typeName ~= 'ID' 13 | and f.typeName ~= 'States' 14 | and f.name:find('$', 1, true) == nil 15 | and f.name:find('<', 1, true) == nil 16 | and f.typeName:find('_', 1, true) ~= 3 17 | and f.typeName:find('_', 1, true) ~= 4 18 | then 19 | table.insert(staticFields, f) 20 | else 21 | --print('Removing '..f.name) 22 | end 23 | end 24 | 25 | print('left with '..tostring(#staticFields)..' fields') 26 | 27 | table.sort(staticFields, function(a, b) return a:getFullName() < b:getFullName() end) 28 | for i,f in ipairs(staticFields) do 29 | print(f.typeName, f:getFullName()) 30 | end 31 | 32 | -------------------------------------------------------------------------------- /Archive/menuitems.lua: -------------------------------------------------------------------------------- 1 | ------------------------------------------------------------------------[[ 2 | Top-level menu support 3 | 4 | ------------------------------------------------------------------------]] 5 | 6 | mono.menu = mono.menu or { 7 | top = nil, -- top-level menu item 8 | selectImage = nil, -- 'Select Image' for mono only 9 | search = nil, -- 'Search' for mono only 10 | notes = nil, -- 'Notes' 11 | } 12 | 13 | mono.setup = mono.setup or { 14 | _oldOnOpenProcess = nil, -- for our handler to call when process opened 15 | _checkedProcessId = nil, -- so we only check a process once for mono dlls 16 | _hasMono = false, -- will check when menu clicked if mono is available for a new process 17 | } 18 | 19 | function mono.createMainFormMenu() 20 | local mfm = createMenuItem(getMainForm().Menu) 21 | if mfm == nil then 22 | print('Cannot find main menu!') 23 | return 24 | end 25 | 26 | if not mono.menu.top then 27 | mono.menu.top = createMenuItem(mfm) 28 | mono.menu.top.Caption = translate("Ext") 29 | mono.menu.top.OnClick = function(sender) mono.menu.clickTop(sender) end 30 | end 31 | 32 | end 33 | 34 | function mono.menu.clickTop(sender) 35 | -- enable mono items if mono is available 36 | -- for that, we keep track of open process id and if it was checked, 37 | -- if the process id changes, we need to check for mono features again 38 | local pid = getOpenedProcessID() 39 | if pid ~= mono.setup.f_checkedPID then 40 | end 41 | end 42 | 43 | mono.createMainFormMenu() 44 | 45 | -------------------------------------------------------------------------------- /Build/Build.md: -------------------------------------------------------------------------------- 1 | # Building `monohelper.lua` 2 | 3 | When developing, you need a CT file that is in the same directory as the source 4 | code, specifically 'bootstrap.lua'. It's easiest to use the included 5 | `CheatEngineMonoHelper.CT`, but if you want to setup your own you need to 6 | follow these steps: 7 | 8 | ## Forms 9 | 10 | The forms will exist embedded in the cheat table during development. 11 | Later when creating the combined file 'monohelper.lua', you will the content 12 | as strings which allow it to add the forms to your cheat table when using it. 13 | 14 | Create a new form for each file in 'FormsFRM' and load it: 15 | 16 | * formMonoClass.FRM 17 | * formMonoImage.FRM 18 | * formMonoSearch.FRM 19 | 20 | Steps for each form: 21 | 22 | 1. In CE do 'Table->Create Form' 23 | 2. In editor do 'File->Load' (*not* 'Load LFM') 24 | 3. Select the file 25 | 4. Close the editor window (form window will still show up, now in run mode instead of design mode) 26 | 5. Close the form window 27 | 6. The form should now appear in the 'Table' menu and you can edit it from there 28 | 29 | ## Bootstrapping 30 | 31 | The cheat table needs to be in the same directory as the source files, 32 | specifically 'bootstrap.lua'. The script below should be added as your 33 | table script. This has some utility functions and executes 'bootstrap.lua', 34 | which itself loads other files and executes them. 35 | 36 | Go to 'Table->Show Cheat Table Lua Script' (or CTRL+ALT+L) 37 | and enter the code below then execute it 38 | 39 | ```lua 40 | function loadTextFile(name, useTableFile) 41 | if useTableFile then 42 | local tableFile = findTableFile(name) 43 | if not tableFile then return nil, 'Unable to open table file "'..tostring(name)..'"' end 44 | local ss = createStringStream() 45 | ss.Position = 0 -- recommended on wiki: https://wiki.cheatengine.org/index.php?title=Lua:Class:TableFile 46 | ss.CopyFrom(tableFile.Stream, 0) 47 | local text = ss.DataString 48 | ss.destroy() 49 | return text 50 | else 51 | local path = getMainForm().openDialog1.InitialDir..name 52 | local f, err = io.open(path, "r") 53 | -- fall back to table file if disk file error (doesn't exist) 54 | if f == nil then return loadTextFile(name, true) end 55 | local text = f:read("*all") 56 | f:close() 57 | return text 58 | end 59 | end 60 | 61 | loadstring(loadTextFile('bootstrap.lua'))() 62 | ``` 63 | 64 | That's it, you should now see a 'Search' option under the 'Mono' menu when 65 | attached to a game that supports CE mono features. 66 | 67 | 68 | ## Getting form text: 69 | 70 | ```lua 71 | local loadTextFile = function(name) 72 | local path = getMainForm().openDialog1.InitialDir..name 73 | local f, err = io.open(path, "r") 74 | -- fall back to table file if disk file error (doesn't exist) 75 | if f == nil then return loadTextFile(name, true) end 76 | local text = f:read("*all") 77 | f:close() 78 | return text 79 | end 80 | 81 | local text = loadTextFile("build/build.lua") 82 | local result = loadstring(text) 83 | return result() 84 | ``` 85 | 86 | ## WORK IN PROGRESS 87 | 88 | Sample script for what I'm thinking, including text of other files in with existing file. 89 | This seems to work fine. If we rely on exactly one space where they are and no spaces 90 | in the file name, we should be able to use the lengths to pick out the file name pretty 91 | easily. 92 | 93 | Ok, this seems to work well. The pattern to seach for returns only the file name, 94 | but the pattern used to replace replaces the whole string from beginning brackets to closing 95 | and allows for spaces in brackets. The search should find the first one, and the replace 96 | is limited by last parameter to one replace. Works online... 97 | 98 | ```lua 99 | local s = "[[-- #INCLUDEFILE(src/lua/mono/monofield.lua) ]]" 100 | 101 | local s2 =[===[ 102 | 103 | [[-- #INCLUDEFILE(src/lua/mono/monofield.lua) ]] 104 | [[-- #INCLUDEFILE(src/lua/mono/monomethod.lua) ]] 105 | [[-- #INCLUDEFILE(src/lua/mono/monoclass.lua) ]] 106 | [[-- #INCLUDEFILE(src/lua/mono/monoimage.lua) ]] 107 | 108 | [[-- #INCLUDEFILE(src/lua/monomenu.lua) ]] 109 | 110 | [[-- #INCLUDEFILE(src/forms/lua/formSelectImage.lua) ]] 111 | [[-- #INCLUDEFILE(src/forms/lua/formSearch.lua) ]] 112 | [[-- #INCLUDEFILE(src/forms/lua/formClass.lua) ]] 113 | 114 | ]===] 115 | 116 | local patternOriginal = "%[%[%-%- *#INCLUDEFILE%([^%)]*%) *%]%]" 117 | local pattern = "%[%[%-%- *#INCLUDEFILE%(([^%)]*)%) *%]%]" 118 | 119 | print("patternOriginal: (entire line with comments and spaces, use for gsub") 120 | local simpleMatch = s:match(patternOriginal ) 121 | print(simpleMatch) 122 | print() 123 | 124 | print("pattern: pick out file name (between parens) alone for loading file") 125 | local s2Match, a, b, c = s2:match(pattern) 126 | print(s2Match) 127 | print() 128 | 129 | 130 | local r = s2:gsub(pattern, "FILE CONTENT", 1) -- limit to 1 replacement 131 | print('result:') 132 | print(r); 133 | print() 134 | 135 | print('s2 now:') 136 | print(s2) 137 | ``` 138 | 139 | Ok, THIS script seems to be very nice, it handles includes like above, prevents 140 | an endless loop of loading files if they have a circular reference: 141 | 142 | ```lua 143 | local alreadyLoaded = {} -- to prevent endless loop 144 | 145 | function includeLuaFile(name) 146 | if alreadyLoaded[name] then error("Already loaded file: "..tostring(name)) end 147 | alreadyLoaded[name] = true -- prevent endless loop 148 | 149 | local path = getMainForm().openDialog1.InitialDir..name 150 | print("loatTextFile() path: "..path); 151 | local f, err = io.open(path, "r") 152 | if f == nil then error("Cannot open path: "..tostring(path)..", "..tostring(err)) end 153 | local text = f:read("*all") 154 | print(" loaded bare text, size: "..tostring(string.len(text))) 155 | f:close() 156 | 157 | text = "\r\n[[--------------------------------------------------------------------------------\r\n -- Included File: "..tostring(name).."\r\n --------------------------------------------------------------------------------]]\r\n"..text 158 | 159 | -- search pattern, returns only the file name because it's a group in parens () 160 | local patternSearch = "%[%[%-%- *#INCLUDEFILE%(([^%)]*)%) *%]%]" 161 | 162 | -- interesting, using a function it should call with file name, returning text 163 | text = text:gsub(patternSearch, includeLuaFile) 164 | return text 165 | end 166 | ``` -------------------------------------------------------------------------------- /Build/DevTableScript.lua: -------------------------------------------------------------------------------- 1 | --[[-------------------------------------------------------------------------------- 2 | Dev table script. Load this into a cheat table in the root directory of this 3 | repository, or into Dev.CT 4 | --]] 5 | 6 | -- only execute once 7 | 8 | DevMenu = DevMenu or {} 9 | 10 | if DevMenu.miTopMenuItem == nil then 11 | -- does not exist, create new menu item second from last (before 'Help') 12 | local mfm=getMainForm().Menu 13 | DevMenu.miTopMenuItem = createMenuItem(mfm) -- create child 14 | DevMenu.miTopMenuItem.Caption = "Dev (MonoHelper)" 15 | mfm.Items.insert(mfm.Items.Count - 1, DevMenu.miTopMenuItem) --add near end before last item ('Help') 16 | else 17 | -- exists already, clear children to recreate 18 | DevMenu.miTopMenuItem.clear() 19 | end 20 | 21 | local mi -- re-used 22 | 23 | mi = createMenuItem(DevMenu.miTopMenuItem) 24 | mi.Caption = "Build and Reload LUA" 25 | mi.OnClick = function() 26 | local all, forms, lua = loadfile(getMainForm().openDialog1.InitialDir.."Build/build.lua")() 27 | loadstring(lua)() 28 | end 29 | mi.Name = 'miDevReloadLua' 30 | DevMenu.miTopMenuItem.Add(mi) 31 | DevMenu.miDevReloadLua = mi 32 | 33 | mi = createMenuItem(DevMenu.miTopMenuItem) 34 | mi.Caption = "Build Only" 35 | mi.OnClick = function() 36 | local all, forms, lua = loadfile(getMainForm().openDialog1.InitialDir.."Build/build.lua")() 37 | showMessage("Output is in Build/output/monohelper.lua, if you've changed the forms, save each one you changed to 'src/forms' and build again") 38 | end 39 | mi.Name = 'miDevBuildLua' 40 | DevMenu.miTopMenuItem.Add(mi) 41 | DevMenu.miDevBuildLua = mi 42 | 43 | -- menu item will build, then copy the file to the CE autorun folder, easy to close and restart CE to see changes 44 | mi = createMenuItem(DevMenu.miTopMenuItem) 45 | mi.Caption = "Build and copy to CE autorun directory" 46 | mi.OnClick = function() 47 | local all, forms, lua = loadfile(getMainForm().openDialog1.InitialDir.."Build/build.lua")() 48 | local path = getCheatEngineDir()..[[\autorun\monohelper.lua]] 49 | local f, err = io.open(path, "w") 50 | if f == nil then return nil, err end 51 | f:write(all) 52 | f:close() 53 | showMessage("Restart CE to see changes") 54 | end 55 | mi.Name = 'miDevBuildToCEAutorunDirectory' 56 | DevMenu.miTopMenuItem.Add(mi) 57 | DevMenu.miDevBuildLua = mi 58 | -------------------------------------------------------------------------------- /Build/build.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | 3 | -- Sample call getting all (both), forms only, and lua only 4 | local all, forms, lua = loadfile(getMainForm().openDialog1.InitialDir.."Build/build.lua")() 5 | return forms, lua 6 | 7 | 8 | -- Sample to reload and run LUA 9 | local all, forms, lua = loadfile(getMainForm().openDialog1.InitialDir.."Build/build.lua")() -- load build script into function and execute 10 | loadstring(lua)() 11 | 12 | --]] 13 | 14 | --[[-------------------------------------------------------------------------------- 15 | - Forms 16 | --------------------------------------------------------------------------------]] 17 | 18 | -- NOTE: this uses special syntax for multi=line strings, any number of equals can 19 | -- appear between the brackets and the close is only when the same number of = 20 | -- are between the close brackets 21 | local sForms = [=====[ 22 | --[[-------------------------------------------------------------------------------- 23 | -- Forms - Save strings as temp files, then load using createFormFromFile() 24 | --------------------------------------------------------------------------------]] 25 | 26 | -- close any open forms 27 | if formMonoClass ~= nil then 28 | formMonoClass.close() 29 | formMonoClass:destroy() 30 | formMonoClass = nil 31 | end 32 | if formMonoImage ~= nil then 33 | formMonoImage.close() 34 | formMonoImage:destroy() 35 | formMonoImage = nil 36 | end 37 | if formMonoSearch ~= nil then 38 | formMonoSearch.close() 39 | formMonoSearch:destroy() 40 | formMonoSearch = nil 41 | end 42 | 43 | -- unselect image, refs won't be correct if re-ran 44 | if mono then mono.selectedImage = nil end 45 | 46 | 47 | -- generate forms from saved XML 48 | local stringFormMonoClass = [[-- #INCLUDEFORM(src/forms/formMonoClass.FRM) ]] 49 | 50 | local stringFormMonoImage = [[-- #INCLUDEFORM(src/forms/formMonoImage.FRM) ]] 51 | 52 | local stringFormMonoSearch = [[-- #INCLUDEFORM(src/forms/formMonoSearch.FRM) ]] 53 | 54 | local function saveForm(text) 55 | local path = os.tmpname() -- get temp file name 56 | local f, err = io.open(path, "w") 57 | if f == nil then return nil, err end 58 | f:write(text) 59 | f:close() 60 | return path 61 | end 62 | 63 | local function createFormFromString(text) 64 | local path = saveForm(text) 65 | local form = createFormFromFile(path) 66 | pcall(os.remove, path) 67 | return form 68 | end 69 | 70 | -- create forms from xml (using temp files) 71 | formMonoClass = createFormFromString(stringFormMonoClass) 72 | formMonoImage = createFormFromString(stringFormMonoImage) 73 | formMonoSearch = createFormFromString(stringFormMonoSearch) 74 | ]=====] 75 | 76 | local function loadFormAsLuaString(name) 77 | local path = getMainForm().openDialog1.InitialDir..name 78 | local f, err = io.open(path, "r") 79 | if f == nil then error("Cannot open path: "..tostring(path)..", "..tostring(err)) end 80 | local text = f:read("*all") 81 | f:close() 82 | return "[==========["..text.."]==========]" 83 | end 84 | 85 | 86 | local patternForm = "%[%[%-%- *#INCLUDEFORM%(([^%)]*)%) *%]%]" 87 | local sectionForms = sForms:gsub(patternForm, loadFormAsLuaString) 88 | 89 | --[[-------------------------------------------------------------------------------- 90 | - Lua 91 | --------------------------------------------------------------------------------]] 92 | 93 | local alreadyLoaded = {} -- to prevent ENDLESS endless loop 94 | 95 | local function includeLuaFile(name) 96 | if alreadyLoaded[name] then error("build.lua - Already loaded file: "..tostring(name)) end 97 | alreadyLoaded[name] = true -- prevent endless loop 98 | 99 | local path = getMainForm().openDialog1.InitialDir..name 100 | local f, err = io.open(path, "r") 101 | if f == nil then error("build.lua - Cannot open path: "..tostring(path)..", "..tostring(err)) end 102 | local text = f:read("*all") 103 | f:close() 104 | 105 | text = "\n--[[--------------------------------------------------------------------------------\n -- Included File: "..tostring(name).."\n --------------------------------------------------------------------------------]]\n"..text 106 | 107 | -- search pattern, returns only the file name because it's a group in parens () 108 | local patternSearch = "%[%[%-%- *#INCLUDEFILE%(([^%)]*)%) *%]%]" 109 | 110 | -- interesting, using a function it should call with file name, returning text 111 | text = text:gsub(patternSearch, includeLuaFile) 112 | return text 113 | end 114 | 115 | local sectionLua = includeLuaFile("src/lua/bootstrap.lua") 116 | 117 | local all = sectionForms.."\n"..sectionLua 118 | 119 | local function saveBuildOutput(name, text) 120 | local path = getMainForm().openDialog1.InitialDir.."Build/output/"..name 121 | local f, err = io.open(path, "w") 122 | if f == nil then return nil, err end 123 | f:write(text) 124 | f:close() 125 | return true 126 | end 127 | 128 | saveBuildOutput("monohelper.lua", all) 129 | saveBuildOutput("sectionForms.lua", sectionForms) 130 | saveBuildOutput("sectionLua.lua", sectionLua) 131 | 132 | print('done') 133 | 134 | -- all is what should be written to 135 | return all, sectionForms, sectionLua 136 | -------------------------------------------------------------------------------- /Build/output/.gitignore: -------------------------------------------------------------------------------- 1 | *.lua -------------------------------------------------------------------------------- /Build/output/.placeholder: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JasonGoemaat/CheatEngineMonoHelper/9a92f8cdcb6017685febef9fde221560c6b08d8b/Build/output/.placeholder -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Jason 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /LuaForm/formMonoClass.lua: -------------------------------------------------------------------------------- 1 | ---- 2 | ---- FORM: formMonoClass 3 | ------------------------------ 4 | formMonoClass = createForm() 5 | formMonoClass.Tag = 0 6 | formMonoClass.AnchorSideLeft.Side = asrTop 7 | formMonoClass.AnchorSideTop.Side = asrTop 8 | formMonoClass.AnchorSideRight.Side = asrTop 9 | formMonoClass.AnchorSideBottom.Side = asrTop 10 | formMonoClass.Left = 2962 11 | formMonoClass.Height = 781 12 | formMonoClass.Hint = '' 13 | formMonoClass.Top = 129 14 | formMonoClass.Width = 1047 15 | formMonoClass.HelpType = htContext 16 | formMonoClass.HelpKeyword = '' 17 | formMonoClass.HelpContext = 0 18 | formMonoClass.HorzScrollBar.Page = 80 19 | formMonoClass.HorzScrollBar.Smooth = false 20 | formMonoClass.HorzScrollBar.Position = 0 21 | formMonoClass.HorzScrollBar.Range = 0 22 | formMonoClass.HorzScrollBar.Tracking = false 23 | formMonoClass.HorzScrollBar.Visible = true 24 | formMonoClass.VertScrollBar.Page = 80 25 | formMonoClass.VertScrollBar.Smooth = false 26 | formMonoClass.VertScrollBar.Position = 0 27 | formMonoClass.VertScrollBar.Range = 0 28 | formMonoClass.VertScrollBar.Tracking = false 29 | formMonoClass.VertScrollBar.Visible = true 30 | formMonoClass.Align = alNone 31 | formMonoClass.AllowDropFiles = false 32 | formMonoClass.AlphaBlend = false 33 | formMonoClass.AlphaBlendValue = 255 34 | formMonoClass.Anchors = '[akTop,akLeft]' 35 | formMonoClass.AutoScroll = false 36 | formMonoClass.AutoSize = false 37 | formMonoClass.BiDiMode = bdLeftToRight 38 | formMonoClass.BorderIcons = '[biSystemMenu,biMinimize,biMaximize]' 39 | formMonoClass.BorderStyle = bsSizeable 40 | formMonoClass.BorderWidth = 0 41 | formMonoClass.Caption = 'Class' 42 | formMonoClass.ClientHeight = 761 43 | formMonoClass.ClientWidth = 1047 44 | formMonoClass.Color = 536870912 45 | formMonoClass.Constraints.MaxWidth = 0 46 | formMonoClass.Constraints.MinHeight = 0 47 | formMonoClass.Constraints.MinWidth = 0 48 | formMonoClass.DefaultMonitor = dmActiveForm 49 | formMonoClass.DockSite = false 50 | formMonoClass.DragKind = dkDrag 51 | formMonoClass.DragMode = dmManual 52 | formMonoClass.Font.Color = 536870912 53 | formMonoClass.Font.Height = 0 54 | formMonoClass.Font.Name = 'default' 55 | formMonoClass.Font.Orientation = 0 56 | formMonoClass.Font.Pitch = fpDefault 57 | formMonoClass.Font.Quality = fqDefault 58 | formMonoClass.Font.Size = 0 59 | formMonoClass.Font.Style = '[]' 60 | formMonoClass.FormStyle = fsNormal 61 | formMonoClass.KeyPreview = false 62 | formMonoClass.ParentBiDiMode = true 63 | formMonoClass.ParentFont = false 64 | formMonoClass.PixelsPerInch = 96 65 | formMonoClass.Position = poDesigned 66 | formMonoClass.ShowInTaskBar = stDefault 67 | formMonoClass.Visible = true 68 | formMonoClass.WindowState = wsNormal 69 | formMonoClass.DoNotSaveInTable = false 70 | ------------------------------ 71 | ---- formMonoClass : Components 72 | ------------------------------ 73 | -- 74 | ---- labelClassName 75 | -------------------- 76 | labelClassName = createLabel(formMonoClass) 77 | labelClassName.Tag = 0 78 | labelClassName.AnchorSideLeft.Side = asrTop 79 | labelClassName.AnchorSideTop.Side = asrTop 80 | labelClassName.AnchorSideRight.Side = asrTop 81 | labelClassName.AnchorSideBottom.Side = asrTop 82 | labelClassName.Left = 10 83 | labelClassName.Height = 22 84 | labelClassName.Hint = '' 85 | labelClassName.Top = 10 86 | labelClassName.Width = 231 87 | labelClassName.HelpType = htContext 88 | labelClassName.HelpKeyword = '' 89 | labelClassName.HelpContext = 0 90 | labelClassName.Align = alNone 91 | labelClassName.Alignment = taLeftJustify 92 | labelClassName.Anchors = '[akTop,akLeft]' 93 | labelClassName.AutoSize = true 94 | labelClassName.BidiMode = bdLeftToRight 95 | labelClassName.BorderSpacing.Top = 10 96 | labelClassName.Caption = 'Mono Class: Attribute' 97 | labelClassName.Color = 536870911 98 | labelClassName.Constraints.MaxWidth = 0 99 | labelClassName.Constraints.MinHeight = 0 100 | labelClassName.Constraints.MinWidth = 0 101 | labelClassName.Enabled = true 102 | labelClassName.Font.Color = 536870912 103 | labelClassName.Font.Height = -19 104 | labelClassName.Font.Name = 'Inconsolata' 105 | labelClassName.Font.Orientation = 0 106 | labelClassName.Font.Pitch = fpFixed 107 | labelClassName.Font.Quality = fqDraft 108 | labelClassName.Font.Size = 14 109 | labelClassName.Font.Style = '[fsBold]' 110 | labelClassName.Layout = tlTop 111 | labelClassName.ParentBidiMode = true 112 | labelClassName.ParentColor = false 113 | labelClassName.ParentFont = false 114 | labelClassName.ParentShowHint = true 115 | labelClassName.ShowAccelChar = true 116 | labelClassName.ShowHint = false 117 | labelClassName.Transparent = true 118 | labelClassName.Visible = true 119 | labelClassName.WordWrap = false 120 | labelClassName.OptimalFill = false 121 | -------------------- 122 | -- 123 | ---- panelMain 124 | -------------------- 125 | panelMain = createPanel(formMonoClass) 126 | panelMain.Tag = 0 127 | panelMain.AnchorSideLeft.Side = asrTop 128 | panelMain.AnchorSideTop.Side = asrBottom 129 | panelMain.AnchorSideRight.Side = asrBottom 130 | panelMain.AnchorSideBottom.Side = asrBottom 131 | panelMain.Left = 10 132 | panelMain.Height = 712 133 | panelMain.Hint = '' 134 | panelMain.Top = 39 135 | panelMain.Width = 1027 136 | panelMain.HelpType = htContext 137 | panelMain.HelpKeyword = '' 138 | panelMain.HelpContext = 0 139 | panelMain.Align = alNone 140 | panelMain.Alignment = 2 141 | panelMain.Anchors = '[akTop,akLeft,akRight,akBottom]' 142 | panelMain.AutoSize = false 143 | panelMain.BorderSpacing.Top = 10 144 | panelMain.BevelInner = 0 145 | panelMain.BevelOuter = 2 146 | panelMain.BevelWidth = 1 147 | panelMain.BidiMode = bdLeftToRight 148 | panelMain.BorderWidth = 0 149 | panelMain.BorderStyle = bsNone 150 | panelMain.Caption = 'panelMain' 151 | panelMain.ClientHeight = 712 152 | panelMain.ClientWidth = 1027 153 | panelMain.Color = 536870912 154 | panelMain.Constraints.MaxWidth = 0 155 | panelMain.Constraints.MinHeight = 0 156 | panelMain.Constraints.MinWidth = 0 157 | panelMain.DockSite = false 158 | panelMain.Enabled = true 159 | panelMain.Font.Color = 536870912 160 | panelMain.Font.Height = 0 161 | panelMain.Font.Name = 'default' 162 | panelMain.Font.Orientation = 0 163 | panelMain.Font.Pitch = fpDefault 164 | panelMain.Font.Quality = fqDefault 165 | panelMain.Font.Size = 0 166 | panelMain.Font.Style = '[]' 167 | panelMain.FullRepaint = true 168 | panelMain.ParentBidiMode = true 169 | panelMain.ParentColor = true 170 | panelMain.ParentFont = true 171 | panelMain.ParentShowHint = true 172 | panelMain.ShowHint = false 173 | panelMain.TabOrder = 0 174 | panelMain.TabStop = false 175 | panelMain.UseDockManager = true 176 | panelMain.Visible = false 177 | -------------------- 178 | -- 179 | ---- CESplitter1 180 | -------------------- 181 | CESplitter1 = createSplitter(panelMain) 182 | CESplitter1.Tag = 0 183 | CESplitter1.AnchorSideLeft.Side = asrTop 184 | CESplitter1.AnchorSideTop.Side = asrTop 185 | CESplitter1.AnchorSideRight.Side = asrTop 186 | CESplitter1.AnchorSideBottom.Side = asrTop 187 | CESplitter1.Left = 482 188 | CESplitter1.Height = 710 189 | CESplitter1.Hint = '' 190 | CESplitter1.Top = 1 191 | CESplitter1.Width = 5 192 | CESplitter1.HelpType = htContext 193 | CESplitter1.HelpKeyword = '' 194 | CESplitter1.HelpContext = 0 195 | CESplitter1.Align = alLeft 196 | CESplitter1.Anchors = '[akTop,akLeft,akBottom]' 197 | CESplitter1.AutoSnap = true 198 | CESplitter1.Beveled = false 199 | CESplitter1.Color = 536870912 200 | CESplitter1.Constraints.MaxWidth = 0 201 | CESplitter1.Constraints.MinHeight = 0 202 | CESplitter1.Constraints.MinWidth = 0 203 | CESplitter1.MinSize = 30 204 | CESplitter1.ParentColor = true 205 | CESplitter1.ParentShowHint = true 206 | CESplitter1.ResizeAnchor = akLeft 207 | CESplitter1.ResizeStyle = rsUpdate 208 | CESplitter1.ShowHint = false 209 | CESplitter1.Visible = true 210 | -------------------- 211 | -- 212 | ---- gbMethods 213 | -------------------- 214 | gbMethods = createGroupBox(panelMain) 215 | gbMethods.Tag = 0 216 | gbMethods.AnchorSideLeft.Side = asrTop 217 | gbMethods.AnchorSideTop.Side = asrTop 218 | gbMethods.AnchorSideRight.Side = asrTop 219 | gbMethods.AnchorSideBottom.Side = asrTop 220 | gbMethods.Left = 487 221 | gbMethods.Height = 710 222 | gbMethods.Hint = '' 223 | gbMethods.Top = 1 224 | gbMethods.Width = 539 225 | gbMethods.HelpType = htContext 226 | gbMethods.HelpKeyword = '' 227 | gbMethods.HelpContext = 0 228 | gbMethods.Align = alClient 229 | gbMethods.Anchors = '[akTop,akLeft,akRight,akBottom]' 230 | gbMethods.AutoSize = false 231 | gbMethods.BidiMode = bdLeftToRight 232 | gbMethods.BorderSpacing.Top = 0 233 | gbMethods.Caption = 'Methods' 234 | gbMethods.ClientHeight = 710 235 | gbMethods.ClientWidth = 539 236 | gbMethods.Color = 536870912 237 | gbMethods.Constraints.MaxWidth = 0 238 | gbMethods.Constraints.MinHeight = 0 239 | gbMethods.Constraints.MinWidth = 0 240 | gbMethods.Enabled = true 241 | gbMethods.Font.Color = 536870912 242 | gbMethods.Font.Height = 0 243 | gbMethods.Font.Name = 'default' 244 | gbMethods.Font.Orientation = 0 245 | gbMethods.Font.Pitch = fpDefault 246 | gbMethods.Font.Quality = fqDefault 247 | gbMethods.Font.Size = 0 248 | gbMethods.Font.Style = '[]' 249 | gbMethods.ParentBidiMode = true 250 | gbMethods.ParentColor = true 251 | gbMethods.ParentFont = true 252 | gbMethods.ParentShowHint = true 253 | gbMethods.ShowHint = false 254 | gbMethods.TabOrder = 1 255 | gbMethods.TabStop = false 256 | gbMethods.Visible = true 257 | -------------------- 258 | -- 259 | ---- gbFields 260 | -------------------- 261 | gbFields = createGroupBox(panelMain) 262 | gbFields.Tag = 0 263 | gbFields.AnchorSideLeft.Side = asrTop 264 | gbFields.AnchorSideTop.Side = asrTop 265 | gbFields.AnchorSideRight.Side = asrTop 266 | gbFields.AnchorSideBottom.Side = asrTop 267 | gbFields.Left = 1 268 | gbFields.Height = 710 269 | gbFields.Hint = '' 270 | gbFields.Top = 1 271 | gbFields.Width = 481 272 | gbFields.HelpType = htContext 273 | gbFields.HelpKeyword = '' 274 | gbFields.HelpContext = 0 275 | gbFields.Align = alLeft 276 | gbFields.Anchors = '[akTop,akLeft,akBottom]' 277 | gbFields.AutoSize = false 278 | gbFields.BidiMode = bdLeftToRight 279 | gbFields.BorderSpacing.Top = 0 280 | gbFields.Caption = 'Fields' 281 | gbFields.ClientHeight = 710 282 | gbFields.ClientWidth = 481 283 | gbFields.Color = 536870912 284 | gbFields.Constraints.MaxWidth = 0 285 | gbFields.Constraints.MinHeight = 0 286 | gbFields.Constraints.MinWidth = 0 287 | gbFields.Enabled = true 288 | gbFields.Font.Color = 536870912 289 | gbFields.Font.Height = 0 290 | gbFields.Font.Name = 'default' 291 | gbFields.Font.Orientation = 0 292 | gbFields.Font.Pitch = fpDefault 293 | gbFields.Font.Quality = fqDefault 294 | gbFields.Font.Size = 0 295 | gbFields.Font.Style = '[]' 296 | gbFields.ParentBidiMode = true 297 | gbFields.ParentColor = true 298 | gbFields.ParentFont = true 299 | gbFields.ParentShowHint = true 300 | gbFields.ShowHint = false 301 | gbFields.TabOrder = 2 302 | gbFields.TabStop = false 303 | gbFields.Visible = true 304 | -------------------- 305 | -- 306 | ---- pageMain 307 | -------------------- 308 | pageMain = createPageControl(formMonoClass) 309 | pageMain.Tag = 0 310 | pageMain.AnchorSideLeft.Side = asrTop 311 | pageMain.AnchorSideTop.Side = asrBottom 312 | pageMain.AnchorSideRight.Side = asrBottom 313 | pageMain.AnchorSideBottom.Side = asrBottom 314 | pageMain.Left = 10 315 | pageMain.Height = 709 316 | pageMain.Hint = '' 317 | pageMain.Top = 42 318 | pageMain.Width = 1027 319 | pageMain.HelpType = htContext 320 | pageMain.HelpKeyword = '' 321 | pageMain.HelpContext = 0 322 | pageMain.TabStop = true 323 | pageMain.Align = alNone 324 | pageMain.Anchors = '[akTop,akLeft,akRight,akBottom]' 325 | pageMain.BorderSpacing.Top = 10 326 | pageMain.BiDiMode = bdLeftToRight 327 | pageMain.Constraints.MaxWidth = 0 328 | pageMain.Constraints.MinHeight = 0 329 | pageMain.Constraints.MinWidth = 0 330 | pageMain.DockSite = false 331 | pageMain.DragCursor = -12 332 | pageMain.DragKind = dkDrag 333 | pageMain.DragMode = dmManual 334 | pageMain.Enabled = true 335 | pageMain.Font.Color = 536870912 336 | pageMain.Font.Height = 0 337 | pageMain.Font.Name = 'default' 338 | pageMain.Font.Orientation = 0 339 | pageMain.Font.Pitch = fpDefault 340 | pageMain.Font.Quality = fqDefault 341 | pageMain.Font.Size = 0 342 | pageMain.Font.Style = '[]' 343 | pageMain.ParentBiDiMode = true 344 | pageMain.ParentFont = true 345 | pageMain.ParentShowHint = true 346 | pageMain.ShowHint = false 347 | pageMain.TabOrder = 1 348 | pageMain.Visible = true 349 | pageMain.Options = '[]' 350 | -------------------- 351 | -- 352 | ---- tabFields 353 | -------------------- 354 | tabFields = createTTabSheet(pageMain) 355 | tabFields.Tag = 0 356 | tabFields.AnchorSideLeft.Side = asrTop 357 | tabFields.AnchorSideTop.Side = asrTop 358 | tabFields.AnchorSideRight.Side = asrTop 359 | tabFields.AnchorSideBottom.Side = asrTop 360 | tabFields.Left = 0 361 | tabFields.Height = 684 362 | tabFields.Hint = '' 363 | tabFields.Top = 0 364 | tabFields.Width = 1019 365 | tabFields.HelpType = htContext 366 | tabFields.HelpKeyword = '' 367 | tabFields.HelpContext = 0 368 | tabFields.BorderWidth = 0 369 | tabFields.BiDiMode = bdLeftToRight 370 | tabFields.Caption = 'Fields' 371 | tabFields.ClientHeight = 684 372 | tabFields.ClientWidth = 1019 373 | tabFields.Enabled = true 374 | tabFields.Font.Color = 536870912 375 | tabFields.Font.Height = 0 376 | tabFields.Font.Name = 'default' 377 | tabFields.Font.Orientation = 0 378 | tabFields.Font.Pitch = fpDefault 379 | tabFields.Font.Quality = fqDefault 380 | tabFields.Font.Size = 0 381 | tabFields.Font.Style = '[]' 382 | tabFields.ParentBiDiMode = true 383 | tabFields.ParentFont = true 384 | tabFields.ParentShowHint = true 385 | tabFields.ShowHint = false 386 | -------------------- 387 | -- 388 | ---- listFields 389 | -------------------- 390 | listFields = createListView(tabFields) 391 | listFields.Tag = 0 392 | listFields.AnchorSideLeft.Side = asrTop 393 | listFields.AnchorSideTop.Side = asrTop 394 | listFields.AnchorSideRight.Side = asrBottom 395 | listFields.AnchorSideBottom.Side = asrBottom 396 | listFields.Left = 0 397 | listFields.Height = 684 398 | listFields.Hint = '' 399 | listFields.Top = 0 400 | listFields.Width = 1019 401 | listFields.HelpType = htContext 402 | listFields.HelpKeyword = '' 403 | listFields.HelpContext = 0 404 | listFields.Align = alNone 405 | listFields.AllocBy = 0 406 | listFields.Anchors = '[akTop,akLeft,akRight,akBottom]' 407 | listFields.AutoSort = true 408 | listFields.BorderSpacing.Top = 0 409 | listFields.BorderStyle = bsSingle 410 | listFields.BorderWidth = 0 411 | listFields.Checkboxes = false 412 | listFields.Color = 536870912 413 | listFields.ColumnClick = true 414 | listFields.Constraints.MaxWidth = 0 415 | listFields.Constraints.MinHeight = 0 416 | listFields.Constraints.MinWidth = 0 417 | listFields.Enabled = true 418 | listFields.Font.Color = 536870912 419 | listFields.Font.Height = 0 420 | listFields.Font.Name = 'default' 421 | listFields.Font.Orientation = 0 422 | listFields.Font.Pitch = fpDefault 423 | listFields.Font.Quality = fqDefault 424 | listFields.Font.Size = 0 425 | listFields.Font.Style = '[]' 426 | listFields.HideSelection = true 427 | listFields.MultiSelect = false 428 | listFields.OwnerData = true 429 | listFields.ParentColor = false 430 | listFields.ParentFont = true 431 | listFields.ParentShowHint = true 432 | listFields.ReadOnly = true 433 | listFields.RowSelect = true 434 | listFields.ScrollBars = ssBoth 435 | listFields.ShowColumnHeaders = true 436 | listFields.ShowHint = false 437 | listFields.SortColumn = -1 438 | listFields.SortType = stNone 439 | listFields.SortDirection = sdAscending 440 | listFields.TabStop = true 441 | listFields.TabOrder = 0 442 | listFields.ToolTips = true 443 | listFields.Visible = true 444 | listFields.ViewStyle = vsReport 445 | -- 446 | ---- 447 | -------------------- 448 | = createTCustomListViewEditor(listFields) 449 | .Tag = 0 450 | .AnchorSideLeft.Side = asrTop 451 | .AnchorSideTop.Side = asrTop 452 | .AnchorSideRight.Side = asrTop 453 | .AnchorSideBottom.Side = asrTop 454 | .Left = 0 455 | .Height = 23 456 | .Hint = '' 457 | .Top = 0 458 | .Width = 80 459 | .HelpType = htContext 460 | .HelpKeyword = '' 461 | .HelpContext = 0 462 | -------------------- 463 | -------------------- 464 | -- 465 | ---- tabMethods 466 | -------------------- 467 | tabMethods = createTTabSheet(pageMain) 468 | tabMethods.Tag = 0 469 | tabMethods.AnchorSideLeft.Side = asrTop 470 | tabMethods.AnchorSideTop.Side = asrTop 471 | tabMethods.AnchorSideRight.Side = asrTop 472 | tabMethods.AnchorSideBottom.Side = asrTop 473 | tabMethods.Left = 0 474 | tabMethods.Height = 684 475 | tabMethods.Hint = '' 476 | tabMethods.Top = 0 477 | tabMethods.Width = 1019 478 | tabMethods.HelpType = htContext 479 | tabMethods.HelpKeyword = '' 480 | tabMethods.HelpContext = 0 481 | tabMethods.BorderWidth = 0 482 | tabMethods.BiDiMode = bdLeftToRight 483 | tabMethods.Caption = 'Methods' 484 | tabMethods.ClientHeight = 684 485 | tabMethods.ClientWidth = 1019 486 | tabMethods.Enabled = true 487 | tabMethods.Font.Color = 536870912 488 | tabMethods.Font.Height = 0 489 | tabMethods.Font.Name = 'default' 490 | tabMethods.Font.Orientation = 0 491 | tabMethods.Font.Pitch = fpDefault 492 | tabMethods.Font.Quality = fqDefault 493 | tabMethods.Font.Size = 0 494 | tabMethods.Font.Style = '[]' 495 | tabMethods.ParentBiDiMode = true 496 | tabMethods.ParentFont = true 497 | tabMethods.ParentShowHint = true 498 | tabMethods.ShowHint = false 499 | -------------------- 500 | -- 501 | ---- listMethods 502 | -------------------- 503 | listMethods = createListView(tabMethods) 504 | listMethods.Tag = 0 505 | listMethods.AnchorSideLeft.Side = asrTop 506 | listMethods.AnchorSideTop.Side = asrTop 507 | listMethods.AnchorSideRight.Side = asrBottom 508 | listMethods.AnchorSideBottom.Side = asrBottom 509 | listMethods.Left = 0 510 | listMethods.Height = 862 511 | listMethods.Hint = '' 512 | listMethods.Top = 0 513 | listMethods.Width = 1038 514 | listMethods.HelpType = htContext 515 | listMethods.HelpKeyword = '' 516 | listMethods.HelpContext = 0 517 | listMethods.Align = alNone 518 | listMethods.AllocBy = 0 519 | listMethods.Anchors = '[akTop,akLeft,akRight,akBottom]' 520 | listMethods.AutoSort = true 521 | listMethods.BorderSpacing.Top = 0 522 | listMethods.BorderStyle = bsSingle 523 | listMethods.BorderWidth = 0 524 | listMethods.Checkboxes = false 525 | listMethods.Color = 536870912 526 | listMethods.ColumnClick = true 527 | listMethods.Constraints.MaxWidth = 0 528 | listMethods.Constraints.MinHeight = 0 529 | listMethods.Constraints.MinWidth = 0 530 | listMethods.Enabled = true 531 | listMethods.Font.Color = 536870912 532 | listMethods.Font.Height = 0 533 | listMethods.Font.Name = 'default' 534 | listMethods.Font.Orientation = 0 535 | listMethods.Font.Pitch = fpDefault 536 | listMethods.Font.Quality = fqDefault 537 | listMethods.Font.Size = 0 538 | listMethods.Font.Style = '[]' 539 | listMethods.HideSelection = true 540 | listMethods.MultiSelect = false 541 | listMethods.OwnerData = true 542 | listMethods.ParentColor = false 543 | listMethods.ParentFont = true 544 | listMethods.ParentShowHint = true 545 | -- listMethods.PopupMenu = userdata: 000000000A132FD8 546 | listMethods.ReadOnly = true 547 | listMethods.RowSelect = true 548 | listMethods.ScrollBars = ssBoth 549 | listMethods.ShowColumnHeaders = true 550 | listMethods.ShowHint = false 551 | listMethods.SortColumn = -1 552 | listMethods.SortType = stNone 553 | listMethods.SortDirection = sdAscending 554 | listMethods.TabStop = true 555 | listMethods.TabOrder = 0 556 | listMethods.ToolTips = true 557 | listMethods.Visible = true 558 | listMethods.ViewStyle = vsReport 559 | -- 560 | ---- 561 | -------------------- 562 | = createTCustomListViewEditor(listMethods) 563 | .Tag = 0 564 | .AnchorSideLeft.Side = asrTop 565 | .AnchorSideTop.Side = asrTop 566 | .AnchorSideRight.Side = asrTop 567 | .AnchorSideBottom.Side = asrTop 568 | .Left = 0 569 | .Height = 23 570 | .Hint = '' 571 | .Top = 0 572 | .Width = 80 573 | .HelpType = htContext 574 | .HelpKeyword = '' 575 | .HelpContext = 0 576 | -------------------- 577 | -------------------- 578 | -- 579 | ---- tabNotes 580 | -------------------- 581 | tabNotes = createTTabSheet(pageMain) 582 | tabNotes.Tag = 0 583 | tabNotes.AnchorSideLeft.Side = asrTop 584 | tabNotes.AnchorSideTop.Side = asrTop 585 | tabNotes.AnchorSideRight.Side = asrTop 586 | tabNotes.AnchorSideBottom.Side = asrTop 587 | tabNotes.Left = 0 588 | tabNotes.Height = 681 589 | tabNotes.Hint = '' 590 | tabNotes.Top = 0 591 | tabNotes.Width = 1019 592 | tabNotes.HelpType = htContext 593 | tabNotes.HelpKeyword = '' 594 | tabNotes.HelpContext = 0 595 | tabNotes.BorderWidth = 0 596 | tabNotes.BiDiMode = bdLeftToRight 597 | tabNotes.Caption = 'Notes' 598 | tabNotes.ClientHeight = 681 599 | tabNotes.ClientWidth = 1019 600 | tabNotes.Enabled = true 601 | tabNotes.Font.Color = 536870912 602 | tabNotes.Font.Height = 0 603 | tabNotes.Font.Name = 'default' 604 | tabNotes.Font.Orientation = 0 605 | tabNotes.Font.Pitch = fpDefault 606 | tabNotes.Font.Quality = fqDefault 607 | tabNotes.Font.Size = 0 608 | tabNotes.Font.Style = '[]' 609 | tabNotes.ParentBiDiMode = true 610 | tabNotes.ParentFont = true 611 | tabNotes.ParentShowHint = true 612 | tabNotes.ShowHint = false 613 | -------------------- 614 | -- 615 | ---- memoNotes 616 | -------------------- 617 | memoNotes = createMemo(tabNotes) 618 | memoNotes.Tag = 0 619 | memoNotes.AnchorSideLeft.Side = asrTop 620 | memoNotes.AnchorSideTop.Side = asrTop 621 | memoNotes.AnchorSideRight.Side = asrBottom 622 | memoNotes.AnchorSideBottom.Side = asrBottom 623 | memoNotes.Left = 0 624 | memoNotes.Height = 681 625 | memoNotes.Hint = '' 626 | memoNotes.Top = 0 627 | memoNotes.Width = 1019 628 | memoNotes.HelpType = htContext 629 | memoNotes.HelpKeyword = '' 630 | memoNotes.HelpContext = 0 631 | memoNotes.Align = alNone 632 | memoNotes.Alignment = taLeftJustify 633 | memoNotes.Anchors = '[akTop,akLeft,akRight,akBottom]' 634 | memoNotes.BidiMode = bdLeftToRight 635 | memoNotes.BorderSpacing.Top = 0 636 | memoNotes.BorderStyle = bsSingle 637 | memoNotes.CharCase = ecNormal 638 | memoNotes.Color = 536870912 639 | memoNotes.Constraints.MaxWidth = 0 640 | memoNotes.Constraints.MinHeight = 0 641 | memoNotes.Constraints.MinWidth = 0 642 | memoNotes.Enabled = true 643 | memoNotes.Font.Color = 536870912 644 | memoNotes.Font.Height = -16 645 | memoNotes.Font.Name = 'Inconsolata' 646 | memoNotes.Font.Orientation = 0 647 | memoNotes.Font.Pitch = fpFixed 648 | memoNotes.Font.Quality = fqDraft 649 | memoNotes.Font.Size = 12 650 | memoNotes.Font.Style = '[]' 651 | memoNotes.HideSelection = true 652 | memoNotes.MaxLength = 0 653 | memoNotes.ParentBidiMode = true 654 | memoNotes.ParentColor = false 655 | memoNotes.ParentFont = false 656 | memoNotes.ParentShowHint = true 657 | memoNotes.ReadOnly = false 658 | memoNotes.ScrollBars = ssNone 659 | memoNotes.ShowHint = false 660 | memoNotes.TabOrder = 0 661 | memoNotes.TabStop = true 662 | memoNotes.Visible = true 663 | memoNotes.WordWrap = true 664 | memoNotes.SelStart = 0 665 | -------------------- 666 | -- 667 | ---- menuMain 668 | -------------------- 669 | menuMain = createMainMenu(formMonoClass) 670 | menuMain.Tag = 0 671 | menuMain.BidiMode = bdLeftToRight 672 | menuMain.ParentBidiMode = true 673 | -------------------- 674 | -- 675 | ---- miOptions 676 | -------------------- 677 | miOptions = createMenuItem(formMonoClass.Menu) 678 | miOptions.Tag = 0 679 | miOptions.Caption = 'Options' 680 | miOptions.Checked = false 681 | miOptions.Default = false 682 | miOptions.Enabled = true 683 | miOptions.HelpContext = 0 684 | miOptions.Hint = '' 685 | miOptions.Visible = true 686 | -------------------- 687 | -- 688 | ---- miSortByClassFirst 689 | -------------------- 690 | miSortByClassFirst = createMenuItem(miOptions) 691 | miSortByClassFirst.Tag = 0 692 | miSortByClassFirst.Caption = 'Sort By Class First' 693 | miSortByClassFirst.Checked = false 694 | miSortByClassFirst.Default = false 695 | miSortByClassFirst.Enabled = true 696 | miSortByClassFirst.HelpContext = 0 697 | miSortByClassFirst.Hint = '' 698 | miSortByClassFirst.Visible = true 699 | -------------------- 700 | -- 701 | ---- miSortFieldsByOffset 702 | -------------------- 703 | miSortFieldsByOffset = createMenuItem(miOptions) 704 | miSortFieldsByOffset.Tag = 0 705 | miSortFieldsByOffset.Caption = 'Sort Fields By Offset' 706 | miSortFieldsByOffset.Checked = false 707 | miSortFieldsByOffset.Default = false 708 | miSortFieldsByOffset.Enabled = true 709 | miSortFieldsByOffset.HelpContext = 0 710 | miSortFieldsByOffset.Hint = '' 711 | miSortFieldsByOffset.Visible = true 712 | -------------------- 713 | -- 714 | ---- miShowInherited 715 | -------------------- 716 | miShowInherited = createMenuItem(miOptions) 717 | miShowInherited.Tag = 0 718 | miShowInherited.Caption = 'Include Inherited' 719 | miShowInherited.Checked = false 720 | miShowInherited.Default = false 721 | miShowInherited.Enabled = true 722 | miShowInherited.HelpContext = 0 723 | miShowInherited.Hint = '' 724 | miShowInherited.Visible = true 725 | -------------------- 726 | -- 727 | ---- miShowUsage 728 | -------------------- 729 | miShowUsage = createMenuItem(miOptions) 730 | miShowUsage.Tag = 0 731 | miShowUsage.Caption = 'Show Usage' 732 | miShowUsage.Checked = false 733 | miShowUsage.Default = false 734 | miShowUsage.Enabled = true 735 | miShowUsage.HelpContext = 0 736 | miShowUsage.Hint = '' 737 | miShowUsage.Visible = true 738 | -------------------- 739 | -- 740 | ---- miGoto 741 | -------------------- 742 | miGoto = createMenuItem(formMonoClass.Menu) 743 | miGoto.Tag = 0 744 | miGoto.Caption = 'Goto' 745 | miGoto.Checked = false 746 | miGoto.Default = false 747 | miGoto.Enabled = true 748 | miGoto.HelpContext = 0 749 | miGoto.Hint = '' 750 | miGoto.Visible = true 751 | -------------------- 752 | -- 753 | ---- miGotoAncestors 754 | -------------------- 755 | miGotoAncestors = createMenuItem(miGoto) 756 | miGotoAncestors.Tag = 0 757 | miGotoAncestors.Caption = 'Ancestors' 758 | miGotoAncestors.Checked = false 759 | miGotoAncestors.Default = false 760 | miGotoAncestors.Enabled = true 761 | miGotoAncestors.HelpContext = 0 762 | miGotoAncestors.Hint = '' 763 | miGotoAncestors.Visible = true 764 | -------------------- 765 | -- 766 | ---- miGotoDescendants 767 | -------------------- 768 | miGotoDescendants = createMenuItem(miGoto) 769 | miGotoDescendants.Tag = 0 770 | miGotoDescendants.Caption = 'Descendants' 771 | miGotoDescendants.Checked = false 772 | miGotoDescendants.Default = false 773 | miGotoDescendants.Enabled = true 774 | miGotoDescendants.HelpContext = 0 775 | miGotoDescendants.Hint = '' 776 | miGotoDescendants.Visible = true 777 | -------------------- 778 | -- 779 | ---- popupMethods 780 | -------------------- 781 | popupMethods = createTPopupMenu() 782 | popupMethods.Tag = 0 783 | popupMethods.BidiMode = bdLeftToRight 784 | popupMethods.ParentBidiMode = true 785 | popupMethods.Alignment = paLeft 786 | popupMethods.HelpContext = 0 787 | -------------------- 788 | ------------------------------ 789 | ---- END FORM: formMonoClass 790 | ---- 791 | -- 792 | -------------------------------------------------------------------------------- /Mono/Classes.md: -------------------------------------------------------------------------------- 1 | # Bootstrap 2 | 3 | To bootstrap you can use just have 'loadTextFile` function in your 4 | table file and load the others with it, here a 'bootstrap.lua' 5 | that loads other files... 6 | 7 | function loadTextFile(name, useTableFile) 8 | if useTableFile then 9 | local tableFile = findTableFile(name) 10 | if not tableFile then return nil, 'Unable to open table file "'..tostring(name)..'"' end 11 | local ss = createStringStream() 12 | ss.Position = 0 -- recommended on wiki: https://wiki.cheatengine.org/index.php?title=Lua:Class:TableFile 13 | ss.CopyFrom(tableFile.Stream, 0) 14 | local text = ss.DataString 15 | ss.destroy() 16 | return text 17 | else 18 | local path = getMainForm().saveDialog1.InitialDir..name 19 | local f, err = io.open(path, "r") 20 | -- fall back to table file if disk file error (doesn't exist) 21 | if f == nil then return loadTextFile(name, true) end 22 | local text = f:read("*all") 23 | f:close() 24 | return text 25 | end 26 | end 27 | 28 | loadstring(loadTextFile('bootstrap.lua'))() 29 | 30 | # bootstrap.lua 31 | 32 | My generic functions `loadTextFile()` and `saveTextFile()`. 33 | 34 | Calls to load other files: 35 | 36 | * config.lua 37 | * notes.lua 38 | * mono.lua 39 | 40 | # config.lua 41 | 42 | # notes.lua 43 | 44 | Has `notes` global defined... 45 | 46 | local ct = ct or {} 47 | ct.notes = notes.new('notes.lua', true) -- optional bool for use TableFile 48 | ct.notes:save() 49 | ct.notes:saveAs('notes.lua') -- optional bool false, so save to disk, does not change settings 50 | ct.notes:show() -- show window to view/edit notes 51 | -------------------------------------------------------------------------------- /Pages/_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-hacker -------------------------------------------------------------------------------- /Pages/index.md: -------------------------------------------------------------------------------- 1 | ## Welcome to GitHub Pages 2 | 3 | You can use the [editor on GitHub](https://github.com/JasonGoemaat/CheatEngineMonoHelper/edit/master/docs/index.md) to maintain and preview the content for your website in Markdown files. 4 | 5 | Whenever you commit to this repository, GitHub Pages will run [Jekyll](https://jekyllrb.com/) to rebuild the pages in your site, from the content in your Markdown files. 6 | 7 | ### Markdown 8 | 9 | Markdown is a lightweight and easy-to-use syntax for styling your writing. It includes conventions for 10 | 11 | ```markdown 12 | Syntax highlighted code block 13 | 14 | # Header 1 15 | ## Header 2 16 | ### Header 3 17 | 18 | - Bulleted 19 | - List 20 | 21 | 1. Numbered 22 | 2. List 23 | 24 | **Bold** and _Italic_ and `Code` text 25 | 26 | [Link](url) and ![Image](src) 27 | ``` 28 | 29 | For more details see [GitHub Flavored Markdown](https://guides.github.com/features/mastering-markdown/). 30 | 31 | ### Jekyll Themes 32 | 33 | Your Pages site will use the layout and styles from the Jekyll theme you have selected in your [repository settings](https://github.com/JasonGoemaat/CheatEngineMonoHelper/settings). The name of this theme is saved in the Jekyll `_config.yml` configuration file. 34 | 35 | ### Support or Contact 36 | 37 | Having trouble with Pages? Check out our [documentation](https://docs.github.com/categories/github-pages-basics/) or [contact support](https://github.com/contact) and we’ll help you sort it out. 38 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Quick Start 2 | 3 | Open the 'Dev.CT' table and copy the 'MonoHelper' entry to your own table. 4 | Activate it when attached to a game that uses mono and use the new 5 | menu option Mono->Search. 6 | 7 | ## Quick Build 8 | 9 | Source code is split into lua files in the `src/lua` directory 10 | with forms in the `src/forms` directory. 11 | 12 | 1. Open Cheat Engine (do not double click file to open) 13 | 2. Using File->Open, open the table 'Dev.CT' (so that directory is set) 14 | 3. Allow the table script to run to create the `Dev (MonoHelper)` menu 15 | 4. Click `Dev (MonoHelper)`->`Build and create table entry` 16 | 5. Copy the new table entry and paste it into your own table 17 | 18 | ## Cheat Engine Mono Helper 19 | 20 | There have been some advances with more recent versions of Cheat Engine, 21 | but this will create some forms that I think make it easier to find 22 | classes and functions in games that use Mono. 23 | 24 | The search pre-processes a single image, which defaults to 'Assembly-CSharp', 25 | so do 'Select Image' to pick a different one: 26 | 27 | ![SelectImage](Docs/SelectImage.png) 28 | 29 | Use the new 'Mono->Search' menu and start typing to show Classes, Fields, 30 | and Methods that contain your text. Double-clicking on any will open 31 | a 'Class' window. 32 | 33 | ![Search](Docs/Search.png) 34 | 35 | In the 'Class' window you can see fields and methods. Right-clicking on 36 | a method gives you a few options: 37 | 38 | 1. Hook - Create an injection script for the start of the method 39 | 2. Disassemble - jump to the method in the disassembler 40 | 3. Create Table Script - Creates table entry hooking the start of the method with a counter for number of times executed and storing the most recent object pointer and parameter values 41 | 42 | ![Class](Docs/Class.png) 43 | 44 | ## Hooking a method 45 | 46 | Double-clicking a method in the Class window will goto the method's address in 47 | the disassembly window and open up an auto-assembly window with code to hook 48 | into the start of the method. For example in Crying Suns you can search for 49 | 'IsConnectedTo' and double-click on the method in the search window to open 50 | up the NavigationSystem class it belongs to. Then double-clicking on the method 51 | there will create this script: 52 | 53 | ``` 54 | define(hook,"CryingSuns.Navigation:NavigationSystem:IsConnectedTo") 55 | define(bytes,55 48 8B EC 56) 56 | 57 | [enable] 58 | 59 | assert(hook, bytes) 60 | alloc(newmem,$1000, hook) 61 | { 62 | RCX: NavigationSystem (this) 63 | RDX: CryingSuns.Navigation.NavigationSystem _system 64 | 65 | Returns (RAX) System.Boolean 66 | } 67 | 68 | newmem: 69 | // original code 70 | push rbp 71 | mov rbp,rsp 72 | push rsi 73 | jmp hook+5 74 | 75 | hook: 76 | jmp newmem 77 | 78 | [disable] 79 | 80 | hook: 81 | db bytes 82 | 83 | dealloc(newmem) 84 | ``` 85 | 86 | You can replace the original code with this to return true so that you can jump from one system to another: 87 | 88 | ``` 89 | mov rax,1 90 | ret 91 | ``` 92 | 93 | ## Static Fields 94 | 95 | The script "Register Statics" shows how to register static fields as symbols so you can use them 96 | in your code or in table values. Here it registers `CryingSuns:GameState:currentRunState` which 97 | is an easy way to find the player state with fuel, commandos, and scrap: 98 | 99 | ``` 100 | [enable] 101 | {$lua} 102 | unregisterSymbol("CryingSuns:GameState:currentRunState") 103 | LaunchMonoDataCollector() 104 | local class = mono_findClass("CryingSuns", "GameState") 105 | for i,f in ipairs(mono_class_enumFields(class)) do 106 | if f.isStatic and f.name == "currentRunState" then 107 | registerSymbol("CryingSuns:GameState:currentRunState", mono_class_getStaticFieldAddress(mono_enumDomains()[1], class) + f.offset, true) 108 | break 109 | end 110 | end 111 | {$asm} 112 | 113 | [disable] 114 | {$lua} 115 | unregisterSymbol("CryingSuns:GameState:currentRunState") 116 | {$asm} 117 | ``` 118 | 119 | ## Building `autorun/monohelper.lua` 120 | 121 | See [`/docs/Building.md`](/docs/Building.md) 122 | 123 | 124 | ## What I'd like to do in the future: 125 | 126 | * Detect/show what are enums 127 | * Right-click class to find instances 128 | * Right-click to create code to find statics 129 | * Show method arguments and return values (method window?) 130 | * Right-click to inject at start of method 131 | * Create AA script that will inject at start, have comments showing arguments and register/stack locations 132 | * Implement notes on class, method, field 133 | * Notes window showing all notes 134 | * ... -------------------------------------------------------------------------------- /Scripts/Enum.lua: -------------------------------------------------------------------------------- 1 | local class = mono_findClass("CryingSuns.Navigation", "NavigationAlertLevel") 2 | return mono_class_enumFields(class) 3 | --[[ 4 | for i,f in ipairs(mono_class_enumFields(class)) do 5 | if f.isStatic and f.name == "currentRunState" then 6 | registerSymbol("CryingSuns:GameState:currentRunState", mono_class_getStaticFieldAddress(mono_enumDomains()[1], class) + f.offset, true) 7 | break 8 | end 9 | end 10 | unregisterSymbol("CryingSuns:GameState:currentRunState") 11 | --local address = mono_class_getStaticFieldAddress(mono_enumDomains()[1], class) 12 | --return string.format("%X", address + field.offset) 13 | --print(string.format("%X", address + field.offset)) 14 | --return field 15 | 16 | --return getStaticAddress("CryingSuns", "GameState", "currentRunState") 17 | --]] 18 | -------------------------------------------------------------------------------- /Scripts/StaticField.lua: -------------------------------------------------------------------------------- 1 | function getStaticAddress(namespace, className, field) 2 | for i,domain in ipairs(mono_enumDomains()) do 3 | for j,assembly in ipairs(mono_enumAssemblies(domain)) do 4 | local image = mono_getImageFromAssembly(assembly) 5 | for k,class in ipairs(mono_image_enumClasses(imageId)) do 6 | local nsn = mono_class_getNamespace(class) 7 | local cn = mono_class_getName(class) 8 | if class.namespace == namespace and class.name == className then 9 | for l,field in ipairs(mono_image_enumFields(class)) do 10 | end 11 | end 12 | end 13 | end 14 | end 15 | end 16 | 17 | --function getStaticAddress(namespaceName, className, fieldName) 18 | unregisterSymbol("CryingSuns:GameState:currentRunState") 19 | unregisterSymbol("CryingSuns:GameState:currentRunState") 20 | LaunchMonoDataCollector() 21 | local class = mono_findClass("CryingSuns", "GameState") 22 | for i,f in ipairs(mono_class_enumFields(class)) do 23 | if f.isStatic and f.name == "currentRunState" then 24 | registerSymbol("CryingSuns:GameState:currentRunState", mono_class_getStaticFieldAddress(mono_enumDomains()[1], class) + f.offset, true) 25 | break 26 | end 27 | end 28 | unregisterSymbol("CryingSuns:GameState:currentRunState") 29 | --local address = mono_class_getStaticFieldAddress(mono_enumDomains()[1], class) 30 | --return string.format("%X", address + field.offset) 31 | --print(string.format("%X", address + field.offset)) 32 | --return field 33 | 34 | --return getStaticAddress("CryingSuns", "GameState", "currentRunState") 35 | --]] 36 | -------------------------------------------------------------------------------- /docs/Building.md: -------------------------------------------------------------------------------- 1 | # How it works 2 | 3 | ## Developing 4 | 5 | For making changes to forms, work with the file 'CheatEngineMonoHelper.CT'. 6 | Edit the form from the Table menu, then save in `src/forms` 7 | 8 | To make the table script after editing LUA files, open 'Dev.CT'. 9 | The easiest way is use the 'Dev (MonoHelper)' menu option 10 | 'Build and create table entry' which will create a new 'MonoHelper' 11 | table entry. This can be copied and pasted into new tables for a game. 12 | 13 | Optionally you can build just as a lua script. 14 | 15 | Source files are combined together with saved form 'frm' files into 16 | `autorun/monohelper.lua` in the right order so that they can all execute 17 | together. -------------------------------------------------------------------------------- /docs/Class.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JasonGoemaat/CheatEngineMonoHelper/9a92f8cdcb6017685febef9fde221560c6b08d8b/docs/Class.png -------------------------------------------------------------------------------- /docs/CryingSuns/Battle.md: -------------------------------------------------------------------------------- 1 | [README.md](Home) 2 | 3 | ## Battle 4 | 5 | Battle is quite complicated. You have the `Battleship` for each side, and 6 | various `HexCellObject`. Plus weapons can be on the battleship or on a 7 | `HCOSquadron` (HexCellObject Squadron). Then you have `DeployableWeapon`. 8 | 9 | ### Battlefield 10 | 11 | * STATIC (10) instance (Battlefield) 12 | * 0070 - EnemyBattleship (BattleshipStateForFight) 13 | * 0078 - PlayerBattleship (BattleshipStateForFight) 14 | * 00E0 - CurrentFightState (FightState) 15 | * Update(single _time) 16 | 17 | ### BattleshipStateForFight 18 | 19 | * **0040 - BattleshipState** 20 | * 00A0 - fightLifeState (LifeState enum - high, low) 21 | 22 | ### BattleshipState 23 | 24 | > Could loop through 25 | 26 | * 0148 - Team (2=player, 3=enemy) 27 | * 00B8 - DeployableSquadrons (DeployableSquadron[]) 28 | * 00F8 - DeployableWeapons (DeployableWeapon[]) 29 | * 013C - FuelMax (float) 30 | * 0140 - StructuresLife (float) 31 | * 00A0 - battleshipLife (BattleshipLife) 32 | * 00E0 - deployableOfficers (DeployableOfficer[]) 33 | * 00D8 - officers (List) 34 | * 00B0 - squadronDocks (List) 35 | * 00A8 - squadrons (List) 36 | * 00F0 - weaponDocks (List) - change enemy state (+28) to 1 to make them undeploy, change mine to 2 to make it deployed, 0 would be undeployed and 3 would be deployign and have to change timer at +7C to 1.0), team is at +94 (2 is good) 37 | * 00E8 - weapons (List) 38 | 39 | ### BattleshipWeaponState (inherited from FleetUnitState) - mostly useless, just changes from in and out of combat 40 | 41 | * 0028 - battleship (BattleshipState) 42 | 43 | ### Dock 44 | 45 | (Battlefield.instance.PlayerBattleship(+78).BattleshipState(+40).weaponDocks(+F0)) 46 | 47 | 0028 - (BYTE) State (2 = deployed, 3 = deploying), 1 = undeploying, 0 = empty) 48 | 49 | ### DeployableWeapon 50 | 51 | * 0030 - LinkedDock 52 | * 0078 timerDuration (3.0) 53 | * 0080 timerPercent (0.25163) - setting to 1.0 completes it 54 | * 0038 - hovered (BOOL) 55 | * 00A0 - selected (BOOL) 56 | 57 | ### Weapon 58 | 59 | * 0048 - deployableWeapon 60 | * 00E0 - Owner (IFleetMember) - could be different types? 61 | * (if battleship weapon) 62 | * 0040 - BattleshipState 63 | 64 | 65 | 66 | ### IFleetMember 67 | 68 | * get_type() returns FleetMemberType (Battleship or Squadron) 69 | 70 | 71 | ### HCOSquadron 72 | 73 | * 0028 - State (4 = battle over?) 74 | * 0080 - abilities 75 | * 0020 - hco (pointer back to HCOSquadron) 76 | * 0028 - abilities 77 | * 0010 - _items 78 | * 0020 - Item[2] (actually first item) 79 | * 0018 - hco (pointer back to HCOSquadron) 80 | * 0020 - kind (1?) 81 | * 0028 - timer (should be 0) 82 | * 002C - isActive 83 | * 0018 - size (count) 84 | * 0030 - TimedAbility (same as Item[2] above - the first ability, could be used to make them more active?) 85 | * 0088 - Definition (possibly shared, don't over-use, perhaps copy? but need to alloc in GC area... change to another? 86 | * 0018 - id 87 | * 0010 - Length 88 | * 0014 - Value (this is the start of the string, i.e. '"frigate-mk2-prototype"`), could be used to seaparate drones, frigates, etc. 89 | * 0030 - skirmishWeapon 90 | * 0048 - rangedWeapon 91 | * 0050 - life (i.e. 110.0) 92 | * 0090 - SkirmishWeapon 93 | * 0048 - deployableWeapon (NULL for frigate weapon) 94 | * 00E0 - Owner (poitner back to HCOSquadron) 95 | * 0108 - DamageFactor (1.0, maybe update?) 96 | * 010C - baseAimingDuration (0.0) 97 | * 0110 - baseFireBySeconds (1.0) 98 | * 0114 - BaseTimeLeftBeforeWeaponCooled (1.0) 99 | * 0118 - BaseTimeLeftBeforeAimOk (0.0) 100 | * 011C - ReadyAtTime (2036.8?) 101 | * 0124 - Range (1.0) 102 | * 00A8 - HcoTypeKey (+10 = Length (21), +14 is start of string 'frigate-mk2-prototype')) 103 | * 0143 - canBeAttacked (BYTE (boolean) = 1, possibly set to 0?) 104 | * 0148 - LifeMax (110.0) 105 | * 014C - RealLifeMax (0.0?) 106 | * 0160 - selected (BYTE or bool 0) 107 | * 0164 - life (110.0) 108 | * 01D0 - Team (2 = player, 3 = enemy) 109 | 110 | -------------------------------------------------------------------------------- /docs/CryingSuns/Cheats/Weapons-QuickDeploy.md: -------------------------------------------------------------------------------- 1 | First, we can find `Battlefield.instance` or hook `Update(time)` or hook `Pause()` 2 | Battlefield has: 3 | 4 | * 0070 - EnemyBattleship (BattleshipStateForFight) 5 | * 0078 - PlayerBattleship (BattleshipStateForFight) 6 | 7 | BattleshipStateForFight can hook `Update(time)`, `TakeDamages(amount, source)` 8 | 9 | * get_Team() - +40 (BattleshipState) then +148 10 | 11 | * 0040 - BattleshipState 12 | 13 | BattleshipState has cool stuff, like weapons (+E8)... 14 | 15 | * 00E8 - weapons (List) 16 | * 00F0 - weaponDocks (List) 17 | 18 | ### WeaponDocks 19 | 20 | So we have Battle 21 | -------------------------------------------------------------------------------- /docs/CryingSuns/DeployableWeapon.md: -------------------------------------------------------------------------------- 1 | # DeployableWeapon 2 | 3 | * 0030 - LinkedDock (check team at +94) 4 | * 0038 - hovered (BYTE) 0 for not, 1 for mouse is over it 5 | * 0080 - Weapon 6 | * 0088 - WeaponData 7 | * 0098 - Owner (??) 8 | * 00A0 - selected (BYTE) 0 for not, 1 for is selected 9 | 10 | # Weapon 11 | 12 | * 0048 - deployableWeapon (DeployableWeapon) (can check LinedDock at +30, then +94 for team) 13 | * 0114 - BaseTimeLeftBeforeWeaponCooled - set to 0.0 for availability 14 | -------------------------------------------------------------------------------- /docs/CryingSuns/Dock.md: -------------------------------------------------------------------------------- 1 | # Dock 2 | 3 | `Dock:Update` is handy to hook... 4 | 5 | ## Structure 6 | 7 | * 0028: state (0=empty, 1=removing, 2=deployed, 3=adding) 8 | * 007C: timerPercent (set to 1.0 when deploying to make deployed) 9 | * 0084: deployableType (0=squadron, 1=weapon, 2=officer) 10 | * 0088: dockType (0=squadron, 1=weapon, 3-5= support squadron, weapon, hull) 11 | * 0094: team (2=player, 3=enemy) 12 | 13 | ## Cheats 14 | 15 | Good one is to hook Dock:Update and make things deploy quickly. 16 | 17 | * Check that team (+94) is 2 for player 18 | * Check that state (+28) is 3 for adding 19 | * Set timerPercent (+7C) to 1.0, it will then be fully deployed 20 | 21 | Another one is to get at the battleship weapons and make them ready to use... 22 | 23 | * Check that team (+94) is 2 for player 24 | * Check that state (+28) is 2 for deployed 25 | * Check that deployableType (+84) is 1 for weapon 26 | * Load currentDeployable (+60) which should be `DeployableWeapon` 27 | * 28 | 29 | 30 | ## Enums 31 | 32 | DeployableType: 33 | 34 | * 0: squadron 35 | * 1: weapon 36 | * 2: officer 37 | 38 | DockType: 39 | 40 | * 0: squadron 41 | * 1: weapon 42 | * 3: supportSquadron 43 | * 4: supportWeapon 44 | * 5: supportHull 45 | * officer 46 | -------------------------------------------------------------------------------- /docs/CryingSuns/Enums.md: -------------------------------------------------------------------------------- 1 | DeployableType: 2 | 3 | * 0: squadron 4 | * 1: weapon 5 | * 2: officer 6 | 7 | DockType: 8 | 9 | * 0: squadron 10 | * 1: weapon 11 | * 3: supportSquadron 12 | * 4: supportWeapon 13 | * 5: supportHull 14 | * officer 15 | -------------------------------------------------------------------------------- /docs/CryingSuns/Events.md: -------------------------------------------------------------------------------- 1 | # Events 2 | 3 | I found `EventsConfiguration` which seems promising. There's a ship aux system 4 | that turns 50/50 events into 100% positive so there has to be something somewhere. 5 | 6 | ### EventsConfiguration 7 | 8 | * 28 - anomalyNegative (single) 9 | * 24 - anomalyPolice (single) 10 | * 2c - anomalyPositive (single) 11 | * 20 - anomalyforcedFightPercent (single) 12 | * **58 - riskySuccessChance** 13 | 14 | ### BattleshipState 15 | 16 | This has `HasAuxiliarySystemType(type)` which might be hacked for player (Team at +148 is '2') 17 | to always return true? 18 | 19 | Types: 20 | 21 | * BattleshipWeaponImmunity 22 | * Boomer 23 | * CineticCharger 24 | * PirateTransponder 25 | * PreIgniter 26 | * QuarksCoolingSystem 27 | * QuickUndeployer 28 | * RepairDrones 29 | * **RiskyWinner - probably the 100% one** 30 | * ScrapRecovery 31 | * ScrapperDeployer 32 | * StealthEngine 33 | * TacticalLogs 34 | -------------------------------------------------------------------------------- /docs/CryingSuns/Expedition.md: -------------------------------------------------------------------------------- 1 | [README.md](Home) 2 | 3 | ## Expedition 4 | 5 | An Expedition is when you go to the planet. An officer and group of commandos goes 6 | from step to step and encounters various 'ExpedictionSituation's. Some of these have 7 | officer skills that can be used, it would be nice to be able to resolve each 8 | situation successfully. 9 | 10 | ### ExpeditionSituation 11 | 12 | I tried just returning 1 from `get_HasSkillToResolve()`, but that didn't work. I think 13 | you'd need to override the other three, which is a problem because those skills need 14 | to be returned and I don't know how without having them. It's possible that 15 | they could be fetched from the result of get_ExpectedOfficerSkills()? Oh, I see.... 16 | `OfficerSkill` is just an enum, so maybe it's possible... 17 | 18 | * bool get_HasSkillToResolve() - tried this, but doesn't work 19 | * bool HasAtLeastOneSkillAndGetIt(OfficerState, OfficerSkill& _skillTested) 20 | * bool HasSkillAtStepAndGetIt(OfficerState, int _stepIndex, OfficerSkill& _skillTested) 21 | * bool HasSkillAtStepAndGetIt(OfficerState, int _stepIndex, int _skillByStep, OfficerSkill& _skillTested) 22 | * void RememberCommandoStates (ExpeditionProgress) 23 | * List get_ExpectedOfficerSkills 24 | 25 | ### OfficerSkill 26 | 27 | * Demolition 28 | * Hack 29 | * Persuasion 30 | * Piloting 31 | * SharpSenses 32 | * Fight 33 | * Engineering 34 | * Erudition 35 | * Discretion 36 | 37 | ### ExpeditionSituationStepResult 38 | 39 | * 0010 - ExpeditionSituation (ExpeditionSituation) 40 | * 0018 - OutcomeResult (OutcomeResult) 41 | * 0030 - TestedSkill (System.Nullable[OfficerSkill]) 42 | * 0038 - SituationResultType (SituationResultType) 43 | * 003C - InjuriesAmount 44 | * 0040 - WasOvercome (Boolean) 45 | * 0044 - InjuredCommandoAmount 46 | * 0048 - DiedCommandoAmount 47 | * 004C - OfficerIsOutOfCombat (Boolean) 48 | * 0050 - OfficerInjuriesAmount 49 | * 0054 - HasResolvedSituation (Boolean) 50 | 51 | * 0048 - DiedCommandoAmount 52 | 53 | 54 | ### SituationResultType 55 | 56 | * Failure (0?) 57 | * Success (1?) 58 | * Neutral (2?) 59 | 60 | ### ExpeditionProgress 61 | 62 | 63 | ## Enum: ExpeditionSituationType 64 | 65 | * Artefact 66 | * Predicament 67 | * Treasure 68 | * Final 69 | * Danger 70 | -------------------------------------------------------------------------------- /docs/CryingSuns/README.md: -------------------------------------------------------------------------------- 1 | ## Crying Suns 2 | 3 | Crying Suns was on sale on Steam and is a recent mono game so I thought 4 | it would be a good game to try out the mono functions on. 5 | 6 | Areas: 7 | 8 | * [Battle](Battle.md) 9 | * [Expedition](Expedition.md) 10 | * [Statics](Statics.md) 11 | -------------------------------------------------------------------------------- /docs/CryingSuns/Statics.md: -------------------------------------------------------------------------------- 1 | [README.md](Home) 2 | 3 | ## Statics 4 | 5 | Battlefield.instance -------------------------------------------------------------------------------- /docs/General.md: -------------------------------------------------------------------------------- 1 | 2 | ## k__BackingField 3 | 4 | For example Crying Suns has BattleshipSTateForFight with at +58: 5 | 6 | System.Collections.Generic.IList`1[CryingSuns.Fight.HcoBattleshipPart] k__BackingField 7 | 8 | When you see `k__BackingField` it means it is a property with the name `BattleshipParts`. Looking at the 9 | methods we see `get_BattleshipParts`: 10 | 11 | System.Collections.Generic.IList () 12 | 13 | -------------------------------------------------------------------------------- /docs/Index.html: -------------------------------------------------------------------------------- 1 |

Cheat Engine Mono Helper

-------------------------------------------------------------------------------- /docs/MonoStructures.md: -------------------------------------------------------------------------------- 1 | # Mono Structures 2 | 3 | Several structures are seen over and over. CE doesn't seem to get their structures quite right. 4 | 5 | ## Array 6 | 7 | An example is in Crying Suns `BattleshipState` at offset 00F8 we have: 8 | 9 | CryingSuns.Fight.DeployableWeapon[] k__BackingField 10 | 11 | The structure doesn't match what CE shows: 12 | 13 | * 0018 - Count 14 | * 0020+ - items (8 bytes each) 15 | 16 | 17 | ## List 18 | 19 | A generic list, for example Crying Suns has BattleshipState with at +E8: 20 | 21 | System.Collections.Generic.IList`1[CryingSuns.PlayerStatus.BattleshipWeaponState] weapons 22 | 23 | The list itself: 24 | 25 | * 0010 - _items - Array with possibly some empty elements 26 | * 0018 - _size - current number of items 27 | * 001C - _version - possibly updated when underlying array is changed due to size change 28 | 29 | _items is then a simple array with count (including empty slots) at +18 and items starting at +_20 30 | -------------------------------------------------------------------------------- /docs/Releases/index.md: -------------------------------------------------------------------------------- 1 | Save file as 'monohelper.lua' in the 'autorun' directory where CE is installed 2 | 3 | ## In progress 4 | 5 | * delete forms and unselect image on reload (mostly nicety for making changes during development of this) 6 | * fixed parameter detection of 'single' and 'double' for XMM registers 7 | * added filtering for field type in search window (Normal/Static/Const) 8 | * uncheck 'HideSelection' on forms so you can see the selected items in listViews after they've lost focus 9 | 10 | ## [Current Release 1.1.0](monohelper-1.1.0.lua) 11 | 12 | Here is is as a table script in a cheat table: [MonoHelper.CT](Releases/MonoHelper.CT) 13 | 14 | * Reconfigured build system (major change) 15 | * Re-captioned windows 'Class' and 'Search' to 'Mono Class' and 'Mono Search' 16 | * Fixed search window appearing off screen by centering it on screen when displayed 17 | * Automatically select 'Assembly-CSharp' image if present (can always change from search form menu) 18 | * First release created with new build system 19 | 20 | ## [Release 1.0.0](monohelper-1.1.0.lua) 21 | 22 | * Original release should work fine, for posterity -------------------------------------------------------------------------------- /docs/Search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JasonGoemaat/CheatEngineMonoHelper/9a92f8cdcb6017685febef9fde221560c6b08d8b/docs/Search.png -------------------------------------------------------------------------------- /docs/SelectImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JasonGoemaat/CheatEngineMonoHelper/9a92f8cdcb6017685febef9fde221560c6b08d8b/docs/SelectImage.png -------------------------------------------------------------------------------- /docs/TODO.md: -------------------------------------------------------------------------------- 1 | Looking at Raft, I found myself doing the same thing a lot during initial investigation: 2 | 3 | * Hook Function 4 | * Add global alloc for temp variable 5 | * Add code to push rax, mov global ref to rax, store rcx (and other parms) at rax along with counter, pop rax 6 | * Save script as table entry 7 | * Activate table entry 8 | * Add variables for RCX, Counter, and any params as children to script 9 | 10 | For investigations like this it would be SUPER handy to have a one or two click method for doing this. 11 | 12 | ## TODO 13 | 14 | Recent (2024-02-25): 15 | 16 | * Default or separate option so 'hook' creates a standard table entry (and opens?) 17 | * Toggle for enabling 14 byte far jmp, default to 5 (current uses 14) 18 | * Toggle to use 'readmem', default on... Mostly for 5 byte jumps where 'sub rsp,xxx' won't overwrite the xxx, helpful for not having to update scripts on game updates 19 | * Put AA commands in comments for classes in parameter list, calling class, and maybe fields? 20 | * `GETMONOSTRUCT(Inventory,:Inventory)` should work 21 | * Test in CE LUA window: `return monoAA_GETMONOSTRUCT("Inventory", ":Inventory", false)` 22 | * Check that parent fields are included, or add parent classes separately and note in comments? 23 | * Better structure dissect for arrays, lists, dictionaries? 24 | * Sucks that the indexed items are often wrong, or there are multiple 25 | * Keys and Values are dumb 26 | * Maybe LUA form to parse and display dictionary contents? Table with Key, Value? 27 | * Look into calling mono method? Creating mono class? 28 | 29 | Older: 30 | 31 | * TODO: popup on method -> generate 'find pointer' script 32 | * Actually adds a new memrec to CE with a script doing my standard stuff to find the pointer 33 | * Method 'PlayerController:Update' will globalalloc 'pPlayerController_Update' 34 | * push rax 35 | * mov rax,pPlayerController_Update 36 | * inc dword ptr [rax] // counter 37 | * mov [rax+8], rcx // parameter 1 38 | * mov [rax+10], rdx // parameter 2 39 | * movss [rax+18], xmm3 // parameter 3 40 | * pop rax 41 | * Table entries under script as a group header for counter and parameters 42 | 43 | 44 | * TODO: Check on why search is funky: Evil Bank Manager 45 | * CountryRelationship has `PoliticSkill[] politicSkills` property, `System.Single k__BackingField`, and `Country k__BackingField` 46 | * Accessible from CountryInfoCard, CountryRelationshipCard, RegionPoliticSkillWindow, DeleteLicenseWindow, DeleteLicenseNotificationWindow, ConversationWindow 47 | * CountryInfoCard has various click methods (Invest, Property, Relationshop) and updates relationship value 48 | * CountryRelationshipCard has OnSkillClick(), OpenConversationWindow() 49 | * RegionPoliticSkillWindow has DeactivateTab, Apply, OnWindowShowing, OnWindowHidint, SetActiveTab 50 | * DeleteLicenseWindow has ONWindowShowing() and ShowNotification() 51 | * DeleteLicenseNotificationWindow has Accept() and Decline() as well as OnWindowShowing() 52 | * ConversationWindow has 53 | 54 | * TODO: Sample of search being buggy: 55 | * EvilBankManager - looking for CountryRelationshipCard, start typing and after the last 'C' in `CountryRelationshipC` it only shows CountryRelationship. With a 'T' instead it works for others, weird... `RelationshipC` works fine 56 | * Searching for ConversationWindow buggy, 'Window' finds it 57 | * Ok, weird, seems to depend on pasting or not, or fixes itself 58 | * Might need to 'prepare' some helper functions as it seems to be spotty, next time run a single function in the LUA console to output the exact terms, etc being used for the search and allow checking of what is called in the search... 59 | * Pretty simple, it does `lower == nil or class.lowerName:find(lower, 1, true) ~= nil` 60 | * Check: return mono.formSearch.found.classes[1].lowerName 61 | * Look for match in `for i,class in ipairs(mono.formSearch.image.classes) do print(class.lowerName) end` 62 | 63 | * TODO: When in 'Show usages' mode of class window, double-clicking on field should open that class 64 | * Currently it does the static field logic, showing 0 as the address in the console 65 | * TODO: Figure out way to hook the right overloaded method 66 | * Samples in EvilBankManager: 67 | * UserBank:GetCapital has two versions, one takes a Single parameter, the other has none 68 | * UserBank:GetResourceCount takes either int or Resource/ResourceType 69 | * Sample: `System.Single GetResourceCount(int resourceId)` 70 | * monoAA_GetOverloadedFunction(SYMBOL_NAME, "UserBank:GetResourceCount", System.Single, int) 71 | * Could be pretty short AA script looking for parameter signatures, or lua script 72 | * TODO: DblClick on static field generates script with {$lua} code to find it and define? 73 | * Alternate: LUA code to get base address for static class and define? - doesn't work with mono generate struct due to conflicting offsets, maybe CLASS_FIELD as define? 74 | * TODO: Alternate for having memrecs for script and pointers - more difficult but cooler 75 | * Able to hook methods from window, list in separate window and enable/disable/remove 76 | * LUA could keep track of a 'globals' memory region and where the pointers for each method are 77 | * LUA could show count, pointers, etc in it's own window 78 | * LUA could open structure dissect and generate structure using it's information for names 79 | * TODO: generate script inside mono method, not at start - three options: 80 | 1. Simple inject - use bytes being replaced and exact address 81 | 2. AOB - use AOB search to find point in code, bytes must remain the same 82 | 3. Advanced AOB - allow hooking code with offset of field 83 | * One option would be to identify the field and change the value based on mono dissect 84 | * Example: 'movss [rax+5c],xmm0 // set current health' 85 | * 5c is offset of 'currentHealth' field, look for that offset in the type the method belongs on 86 | * LUA code in top of script will alter the AOB to search for 87 | * LUA code in top of script will search for AOB only inside method boundaries (or start of method + x + 100) for instance where x is offset we're hooking, or will stop when it finds another 'push rbp; mov rbp, rsp', or when it finds a ret (though there could be more than one of these) 88 | * use readmem/writemem to replace with exact code, or use AOB found in step 1 that we're replacing (how? separate enable/disable sections) 89 | 90 | 91 | ## Ideas: 92 | 93 | * Right-click pop-up to add to list (at the least could add as a menu option) 94 | * One option is to create the entire table entry with script and supporting variables, default name "TEST: Class.Method" 95 | * Another option would be to have method 'hooked', using popup or menu if popup not working right 96 | 97 | * We could keep list of methods separate and show like check or '(hooked)' in list 98 | * Could have separate page or tab displaying these, and enable/disable individually 99 | * Could click on one to open in structure dissector, possibly create and select structure automatically 100 | * Show list of hooked (enabled or disabled) methods in different tab 101 | * Show Counter of executions and last parameter values 102 | * Click to open structure dissector with RCX value (maybe other params too) 103 | * Should be able to select text of parameter values to copy/paste 104 | * Maybe popup ('Copy RCX: 78919199FC' for example) 105 | * 106 | 107 | ## Other Todo 108 | 109 | * AA vars in generated scripts are always shown in comments as int registers, it would be nice for singles/doubles to have the XMM register specified instead 110 | * Maybe for Int32 have only EDX listed for example, not RDX? 111 | * Pretty sure Registers are RCX, RDX, R8?, R9?, and corresponding XMM0-XMM3 112 | * i.e. RCX will be instance pointer, first single param will be RDX or XMM1, etc. 113 | * Detect when mono not running and start instead of print error to console 114 | * could be when activate/reactivate we have to re-select assembly 115 | * auto-reselect assembly in this case? 116 | * probably possible to check and trap error when switching to window or performing operations and auto-reselect assembly by name 117 | * Auto-select 'Assembly-CSharp' if present? 118 | * Figure out static variables and how to access them 119 | * Finish 'Notes' you can enter for classes, methods, fields 120 | * Finish 'Notes' page listing all notes, ability to go to class, method, field 121 | 122 | ## General MONO 123 | 124 | * Nice to have single command to launch mono if it isn't already there in AA, no {$lua} required 125 | * Nice to have static variables accessible as symbols (i.e. `mov rax, Class.StaticVariableName` then `mov eax, [rax]`) 126 | * Way to reference certain overloaded methods 127 | * It should AT LEAST be possible to do it with a little LUA code in the script, instead of using AOB which is HORRIBLY slow (~10 seconds on my laptop for Raft) 128 | * Maybe something like 'ClassName.MethodName`2' or something 129 | * Check out monoform_AddStaticClassField 130 | * and monoAA_GETMONOSTATICFIELDDATA(assemblyname, namespace, classname, fieldname, symclassname, false) 131 | 132 | local addrs = getAddressList() 133 | local classname=mono_class_getName(class) 134 | local namespace=mono_class_getNamespace(class) 135 | local assemblyname=mono_image_get_name(image) 136 | 137 | * hmmm... "if monopipe.il2cpp then return end" when 138 | * try registerAutoAssemblerCommand("GETMONOSTRUCT", monoAA_GETMONOSTRUCT) 139 | * Interesting, seems like it just skips the . in the name 140 | name=name:match "^%s*(.-)%s*$" 141 | classname=classname:match "^%s*(.-)%s*$" 142 | namespace=namespace:match "^%s*(.-)%s*$" 143 | 144 | local class=mono_findClass(namespace, classname) 145 | if (class==nil) or (class==0) then 146 | return nil,translate("The class ")..namespace..":"..classname..translate(" could not be found") 147 | end 148 | * can just pass class to mono_class_getStaticFieldAddress and itwill use domain = 0, which seems to work 149 | 150 | ## Wishlist 151 | 152 | * Enum values - They are *usually* in order, starting at 0, but not always (i.e. CryingSuns) 153 | -------------------------------------------------------------------------------- /docs/Tutorials/7DaysToDie/dropOneILSpy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JasonGoemaat/CheatEngineMonoHelper/9a92f8cdcb6017685febef9fde221560c6b08d8b/docs/Tutorials/7DaysToDie/dropOneILSpy.png -------------------------------------------------------------------------------- /docs/Tutorials/7DaysToDie/index.md: -------------------------------------------------------------------------------- 1 | # 7 Days to Die Turotial 2 | 3 | ## Setup 4 | 5 | ### ILSpy is awesome 6 | 7 | [Download ILSpy](https://github.com/icsharpcode/ILSpy/releases), it's pretty 8 | awesome. don't know about the vsi, I downloaded the binaries zip, unzipped 9 | it into `O:\apps\ILSpy`, opened that directory, right-clicked on the `ILSpy.exe` 10 | and created a shortcut, then added that shortcut to my start menu. 11 | 12 | You can now open ILSpy either with the exe, the shortcut, or now it's on 13 | your start page. I removed the assemblies that were lisetd, and loaded 14 | `Assembly-CSharp.dll` from where it was installed with my game: 15 | 16 | O:\Games\Steam\steamapps\common\7 Days To Die\7DaysToDie_Data\Managed 17 | 18 | ILSpy decompiles the source code. It really helps understanding the assembly 19 | code to look at the method sources. 20 | 21 | ### Make sure MonoHelper is loaded 22 | 23 | You need [Release](../../Releases) 1.2.0 at least for some of the functionality 24 | I'll be using. You can add the LUA script to your CE autorun directory, make 25 | it your table script, or add it in an AA script in a `{$lua}` section. The 26 | script does NOT need to be included with your table, the scripts it generates 27 | work without it. This will add the 'Search' action under the 'Mono' menu that 28 | appears when you attach to a game that uses Mono. 29 | 30 | ## Investigating 31 | 32 | I'm starting with knowledge of what I want to hook. Doing investigation before 33 | I found a method `XUiC_ItemStack.HandleDropOne()` which is sounds pretty 34 | interesting. What I want to do is make it increase the number of items 35 | we have. I'd like to make it drop a full stack instead of just one. 36 | 37 | Search for 'dropone' in ILSpy and you'll see this method: 38 | 39 | ```c# 40 | // XUiC_ItemStack 41 | using Audio; 42 | 43 | protected virtual void HandleDropOne() 44 | { 45 | ItemStack currentStack = base.xui.dragAndDrop.CurrentStack; 46 | if (!currentStack.IsEmpty()) 47 | { 48 | int num = 1; 49 | if (this.itemStack.IsEmpty()) 50 | { 51 | ItemStack itemStack = currentStack.Clone(); 52 | itemStack.count = num; 53 | currentStack.count -= num; 54 | base.xui.dragAndDrop.CurrentStack = currentStack; 55 | ItemStack = itemStack; 56 | if (placeSound != null) 57 | { 58 | Manager.PlayXUiSound(placeSound, 0.75f); 59 | } 60 | } 61 | else if (currentStack.itemValue.type == this.itemStack.itemValue.type) 62 | { 63 | int value = currentStack.itemValue.ItemClass.Stacknumber.Value; 64 | if (this.itemStack.count + 1 <= value) 65 | { 66 | ItemStack itemStack2 = this.itemStack.Clone(); 67 | ``` 68 | 69 | This looks like it would be a nice place to hook. It would be nice to have it 70 | drop a full stack and not take any from the stack we have (or make it full 71 | as well), but that would involve getting deeper into the code than I'd like. 72 | 73 | What's super easy is injecting at the start of a method and just using 74 | the point to the instance that is in rcx or using or altering one of the 75 | parameters to perform a cheat. 76 | 77 | So open up CE, attach to the game, make sure you've run the MonoHelper script, 78 | and do `Mono->Search`. Enter 'dropone' in the search box and double-click 79 | on the method we want (the one on XUiC_ItemStack, not XUiC_RequiredItemStack). 80 | This will open the class window. 81 | 82 | Note that at Offset A8 is a field naed 'itemStack of type 'ItemStack'. From 83 | looking at the C# code above, this appears to be the target of the drop, not 84 | the stack that is being held, which is 'currentStack' in the code above. 85 | 86 | 'currentStack' is found by looking at `base.xui.dragAndDrop.CurrentStack` 87 | Click on 'Options -> Include Inherited' to show inherited fields and methods. 88 | Near the top is a field named `k__BackingField` at Offset 90. This weird 89 | naming common for some fields, I'm not 100% sure why, but it seems to be 90 | renamed like that if it's a property wit hgeters and setters, but not all the 91 | time. 92 | 93 | Now click on the 'Methods' tab and find `HandleDropOne`. This is the method 94 | we want to hook. Right-click on it and select the third option, 95 | `Create Table Script`. This should create a new table entry with the 96 | description `XUiC_ItemStack:HandleDropOne`. If you activate it you will see 97 | an entry for `pXUiC_ItemStack_HandleDropOne` which we be a pointer to the 98 | last `XUiC_ItemStack` the method was called on. There's also a `Counter` 99 | memory record showing you how many times it's been called. 100 | 101 | Open the script to see the source: 102 | 103 | ```asm 104 | define(hook,"XUiC_ItemStack:HandleDropOne") 105 | define(bytes,55 48 8B EC 48 83 EC 70) 106 | 107 | [enable] 108 | 109 | assert(hook, bytes) 110 | alloc(newmem,$1000, hook) 111 | label(pXUiC_ItemStack_HandleDropOne) 112 | 113 | { 114 | RCX: XUiC_ItemStack (this) 115 | 116 | Returns (RAX) System.Void 117 | } 118 | 119 | newmem: 120 | // increment counter, store instance and parameters (could be off for static method?) 121 | push rax 122 | mov [pXUiC_ItemStack_HandleDropOne], rcx 123 | inc dword ptr [pXUiC_ItemStack_HandleDropOne+8] 124 | pop rax 125 | 126 | // original code 127 | push rbp 128 | mov rbp,rsp 129 | sub rsp,70 130 | jmp hook+8 131 | 132 | align 10 133 | pXUiC_ItemStack_HandleDropOne: 134 | dq 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 135 | 136 | hook: 137 | jmp newmem 138 | 139 | registersymbol(pXUiC_ItemStack_HandleDropOne) 140 | 141 | [disable] 142 | 143 | hook: 144 | db bytes 145 | 146 | unregistersymbol(pXUiC_ItemStack_HandleDropOne) 147 | 148 | dealloc(newmem) 149 | ``` 150 | 151 | Let's do some analysis of the assemblyin the actual method. Right-click on 152 | the method in the Class window again and select 'Disassemble'. If you've 153 | activated the table entry, you'll see the jmp to the new ode and then the rest 154 | of the method: 155 | 156 | ``` 157 | XUiC_ItemStack:HandleDropOne - E9 5B05ECE5 - jmp 1F39B5C0000 158 | XUiC_ItemStack:HandleDropOne+5- 83 EC 70 - sub esp,70 159 | XUiC_ItemStack:HandleDropOne+8- 48 89 75 D8 - mov [rbp-28],rsi 160 | XUiC_ItemStack:HandleDropOne+c- 48 89 7D E0 - mov [rbp-20],rdi 161 | XUiC_ItemStack:HandleDropOne+10- 4C 89 65 E8 - mov [rbp-18],r12 162 | XUiC_ItemStack:HandleDropOne+14- 4C 89 6D F0 - mov [rbp-10],r13 163 | XUiC_ItemStack:HandleDropOne+18- 4C 89 7D F8 - mov [rbp-08],r15 164 | XUiC_ItemStack:HandleDropOne+1c- 48 8B F1 - mov rsi,rcx 165 | XUiC_ItemStack:HandleDropOne+1f- 48 8B 86 90000000 - mov rax,[rsi+00000090] 166 | XUiC_ItemStack:HandleDropOne+26- 48 8B C8 - mov rcx,rax 167 | XUiC_ItemStack:HandleDropOne+29- 83 39 00 - cmp dword ptr [rcx],00 168 | XUiC_ItemStack:HandleDropOne+2c- 48 8B 80 50010000 - mov rax,[rax+00000150] 169 | XUiC_ItemStack:HandleDropOne+33- 48 8B C8 - mov rcx,rax 170 | XUiC_ItemStack:HandleDropOne+36- 83 39 00 - cmp dword ptr [rcx],00 171 | XUiC_ItemStack:HandleDropOne+39- 4C 8B A0 B0000000 - mov r12,[rax+000000B0] 172 | XUiC_ItemStack:HandleDropOne+40- 49 8B C4 - mov rax,r12 173 | XUiC_ItemStack:HandleDropOne+43- 48 8B C8 - mov rcx,rax 174 | XUiC_ItemStack:HandleDropOne+46- 83 38 00 - cmp dword ptr [rax],00 175 | XUiC_ItemStack:HandleDropOne+49- 48 8D 64 24 00 - lea rsp,[rsp+00] 176 | XUiC_ItemStack:HandleDropOne+4e- 49 BB 4B9FA2B5F3010000 - mov r11,000001F3B5A29F4B 177 | XUiC_ItemStack:HandleDropOne+58- 41 FF D3 - call r11 178 | ``` 179 | 180 | If you know the windows x64 calling conventions (or read the comment near the 181 | top of the generated script), you know that `rcx` starts off as the instance 182 | of `XUiC_ItemStack` the method is being executed on. We have this section 183 | of code pretty early: 184 | 185 | ``` 186 | mov rsi,rcx 187 | mov rax,[rsi+00000090] 188 | mov rcx,rax 189 | cmp dword ptr [rcx],00 190 | mov rax,[rax+00000150] 191 | mov rcx,rax 192 | cmp dword ptr [rcx],00 193 | mov r12,[rax+000000B0] 194 | ``` 195 | 196 | It moves the XUiC_ItemStack pointer to rsi and loads rax and cx with what's 197 | at offset 90, which we know from looking at the Class window is 198 | `k__BackingField`. It then loads rax with `[rax+150]`. If we look 199 | at the type 'XUi', offset 150 is type `XUiC_DragAndDropWindow` and is 200 | named `k__BackingField`. This is `base.xui.dragAndDrop` 201 | in the source code. The next th ing it does is load r12 with what's 202 | at `[rax+b0]`. I bet you can figure out what that does just looking at the 203 | source: 204 | 205 | ```c# 206 | ItemStack currentStack = base.xui.dragAndDrop.CurrentStack; 207 | ``` 208 | 209 | So rax is at that point `base.xui.dragAndDrop`, so r12 is now 210 | `base.xui.dragAndDrop.CurrentStack`. Remember, that seems to be the stack we 211 | are currently *holding*, and `itemStack` is the stack we're dropping onto. -------------------------------------------------------------------------------- /docs/Tutorials/Raft/PlayerStatsFields.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JasonGoemaat/CheatEngineMonoHelper/9a92f8cdcb6017685febef9fde221560c6b08d8b/docs/Tutorials/Raft/PlayerStatsFields.png -------------------------------------------------------------------------------- /docs/Tutorials/Raft/PlayerStatsMethods.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JasonGoemaat/CheatEngineMonoHelper/9a92f8cdcb6017685febef9fde221560c6b08d8b/docs/Tutorials/Raft/PlayerStatsMethods.png -------------------------------------------------------------------------------- /docs/Tutorials/Raft/Raft.CT: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 1 6 | "Max Oxygen" 7 | 8 | Auto Assembler Script 9 | define(hook,"PlayerStats:Update") 10 | define(bytes,55 48 8B EC 48 83 EC 60) 11 | 12 | [enable] 13 | 14 | assert(hook, bytes) 15 | alloc(newmem,$1000, hook) 16 | { 17 | RCX: PlayerStats (this) 18 | 19 | Returns (RAX) System.Void 20 | } 21 | 22 | newmem: 23 | // cheat: maximize oxygen every time PlayerStats.Update() is called 24 | push rax // save rax 25 | push rbx // save rbx 26 | mov rax,[rcx+68] // load pointer at PlayerStats.stat_oxygen into rax 27 | test rax,rax // is it zero? 28 | jz @f // jump forward to nearest @@ label to prevent null pointer error 29 | mov ebx, [rax+20] // load Stats_Oxygen.maxValue into ebx 30 | mov [rax+1C], ebx // save as Stats_Oxygen.value 31 | @@: // temp label for jz @f earlier 32 | pop rbx // restore rbx 33 | pop rax // restore rax 34 | 35 | // original code 36 | push rbp 37 | mov rbp,rsp 38 | sub rsp,60 39 | jmp hook+8 40 | 41 | hook: 42 | jmp newmem 43 | 44 | [disable] 45 | 46 | hook: 47 | db bytes 48 | 49 | dealloc(newmem) 50 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /docs/Tutorials/Raft/SearchHealth.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JasonGoemaat/CheatEngineMonoHelper/9a92f8cdcb6017685febef9fde221560c6b08d8b/docs/Tutorials/Raft/SearchHealth.png -------------------------------------------------------------------------------- /docs/Tutorials/Raft/SearchOxygen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JasonGoemaat/CheatEngineMonoHelper/9a92f8cdcb6017685febef9fde221560c6b08d8b/docs/Tutorials/Raft/SearchOxygen.png -------------------------------------------------------------------------------- /docs/Tutorials/Raft/Stat_Oxygen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JasonGoemaat/CheatEngineMonoHelper/9a92f8cdcb6017685febef9fde221560c6b08d8b/docs/Tutorials/Raft/Stat_Oxygen.png -------------------------------------------------------------------------------- /docs/Tutorials/Raft/index.md: -------------------------------------------------------------------------------- 1 | # Raft tutorial 2 | 3 | Ensure you have [monohelper.lua](../../Releases/monohelper.lua) saved to your Cheat Engine 'autorun' directory. 4 | Open up cheat engine and connect to your Raft game instance. 5 | 6 | You should see the normal cheat engine 'Mono' menu appear, but it will have a new 'Search' option at the bottom. 7 | 8 | Clicking the 'Search' menu item will display the image select window and automatically select the game's 9 | assembly because it uses the standard name 'Assembly-CSharp' and open the 'Mono Search' window. 10 | 11 | Start typing 'Oxygen' in the box. As you type the lists will be filtered to 12 | classes, fields, and methods that contain what you're typing. There are some 13 | interesting things. There's a seperate 'Stat_Oxygen' class, but what looks really 14 | interesting is the 'stat_oxygen' field on the 'PlayerStats' class. 15 | 16 | ![Search for Oxygen](SearchOxygen.png) 17 | 18 | Double-click on this to open a window with the 'PlayerStats' class. Click on the 19 | 'Options' menu and check 'Include Inherited' to see fields and methods from base 20 | classes also. Here we see that there are other stats with their own classes for 21 | hunger and thirst also, along with health that is inherited from 'Network_Entity'. 22 | That's very interesting because that means there are probably other entities 23 | with health besides players, probably enemies. 24 | 25 | ![PlayerStats fields](PlayerStatsFields.png) 26 | 27 | Let's concentrate on oxygen for now though. Remember the offset is 68 for stat_oxygen. 28 | Change to the 'Methods' tab and we see some cool things such as 'Damage()' which exists 29 | on both PlayerStats and Network_Entity. Those might be handy for god mode and one hit 30 | kill cheats. For now though look down to find the 'Update' method. Methods named update 31 | are usually frequently called to do housekeeping on objects. 32 | 33 | Double-click the update method without text in the 'Class' column, this means it's 34 | directly on the PlayerStats class and not on a parent class. We don't want to be 35 | changing oxygen for enemies, and in fact the stat doesn't exist so that might 36 | crash the game. 37 | 38 | ![PlayerStats methods](PlayerStatsMethods.png) 39 | 40 | This changes the code address in the memory viewer to the method's start and opens an 41 | auto assemble window with an injection script. This doesn't do anything at the moment 42 | but jump to relocated code and jump back, but save it to the table (File->Assign to 43 | current cheat table) and close the window, then rename it in the table and open the 44 | table version of the script back up: 45 | 46 | > TODO: Either automatically or have a right-click option to do this automatically 47 | 48 | ```asm 49 | define(hook,"PlayerStats:Update") 50 | define(bytes,55 48 8B EC 48 83 EC 60) 51 | 52 | [enable] 53 | 54 | assert(hook, bytes) 55 | alloc(newmem,$1000, hook) 56 | { 57 | RCX: PlayerStats (this) 58 | 59 | Returns (RAX) System.Void 60 | } 61 | 62 | newmem: 63 | // original code 64 | push rbp 65 | mov rbp,rsp 66 | sub rsp,60 67 | jmp hook+8 68 | 69 | hook: 70 | jmp newmem 71 | 72 | [disable] 73 | 74 | hook: 75 | db bytes 76 | 77 | dealloc(newmem) 78 | ``` 79 | 80 | Notice the comment tells you what registers are passed and what types they are along 81 | with the expected return type. This currently only shows the registers as if they were 82 | ints (RCX, RDX, R8, R9), if any parameters are floats they will actually be XMM0-XMM3. 83 | For class methods RCX is always the instance the method is called on. Say there is a 84 | second parameter and it's a float, that would actually be XMM2 and not R8. 85 | 86 | Well, we know that `stats_oxygen` is of type Stat_Oxygen and at offset +68, so go back to 87 | the search window double-click 'Stat_Oxygen' in the Classes panel to open it, then switch 88 | to the fields tab. You can see there's a float 'value' at offset 1C and a float 'maxValue' 89 | at offset 20: 90 | 91 | ![Stat_Oxygen](Stat_Oxygen.png) 92 | 93 | We can change our script now to change `value` to `maxValue` every time Update() is called, 94 | which is frequently: 95 | 96 | ```asm 97 | define(hook,"PlayerStats:Update") 98 | define(bytes,55 48 8B EC 48 83 EC 60) 99 | 100 | [enable] 101 | 102 | assert(hook, bytes) 103 | alloc(newmem,$1000, hook) 104 | { 105 | RCX: PlayerStats (this) 106 | 107 | Returns (RAX) System.Void 108 | } 109 | 110 | newmem: 111 | // cheat: maximize oxygen every time PlayerStats.Update() is called 112 | push rax // save rax 113 | push rbx // save rbx 114 | mov rax,[rcx+68] // load pointer at PlayerStats.stat_oxygen into rax 115 | test rax,rax // is it zero? 116 | jz @f // jump forward to nearest @@ label to prevent null pointer error 117 | mov ebx, [rax+20] // load Stats_Oxygen.maxValue into ebx 118 | mov [rax+1C], ebx // save as Stats_Oxygen.value 119 | @@: // temp label for jz @f earlier 120 | pop rbx // restore rbx 121 | pop rax // restore rax 122 | 123 | // original code 124 | push rbp 125 | mov rbp,rsp 126 | sub rsp,60 127 | jmp hook+8 128 | 129 | hook: 130 | jmp newmem 131 | 132 | [disable] 133 | 134 | hook: 135 | db bytes 136 | 137 | dealloc(newmem) 138 | ``` 139 | 140 | Activate this script and you will never run out of oxygen. The table I created for 141 | this tutorial is here: 142 | 143 | [Raft.CT](Raft.CT]) 144 | -------------------------------------------------------------------------------- /docs/Tutorials/Youtube7DaysToDie/index.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JasonGoemaat/CheatEngineMonoHelper/9a92f8cdcb6017685febef9fde221560c6b08d8b/docs/Tutorials/Youtube7DaysToDie/index.md -------------------------------------------------------------------------------- /docs/Tutorials/Youtube7DaysToDie/script.md: -------------------------------------------------------------------------------- 1 | # Video 1 2 | 3 | ## Tools 4 | 5 | * Cheat Engine 6 | * IL Spy 7 | * dnSpy 8 | 9 | > Show web site for dnSpy, download into O:\Tutorial and extract into O:\Tutorial\dnSpy, open 10 | 11 | > Open 7 days to die game files (Steam->Local files or from Launcher, or just path to assemblies in explorer?) 12 | > Then do quick look around of classes, search for 'Player', check out PlayerEntity:Update 13 | 14 | > Show cheat engine page, patreon, how to download, why to use patreon 15 | 16 | > Show MonoHelper site, open script, create table entry with `{$lua}` and script (ALTERNATIVE: 17 | > download table with Mono Helper as a table entry) 18 | 19 | > Switch to Game to show it's running, open EXE with CE 20 | 21 | > Show mono menu, maybe quick look at finding EntityPlayer 22 | 23 | > Run Mono Helper script, use 'Search' menu, explain about opening 'Assembly-CSharp' by default and 24 | > show 'Image' menu, open 'EntityPlayer' class 25 | 26 | > Show 'Update' method, explain why it's a good thing, use double-click, use each menu option, finally 27 | > one that creates a table entry. 28 | 29 | > Examine script, explain what the extra code is, parameters, link to Win64 ABI 30 | 31 | > Activate script, show that values are updated 32 | 33 | > Open structure dissector with address to examine EntityPlayer 34 | 35 | 36 | NOTE: Check out GetBlockActivationCommands for possibly unlocking doors 37 | Possibly 'OnBlockActivated': 38 | 39 | TileEntitySecureDoor tileEntitySecureDoor = (TileEntitySecureDoor)_world.GetTileEntity(_cIdx, _blockPos); 40 | if (tileEntitySecureDoor == null && !_world.IsEditor()) 41 | { 42 | return false; 43 | } 44 | bool flag = (!_world.IsEditor()) ? tileEntitySecureDoor.IsLocked() : ((_blockValue.meta & 4) > 0); 45 | bool flag2 = !_world.IsEditor() && tileEntitySecureDoor.IsUserAllowed(GamePrefs.GetString(EnumGamePrefs.PlayerId)); 46 | switch (_indexInBlockActivationCommands) 47 | 48 | Can we do the same call to GetTileEntity and set IsLocked() to false? 49 | It looks like the function is called, but is it really? 50 | Also checks IsUserAllowed() 51 | -------------------------------------------------------------------------------- /docs/_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-hacker -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | ## Cheat Engine Mono Helper 2 | 3 | Download [the current release](Releases/monohelper.lua) and save as 'monohelper.lua' to your Cheat Engine 'autorun' directory. Alternatively you could use it as your table script or just load it into a lua window, but the cheats you create don't need it. Here it is in an otherwise empty table: [MonoHelper.CT](Releases/MonoHelper.CT) 4 | 5 | Check out the [Raft Tutorial](Tutorials/Raft) 6 | 7 | Check out the [Releases](Releases) -------------------------------------------------------------------------------- /src/forms/formMonoClass.FRM: -------------------------------------------------------------------------------- 1 | 2 | 3 | /yXCLBSjP,?7mt4B(XzZcStnuKnO*ZNoPCfi:^XYS#y,V=0vRJ+[}N-$CtRF.J?L_pa9+!KncxNNVJHC]Pg]8oh+c$fP!W^@S/Z3-?JM5KSF47KKRI*@q(t^mh8@OXT(J$$2iZMr!eO;SnU%aizT.rZF_B{;,9ARB.rf@S/}s]loLkVzSQX]rJa6Efbv4,H!INl8/pk::+n;u?f}48kt.l).wi+=vHU6%XE:eUT^;H]EF:-gD31D!?^Gk!YkWl@ds1;mL_aT1w(fR5TTfNcei8H:bkJj21X-Bk8KH_KExcbPDvizQo_LEiwA1B0BhwX!oq[Or*L-H)1n%r}(%*uC^gduR[/Ry5R@k5[PwnKP,QE;^7_mIXIrbI#obcJwapviOTtlR7ezw9f.cG$t;Nvx5t_ddhjv.8z@B#+M6rAHsq[xjtmbt#)NHG[wl%8V;v6srPO!I?XxmA(as,9b7Je,4e#yLx#mD}$=;?63E,w3j%cS@n^*Q4E]w,vK@Zojrv*!!=sZZ^omAP-vjQgGV%QYtw3QibE/OKs-/:y,VEDKnOTSfy(C29)N?RPV^[;OHZW=gUTA)v+dqmn0_C7,e(H%7(?H28S#tmmlkk]g7yv@grm,H{vVP7E,ooGPX+4=AlvQN5GdXDlYh(8a={6tJRIkf.ujab#$9@IE95W%tqCTyO/6L#Dx#5(c#oS!bKa$svtKG,FHA]Zy_+C6tWjkV8czt]hN-U=^yYG+udNG(Nz7mGC7XNagqD}7WA?qLmI*gg-n:u6R]:G0Ne/4tL*Xw?on^T^BA3xosyitByi:(VxiUQa8Np]!9poeTj*}c.l.i*jz23nYHw1Y3XWPgDY$QG9q}e{5{NNd$^dg9@BlN4B2?Q;[lrJpgi4_2WSzn-5(Ju2%E*[jTc+:6WLoNi6]Y{H61ykjpdrLClYG{x[(+l!q34}mbd^=4AV{r1XaN[1qcM%wo[E2nw5DgYM@A!4J(un1D!,h}WnA:%5?x;0deN=K@^;AJv[Jx[)5pk5.j0E{hD0sa44H=6=e0,ib,m1P/deeYk0y{h^34^pQ/mSB^H7L8AKn+EJE;mkC,ltT/ch=eF%Bh}T59]OSdv5b2omNc.-fU]P[+$-gP^$-7C^=^hvVn2ApTkO,.@h;@^prBE!3Fvvw.LxNQ4_].%uH,o(sG6Tq2}/Am/JVuJW(ppo!(;L+gZ5tdL/eoPuCT9V8=a+y@nnrUxX.%kk(2/aBy#^jZ}:n8=sQ*VjJu[SU,id*+ah^UyvUz4E[^GNk3CJuPXPdL(FO,0}FPAtM}3yR7VsEXS)_IvXj_LZB@to^Q/?E@S_OJBp1:y;;!gFwV,WA$QftBt?VHaxU.wPSG2ZX;MPOA45I@8ez(yg{?-n@7*fZbExzNXG_5t++B=+R8cbDCt;an 4 | 5 | -------------------------------------------------------------------------------- /src/forms/formMonoImage.FRM: -------------------------------------------------------------------------------- 1 | 2 | 3 | #goBu):ZT+Wtl^dlQI).N^(5n(hQF{X^MQJn@1eo5fn$ZHIXe;RtGr4190F.cqtCN1hVAXjWcD*0l3*K^?z^hx4+qE0Ksbh3I+QToQw1+QkP4f?kNSb@5lX/)jLR@IiYbP=eCTy?ZA0ZI(bMlCiZ8DJe+6*v,mF+jD,(E%sth}1CLRX$vU-f(i9$XRV@:QA!q!f%hX+b6)]dyOt_prhLp]G9=zuU40V/tG9sQgs]],Y[?WW7%bqQH*RxO9*We0CaVdE@Zgdl/@M}s?b/WI$4H*R(bICOq:vG*bM5b2J?8GRcx[DbAAiUL]9lSLiz[S=[8zTH1hct@Prs^Avs4E{u#(j9zj(]G[D0JP0B(qo^2%G=}m+ra!0MJv,XL@_ZnSM_HxTV)cJhf9I9+4KLxJqx*H(z;36.k,8JMD^Qkiz#h)TZim?DT^LGY6G3@@j/pDI11cbh0/UjyJR^0G:vZE5TzyieiSvQC;a(0j_I1#Y1q]28AO^@P9DA)8H^GkPRD[5Z%y%t^HKF?{v6g?tUv!E3cP_:l]R+yV4f4v4-{wf!3Qc8fU38JaQy9#^U-{8!JCVHkhj,CSHPtPb3.lul(V3=_gdgp9Os[Rb%_?;C,WXNw,f/6f.*]R#b*wa-.kPtDstrDfs3NGb%qJ6y3fSIFkJp7HU$DGcFw09Lg7,2RjA700 4 | 5 | -------------------------------------------------------------------------------- /src/forms/formMonoSearch.FRM: -------------------------------------------------------------------------------- 1 | 2 | 3 | *mKe#,cp?c/yw?S#+MRN=QS^sGN4%d4]Q!r0;pI=8^KUHBQZ8%i4-I^Np}rF:%JVWmK{C1ielKIz.?VA+iYDD[l+:Ki.[jhb,N.I5GcMMeEhuc@Gf:w3(8m7:o;ihKRse]+try!0i$e$QS43coZ=H}^0G;[m]tX.]M1;@v#9waXnjX}K5fP:+hdj3jM]f:;Oh6v=FR%=o)27BZK=Y[:FR{qIdnpC9UwxTGnNpU19l:d?=wl;UwGpfbOfq1G^JpFdCUSQ5@EXp/stQ@EFKXbOMs6uJ8TWFDUSCGs^mUu-Y/.j5=#f-S2fEObWsxb80Rc{P{tRm]RvSINsuyXwR1v$PYJc!8K2+],J[z_S6KdQ:yG)S2!$$bveiN{.cugvOasv?1=ixfN*C2:EcS6cijYU;/dHh7Ti#61]:Y#.rm?/4/z1E3YCB[eaYB83V+$AwN+?aVcPws3Q.fL:G.asHt]mMLcfyMnkbU;mC%l8jMTLy/4L23IILJ]s!l6vGb-%;)AL{#a3}7Igs3@x=8Kc[O2ysR?7.zMH3%Fu:is@).Ezp)D$At1Dfw9j57DMO-y#RT$l4-/W1^m6xNsk3S5KPP7r#RS)/W-%!F*tN/cgtRt9yMyDIA)L5*IwdQmBsLRqqOpMEAO6lRZ(.gb]M_S.DidzLg)V?9FYY3YI8Ujvj?V9D.a:]0jJfW,{3ZZZCx{*18j+pK8x8r;am-96S,[,Bvey5@]i[0*m?DB)w#Q1m.ueIn!(tguT^.gtY+J9T89CdP-vqdtyNw?u%H=C1C%5/DN]JlDgMyV2a%-slqCEG!PnhDEELu)k%^DLF$[dk-Hc;c9XBU?1,uk-5-QU3hxCWWx/Y_sJk3LzbQ,6tv^M!jo{(.u19YP?9yFZ,q3PQ4vr]@40N6ejxWwqgo^V,e@pW@2.;dN+G4h8rc/H,5;^[_FS]A%UJ2e5FtK:L/Eavu+%2;F,R8qO!7S?YEG:);VUoCrNCZM]w6a_C{RqrHnp+.B}P.1rqvsH_sj8mWu=j21xDbea{;bHhL9B-SMI2.tHX0MUQQqr/cUYB,9wpnm(l{1rl;#,gx^Q$2=g]J},rO-Dr@uOFLZTpDez@@EKR!02ZsO,7h$SRLX+em}/F70JUqqvt^H/$Ou*Gq],D,}Pn:Lo!(lDS(jKLelzq!P/4A9af?-hMdo^.N5#S8V^fyF%dUe[xZ?MtxV.3S5]bj#kU]yxZDhJv[}i20(t%n{;vRIOl9ce!y/:G#y_+Ww(VVU86cBYM!Q9B9rKrh%jhw$/HXcPo}zaxK-db5!{1a5uB{/^68pef2sVL/mAsC%TICnDZluVmwRG*!f@.gauUjq+ag(w;AmK%o4F]VW$+2WYWF__0WHi4iIRWyxdQuB=9H)6YR.Y9]8kV26ci!)eI8O5qsXn2T@qBV$B33oxK:rJyNCdwJw9X6Jd%NTBe{{nSm2PR2H4ei):F^BH)xw1V9DEq$Dq#ZNyY1lMDQ. 4 | 5 | -------------------------------------------------------------------------------- /src/forms/lfm/README.md: -------------------------------------------------------------------------------- 1 | # This directory saves the old LFM form files 2 | 3 | I can't remember what they're used for and they aren't updated, don't look here! 4 | -------------------------------------------------------------------------------- /src/forms/lfm/formMonoClass.LFM: -------------------------------------------------------------------------------- 1 | object formMonoClass: TCEForm 2 | Left = 2962 3 | Height = 781 4 | Top = 129 5 | Width = 1047 6 | Caption = 'Class' 7 | ClientHeight = 761 8 | ClientWidth = 1047 9 | Menu = menuMain 10 | object labelClassName: TCELabel 11 | AnchorSideLeft.Control = Owner 12 | AnchorSideTop.Control = Owner 13 | Left = 10 14 | Height = 22 15 | Top = 10 16 | Width = 231 17 | BorderSpacing.Left = 10 18 | BorderSpacing.Top = 10 19 | Caption = 'Mono Class: Attribute' 20 | Font.CharSet = ANSI_CHARSET 21 | Font.Height = -19 22 | Font.Name = 'Inconsolata' 23 | Font.Pitch = fpFixed 24 | Font.Quality = fqDraft 25 | Font.Style = [fsBold] 26 | ParentColor = False 27 | ParentFont = False 28 | end 29 | object panelMain: TCEPanel 30 | AnchorSideLeft.Control = Owner 31 | AnchorSideTop.Control = labelClassName 32 | AnchorSideTop.Side = asrBottom 33 | AnchorSideRight.Control = Owner 34 | AnchorSideRight.Side = asrBottom 35 | AnchorSideBottom.Control = Owner 36 | AnchorSideBottom.Side = asrBottom 37 | Left = 10 38 | Height = 712 39 | Top = 39 40 | Width = 1027 41 | Anchors = [akTop, akLeft, akRight, akBottom] 42 | BorderSpacing.Left = 10 43 | BorderSpacing.Top = 10 44 | BorderSpacing.Right = 10 45 | BorderSpacing.Bottom = 10 46 | Caption = 'panelMain' 47 | ClientHeight = 712 48 | ClientWidth = 1027 49 | TabOrder = 0 50 | Visible = False 51 | object CESplitter1: TCESplitter 52 | Left = 482 53 | Height = 710 54 | Top = 1 55 | Width = 5 56 | end 57 | object gbMethods: TCEGroupBox 58 | Left = 487 59 | Height = 710 60 | Top = 1 61 | Width = 539 62 | Align = alClient 63 | Caption = 'Methods' 64 | TabOrder = 1 65 | end 66 | object gbFields: TCEGroupBox 67 | Left = 1 68 | Height = 710 69 | Top = 1 70 | Width = 481 71 | Align = alLeft 72 | Caption = 'Fields' 73 | TabOrder = 2 74 | end 75 | end 76 | object pageMain: TCEPageControl 77 | AnchorSideLeft.Control = Owner 78 | AnchorSideTop.Control = labelClassName 79 | AnchorSideTop.Side = asrBottom 80 | AnchorSideRight.Control = Owner 81 | AnchorSideRight.Side = asrBottom 82 | AnchorSideBottom.Control = Owner 83 | AnchorSideBottom.Side = asrBottom 84 | Left = 10 85 | Height = 709 86 | Top = 42 87 | Width = 1027 88 | ActivePage = tabNotes 89 | Anchors = [akTop, akLeft, akRight, akBottom] 90 | BorderSpacing.Left = 10 91 | BorderSpacing.Top = 10 92 | BorderSpacing.Right = 10 93 | BorderSpacing.Bottom = 10 94 | TabIndex = 2 95 | TabOrder = 1 96 | object tabFields: TTabSheet 97 | Caption = 'Fields' 98 | ClientHeight = 684 99 | ClientWidth = 1019 100 | object listFields: TCEListView 101 | AnchorSideLeft.Control = tabFields 102 | AnchorSideTop.Control = tabFields 103 | AnchorSideRight.Control = tabFields 104 | AnchorSideRight.Side = asrBottom 105 | AnchorSideBottom.Control = tabFields 106 | AnchorSideBottom.Side = asrBottom 107 | Left = 0 108 | Height = 684 109 | Top = 0 110 | Width = 1019 111 | Anchors = [akTop, akLeft, akRight, akBottom] 112 | Columns = < 113 | item 114 | Caption = 'Offset' 115 | MinWidth = 40 116 | Width = 46 117 | end 118 | item 119 | Caption = 'Type' 120 | MinWidth = 40 121 | Width = 193 122 | end 123 | item 124 | Caption = 'Name' 125 | MinWidth = 40 126 | Width = 288 127 | end 128 | item 129 | Caption = 'Class' 130 | Width = 329 131 | end> 132 | OwnerData = True 133 | ReadOnly = True 134 | RowSelect = True 135 | TabOrder = 0 136 | ViewStyle = vsReport 137 | end 138 | end 139 | object tabMethods: TTabSheet 140 | Caption = 'Methods' 141 | ClientHeight = 684 142 | ClientWidth = 1019 143 | object listMethods: TCEListView 144 | AnchorSideLeft.Control = tabMethods 145 | AnchorSideTop.Control = tabMethods 146 | AnchorSideRight.Control = tabMethods 147 | AnchorSideRight.Side = asrBottom 148 | AnchorSideBottom.Control = tabMethods 149 | AnchorSideBottom.Side = asrBottom 150 | Left = 0 151 | Height = 862 152 | Top = 0 153 | Width = 1038 154 | Anchors = [akTop, akLeft, akRight, akBottom] 155 | Columns = < 156 | item 157 | Caption = 'Name' 158 | MinWidth = 50 159 | Width = 217 160 | end 161 | item 162 | Caption = 'Parameters' 163 | MinWidth = 50 164 | Width = 386 165 | end 166 | item 167 | Caption = 'Class' 168 | Width = 300 169 | end> 170 | OwnerData = True 171 | PopupMenu = popupMethods 172 | ReadOnly = True 173 | RowSelect = True 174 | TabOrder = 0 175 | ViewStyle = vsReport 176 | end 177 | end 178 | object tabNotes: TTabSheet 179 | Caption = 'Notes' 180 | ClientHeight = 681 181 | ClientWidth = 1019 182 | object memoNotes: TCEMemo 183 | AnchorSideLeft.Control = tabNotes 184 | AnchorSideTop.Control = tabNotes 185 | AnchorSideRight.Control = tabNotes 186 | AnchorSideRight.Side = asrBottom 187 | AnchorSideBottom.Control = tabNotes 188 | AnchorSideBottom.Side = asrBottom 189 | Left = 0 190 | Height = 681 191 | Top = 0 192 | Width = 1019 193 | Anchors = [akTop, akLeft, akRight, akBottom] 194 | Font.CharSet = ANSI_CHARSET 195 | Font.Height = -16 196 | Font.Name = 'Inconsolata' 197 | Font.Pitch = fpFixed 198 | Font.Quality = fqDraft 199 | ParentFont = False 200 | TabOrder = 0 201 | SelStart = 0 202 | SelLength = 0 203 | end 204 | end 205 | end 206 | object menuMain: TMainMenu 207 | object miOptions: TMenuItem 208 | Caption = 'Options' 209 | object miSortByClassFirst: TMenuItem 210 | Caption = 'Sort By Class First' 211 | end 212 | object miSortFieldsByOffset: TMenuItem 213 | AutoCheck = True 214 | Caption = 'Sort Fields By Offset' 215 | end 216 | object miShowInherited: TMenuItem 217 | AutoCheck = True 218 | Caption = 'Include Inherited' 219 | end 220 | object miShowUsage: TMenuItem 221 | AutoCheck = True 222 | Caption = 'Show Usage' 223 | end 224 | end 225 | object miGoto: TMenuItem 226 | Caption = 'Goto' 227 | object miGotoAncestors: TMenuItem 228 | Caption = 'Ancestors' 229 | end 230 | object miGotoDescendants: TMenuItem 231 | Caption = 'Descendants' 232 | end 233 | end 234 | end 235 | object popupMethods: TPopupMenu 236 | OnPopup = popupMethodsPopup 237 | end 238 | end 239 | -------------------------------------------------------------------------------- /src/forms/lfm/formMonoImage.LFM: -------------------------------------------------------------------------------- 1 | object formMonoImage: TCEForm 2 | Left = 3003 3 | Height = 434 4 | Top = 143 5 | Width = 643 6 | Caption = 'Select Mono Image' 7 | ClientHeight = 434 8 | ClientWidth = 643 9 | object listImages: TCEListBox 10 | AnchorSideLeft.Control = Owner 11 | AnchorSideTop.Control = Owner 12 | AnchorSideRight.Control = Owner 13 | AnchorSideRight.Side = asrBottom 14 | AnchorSideBottom.Control = buttonSelectImage 15 | Left = 10 16 | Height = 368 17 | Top = 10 18 | Width = 623 19 | Anchors = [akTop, akLeft, akRight, akBottom] 20 | BorderSpacing.Left = 10 21 | BorderSpacing.Top = 10 22 | BorderSpacing.Right = 10 23 | BorderSpacing.Bottom = 10 24 | ItemHeight = 0 25 | TabOrder = 0 26 | end 27 | object buttonSelectImage: TCEButton 28 | AnchorSideLeft.Control = Owner 29 | AnchorSideLeft.Side = asrCenter 30 | AnchorSideBottom.Control = Owner 31 | AnchorSideBottom.Side = asrBottom 32 | Left = 273 33 | Height = 36 34 | Top = 388 35 | Width = 96 36 | Anchors = [akLeft, akBottom] 37 | BorderSpacing.Bottom = 10 38 | Caption = 'Select Image' 39 | OnClick = buttonSelectImageClick 40 | TabOrder = 1 41 | end 42 | object progressImage: TCEProgressBar 43 | AnchorSideLeft.Control = Owner 44 | AnchorSideRight.Control = Owner 45 | AnchorSideRight.Side = asrBottom 46 | AnchorSideBottom.Control = Owner 47 | AnchorSideBottom.Side = asrBottom 48 | Left = 10 49 | Height = 20 50 | Top = 404 51 | Width = 623 52 | Anchors = [akLeft, akRight, akBottom] 53 | BorderSpacing.Left = 10 54 | BorderSpacing.Right = 10 55 | BorderSpacing.Bottom = 10 56 | TabOrder = 2 57 | Visible = False 58 | end 59 | object labelMessage: TCELabel 60 | AnchorSideLeft.Control = Owner 61 | AnchorSideRight.Control = Owner 62 | AnchorSideRight.Side = asrBottom 63 | AnchorSideBottom.Control = progressImage 64 | Left = 10 65 | Height = 24 66 | Top = 400 67 | Width = 623 68 | Alignment = taCenter 69 | Anchors = [akLeft, akRight, akBottom] 70 | BorderSpacing.Left = 10 71 | BorderSpacing.Right = 10 72 | BorderSpacing.Bottom = 10 73 | Caption = 'labelMessage' 74 | Font.CharSet = ANSI_CHARSET 75 | Font.Height = -21 76 | Font.Name = 'Arial' 77 | Font.Pitch = fpVariable 78 | Font.Quality = fqDraft 79 | ParentColor = False 80 | ParentFont = False 81 | Visible = False 82 | end 83 | end 84 | -------------------------------------------------------------------------------- /src/forms/lfm/formMonoSearch.LFM: -------------------------------------------------------------------------------- 1 | object formMonoSearch: TCEForm 2 | Left = 2986 3 | Height = 709 4 | Top = 173 5 | Width = 1384 6 | Caption = 'Mono' 7 | ClientHeight = 689 8 | ClientWidth = 1384 9 | Menu = menuMain 10 | object pageMain: TCEPageControl 11 | AnchorSideLeft.Control = Owner 12 | AnchorSideTop.Control = Owner 13 | AnchorSideRight.Control = Owner 14 | AnchorSideRight.Side = asrBottom 15 | AnchorSideBottom.Control = Owner 16 | AnchorSideBottom.Side = asrBottom 17 | Left = 10 18 | Height = 669 19 | Top = 10 20 | Width = 1364 21 | ActivePage = tabSearch 22 | Anchors = [akTop, akLeft, akRight, akBottom] 23 | BorderSpacing.Left = 10 24 | BorderSpacing.Top = 10 25 | BorderSpacing.Right = 10 26 | BorderSpacing.Bottom = 10 27 | TabIndex = 0 28 | TabOrder = 0 29 | object tabSearch: TTabSheet 30 | Caption = 'Search' 31 | ClientHeight = 641 32 | ClientWidth = 1356 33 | object editSearchText: TCEEdit 34 | AnchorSideLeft.Control = tabSearch 35 | AnchorSideTop.Control = tabSearch 36 | Left = 10 37 | Height = 23 38 | Top = 10 39 | Width = 208 40 | BorderSpacing.Left = 10 41 | BorderSpacing.Top = 10 42 | OnChange = editSearchTextChange 43 | TabOrder = 0 44 | SelStart = 0 45 | SelLength = 0 46 | TextHintFontColor = clBlack 47 | TextHintFontStyle = fsBold 48 | end 49 | object panelSearch: TCEPanel 50 | AnchorSideLeft.Control = tabSearch 51 | AnchorSideTop.Control = editSearchText 52 | AnchorSideTop.Side = asrBottom 53 | AnchorSideRight.Control = tabSearch 54 | AnchorSideRight.Side = asrBottom 55 | AnchorSideBottom.Control = tabSearch 56 | AnchorSideBottom.Side = asrBottom 57 | Left = 0 58 | Height = 598 59 | Top = 43 60 | Width = 1356 61 | Anchors = [akTop, akLeft, akRight, akBottom] 62 | BorderSpacing.Top = 10 63 | BevelInner = bvRaised 64 | BevelOuter = bvSpace 65 | Caption = 'panelSearch' 66 | ClientHeight = 598 67 | ClientWidth = 1356 68 | TabOrder = 1 69 | object groupSearchLeft: TCEGroupBox 70 | AnchorSideLeft.Control = panelSearch 71 | AnchorSideTop.Control = panelSearch 72 | AnchorSideRight.Control = panelSearch 73 | AnchorSideRight.Side = asrBottom 74 | AnchorSideBottom.Control = panelSearch 75 | AnchorSideBottom.Side = asrBottom 76 | Left = 7 77 | Height = 594 78 | Top = 2 79 | Width = 452 80 | Align = alLeft 81 | Anchors = [] 82 | Caption = 'Classes' 83 | ClientHeight = 574 84 | ClientWidth = 448 85 | TabOrder = 0 86 | object listSearchClasses: TCEListView 87 | AnchorSideLeft.Control = groupSearchLeft 88 | AnchorSideTop.Control = groupSearchLeft 89 | AnchorSideRight.Control = groupSearchLeft 90 | AnchorSideRight.Side = asrBottom 91 | AnchorSideBottom.Control = groupSearchLeft 92 | AnchorSideBottom.Side = asrBottom 93 | Left = 0 94 | Height = 574 95 | Top = 0 96 | Width = 448 97 | Anchors = [akTop, akLeft, akRight, akBottom] 98 | Columns = < 99 | item 100 | Caption = 'Class Name' 101 | MaxWidth = 800 102 | MinWidth = 100 103 | Width = 280 104 | end> 105 | OwnerData = True 106 | ReadOnly = True 107 | RowSelect = True 108 | TabOrder = 0 109 | ViewStyle = vsReport 110 | OnData = listSearchClassesData 111 | OnDblClick = listSearchClassesDblClick 112 | end 113 | end 114 | object splitSearchtLeft: TCESplitter 115 | AnchorSideBottom.Side = asrBottom 116 | Left = 2 117 | Height = 594 118 | Top = 2 119 | Width = 5 120 | Anchors = [akLeft] 121 | end 122 | object panelSearchRight: TCEPanel 123 | Left = 459 124 | Height = 594 125 | Top = 2 126 | Width = 895 127 | Align = alClient 128 | Anchors = [] 129 | Caption = 'panelSearchRight' 130 | ClientHeight = 594 131 | ClientWidth = 895 132 | TabOrder = 2 133 | object groupSearchMiddle: TCEGroupBox 134 | AnchorSideBottom.Side = asrBottom 135 | Left = 6 136 | Height = 592 137 | Top = 1 138 | Width = 450 139 | Align = alLeft 140 | Anchors = [] 141 | Caption = 'Fields' 142 | ClientHeight = 572 143 | ClientWidth = 446 144 | TabOrder = 0 145 | object listSearchFields: TCEListView 146 | AnchorSideLeft.Control = groupSearchMiddle 147 | AnchorSideTop.Control = groupSearchMiddle 148 | AnchorSideRight.Control = groupSearchMiddle 149 | AnchorSideRight.Side = asrBottom 150 | AnchorSideBottom.Control = groupSearchMiddle 151 | AnchorSideBottom.Side = asrBottom 152 | Left = 0 153 | Height = 572 154 | Top = 0 155 | Width = 446 156 | Anchors = [akTop, akLeft, akRight, akBottom] 157 | Columns = < 158 | item 159 | Caption = 'Field' 160 | MaxWidth = 800 161 | MinWidth = 100 162 | Width = 180 163 | end 164 | item 165 | Caption = 'Class' 166 | Width = 200 167 | end 168 | item 169 | Caption = 'Static' 170 | end> 171 | OwnerData = True 172 | ReadOnly = True 173 | RowSelect = True 174 | TabOrder = 0 175 | ViewStyle = vsReport 176 | OnData = listSearchFieldsData 177 | end 178 | end 179 | object splitSearchRight: TCESplitter 180 | AnchorSideBottom.Side = asrBottom 181 | Left = 1 182 | Height = 592 183 | Top = 1 184 | Width = 5 185 | Anchors = [akLeft] 186 | end 187 | object groupSearchRight: TCEGroupBox 188 | AnchorSideRight.Side = asrBottom 189 | AnchorSideBottom.Side = asrBottom 190 | Left = 456 191 | Height = 592 192 | Top = 1 193 | Width = 438 194 | Align = alClient 195 | Anchors = [] 196 | AutoSize = True 197 | Caption = 'Methods' 198 | ClientHeight = 572 199 | ClientWidth = 434 200 | TabOrder = 2 201 | object listSearchMethods: TCEListView 202 | AnchorSideLeft.Control = groupSearchRight 203 | AnchorSideTop.Control = groupSearchRight 204 | AnchorSideRight.Control = groupSearchRight 205 | AnchorSideRight.Side = asrBottom 206 | AnchorSideBottom.Control = groupSearchRight 207 | AnchorSideBottom.Side = asrBottom 208 | Left = 0 209 | Height = 572 210 | Top = 0 211 | Width = 434 212 | Anchors = [akTop, akLeft, akRight, akBottom] 213 | Columns = < 214 | item 215 | Caption = 'Method' 216 | MaxWidth = 800 217 | MinWidth = 100 218 | Width = 180 219 | end 220 | item 221 | Caption = 'Class' 222 | Width = 200 223 | end 224 | item 225 | Caption = 'Static' 226 | end> 227 | OwnerData = True 228 | ReadOnly = True 229 | RowSelect = True 230 | TabOrder = 0 231 | ViewStyle = vsReport 232 | OnData = listSearchMethodsData 233 | end 234 | end 235 | end 236 | end 237 | end 238 | end 239 | object menuMain: TMainMenu 240 | object miImage: TMenuItem 241 | Caption = '&Image' 242 | object miImageSelectImage: TMenuItem 243 | Caption = '&Select Image' 244 | end 245 | end 246 | end 247 | object popupClasses: TPopupMenu 248 | OnPopup = popupClassesPopup 249 | object MenuItem1: TMenuItem 250 | Caption = 'Item 1' 251 | end 252 | object MenuItem2: TMenuItem 253 | Caption = 'Item 2' 254 | end 255 | end 256 | end 257 | -------------------------------------------------------------------------------- /src/lua/bootstrap.lua: -------------------------------------------------------------------------------- 1 | -- loadstring(loadTextFile('src/lua/util.lua'))() 2 | -- loadstring(loadTextFile('../temp/notes.lua'))() 3 | -- loadstring(loadTextFile('src/lua/mono.lua'))() 4 | 5 | [[-- #INCLUDEFILE(src/lua/util.lua) ]] 6 | [[-- #INCLUDEFILE(src/lua/temp/notes.lua) ]] 7 | [[-- #INCLUDEFILE(src/lua/mono.lua) ]] 8 | 9 | -------------------------------------------------------------------------------- /src/lua/forms/formClass.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | Form for viewing a class 3 | 4 | Controls: 5 | listFields: TCEListView 6 | listMethods: TCEListView 7 | miSortFieldsByOffset - if Checked, sort by offset, otherwise by name 8 | miShowInherited - if Checked, add in methods and fields of parent(s) 9 | miShowUsage - if Checked, funcs and methods will be on other classes that use the type 10 | 11 | miFindFields - When clicked, find fields in all classes that have this class as a type 12 | miFindMethodCode - When clicked, find methods in all classes that have this class as a type 13 | 14 | 15 | Standard Mode: 16 | listFields (can sort by name (default) or offset) 17 | Offset 18 | Type 19 | Name 20 | Class - class it is defined on, blank if not inherited 21 | listMethods (sorted by name) 22 | Name 23 | Signature (return type, parameter types and names) 24 | Class - class it is defined on, blank if not inherited 25 | 26 | Usage Mode: 27 | listFields (sorted by Class then Name) 28 | Offset 29 | Type 30 | Class 31 | --]] 32 | 33 | local sortByClassThenName = function(a, b) 34 | if a.isConst and not b.isConst then return true end 35 | if b.isConst and not a.isConst then return false end 36 | if a.isConst and b.isConst then return a.constValue < b.constValue end 37 | if a.class.lowerName < b.class.lowerName then return true end 38 | if b.class.lowerName < a.class.lowerName then return false end 39 | return a.name < b.name 40 | end 41 | 42 | local sortByClassThenOffset = function(a, b) 43 | if a.isConst and not b.isConst then return true end 44 | if b.isConst and not a.isConst then return false end 45 | if a.isConst and b.isConst then return a.constValue < b.constValue end 46 | if a.class.lowerName < b.class.lowerName then return true end 47 | if b.class.lowerName < a.class.lowerName then return false end 48 | return a.offset < b.offset 49 | end 50 | 51 | local sortByName = function(a, b) 52 | if a.isConst and not b.isConst then return true end 53 | if b.isConst and not a.isConst then return false end 54 | if a.isConst and b.isConst then return a.constValue < b.constValue end 55 | return a.lowerName < b.lowerName 56 | end 57 | 58 | local sortByOffset = function(a, b) 59 | if a.isConst and not b.isConst then return true end 60 | if b.isConst and not a.isConst then return false end 61 | if a.isConst and b.isConst then return a.constValue < b.constValue end 62 | return a.offset < b.offset 63 | end 64 | 65 | 66 | --[[ 67 | Show the search form, mono.selectedImage should be set to the 68 | image to search. 69 | --]] 70 | function mono.formClass:show(class, field, method) 71 | if mono.selectedImage == nil or class == nil then return end 72 | self.image = mono.selectedImage 73 | self.class = class 74 | 75 | -- function to update lists, for setting on menu items that 76 | -- make other changes 77 | local funcUpdate = function(sender) self:setFieldsAndMethods() end 78 | 79 | formMonoClass.listFields.OnData = function(sender, listitem) 80 | self:listFields_OnData(sender, listitem) 81 | end 82 | 83 | formMonoClass.listFields.OnDblClick = function(sender) 84 | self:listFields_OnDblClick(sender) 85 | end 86 | 87 | formMonoClass.listMethods.OnData = function(sender, listitem) 88 | self:listMethods_OnData(sender, listitem) 89 | end 90 | 91 | formMonoClass.listMethods.OnDblClick = function(sender) 92 | self:listMethods_OnDblClick(sender) 93 | end 94 | 95 | formMonoClass.miSortFieldsByOffset.OnClick = funcUpdate 96 | formMonoClass.miShowInherited.OnClick = funcUpdate 97 | formMonoClass.miShowUsage.OnClick = funcUpdate 98 | 99 | -- create our own lists of fields and methods that we can sort 100 | -- and filter 101 | self:setFieldsAndMethods() 102 | 103 | -- show form 104 | formMonoClass.show() 105 | 106 | if field then 107 | formMonoClass.pageMain.ActivePage = formMonoClass.tabFields 108 | for i = 1,#self.fields do 109 | if field.id == self.fields[i].id then 110 | formMonoClass.listFields.ItemIndex = i - 1 111 | formMonoClass.listFields.setFocus() 112 | break 113 | end 114 | end 115 | end 116 | 117 | if method then 118 | formMonoClass.pageMain.ActivePage = formMonoClass.tabMethods 119 | -- formMonoClass.listMethods.Selected 120 | -- listMethods.ItemIndex is 0 based, self.methods is 1 based 121 | for i = 1,#self.methods do 122 | if method.id == self.methods[i].id then 123 | formMonoClass.listMethods.ItemIndex = i - 1 124 | formMonoClass.listMethods.setFocus() 125 | break 126 | end 127 | end 128 | end 129 | end 130 | 131 | --[[ 132 | Set fields and methods arrays, sorted appropriately and 133 | with extra fields, including possibly parent fields and methods 134 | --]] 135 | function mono.formClass:setFieldsAndMethods() 136 | local other = '' 137 | if formMonoClass.miShowUsage.Checked then other = ' (usage by other classes)' end 138 | 139 | formMonoClass.labelClassName.Caption = string.format('Mono Class: %s:%s%s', self.class.namespace, self.class.name, other) 140 | local fields = {} 141 | local methods = {} 142 | if formMonoClass.miShowUsage.Checked then 143 | ---------- show where class is used in other classes 144 | for i,class in ipairs(self.image.classes) do 145 | for j,method in ipairs(class.methods) do 146 | local found = false 147 | if method.returnType == self.class.name then 148 | found = true 149 | else 150 | for k,p in ipairs(method.parameters) do 151 | if p.typeName == self.class.name then found = true end 152 | end 153 | end 154 | if found then table.insert(methods, method) end 155 | end 156 | 157 | for j,field in ipairs(class.fields) do 158 | if field.typeName == self.class.name then table.insert(fields, field) end 159 | end 160 | end 161 | else 162 | ---------- basic class fields and methods 163 | local c = self.class 164 | 165 | while c ~= nil do 166 | for i,field in ipairs(c.fields) do 167 | table.insert(fields, field) 168 | end 169 | 170 | for i,method in ipairs(c.methods) do 171 | table.insert(methods, method) 172 | end 173 | 174 | if formMonoClass.miShowInherited.Checked then 175 | c = c.parent 176 | else 177 | c = nil 178 | end 179 | end 180 | end 181 | 182 | 183 | if formMonoClass.miSortByClassFirst.Checked then 184 | table.sort(fields, formMonoClass.miSortFieldsByOffset.Checked and sortByClassThenOffset or sortByClassThenName) 185 | table.sort(methods, sortByClassThenName) 186 | else 187 | table.sort(fields, formMonoClass.miSortFieldsByOffset.Checked and sortByOffset or sortByName) 188 | table.sort(methods, sortByName) 189 | end 190 | 191 | self.fields = fields 192 | self.methods = methods 193 | formMonoClass.listFields.Items.Count = 0 194 | formMonoClass.listFields.Items.Count = #fields 195 | formMonoClass.listMethods.Items.Count = 0 196 | formMonoClass.listMethods.Items.Count = #methods 197 | end 198 | 199 | -- handler to display fields in list view 200 | function mono.formClass:listFields_OnData(sender, listitem) 201 | -- columns are offset (or 'STATIC'), Type, Name 202 | local index = listitem.Index + 1 203 | local field = self.fields[index] 204 | 205 | -- columns are Offset, Type, Name 206 | if field.isStatic then 207 | if field.isConst then 208 | listitem.Caption = 'Const:'..string.format('%2X', field.constValue or 0) 209 | else 210 | listitem.Caption = 'Static:'..string.format('%2X', field.offset or 0) 211 | end 212 | else 213 | listitem.Caption = string.format('%02X', field.offset or 0) 214 | end 215 | local className = '' 216 | if field.class.name ~= self.class.name then className = field.class.name end 217 | listitem.SubItems.text = table.concat({field.typeName or '??', field.name, className}, '\n') 218 | end 219 | 220 | -- handler to display methods in list view 221 | function mono.formClass:listMethods_OnData(sender, listitem) 222 | local method = self.methods[listitem.Index + 1] 223 | if method == nil then 224 | listitem.Caption = 'nil index '..tostring(listitem.Index + 1) 225 | else 226 | listitem.Caption = method.name 227 | 228 | local className = '' 229 | if method.class.name ~= self.class.name then className = method.class.name end 230 | 231 | local ps = {} 232 | for i,p in ipairs(method.parameters) do 233 | table.insert(ps, string.format('%s %s', p.type, p.name)) 234 | end 235 | local parms = method.returnType..' ('..table.concat(ps, ', ')..')' 236 | 237 | --print('className, method.class.name', className, method.class.name) 238 | listitem.SubItems.text = table.concat({ parms, className }, '\n') 239 | end 240 | end 241 | 242 | local getParameter = function(index, monoParam) 243 | local param = { index = index } 244 | if monoParam ~= nil then 245 | param.name = monoParam.name 246 | param.type = monoParam.type 247 | end 248 | end 249 | 250 | local parameters = { 'RCX', 'RDX', 'R8', 'R9', '[RBP+30]', '[RBP+38]', '[RBP+40]', '[RBP+48]', '[RBP+50]', '[RBP+58]', '[RBP+60]', '[RBP+68]', '[RBP+70]', '[RBP+78]' } 251 | local floatParameters = { 'XMM0', 'XMM1', 'XMM2', 'XMM3', '[RBP+30]', '[RBP+38]', '[RBP+40]', '[RBP+48]', '[RBP+50]', '[RBP+58]', '[RBP+60]', '[RBP+68]', '[RBP+70]', '[RBP+78]' } 252 | 253 | local parameters32 = { '[ebp+08]', '[ebp+0c]', '[ebp+10]', '[ebp+14]', '[ebp+18]', '[ebp+1c]', '[ebp+20]', '[ebp+24]', '[ebp+28]', '[ebp+2c]', '[ebp+30]', '[ebp+34]', '[ebp+38]', '[ebp+3c]' } 254 | 255 | local addMenuItem = function(popup, name, caption, func) 256 | local mi = createMenuItem(popup.Items) 257 | mi.Name = name 258 | mi.Caption = caption 259 | mi.OnClick = func 260 | popup.Items.add(mi) 261 | end 262 | 263 | --[[ 264 | Popup for methods 265 | ]] 266 | function mono.formClass:popupMethods_OnPopup(popup) 267 | local popup = formMonoClass.popupMethods 268 | popup.Items:clear() 269 | 270 | local method = self:getSelectedMethod() 271 | if method == nil then return end 272 | 273 | addMenuItem(popup, "miMethodsHook", "Hook", mono.formClass.methodHook) 274 | addMenuItem(popup, "miMethodsHookEntry", "Hook (Table Entry)", mono.formClass.methodHookEntry) 275 | addMenuItem(popup, "methodHookEntryOverridden", "Hook (Table Entry, Overridden)", mono.formClass.methodHookEntryOverridden) 276 | addMenuItem(popup, "miMethodsDisassemble", "Disassemble", mono.formClass.methodDisassemble) 277 | addMenuItem(popup, "miMethodsCreateTableScript", "Create Debug Entry", mono.formClass.methodCreateTableScript) 278 | end 279 | 280 | formMonoClass.popupMethods.OnPopup = function(sender) mono.formClass:popupMethods_OnPopup(sender) end 281 | 282 | mono.formClass.methodHookEntryOverridden = function() 283 | mono.formClass.methodHook(1, 1) 284 | end 285 | 286 | mono.formClass.methodHookEntry = function() 287 | mono.formClass.methodHook(1) 288 | end 289 | 290 | local methodHook64 = function(entry_flag, override_flag) 291 | local self = mono.formClass 292 | local method = self:getSelectedMethod() 293 | if method == nil then 294 | print("No method selected!") 295 | return 296 | end 297 | 298 | local address = mono_compile_method(method.id) 299 | local hookInfo = hookAt(address) 300 | -- have aobString, hookString, returnString, instructions 301 | --[[ how to get method signature 302 | local ps = {} 303 | for i,p in ipairs(method.parameters) do 304 | table.insert(ps, string.format('%s %s', p.type, p.name)) 305 | end 306 | local parms = method.returnType..' ('..table.concat(ps, ', ')..')' 307 | ]] 308 | 309 | local lines = {} 310 | 311 | if override_flag then 312 | local signature = mono_method_getSignature(method.id) 313 | table.insert(lines, '{$lua}') 314 | table.insert(lines, 'if syntaxcheck then return "define(hook,0)" end') 315 | table.insert(lines, 'local class_id = mono_findClass("'..method.class.namespace..'", "'..method.class.name..'")') 316 | table.insert(lines, 'local methods = mono_class_enumMethods(class_id)') 317 | table.insert(lines, 'for i = 1,#methods do') 318 | table.insert(lines, ' local m = methods[i]') 319 | table.insert(lines, ' if m.name == "'..method.name..'" and mono_method_getSignature(m.method) == "'..signature..'" then') 320 | table.insert(lines, ' local address = mono_compile_method(m.method)') 321 | table.insert(lines, ' return string.format("define(hook,%x)",address)') 322 | table.insert(lines, ' end') 323 | table.insert(lines, 'end') 324 | table.insert(lines, 'return nil, "COULD NOT FIND METHOD WITH SIGNATURE"') 325 | table.insert(lines, '{$asm}') 326 | --[[ This works in Underminer 327 | if syntaxcheck then return "define(hook,0)" end 328 | local class_id = mono_findClass("", "Inventory") 329 | local methods = mono_class_enumMethods(class_id) 330 | for i = 1,#methods do 331 | local m = methods[i] 332 | print(m.name.." signature: "..mono_method_getSignature(m.method)) 333 | if m.name == "TryRemoveItem" and mono_method_getSignature(m.method) == "Item,int" then 334 | local address = mono_compile_method(m.method) 335 | return string.format("define(hook,%x)",address) 336 | end 337 | end 338 | return nil, "COULD NOT FIND METHOD WITH SIGNATURE" 339 | ]] 340 | else 341 | table.insert(lines, "define(hook,"..hookInfo.hookString..")") 342 | end 343 | table.insert(lines, "define(bytes,"..hookInfo.aobString..")") 344 | table.insert(lines, "") 345 | table.insert(lines, "[enable]") 346 | table.insert(lines, "") 347 | table.insert(lines, "assert(hook, bytes)") 348 | table.insert(lines, "alloc(newmem,$1000, hook)") 349 | table.insert(lines, "{") 350 | 351 | 352 | -- note: per x64 calling convention, RCX might actually be space for 353 | -- a pre-allocated structure for the return value and other parameters 354 | -- might be moved one further down the list 355 | table.insert(lines, " RCX: "..method.class.name.." (this)") 356 | for i,p in ipairs(method.parameters) do 357 | local param = parameters[i + 1] 358 | if (p.type == "single" or p.type == "double" or p.type == "System.Single" or p.type == "System.Double") then param = floatParameters[i + 1] end 359 | table.insert(lines, " "..param..": "..tostring(p.type).." "..tostring(p.name)) 360 | end 361 | table.insert(lines, "") 362 | 363 | if (method.returnType == "single" or method.returnType == "double" or method.returnType == "System.Single" or method.returnType == "System.Double") then 364 | table.insert(lines, " Returns XMM0 ("..method.returnType..")") 365 | else 366 | table.insert(lines, " Returns RAX ("..method.returnType..")") 367 | end 368 | table.insert(lines, "}") 369 | table.insert(lines, "") 370 | table.insert(lines, "newmem:") 371 | table.insert(lines, " // original code") 372 | for i,c in ipairs(hookInfo.instructions) do 373 | table.insert(lines, " "..c) 374 | end 375 | table.insert(lines, " jmp hook+"..string.format("%X", hookInfo.returnOffset)) 376 | table.insert(lines, "") 377 | table.insert(lines, "hook:") 378 | table.insert(lines, " jmp long newmem") 379 | table.insert(lines, "") 380 | table.insert(lines, "[disable]") 381 | table.insert(lines, "") 382 | table.insert(lines, "hook:") 383 | table.insert(lines, " db bytes") 384 | table.insert(lines, "") 385 | table.insert(lines, "dealloc(newmem)") 386 | 387 | local t = {} 388 | for i,v in ipairs(lines) do 389 | table.insert(t, v); 390 | table.insert(t, "\r\n") 391 | end 392 | 393 | local aa = table.concat(t) 394 | 395 | if (entry_flag) then 396 | -- create table entry with hook code named after address 397 | local entry = getAddressList().createMemoryRecord() 398 | entry.setDescription("HOOK: "..method.class.name.."."..method.name) 399 | entry.Type = vtAutoAssembler -- must be set before setting 'Script' 400 | entry.Script = aa 401 | entry.Options = '[moHideChildren]' 402 | getAddressList().SelectedRecord = entry -- select new entry 403 | getAddressList().doValueChange() -- open editor 404 | else 405 | -- open up AA window with hook code 406 | getMemoryViewForm().AutoInject1.DoClick() 407 | 408 | for i=0,getFormCount()-1 do --this is sorted from z-level. 0 is top 409 | local f=getForm(i) 410 | 411 | if getForm(i).ClassName == 'TfrmAutoInject' then 412 | f.assemblescreen.Lines.Text = aa 413 | break 414 | end 415 | end 416 | end 417 | end 418 | 419 | local methodHook32 = function(entry_flag, override_flag) 420 | local self = mono.formClass 421 | local method = self:getSelectedMethod() 422 | if method == nil then 423 | print("No method selected!") 424 | return 425 | end 426 | 427 | local address = mono_compile_method(method.id) 428 | local hookInfo = hookAt(address) 429 | -- have aobString, hookString, returnString, instructions 430 | --[[ how to get method signature 431 | local ps = {} 432 | for i,p in ipairs(method.parameters) do 433 | table.insert(ps, string.format('%s %s', p.type, p.name)) 434 | end 435 | local parms = method.returnType..' ('..table.concat(ps, ', ')..')' 436 | ]] 437 | 438 | local lines = {} 439 | 440 | if override_flag then 441 | local signature = mono_method_getSignature(method.id) 442 | table.insert(lines, '{$lua}') 443 | table.insert(lines, 'if syntaxcheck then return "define(hook,0)" end') 444 | table.insert(lines, 'local class_id = mono_findClass("'..method.class.namespace..'", "'..method.class.name..'")') 445 | table.insert(lines, 'local methods = mono_class_enumMethods(class_id)') 446 | table.insert(lines, 'for i = 1,#methods do') 447 | table.insert(lines, ' local m = methods[i]') 448 | table.insert(lines, ' if m.name == "'..method.name..'" and mono_method_getSignature(m.method) == "'..signature..'" then') 449 | table.insert(lines, ' local address = mono_compile_method(m.method)') 450 | table.insert(lines, ' return string.format("define(hook,%x)",address)') 451 | table.insert(lines, ' end') 452 | table.insert(lines, 'end') 453 | table.insert(lines, 'return nil, "COULD NOT FIND METHOD WITH SIGNATURE"') 454 | table.insert(lines, '{$asm}') 455 | --[[ This works in Underminer 456 | if syntaxcheck then return "define(hook,0)" end 457 | local class_id = mono_findClass("", "Inventory") 458 | local methods = mono_class_enumMethods(class_id) 459 | for i = 1,#methods do 460 | local m = methods[i] 461 | print(m.name.." signature: "..mono_method_getSignature(m.method)) 462 | if m.name == "TryRemoveItem" and mono_method_getSignature(m.method) == "Item,int" then 463 | local address = mono_compile_method(m.method) 464 | return string.format("define(hook,%x)",address) 465 | end 466 | end 467 | return nil, "COULD NOT FIND METHOD WITH SIGNATURE" 468 | ]] 469 | else 470 | table.insert(lines, "define(hook,"..hookInfo.hookString..")") 471 | end 472 | table.insert(lines, "define(bytes,"..hookInfo.aobString..")") 473 | table.insert(lines, "") 474 | table.insert(lines, "[enable]") 475 | table.insert(lines, "") 476 | table.insert(lines, "assert(hook, bytes)") 477 | table.insert(lines, "alloc(newmem,$1000, hook)") 478 | table.insert(lines, "{") 479 | 480 | 481 | -- note: per x64 calling convention, RCX might actually be space for 482 | -- a pre-allocated structure for the return value and other parameters 483 | -- might be moved one further down the list 484 | table.insert(lines, " [ebp+08]: "..method.class.name.." (this)") 485 | for i,p in ipairs(method.parameters) do 486 | local param = parameters32[i + 1] 487 | table.insert(lines, " "..param..": "..tostring(p.type).." "..tostring(p.name)) 488 | end 489 | table.insert(lines, "") 490 | 491 | if (method.returnType == "single" or method.returnType == "double" or method.returnType == "System.Single" or method.returnType == "System.Double") then 492 | table.insert(lines, " Returns XMM0 ("..method.returnType..")") 493 | else 494 | table.insert(lines, " Returns EAX ("..method.returnType..")") 495 | end 496 | table.insert(lines, "}") 497 | table.insert(lines, "") 498 | table.insert(lines, "newmem:") 499 | table.insert(lines, " // original code") 500 | for i,c in ipairs(hookInfo.instructions) do 501 | table.insert(lines, " "..c) 502 | end 503 | table.insert(lines, " jmp hook+"..string.format("%X", hookInfo.returnOffset)) 504 | table.insert(lines, "") 505 | table.insert(lines, "hook:") 506 | table.insert(lines, " jmp long newmem") 507 | table.insert(lines, "") 508 | table.insert(lines, "[disable]") 509 | table.insert(lines, "") 510 | table.insert(lines, "hook:") 511 | table.insert(lines, " db bytes") 512 | table.insert(lines, "") 513 | table.insert(lines, "dealloc(newmem)") 514 | 515 | local t = {} 516 | for i,v in ipairs(lines) do 517 | table.insert(t, v); 518 | table.insert(t, "\r\n") 519 | end 520 | 521 | local aa = table.concat(t) 522 | 523 | if (entry_flag) then 524 | -- create table entry with hook code named after address 525 | local entry = getAddressList().createMemoryRecord() 526 | entry.setDescription("HOOK: "..method.class.name.."."..method.name) 527 | entry.Type = vtAutoAssembler -- must be set before setting 'Script' 528 | entry.Script = aa 529 | entry.Options = '[moHideChildren]' 530 | getAddressList().SelectedRecord = entry -- select new entry 531 | getAddressList().doValueChange() -- open editor 532 | else 533 | -- open up AA window with hook code 534 | getMemoryViewForm().AutoInject1.DoClick() 535 | 536 | for i=0,getFormCount()-1 do --this is sorted from z-level. 0 is top 537 | local f=getForm(i) 538 | 539 | if getForm(i).ClassName == 'TfrmAutoInject' then 540 | f.assemblescreen.Lines.Text = aa 541 | break 542 | end 543 | end 544 | end 545 | end 546 | 547 | mono.formClass.methodHook = function(entry_flag, override_flag) 548 | if targetIs64Bit() then 549 | methodHook64(entry_flag, override_flag) 550 | else 551 | methodHook32(entry_flag, override_flag) 552 | end 553 | end 554 | 555 | mono.formClass.methodDisassemble = function() 556 | local self = mono.formClass 557 | local method = self:getSelectedMethod() 558 | if method == nil then 559 | print("No method selected!") 560 | return 561 | end 562 | 563 | local address = mono_compile_method(method.id) 564 | getMemoryViewForm().DisassemblerView.SelectedAddress = address 565 | getMemoryViewForm().show() 566 | end 567 | 568 | --[[ 569 | -------------------------------------------------------------------------------- 570 | -- Currently working on 571 | -------------------------------------------------------------------------------- 572 | ]] 573 | 574 | local createTableScript64 = function() 575 | local self = mono.formClass 576 | local method = self:getSelectedMethod() 577 | if method == nil then 578 | print("No method selected!") 579 | return 580 | end 581 | 582 | local address = mono_compile_method(method.id) 583 | local hookInfo = hookAt(address) 584 | local pointerLabel = "p"..method.class.name.."_"..method.name 585 | 586 | local lines = {} 587 | table.insert(lines, "define(hook,"..hookInfo.hookString..")") 588 | table.insert(lines, "define(bytes,"..hookInfo.aobString..")") 589 | table.insert(lines, "") 590 | table.insert(lines, "[enable]") 591 | table.insert(lines, "") 592 | table.insert(lines, "assert(hook, bytes)") 593 | table.insert(lines, "alloc(newmem,$1000, hook)") 594 | table.insert(lines, "label("..pointerLabel..")") 595 | table.insert(lines, "") 596 | table.insert(lines, "{") 597 | 598 | 599 | -- note: per x64 calling convention, RCX might actually be space for 600 | -- a pre-allocated structure for the return value and other parameters 601 | -- might be moved one further down the list 602 | table.insert(lines, " RCX: "..method.class.name.." (this)") 603 | for i,p in ipairs(method.parameters) do 604 | local param = parameters[i + 1] 605 | if (p.type == "single" or p.type == "double" or p.type == "System.Single" or p.type == "System.Double") then param = floatParameters[i + 1] end 606 | table.insert(lines, " "..param..": "..tostring(p.type).." "..tostring(p.name)) 607 | end 608 | table.insert(lines, "") 609 | 610 | table.insert(lines, " Returns (RAX) "..method.returnType) 611 | table.insert(lines, "}") 612 | table.insert(lines, "") 613 | table.insert(lines, "newmem:") 614 | 615 | table.insert(lines, " // increment counter, store instance and parameters (could be off for static method?)") 616 | table.insert(lines, " push rbp") 617 | table.insert(lines, " mov rbp,rsp") 618 | table.insert(lines, " push rax") 619 | table.insert(lines, " mov ["..pointerLabel.."], rcx") 620 | table.insert(lines, " inc dword ptr ["..pointerLabel.."+8]") 621 | local parameterOffset = 0x10 622 | for i,p in ipairs(method.parameters) do 623 | local param = parameters[i + 1] 624 | if i < 4 then 625 | -- windows 64 ABI: first 3 parameters (plus 'this' in rcx) are in registers 626 | if (p.type == "single" or p.type == "double" or p.type == "System.Single" or p.type == "System.Double") then 627 | table.insert(lines, " movss ["..pointerLabel.."+"..string.format("%x", parameterOffset).."], "..floatParameters[i + 1].." // "..p.name) 628 | else 629 | table.insert(lines, " mov ["..pointerLabel.."+"..string.format("%x", parameterOffset).."], "..parameters[i + 1].." // "..p.name) 630 | end 631 | else 632 | -- doesn't really matter if it's float or not, we use [ebp+XX] as source and RAX as temp register to copy value 633 | table.insert(lines, " mov rax,[rbp+"..string.format("%x", parameterOffset + 0x08).."] // "..p.name) 634 | table.insert(lines, " mov ["..pointerLabel.."+"..string.format("%x", parameterOffset).."], rax") 635 | end 636 | parameterOffset = parameterOffset + 8 637 | end 638 | table.insert(lines, " pop rax") 639 | table.insert(lines, " pop rbp") 640 | table.insert(lines, "") 641 | 642 | table.insert(lines, " // original code") 643 | for i,c in ipairs(hookInfo.instructions) do 644 | table.insert(lines, " "..c) 645 | end 646 | table.insert(lines, " jmp hook+"..string.format("%X", hookInfo.returnOffset)) 647 | table.insert(lines, "") 648 | table.insert(lines, "align 10") 649 | table.insert(lines, pointerLabel..":") 650 | table.insert(lines, " dq 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0") 651 | table.insert(lines, "") 652 | 653 | table.insert(lines, "hook:") 654 | table.insert(lines, " jmp newmem") 655 | table.insert(lines, "") 656 | table.insert(lines, "registersymbol("..pointerLabel..")") 657 | table.insert(lines, "") 658 | table.insert(lines, "[disable]") 659 | table.insert(lines, "") 660 | table.insert(lines, "hook:") 661 | table.insert(lines, " db bytes") 662 | table.insert(lines, "") 663 | table.insert(lines, "unregistersymbol("..pointerLabel..")") 664 | table.insert(lines, "") 665 | table.insert(lines, "dealloc(newmem)") 666 | 667 | local t = {} 668 | for i,v in ipairs(lines) do 669 | table.insert(t, v); 670 | table.insert(t, "\r\n") 671 | end 672 | 673 | local aa = table.concat(t) 674 | 675 | local parent = getAddressList().createMemoryRecord() 676 | parent.setDescription(method.class.name..":"..method.name) 677 | parent.Type = vtAutoAssembler -- must be set before setting 'Script' 678 | parent.Script = aa 679 | parent.Options = '[moHideChildren]' 680 | getAddressList().SelectedRecord = parent -- select record 681 | 682 | addMemoryRecord(parent, pointerLabel, pointerLabel, vtQword, true) 683 | addMemoryRecord(parent, "Counter", pointerLabel.."+8", vtDword, false) 684 | parameterOffset = 0x10 685 | for i,p in ipairs(method.parameters) do 686 | local valueType = vtQword 687 | local showAsHex = false 688 | local param = parameters[i + 1] 689 | if (p.type == "single" or p.type == "System.Single") then 690 | valueType = vtSingle 691 | elseif (p.type == "double" or p.type == "System.Double") then 692 | valueType = vtDouble 693 | elseif (p.type == "int" or p.type == "System.Int32") then 694 | valueType = vtDword 695 | elseif (p.type == "long" or p.type == "System.Int64") then 696 | valueType = vtQword 697 | -- TODO: add pointer for string? 698 | else 699 | valueType = vtQword 700 | showAsHex = true 701 | end 702 | addMemoryRecord(parent, p.name.." ("..p.type..")", pointerLabel.."+"..string.format("%x", parameterOffset), valueType, showAsHex) 703 | parameterOffset = parameterOffset + 8 704 | end 705 | 706 | -- bring main window to front 707 | getMainForm().bringToFront() 708 | end 709 | 710 | local createTableScript32 = function() 711 | local self = mono.formClass 712 | local method = self:getSelectedMethod() 713 | if method == nil then 714 | print("No method selected!") 715 | return 716 | end 717 | 718 | local address = mono_compile_method(method.id) 719 | local hookInfo = hookAt(address) 720 | local pointerLabel = "p"..method.class.name.."_"..method.name 721 | 722 | local lines = {} 723 | table.insert(lines, "define(hook,"..hookInfo.hookString..")") 724 | table.insert(lines, "define(bytes,"..hookInfo.aobString..")") 725 | table.insert(lines, "") 726 | table.insert(lines, "[enable]") 727 | table.insert(lines, "") 728 | table.insert(lines, "assert(hook, bytes)") 729 | table.insert(lines, "alloc(newmem,$1000, hook)") 730 | table.insert(lines, "label("..pointerLabel..")") 731 | table.insert(lines, "") 732 | table.insert(lines, "{") 733 | 734 | -- note: per x64 calling convention, RCX might actually be space for 735 | -- a pre-allocated structure for the return value and other parameters 736 | -- might be moved one further down the list 737 | table.insert(lines, " Parameters:") 738 | table.insert(lines, " [ebp+08]: "..method.class.name.." (this)") 739 | for i,p in ipairs(method.parameters) do 740 | local param = parameters32[i + 1] 741 | table.insert(lines, " "..param..": "..tostring(p.type).." "..tostring(p.name)) 742 | end 743 | table.insert(lines, "") 744 | 745 | if method.returnType ~= "(System.Void)" then 746 | table.insert(lines, " Returns (EAX) "..method.returnType) 747 | end 748 | table.insert(lines, "}") 749 | table.insert(lines, "") 750 | table.insert(lines, "newmem:") 751 | 752 | table.insert(lines, " // increment counter, store instance and parameters (could be off for static method?)") 753 | table.insert(lines, " push ebp") 754 | table.insert(lines, " mov ebp,esp") 755 | table.insert(lines, " push eax") 756 | table.insert(lines, " mov eax,[ebp+08]") 757 | table.insert(lines, " mov ["..pointerLabel.."], eax") 758 | table.insert(lines, " inc dword ptr ["..pointerLabel.."+4]") 759 | local parameterOffset = 0x08 760 | for i,p in ipairs(method.parameters) do 761 | local param = parameters32[i + 1] 762 | table.insert(lines, " mov eax,"..param) 763 | table.insert(lines, " mov ["..pointerLabel.."+"..string.format("%x", parameterOffset).."],eax") 764 | parameterOffset = parameterOffset + 4 765 | end 766 | table.insert(lines, " pop eax") 767 | table.insert(lines, " pop ebp") 768 | table.insert(lines, "") 769 | 770 | table.insert(lines, " // original code") 771 | for i,c in ipairs(hookInfo.instructions) do 772 | table.insert(lines, " "..c) 773 | end 774 | table.insert(lines, " jmp hook+"..string.format("%X", hookInfo.returnOffset)) 775 | table.insert(lines, "") 776 | table.insert(lines, "align 10") 777 | table.insert(lines, pointerLabel..":") 778 | table.insert(lines, " dq 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0") 779 | table.insert(lines, "") 780 | 781 | table.insert(lines, "hook:") 782 | table.insert(lines, " jmp newmem") 783 | table.insert(lines, "") 784 | table.insert(lines, "registersymbol("..pointerLabel..")") 785 | table.insert(lines, "") 786 | table.insert(lines, "[disable]") 787 | table.insert(lines, "") 788 | table.insert(lines, "hook:") 789 | table.insert(lines, " db bytes") 790 | table.insert(lines, "") 791 | table.insert(lines, "unregistersymbol("..pointerLabel..")") 792 | table.insert(lines, "") 793 | table.insert(lines, "dealloc(newmem)") 794 | 795 | local t = {} 796 | for i,v in ipairs(lines) do 797 | table.insert(t, v); 798 | table.insert(t, "\r\n") 799 | end 800 | 801 | local aa = table.concat(t) 802 | 803 | local parent = getAddressList().createMemoryRecord() 804 | parent.setDescription(method.class.name..":"..method.name) 805 | parent.Type = vtAutoAssembler -- must be set before setting 'Script' 806 | parent.Script = aa 807 | parent.Options = '[moHideChildren]' 808 | getAddressList().SelectedRecord = parent -- select record 809 | 810 | addMemoryRecord(parent, pointerLabel, pointerLabel, vtDword, true) 811 | addMemoryRecord(parent, "Counter", pointerLabel.."+4", vtDword, false) 812 | parameterOffset = 0x08 813 | for i,p in ipairs(method.parameters) do 814 | local valueType = vtDword 815 | local showAsHex = false 816 | local param = parameters[i + 1] 817 | if (p.type == "single" or p.type == "System.Single") then 818 | valueType = vtSingle 819 | elseif (p.type == "double" or p.type == "System.Double") then 820 | valueType = vtDouble 821 | elseif (p.type == "int" or p.type == "System.Int32") then 822 | valueType = vtDword 823 | elseif (p.type == "long" or p.type == "System.Int64") then 824 | valueType = vtQword 825 | -- TODO: add pointer for string? 826 | else 827 | valueType = vtQword 828 | showAsHex = true 829 | end 830 | addMemoryRecord(parent, p.name.." ("..p.type..")", pointerLabel.."+"..string.format("%x", parameterOffset), valueType, showAsHex) 831 | parameterOffset = parameterOffset + 4 832 | end 833 | 834 | -- bring main window to front 835 | getMainForm().bringToFront() 836 | end 837 | 838 | mono.formClass.methodCreateTableScript = function() 839 | if targetIs64Bit() then 840 | createTableScript64() 841 | else 842 | createTableScript32() 843 | end 844 | end 845 | 846 | function addMemoryRecord(parent, description, address, type, hex) 847 | local memrec = getAddressList().createMemoryRecord() 848 | memrec.setDescription(description) 849 | memrec.Address = address 850 | memrec.Type = type 851 | memrec.ShowAsHex = hex 852 | memrec.appendToEntry(parent) -- also works: n.Parent = parent 853 | end 854 | 855 | 856 | function mono.formClass:getSelectedMethod() 857 | local index = formMonoClass.listMethods.ItemIndex 858 | if index < 0 then return nil end 859 | return self.methods[index + 1] 860 | end 861 | 862 | function mono.formClass:listMethods_OnDblClick(sender) 863 | local method = self.methods[sender.ItemIndex + 1] 864 | --print("method: "..tostring(method.id)) 865 | if method then 866 | local address = mono_compile_method(method.id) 867 | --print("address: "..tostring(address)) 868 | getMemoryViewForm().DisassemblerView.SelectedAddress = address 869 | getMemoryViewForm().show() 870 | local hookInfo = hookAt(address) 871 | -- have aobString, hookString, returnString, instructions 872 | --[[ how to get method signature 873 | local ps = {} 874 | for i,p in ipairs(method.parameters) do 875 | table.insert(ps, string.format('%s %s', p.type, p.name)) 876 | end 877 | local parms = method.returnType..' ('..table.concat(ps, ', ')..')' 878 | ]] 879 | 880 | local lines = {} 881 | table.insert(lines, "define(hook,"..hookInfo.hookString..")") 882 | table.insert(lines, "define(bytes,"..hookInfo.aobString..")") 883 | table.insert(lines, "") 884 | table.insert(lines, "[enable]") 885 | table.insert(lines, "") 886 | table.insert(lines, "assert(hook, bytes)") 887 | table.insert(lines, "alloc(newmem,$1000, hook)") 888 | table.insert(lines, "{") 889 | 890 | 891 | -- note: per x64 calling convention, RCX might actually be space for 892 | -- a pre-allocated structure for the return value and other parameters 893 | -- might be moved one further down the list 894 | table.insert(lines, " RCX: "..method.class.name.." (this)") 895 | for i,p in ipairs(method.parameters) do 896 | local param = parameters[i + 1] 897 | if (p.type == "single" or p.type == "double" or p.type == "System.Single" or p.type == "System.Double") then param = floatParameters[i + 1] end 898 | table.insert(lines, " "..param..": "..tostring(p.type).." "..tostring(p.name)) 899 | end 900 | table.insert(lines, "") 901 | 902 | table.insert(lines, " Returns (RAX) "..method.returnType) 903 | table.insert(lines, "}") 904 | table.insert(lines, "") 905 | table.insert(lines, "newmem:") 906 | table.insert(lines, " // original code") 907 | for i,c in ipairs(hookInfo.instructions) do 908 | table.insert(lines, " "..c) 909 | end 910 | table.insert(lines, " jmp hook+"..string.format("%X", hookInfo.returnOffset)) 911 | table.insert(lines, "") 912 | table.insert(lines, "hook:") 913 | table.insert(lines, " jmp newmem") 914 | table.insert(lines, "") 915 | table.insert(lines, "[disable]") 916 | table.insert(lines, "") 917 | table.insert(lines, "hook:") 918 | table.insert(lines, " db bytes") 919 | table.insert(lines, "") 920 | table.insert(lines, "dealloc(newmem)") 921 | 922 | local t = {} 923 | for i,v in ipairs(lines) do 924 | table.insert(t, v); 925 | table.insert(t, "\r\n") 926 | end 927 | 928 | local aa = table.concat(t) 929 | 930 | getMemoryViewForm().AutoInject1.DoClick() 931 | 932 | for i=0,getFormCount()-1 do --this is sorted from z-level. 0 is top 933 | local f=getForm(i) 934 | 935 | if getForm(i).ClassName == 'TfrmAutoInject' then 936 | f.assemblescreen.Lines.Text = aa 937 | break 938 | end 939 | end 940 | end 941 | end 942 | 943 | --[[ 944 | When double-clicking a field, print out the base address for the statics of the 945 | class and the address of the clicked-on static field. 946 | 947 | TODO: Create table entry that will use lua to find statics: 948 | * mono_class_getStaticFieldAddress(mono_enumDomains()[1], mono_findClass("", "GameManager")) 949 | --]] 950 | function mono.formClass:listFields_OnDblClick(sender) 951 | local field = self.fields[sender.ItemIndex + 1] 952 | if field then 953 | local class = field.class 954 | local image = class.image 955 | local domainId = image.domain 956 | print('double-clicked on class '..tostring(field.class.name)..' field '..tostring(field.name)..' domain '..tostring(domainId)) 957 | local address = mono_class_getStaticFieldAddress(domainId, class.id) 958 | print('statics base address: '..string.format("%x", address)) 959 | print(class.name..'.'..field.name..': '..string.format("%x", address + field.offset)) 960 | end 961 | end 962 | 963 | --[[ 964 | IN PROGRESS - take an address and create an AA script to hook at that address 965 | expecting MONO code. Will process instructions until 5 bytes (for jmp) are 966 | processed. Basic format is like this for address 'CryingSuns.PlayerStatus:BattleshipState:HasAuxiliarySystemType+28' 967 | 968 | // [enable] 969 | // assert("CryingSuns.PlayerStatus:BattleshipState:HasAuxiliarySystemType":+28, ) 970 | ]] 971 | function hookAtFar(address) 972 | local pos = string.find(address, "+", 1, true) 973 | local name = address 974 | local offset = 0 975 | if (pos ~= nil) then 976 | name = string.substring(1, pos - 1) 977 | offset = tonumber(string.sub(pos + 1), 16) 978 | end 979 | local actualAddress = getAddress(name) + offset 980 | 981 | local data = { 982 | hookString = util.safeAddress(getNameFromAddress(actualAddress)), -- used for injection, etc 983 | instructions = {}, 984 | aobString = "" 985 | } 986 | 987 | local aobs = {} 988 | 989 | while (offset < 14) do 990 | local parsed = disassembleAndParse(actualAddress + offset) 991 | if #aobs > 0 then table.insert(aobs, " ") end 992 | table.insert(aobs, parsed.bytesString) 993 | table.insert(data.instructions, parsed.instructionString) 994 | offset = offset + parsed.length 995 | end 996 | 997 | data.aobString = table.concat(aobs) 998 | data.returnString = util.safeAddress(getNameFromAddress(actualAddress + offset)) 999 | data.returnOffset = offset 1000 | return data 1001 | end 1002 | 1003 | function hookAt(address) 1004 | local pos = string.find(address, "+", 1, true) 1005 | local name = address 1006 | local offset = 0 1007 | if (pos ~= nil) then 1008 | name = string.substring(1, pos - 1) 1009 | offset = tonumber(string.sub(pos + 1), 16) 1010 | end 1011 | local actualAddress = getAddress(name) + offset 1012 | 1013 | local data = { 1014 | hookString = util.safeAddress(getNameFromAddress(actualAddress)), -- used for injection, etc 1015 | instructions = {}, 1016 | aobString = "" 1017 | } 1018 | 1019 | local aobs = {} 1020 | 1021 | while (offset < 5) do 1022 | local parsed = disassembleAndParse(actualAddress + offset) 1023 | if #aobs > 0 then table.insert(aobs, " ") end 1024 | table.insert(aobs, parsed.bytesString) 1025 | table.insert(data.instructions, "// "..parsed.instructionString) 1026 | offset = offset + parsed.length 1027 | end 1028 | 1029 | -- Use readmem to get all instruction bytes, but we only need to replace 5. 1030 | -- Many times the instructions will look like this: 1031 | -- FuelController:UseRecoverItem - 55 - push rbp 1032 | -- FuelController:UseRecoverItem+1- 48 8B EC - mov rbp,rsp 1033 | -- FuelController:UseRecoverItem+4- 48 83 EC 40 - sub rsp,40 { 64 } 1034 | -- Hooking will overwrite the sub rsp instruction, but we only need to overwrite 1035 | -- the '48 83' for the last 2 bytes of the jmp. Since this is a value 1036 | -- that frequently causes game updates to break scripts, we don't need 1037 | -- to have it hard-coded. 1038 | table.insert(data.instructions, "readmem(hook,"..tostring(offset)..")") 1039 | 1040 | data.aobString = table.concat(aobs):sub(1,14) -- 5 bytes is what we need, 14 characters 1041 | data.returnString = util.safeAddress(getNameFromAddress(actualAddress + offset)) 1042 | data.returnOffset = offset 1043 | return data 1044 | end 1045 | 1046 | function hookMethod(method) 1047 | print('hookMethod() is not implemented!') 1048 | end 1049 | 1050 | --[[ 1051 | Expects address to be a number 1052 | ]] 1053 | function disassembleAndParse(address) 1054 | local disassembly = disassemble(address) 1055 | local parts = util.split(disassembly, "-") 1056 | for i = 1,#parts do 1057 | if i == 2 then 1058 | parts[i] = parts[i]:gsub("%s+", "") -- remove ALL whitespace from bytes 1059 | else 1060 | parts[i] = parts[i]:gsub("^%s*(.-)%s*$", "%1") -- remove whitespace from ends 1061 | end 1062 | end 1063 | 1064 | local aob = {} 1065 | local i = 1 1066 | while i < string.len(parts[2]) do 1067 | if (i ~= 1) then table.insert(aob, " ") end 1068 | table.insert(aob, string.sub(parts[2], i, i+1)) 1069 | i = i + 2 1070 | end 1071 | 1072 | if (#parts > 3) then 1073 | local result = util.slice(parts, 3) 1074 | parts[3] = table.concat(result, '-') 1075 | end 1076 | 1077 | local instructionString = parts[3] 1078 | for k,v in parts[3]:gmatch("[0-9a-fA-F]+") do 1079 | if k:len() == 8 or k:len() == 16 then 1080 | instructionString = instructionString:gsub(k, getNameFromAddress(k)) 1081 | end 1082 | end 1083 | 1084 | local result = { 1085 | address = getAddress(address), 1086 | addressString = util.safeAddress(getNameFromAddress(address)), 1087 | aob = aob, 1088 | bytesString = table.concat(aob), 1089 | disassembly = disassembly, 1090 | instructionString = instructionString, 1091 | length = getInstructionSize(address), 1092 | originalInstructionString = parts[3] 1093 | } 1094 | 1095 | return result 1096 | end 1097 | -------------------------------------------------------------------------------- /src/lua/forms/formSearch.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | Form for searching an image. 3 | 4 | Controls: 5 | pageMain.tabSearch - main tab 6 | editSearchText: TCEEdit 7 | listSearchClasses: TCEListView 8 | listSearchFields: TCEListView 9 | listSearchMethods: TCEListView 10 | miImage: TMenuItem - main menu Image entry 11 | miImageSelectImage - entry to re-open select image form 12 | --]] 13 | 14 | mono.formSearch.found = mono.formSearch.found or {} 15 | mono.formSearch.found.classes = mono.formSearch.found.classes or {} 16 | mono.formSearch.found.fields = mono.formSearch.found.fields or {} 17 | mono.formSearch.found.methods = mono.formSearch.found.methods or {} 18 | 19 | --[[ 20 | Show the search form, mono.selectedImage should be set to the 21 | image to search. 22 | --]] 23 | function mono.formSearch:show() 24 | if mono.selectedImage == nil then return end 25 | self.image = mono.selectedImage 26 | 27 | -- setup event handlers 28 | formMonoSearch.miImageSelectImage.OnClick = function(sender) self:SelectImageClick() end 29 | 30 | formMonoSearch.listSearchClasses.OnData = function(sender, listitem) 31 | self:listSearchClasses_OnData(sender, listitem) 32 | end 33 | 34 | formMonoSearch.listSearchClasses.OnDblClick = function(sender) 35 | local class = self.found.classes[sender.ItemIndex + 1] 36 | mono.formClass:show(class, nil, nil) 37 | end 38 | 39 | formMonoSearch.listSearchFields.OnData = function(sender, listitem) 40 | self:listSearchFields_OnData(sender, listitem) 41 | end 42 | 43 | formMonoSearch.listSearchFields.OnDblClick = function(sender) 44 | local field = self.found.fields[sender.ItemIndex + 1] 45 | if field then 46 | mono.formClass:show(field.class, field, nil) 47 | end 48 | end 49 | 50 | formMonoSearch.listSearchMethods.OnData = function(sender, listitem) 51 | self:listSearchMethods_OnData(sender, listitem) 52 | end 53 | 54 | formMonoSearch.listSearchMethods.OnDblClick = function(sender) 55 | local method = self.found.methods[sender.ItemIndex + 1] 56 | if method then 57 | mono.formClass:show(method.class, nil, method) 58 | end 59 | end 60 | 61 | local onCheckedMenuClick = function(sender) 62 | sender.Checked = not sender.Checked 63 | mono.formSearch:search() 64 | end 65 | 66 | formMonoSearch.editSearchText.OnChange = function() mono.formSearch:search() end 67 | formMonoSearch.miFieldsNormal.OnClick = onCheckedMenuClick 68 | formMonoSearch.miFieldsConst.OnClick = onCheckedMenuClick 69 | formMonoSearch.miFieldsStatic.OnClick = onCheckedMenuClick 70 | 71 | -- popups 72 | formMonoSearch.listSearchClasses.PopupMenu = formMonoSearch.popupClasses 73 | local count = 0 74 | formMonoSearch.popupClasses.OnPopup = function(sender) 75 | -- print('formMonoSearch.popupClasses.OnPopup "'..tostring(sender.name)..'" count '..tostring(count)) 76 | count = count + 1 77 | end 78 | 79 | -- perform initial search to set 'found' results, probably empty text 80 | self:search() 81 | 82 | -- show form 83 | formMonoSearch.show() 84 | end 85 | 86 | --[[ 87 | When menu item to select an image is clicked, hide this form and 88 | show the image select form 89 | --]] 90 | function mono.formSearch:SelectImageClick() 91 | formMonoSearch.close() 92 | mono.formSelectImage:show() 93 | end 94 | 95 | --[[ 96 | When typing in the edit box to filter results, filter the lists of 97 | classes, fields, and methods with the lower case of the text 98 | --]] 99 | function mono.formSearch:search() 100 | local includeConst = formMonoSearch.miFieldsConst.Checked 101 | local includeStatic = formMonoSearch.miFieldsStatic.Checked 102 | local includeNormal = formMonoSearch.miFieldsNormal.Checked 103 | local text = formMonoSearch.editSearchText.Text 104 | 105 | local lower = nil 106 | if text ~= nil then lower = text:lower() end 107 | 108 | local classes = {} 109 | local methods = {} 110 | local fields = {} 111 | for i,class in ipairs(self.image.classes) do 112 | if lower == nil or class.lowerName:find(lower, 1, true) ~= nil then table.insert(classes, class) end 113 | for j,method in ipairs(class.methods) do 114 | if lower == nil or method.lowerName:find(lower, 1, true) ~= nil then table.insert(methods, method) end 115 | end 116 | for j,field in ipairs(class.fields) do 117 | if lower == nil or field.lowerName:find(lower, 1, true) ~= nil then 118 | if field.isConst then 119 | if includeConst then table.insert(fields, field) end 120 | elseif field.isStatic then 121 | if includeStatic then table.insert(fields, field) end 122 | else 123 | if includeNormal then table.insert(fields, field) end 124 | end 125 | end 126 | end 127 | end 128 | self.found.classes = classes 129 | self.found.fields = fields 130 | self.found.methods = methods 131 | formMonoSearch.listSearchClasses.Items.Count = #classes 132 | formMonoSearch.listSearchFields.Items.Count = #fields 133 | formMonoSearch.listSearchMethods.Items.Count = #methods 134 | end 135 | 136 | -- handler to display classes in list view 137 | function mono.formSearch:listSearchClasses_OnData(sender, listitem) 138 | local index = listitem.Index + 1 139 | local class = self.found.classes[index] 140 | listitem.Caption = class.name 141 | end 142 | 143 | -- handler to display fields in list view 144 | function mono.formSearch:listSearchFields_OnData(sender, listitem) 145 | local field = self.found.fields[listitem.Index + 1] 146 | if field == nil then 147 | listitem.Caption = 'nil index '..tostring(index) 148 | else 149 | listitem.Caption = field.name 150 | local staticString = "" 151 | if field.isStatic then staticString = "Static" end 152 | if field.isConst then staticString = "Const" end 153 | local xtra = { field.class.name, staticString } 154 | listitem.SubItems.text = table.concat(xtra, '\n') 155 | end 156 | end 157 | 158 | -- handler to display methods in list view 159 | function mono.formSearch:listSearchMethods_OnData(sender, listitem) 160 | local method = self.found.methods[listitem.Index + 1] 161 | if method == nil then 162 | listitem.Caption = 'nil index '..tostring(index) 163 | else 164 | listitem.Caption = method.name 165 | listitem.SubItems.text = method.class.name 166 | end 167 | end 168 | -------------------------------------------------------------------------------- /src/lua/forms/formSelectImage.lua: -------------------------------------------------------------------------------- 1 | local DEFAULT_ASSEMBLY_NAME = "Assembly-CSharp" 2 | local imageHasBeenSelected = false 3 | 4 | function mono.formSelectImage:show() 5 | LaunchMonoDataCollector() 6 | formMonoImage.listImages.Visible = true 7 | formMonoImage.buttonSelectImage.Visible = true 8 | formMonoImage.progressImage.Visible = false 9 | formMonoImage.labelMessage.Visible = false 10 | 11 | self.imageNames = mono.MonoImage.enumerate() 12 | table.sort(self.imageNames) 13 | local items = formMonoImage.listImages.Items 14 | items.Clear() 15 | 16 | local foundIndex = 0 17 | for i,name in ipairs(self.imageNames) do 18 | items.add(name) 19 | if name == DEFAULT_ASSEMBLY_NAME then foundIndex = i end 20 | end 21 | 22 | local handler = function(sender) 23 | mono.formSelectImage.OnSelectImage(self) 24 | end 25 | 26 | formMonoImage.buttonSelectImage.OnClick = handler 27 | formMonoImage.listImages.OnDblClick = handler 28 | 29 | formMonoImage.show() 30 | 31 | if foundIndex ~= 0 and imageHasBeenSelected == false then 32 | formMonoImage.listImages.ItemIndex = foundIndex - 1 33 | mono.formSelectImage:OnSelectImage() 34 | end 35 | end 36 | 37 | function mono.formSelectImage:OnSelectImage() 38 | imageHasBeenSelected = true 39 | local index = formMonoImage.listImages.ItemIndex + 1 40 | if self.imageNames == nil or index < 1 or index > #self.imageNames then return end 41 | local imageName = self.imageNames[index] 42 | 43 | formMonoImage.listImages.Visible = false 44 | formMonoImage.buttonSelectImage.Visible = false 45 | formMonoImage.progressImage.Visible = true 46 | formMonoImage.labelMessage.Visible = true 47 | formMonoImage.labelMessage.Caption = 'Finding classes...' 48 | 49 | local image = mono.MonoImage.new() 50 | 51 | formMonoImage.progressImage.Min = 0 52 | local progressHandler = function(done, image, message, count, total) 53 | if total ~= nil and total > 0 and count ~= nil and count >= 0 then 54 | message = string.format('%s %d/%d', message, count, total) 55 | formMonoImage.progressImage.Max = total 56 | formMonoImage.progressImage.Position = count 57 | end 58 | 59 | formMonoImage.labelMessage.Caption = message 60 | if done then self:OnImageComplete(image) end 61 | end 62 | 63 | image:init(imageName, progressHandler) 64 | end 65 | 66 | function mono.formSelectImage:OnImageComplete(image) 67 | self.image = image 68 | mono.selectedImage = image 69 | formMonoImage.Close() 70 | mono.formSearch:show() 71 | formMonoSearch:centerScreen() 72 | end 73 | -------------------------------------------------------------------------------- /src/lua/generators/index.lua: -------------------------------------------------------------------------------- 1 | [[-- #INCLUDEFILE(src/lua/generators/original_hook.lua) ]] 2 | -------------------------------------------------------------------------------- /src/lua/generators/original_hook.lua: -------------------------------------------------------------------------------- 1 | mono.generators = mono.generators or {} 2 | 3 | mono.generators.original_hook = function(method) 4 | if method == nil then return nil, nil end 5 | local address = mono_compile_method(method.id) 6 | end 7 | 8 | 9 | function mono.formClass:listMethods_OnDblClick(sender) 10 | local method = self.methods[sender.ItemIndex + 1] 11 | --print("method: "..tostring(method.id)) 12 | if method then 13 | local address = mono_compile_method(method.id) 14 | --print("address: "..tostring(address)) 15 | getMemoryViewForm().DisassemblerView.SelectedAddress = address 16 | getMemoryViewForm().show() 17 | local hookInfo = hookAt(address) 18 | -- have aobString, hookString, returnString, instructions 19 | --[[ how to get method signature 20 | local ps = {} 21 | for i,p in ipairs(method.parameters) do 22 | table.insert(ps, string.format('%s %s', p.type, p.name)) 23 | end 24 | local parms = method.returnType..' ('..table.concat(ps, ', ')..')' 25 | ]] 26 | 27 | local lines = {} 28 | table.insert(lines, "define(hook,"..hookInfo.hookString..")") 29 | table.insert(lines, "define(bytes,"..hookInfo.aobString..")") 30 | table.insert(lines, "") 31 | table.insert(lines, "[enable]") 32 | table.insert(lines, "") 33 | table.insert(lines, "assert(hook, bytes)") 34 | table.insert(lines, "alloc(newmem,$1000, hook)") 35 | table.insert(lines, "{") 36 | 37 | 38 | -- note: per x64 calling convention, RCX might actually be space for 39 | -- a pre-allocated structure for the return value and other parameters 40 | -- might be moved one further down the list 41 | table.insert(lines, " RCX: "..method.class.name.." (this)") 42 | for i,p in ipairs(method.parameters) do 43 | local param = parameters[i + 1] 44 | if (p.type == "single" or p.type == "double" or p.type == "System.Single" or p.type == "System.Double") then param = floatParameters[i + 1] end 45 | table.insert(lines, " "..param..": "..tostring(p.type).." "..tostring(p.name)) 46 | end 47 | table.insert(lines, "") 48 | 49 | table.insert(lines, " Returns (RAX) "..method.returnType) 50 | table.insert(lines, "}") 51 | table.insert(lines, "") 52 | table.insert(lines, "newmem:") 53 | table.insert(lines, " // original code") 54 | for i,c in ipairs(hookInfo.instructions) do 55 | table.insert(lines, " "..c) 56 | end 57 | table.insert(lines, " jmp hook+"..string.format("%X", hookInfo.returnOffset)) 58 | table.insert(lines, "") 59 | table.insert(lines, "hook:") 60 | table.insert(lines, " jmp newmem") 61 | table.insert(lines, "") 62 | table.insert(lines, "[disable]") 63 | table.insert(lines, "") 64 | table.insert(lines, "hook:") 65 | table.insert(lines, " db bytes") 66 | table.insert(lines, "") 67 | table.insert(lines, "dealloc(newmem)") 68 | 69 | local t = {} 70 | for i,v in ipairs(lines) do 71 | table.insert(t, v); 72 | table.insert(t, "\r\n") 73 | end 74 | 75 | local aa = table.concat(t) 76 | 77 | getMemoryViewForm().AutoInject1.DoClick() 78 | 79 | for i=0,getFormCount()-1 do --this is sorted from z-level. 0 is top 80 | local f=getForm(i) 81 | 82 | if getForm(i).ClassName == 'TfrmAutoInject' then 83 | f.assemblescreen.Lines.Text = aa 84 | break 85 | end 86 | end 87 | end 88 | end -------------------------------------------------------------------------------- /src/lua/mono.lua: -------------------------------------------------------------------------------- 1 | 2 | -- moduled 3 | mono = mono or {} 4 | 5 | -- classes 6 | mono.MonoClass = mono.MonoClass or {} 7 | mono.MonoField = mono.MonoField or {} 8 | mono.MonoMethod = mono.MonoMethod or {} 9 | mono.MonoImage = mono.MonoImage or {} 10 | 11 | mono.menu = mono.menu or {} 12 | 13 | mono.popups = mono.popups or {} 14 | 15 | mono.formSelectImage = mono.formSelectImage or {} 16 | mono.formSearch = mono.formSearch or {} 17 | mono.formClass = mono.formClass or {} 18 | 19 | -- defines 20 | mono.TYPE_NAMES = { 21 | [0] = 'END', 22 | [1] = 'void', 23 | [2] = 'bool', 24 | [3] = 'char', 25 | [4] = 'sbyte', 26 | [5] = 'byte', 27 | [6] = 'short', 28 | [7] = 'ushort', 29 | [8]= 'int', 30 | [9]= 'uint', 31 | [10] = 'long', 32 | [11] = 'ulong', 33 | [12] = 'float', 34 | [13] = 'double', 35 | [14] = 'string', 36 | [15] = 'ptr', 37 | [16] = 'byref', 38 | [17] = 'valuetype', 39 | [18] = 'class', 40 | [19] = 'var', 41 | [20] = 'array', 42 | [21] = 'genericinst', 43 | [22] = 'typedbyref', 44 | [24] = 'I', 45 | [25] = 'U', 46 | [0x1b] = 'FNPTR', 47 | [0x1c] = 'object', 48 | [0x1d] = 'szarray', -- 0-based one-dim-array 49 | [0x1e] = 'mvar', 50 | [0x1f] = 'CMOD_REQD', -- typedef or typeref token 51 | [0x20] = 'CMOD_OPT', -- optional arg: typedef or typeref token 52 | [0x21] = 'INTERNAL', 53 | [0x40] = 'MODIFIER', -- Or with the following types 54 | [0x41] = 'SENTINEL', -- sentinel for varargs method signature 55 | [0x45] = 'PINNED', -- local var that points to pinned object 56 | [0x55] = 'ENUM', -- an enumeration 57 | } 58 | 59 | mono.reset = function() 60 | -- close any open forms 61 | if formMonoClass ~= nil then 62 | formMonoClass.close() 63 | formMonoClass:destroy() 64 | end 65 | if formMonoImage ~= nil then 66 | formMonoImage.close() 67 | formMonoImage:destroy() 68 | end 69 | if formMonoSearch ~= nil then 70 | formMonoSearch.close() 71 | formMonoSearch:destroy() 72 | end 73 | 74 | -- unselect image, refs won't be correct if re-ran 75 | if mono then 76 | mono.selectedImage = nil 77 | if mono.timer then mono.clearTimer() end 78 | end 79 | end 80 | 81 | mono.clearTimer = function() 82 | if mono.timer then 83 | mono.timer.enabled = false 84 | mono.timer = nil 85 | end 86 | end 87 | 88 | -- if mono.hookedOnProcessOpened ~= nil then 89 | -- MainForm.OnOnProcessOpened = mono.hookedOnProcessOpened 90 | -- mono.hookedOnProcessOpened = nil 91 | -- else 92 | -- mono.hookedOnProcessOpened = MainForm.OnProcessOpened 93 | -- end 94 | 95 | -- MainForm.OnProcessOpened = function() 96 | -- if mono.hookedOnProcessOpened ~= nil then 97 | -- mono.reset() 98 | -- end 99 | 100 | 101 | [[-- #INCLUDEFILE(src/lua/mono/monofield.lua) ]] 102 | [[-- #INCLUDEFILE(src/lua/mono/monomethod.lua) ]] 103 | [[-- #INCLUDEFILE(src/lua/mono/monoclass.lua) ]] 104 | [[-- #INCLUDEFILE(src/lua/mono/monoimage.lua) ]] 105 | 106 | [[-- #INCLUDEFILE(src/lua/monomenu.lua) ]] 107 | 108 | [[-- #INCLUDEFILE(src/lua/generators/index.lua) ]] 109 | 110 | [[-- #INCLUDEFILE(src/lua/forms/formSelectImage.lua) ]] 111 | [[-- #INCLUDEFILE(src/lua/forms/formSearch.lua) ]] 112 | [[-- #INCLUDEFILE(src/lua/forms/formClass.lua) ]] 113 | -------------------------------------------------------------------------------- /src/lua/mono/MonoClass.lua: -------------------------------------------------------------------------------- 1 | local MonoClass = mono.MonoClass 2 | MonoClass.mt = { 3 | __index = MonoClass, 4 | __tostring = function(t) 5 | return 'MonoClass '..tostring(t.id)..' "'..tostring(t.name)..'"' 6 | end, 7 | __lt = function(a, b) 8 | return a.lowerName < b.lowerName 9 | end 10 | 11 | } 12 | 13 | local MonoField = mono.MonoField 14 | local MonoMethod = mono.MonoMethod 15 | 16 | --[[ 17 | Called from MonoImage:_init, 'c' is what is returned from enumerating classes. 18 | --]] 19 | function MonoClass.new(image, c) 20 | local obj = { 21 | name = c.classname, 22 | namespace = c.namespace, 23 | id = c.class, 24 | lowerName = string.lower(c.classname), 25 | image = image 26 | } 27 | 28 | setmetatable(obj, MonoClass.mt) 29 | return obj 30 | end 31 | 32 | function MonoClass:initFields() 33 | self.fields = {} 34 | self.fieldsByName = {} 35 | self.fieldsByLowerName = {} 36 | local constFieldCount = 0 37 | 38 | local temp = mono_class_enumFields(self.id) 39 | for i,f in ipairs(temp) do 40 | local field = MonoField.new(self, f) 41 | table.insert(self.fields, field) 42 | self.fieldsByLowerName[field.lowerName] = field; 43 | self.fieldsByName[field.name] = field 44 | 45 | if field.isStatic and field.isConst then 46 | field.constValue = constFieldCount 47 | constFieldCount = constFieldCount + 1 48 | end 49 | end 50 | 51 | table.sort(self.fields) 52 | end 53 | 54 | function MonoClass:initMethods() 55 | self.methods = {} 56 | self.methodsByName = {} 57 | self.methodsByLowerName = {} 58 | 59 | local temp = mono_class_enumMethods(self.id) 60 | for i,m in ipairs(temp) do 61 | local method = MonoMethod.new(self, m) 62 | table.insert(self.methods, method) 63 | self.methodsByName[method.name] = method 64 | self.methodsByLowerName[method.lowerName] = method 65 | end 66 | 67 | table.sort(self.methods) 68 | end 69 | -------------------------------------------------------------------------------- /src/lua/mono/MonoField.lua: -------------------------------------------------------------------------------- 1 | local MonoField = mono.MonoField 2 | MonoField.mt = { 3 | __index = MonoField, 4 | __tostring = function(t) 5 | return 'MonoField '..tostring(t.id)..' "'..tostring(t.name)..'"' 6 | end, 7 | __lt = function(a, b) 8 | return a.lowerName < b.lowerName 9 | end 10 | } 11 | 12 | -- class is a monoclass table, f is a table with results from 13 | -- mono_class_enumFields 14 | function MonoField.new(class, f) 15 | local obj = { 16 | class = class, 17 | id = f.field, 18 | name = f.name, 19 | isStatic = f.isStatic, 20 | isConst = f.isConst, 21 | offset = f.offset, 22 | monoType = f.monotype, 23 | lowerName = string.lower(f.name), 24 | foundTypeName = f.typeName, 25 | typeName = mono.TYPE_NAMES[f.monotype] or 'UnknownType'..tostring(f.monotype) 26 | } 27 | 28 | -- should be types where we might have a 'Class' 29 | --if f.monotype == 15 or f.monotype == 16 or f.monotype == 18 or f.monotype then 30 | --end 31 | 32 | obj.typeClassId = mono_field_getClass(obj.id) -- Fix change in CE 7.5.2 33 | if obj.typeClassId ~= nil then 34 | -- this is the 'type' of the field 35 | obj.typeClass = class.image.classesById[obj.typeClassId] 36 | if obj.typeClass ~= nil then 37 | obj.typeName = obj.typeClass.name 38 | else 39 | if class.image.missingNames[obj.typeClassId] then 40 | obj.typeName = class.image.missingNames[obj.typeClassId] 41 | else 42 | obj.typeName = mono_class_getFullName(obj.typeClassId) 43 | class.image.missingNames[obj.typeClassId] = obj.typeName 44 | end 45 | end 46 | end 47 | 48 | setmetatable(obj, MonoField.mt) 49 | return obj 50 | end 51 | 52 | function MonoField:getFullName() 53 | local s = "" 54 | if self.class.namespace ~= nil and self.class.namespace:len() > 0 then s = self.class.namespace..':' end 55 | s = s..self.class.name..'.' 56 | s = s..self.name 57 | return s 58 | end 59 | -------------------------------------------------------------------------------- /src/lua/mono/MonoImage.lua: -------------------------------------------------------------------------------- 1 | local MonoImage = mono.MonoImage 2 | MonoImage.mt = { 3 | __index = MonoImage, 4 | __tostring = function(t) 5 | return 'MonoImage '..tostring(t.name) 6 | end 7 | } 8 | 9 | local MonoClass = mono.MonoClass 10 | 11 | --[[ 12 | List names of images, a name can be passed to MonoImage.new() 13 | or MonoImage(name) to create a MonoImage instance. 14 | --]] 15 | function MonoImage.enumerate() 16 | local names = {} 17 | mono_enumImages(function(img) 18 | local name = mono_image_get_name(img) 19 | table.insert(names, name) 20 | end) 21 | table.sort(names) 22 | return names 23 | end 24 | 25 | --[[ 26 | Constructor doesn't do much, call :init(name, progress) 27 | --]] 28 | function MonoImage.new() 29 | local obj = { } 30 | setmetatable(obj, MonoImage.mt) 31 | return obj 32 | end 33 | 34 | --[[ 35 | init() takes the image name and an optional callback for reporting progress 36 | and when it is complete. The signature for this function is: 37 | function progress(complete, image, message, processed, total) 38 | This callback should be executed on the main CE thread. 39 | --]] 40 | function MonoImage:init(name, progress) 41 | if monopipe == nil then 42 | print('Launching mono data collector...') 43 | LaunchMonoDataCollector() 44 | end 45 | 46 | self.domains = mono_enumDomains() 47 | self.domain = self.domains[1] 48 | 49 | self.classes = {} -- straight list of all classes 50 | self.classesByName = {} -- dictionary for access by name 51 | self.classesByLowerName = {} -- for access by lower case name 52 | self.classesById = {} -- for access by id 53 | self.ignoredClassesByName = {} -- for ignoring classes in searches, etc. 54 | self.progress = progress 55 | self.name = name or 'Assembly-CSharp' -- intelligent default for unity 56 | 57 | -- get id 58 | self.id = nil 59 | mono_enumImages(function(img) 60 | local foundName = mono_image_get_name(img) 61 | if foundName == self.name then self.id = img end 62 | end) 63 | 64 | if not self.id then 65 | print('NO ID!') 66 | print('name is', name, self.name) 67 | self:report(false, 'Error finding image named '..tostring(name)) 68 | self.progress = nil 69 | self.total = nil 70 | self.count = nil 71 | return false 72 | end 73 | 74 | createThread(function(thread) 75 | self.thread = thread 76 | self:_init(thread) 77 | end) 78 | end 79 | 80 | --[[ 81 | This is the function called by init() on another thread 82 | --]] 83 | function MonoImage:_init(thread) 84 | --print('MonoImage:_init thread is', thread, 'self is', tostring(self)) 85 | self.thread = thread 86 | local temp = mono_image_enumClasses(self.id) 87 | -- thread.synchronize(function(th) 88 | -- print('Image '..tostring(self.id)..' has classes: '..tostring(temp)) 89 | -- end) 90 | 91 | self.classes = {} 92 | self.classesByName = {} 93 | self.classesByLowerName = {} 94 | self.classesById = {} 95 | self.missingNames = {} 96 | 97 | self.total = #temp 98 | self.count = 0 99 | self.message = "Getting classes" 100 | self:report(false, "Getting classes") 101 | 102 | -- populate classes without much information first so we can 103 | -- access our definitions when filling in parents, properties, etc. 104 | for i,c in ipairs(temp) do 105 | local class = MonoClass.new(self, c) 106 | if class.lowerName ~= nil then 107 | table.insert(self.classes, class) 108 | self.classesByName[class.name] = class 109 | self.classesByLowerName[class.lowerName] = class 110 | self.classesById[class.id] = class 111 | else 112 | --print("Nil lowername for class", c.class) 113 | end 114 | self.count = i 115 | if (i % 100 == 0 or i == #temp) then 116 | self:report(false, 'Fetching classes') 117 | end 118 | end 119 | table.sort(self.classes) 120 | 121 | for i,class in ipairs(self.classes) do 122 | class.parentId = mono_class_getParent(class.id) 123 | class.parent = self.classesById[class.parentId] 124 | self.count = i 125 | if (i % 100 == 0 or i == #temp) then 126 | self:report(false, 'Fetching parents') 127 | end 128 | end 129 | 130 | self.count = 0 131 | for i,class in ipairs(self.classes) do 132 | class:initFields() 133 | class:initMethods() 134 | self.count = i 135 | if (i % 100 == 0 or i == #temp) then 136 | self:report(false, 'Initializing fields and methods') 137 | end 138 | end 139 | 140 | self:report(true, 'Done') 141 | end 142 | 143 | --[[ 144 | Report progress on the main CE thread to do gui updates 145 | --]] 146 | function MonoImage:report(done, message) 147 | if self.progress ~= nil and self.thread ~= nil then 148 | self.thread.synchronize(function(thread) 149 | self.progress(done, self, message, self.count, self.total) 150 | end) 151 | end 152 | 153 | if done then 154 | -- do cleanup here 155 | self.count = nil 156 | self.total = nil 157 | self.progress = nil 158 | self.thread = nil 159 | end 160 | end 161 | 162 | 163 | --[[ Sample code... weird 164 | 165 | --return util.pretty(mono.MonoImage.enumerate()) 166 | mi = mono.MonoImage.new() 167 | print('mi is "'..tostring(mi)..'"') 168 | mi:init('Assembly-CSharp', function(c, i, m, p, t) 169 | print(m, p, t) 170 | end) 171 | 172 | --LaunchMonoDataCollector() 173 | local thread = createThread(function(th) 174 | TESTIMAGES = {} 175 | mono_enumImages(function(img) 176 | th.synchronize(function() print(img) end) 177 | end) 178 | end) 179 | 180 | --]] 181 | -------------------------------------------------------------------------------- /src/lua/mono/MonoMethod.lua: -------------------------------------------------------------------------------- 1 | local MonoMethod = mono.MonoMethod 2 | MonoMethod.mt = { 3 | __index = MonoMethod, 4 | __tostring = function(t) 5 | return 'MonoMethod '..tostring(t.id)..' "'..tostring(t.name)..'"' 6 | end, 7 | __lt = function(a, b) 8 | return a.lowerName < b.lowerName 9 | end 10 | } 11 | 12 | function MonoMethod.new(class, m) 13 | local obj = { 14 | class = class, 15 | id = m.method, 16 | name = m.name, 17 | lowerName = string.lower(m.name), 18 | parameters = {} 19 | } 20 | setmetatable(obj, MonoMethod.mt) 21 | 22 | lastmethod = obj 23 | local types, parameternames, returntype = mono_method_getSignature(obj.id) 24 | local typenames={} 25 | local tn 26 | if types ~= nil then 27 | for tn in string.gmatch(types, '([^,]+)') do 28 | table.insert(typenames, tn) 29 | end 30 | end 31 | 32 | for i=1,#typenames do 33 | table.insert(obj.parameters, { name = parameternames[i], type = typenames[i] }) 34 | end 35 | obj.returnType = returntype 36 | 37 | return obj 38 | end 39 | 40 | --[[ 41 | Get parameters for method 42 | --]] 43 | function MonoMethod:fetchParms() 44 | if self.parms ~= nil then return nil end 45 | if self.class.image.ignoredClassesByName[self.class.name] ~= nil then return nil end 46 | local status, parms = pcall(function() 47 | local result = mono_method_get_parameters(self.id) 48 | return result 49 | end) 50 | 51 | if status then 52 | -- success! 53 | self.parms = parms 54 | else 55 | self.class.image.ignoredClassesByName[self.class.name] = true 56 | print('Error with class '..tostring(self.class.name)..' method '..tostring(self.name)) 57 | error('Error fetching parameters for '..tostring(self.class.name)..'.'..tostring(self.name)) 58 | end 59 | end 60 | -------------------------------------------------------------------------------- /src/lua/monomenu.lua: -------------------------------------------------------------------------------- 1 | if mono.menu.timer then 2 | mono.menu.timer.destroy() 3 | mono.menu.timer = nil 4 | end 5 | 6 | function mono.menu:init() 7 | --if self.menuSearch or self.timer then return end 8 | self.timer = createTimer() 9 | self.timer.Interval = 1000 10 | self.timer.OnTimer = function(timer) 11 | -- wait for normal mono script to create mono menu, check every 5 seconds 12 | if not miMonoTopMenuItem then return end 13 | 14 | self.timer.destroy() 15 | self.timer = nil 16 | 17 | local existing = util.getSubmenuByCaption(miMonoTopMenuItem, 'Search') 18 | if existing ~= nil then self.menuSearch = existing else self.menuSearch = createMenuItem(miMonoTopMenuItem) end 19 | self.menuSearch.Caption = 'Search' 20 | self.menuSearch.Name = 'miMonoSearch' 21 | self.menuSearch.OnClick = function(sender) self:OnSearchClick() end 22 | if existing == nil then miMonoTopMenuItem.add(self.menuSearch) end 23 | end 24 | end 25 | 26 | function mono.menu:OnSearchClick() 27 | if mono.selectedImage then 28 | mono.formSearch:show() 29 | formMonoSearch:centerScreen() 30 | else 31 | mono.formSelectImage:show() 32 | end 33 | end 34 | 35 | mono.menu:init() 36 | -------------------------------------------------------------------------------- /src/lua/temp/README.md: -------------------------------------------------------------------------------- 1 | # This folder has code I'm messing around with, not part of the main project yet. -------------------------------------------------------------------------------- /src/lua/temp/monopopups.lua: -------------------------------------------------------------------------------- 1 | mono.popups.class = mono.popups.class or {} 2 | mono.popups.field = mono.popups.field or {} 3 | mono.popups.method = mono.popups.method or {} 4 | 5 | --[[ 6 | Popup on a class will let you open the class and add/edit/remove 7 | a note. It is recreated each time when opened. 8 | --]] 9 | function mono.popups.class:update(menu, class, openedClass) 10 | end 11 | -------------------------------------------------------------------------------- /src/lua/temp/notes.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | A note has 'key' and 'text' fields, lists are sorted by key. 3 | --]] 4 | 5 | notes = notes or {} 6 | notesmt = notesmt or {} 7 | 8 | note = note or {} 9 | notemt = notemt or {} 10 | 11 | notemt.__index = note 12 | notemt.__lt = function(a,b) return a.key < b.key end 13 | 14 | function notes.new(filename, useTableFile) 15 | local obj = {} 16 | setmetatable(obj, {__index = notes}) 17 | obj:load(filename, useTableFile) 18 | return obj 19 | end 20 | 21 | function notes:load(filename, useTableFile) 22 | self.filename = filename 23 | self.useTableFile = useTableFile 24 | local s = loadTextFile(self.filename, self.useTableFile) 25 | self.dict = {} 26 | if s then self.dict = loadstring(s)() end 27 | self.lastSave = os.clock() 28 | self.lastChange = 0 29 | end 30 | 31 | function notes:save() 32 | local text = util.serialize(self.dict) 33 | saveTextFile(self.filename, text, self.useTableFile) 34 | self.lastSave = os.clock() 35 | end 36 | 37 | function notes:saveAs(filename, useTableFile) 38 | self.filename = filename 39 | self.useTableFile = useTableFile 40 | self:save() 41 | end 42 | 43 | 44 | function notes:update(key, text) 45 | if text == nil or string.len(text) == 0 then 46 | if self.dict[key] then self.dict[key] = nil end 47 | else 48 | self.dict[key] = {key = key, text = text} 49 | end 50 | self.lastUpdate = os.clock() 51 | end 52 | 53 | function notes:getList() 54 | local keys = {} 55 | for k,n in pairs(self.dict) do table.insert(keys, n) end 56 | return keys 57 | end 58 | -------------------------------------------------------------------------------- /src/lua/util.lua: -------------------------------------------------------------------------------- 1 | util = util or {} 2 | 3 | util.loadTextFile = function (name) 4 | local path = getMainForm().openDialog1.InitialDir..name 5 | local f, err = io.open(path, "r") 6 | -- fall back to table file if disk file error (doesn't exist) 7 | if f == nil then return loadTextFile(name, true) end 8 | local text = f:read("*all") 9 | f:close() 10 | return text 11 | end 12 | 13 | --[[ 14 | Save a string to a text file. If useTableFile is true it will be saved as 15 | a TableFile. The directory should be where the cheat file is, it is the 16 | initial directory for the dialog when you are saving your cheat table. 17 | --]] 18 | util.saveTextFile = function(name, text) 19 | local path = getMainForm().saveDialog1.InitialDir..name 20 | local f, err = io.open(path, "w") 21 | if f == nil then return nil, err end 22 | f:write(text) 23 | f:close() 24 | return true 25 | end 26 | 27 | util.getSubmenuByCaption = function(menuItem, caption) 28 | for i = 0, menuItem.getCount() - 1 do 29 | if menuItem.Item[i].Caption == caption then return menuItem.Item[i] end 30 | end 31 | return nil 32 | end 33 | 34 | --[[ 35 | For mono symbols, you have to surround them in quotes for use in AA 36 | ]] 37 | function util.safeAddress(s) 38 | local tbl = {} 39 | for a in string.gmatch(s, "([^+]+)[+]?") do table.insert(tbl, a) end 40 | local result = '"'..tbl[1]..'"' 41 | if #tbl > 1 then result = result..'+'..tbl[2] end 42 | return result 43 | end 44 | 45 | function util.split(s, separator) 46 | local tbl = {} 47 | for str in string.gmatch(s, "([^"..separator.."]+)") do table.insert(tbl, str) end 48 | return tbl 49 | end 50 | 51 | function util.trim(s) 52 | return (s:gsub("^%s*(.-)%s*$", "%1")) 53 | end 54 | 55 | --[[ 56 | Pretty-print serialize the passed object and return the results 57 | as a string. This does nice indenting and follows metatables. It 58 | is not guaranteed to provide the same object back if the string is 59 | loaded because it just replaces circular references with a string 60 | among other things. It does try to keep tables/arrays on one line 61 | if there is room (120 characters). 62 | --]] 63 | function util.pretty(value, funcs, indent, done, stack) 64 | indent = indent or 0 65 | funcs = funcs or {} 66 | done = done or {} 67 | stack = stack or { {value=value} } -- initialize with passed value, no key 68 | 69 | -- if a 'userdata', use metatable 70 | local l = value 71 | if type(value) == "userdata" then 72 | l = getmetatable(value) 73 | end 74 | 75 | if type(l) == "table" then 76 | -- if we've already serialized the table, return string representation 77 | -- to avoid self-referencing in recursion. Only the first serialization 78 | -- will have the full serialized string 79 | if done[l] then return tostring(l) end 80 | done[l] = true 81 | 82 | local list = {} 83 | 84 | -- here we return "{", the values, and "}" 85 | if (#l > 0) then 86 | -- if we have a count ('#'), then it's just an array of values 87 | for i,v in ipairs(l) do 88 | table.insert(list, util.pretty(v, funcs, indent + 2, done, stack)) 89 | end 90 | else 91 | -- no count, so either empty or object with key/value 92 | -- first we sort the keys 93 | local keys = {} 94 | for k,v in pairs(l) do table.insert(keys, k) end 95 | table.sort(keys) 96 | 97 | local last = {key = "UNKNOWN", value = "NONE"} 98 | table.insert(stack, last) 99 | for i,k in ipairs(keys) do 100 | local propValue = value[k] 101 | last.key = k 102 | last.value = value[k] 103 | for j,f in ipairs(funcs) do 104 | local returnValue = f(value, k, value[k], stack, indent) 105 | if returnValue ~= nil then 106 | propValue = returnValue 107 | break 108 | end 109 | end 110 | table.insert(list, string.format("%s = %s", tostring(k), util.pretty(propValue, funcs, indent+2, done, stack))) 111 | end 112 | table.remove(stack, #stack) 113 | end 114 | 115 | -- now we find the total size, add 200 if there are line breaks 116 | local size = 0 117 | for i,v in ipairs(list) do 118 | size = size + string.len(v) + 2 119 | if string.find(v, "\r\n") then 120 | size = 200 121 | break; 122 | end 123 | end 124 | -- if it's small enough, join with commas 125 | if (size < 120) then 126 | local result = {"{ "} 127 | for i,v in ipairs(list) do 128 | if i > 1 then table.insert(result, ", ") end 129 | table.insert(result, v) 130 | end 131 | table.insert(result, " }") 132 | return table.concat(result); 133 | else 134 | local indentString1 = string.rep(" ", indent) 135 | local indentString2 = string.rep(" ", indent + 2) 136 | local result = {"{\r\n"} 137 | for i,v in ipairs(list) do 138 | table.insert(result, indentString2) 139 | table.insert(result, v) 140 | if (i < #list) then table.insert(result, ",") end 141 | table.insert(result, "\r\n") 142 | end 143 | -- table.insert(result, indentString) 144 | table.insert(result, indentString1) 145 | table.insert(result, "}") 146 | return table.concat(result); 147 | end 148 | end 149 | 150 | if type(value) == "string" then 151 | return string.format("\"%s\"", value) 152 | end 153 | return tostring(value) 154 | end 155 | 156 | 157 | 158 | --[[ 159 | Values and functions used by util.serialize(t) 160 | ---]] 161 | 162 | local oddvals = {[tostring(1/0)] = '1/0', [tostring(-1/0)] = '-1/0', [tostring(-(0/0))] = '-(0/0)', [tostring(0/0)] = '0/0'} 163 | 164 | local kw = {['and'] = true, ['break'] = true, ['do'] = true, ['else'] = true, 165 | ['elseif'] = true, ['end'] = true, ['false'] = true, ['for'] = true, 166 | ['function'] = true, ['goto'] = true, ['if'] = true, ['in'] = true, 167 | ['local'] = true, ['nil'] = true, ['not'] = true, ['or'] = true, 168 | ['repeat'] = true, ['return'] = true, ['then'] = true, ['true'] = true, 169 | ['until'] = true, ['while'] = true} 170 | 171 | local getchr = function(c) 172 | return "\\" .. c:byte() 173 | end 174 | 175 | local make_safe = function(text) 176 | return ("%q"):format(text):gsub('\n', 'n'):gsub("[\128-\255]", getchr) 177 | end 178 | 179 | local write = function(t, memo, rev_memo) 180 | local ty = type(t) 181 | if ty == 'number' then 182 | t = format("%.17g", t) 183 | return oddvals[t] or t 184 | elseif ty == 'boolean' or ty == 'nil' then 185 | return tostring(t) 186 | elseif ty == 'string' then 187 | return make_safe(t) 188 | elseif ty == 'table' or ty == 'function' then 189 | if not memo[t] then 190 | local index = #rev_memo + 1 191 | memo[t] = index 192 | rev_memo[index] = t 193 | end 194 | return '_[' .. memo[t] .. ']' 195 | else 196 | error("Trying to serialize unsupported type " .. ty) 197 | end 198 | end 199 | 200 | local write_key_value_pair = function(k, v, memo, rev_memo, name) 201 | if type(k) == 'string' and k:match '^[_%a][_%w]*$' and not kw[k] then 202 | return (name and name .. '.' or '') .. k ..'=' .. write(v, memo, rev_memo) 203 | else 204 | return (name or '') .. '[' .. write(k, memo, rev_memo) .. ']=' .. write(v, memo, rev_memo) 205 | end 206 | end 207 | 208 | local is_cyclic = function(memo, sub, super) 209 | local m = memo[sub] 210 | local p = memo[super] 211 | return m and p and m < p 212 | end 213 | 214 | local write_table_ex = function(t, memo, rev_memo, srefs, name) 215 | if type(t) == 'function' then 216 | return '_[' .. name .. ']=loadstring' .. make_safe(string.dump(t)) 217 | end 218 | local m = {} 219 | local mi = 1 220 | for i = 1, #t do -- don't use ipairs here, we need the gaps 221 | local v = t[i] 222 | if v == t or is_cyclic(memo, v, t) then 223 | srefs[#srefs + 1] = {name, i, v} 224 | m[mi] = 'nil' 225 | mi = mi + 1 226 | else 227 | m[mi] = write(v, memo, rev_memo) 228 | mi = mi + 1 229 | end 230 | end 231 | for k,v in pairs(t) do 232 | if type(k) ~= 'number' or math.floor(k) ~= k or k < 1 or k > #t then 233 | if v == t or k == t or is_cyclic(memo, v, t) or is_cyclic(memo, k, t) then 234 | srefs[#srefs + 1] = {name, k, v} 235 | else 236 | m[mi] = write_key_value_pair(k, v, memo, rev_memo) 237 | mi = mi + 1 238 | end 239 | end 240 | end 241 | return '_[' .. name .. ']={' .. table.concat(m, ',') .. '}' 242 | end 243 | 244 | 245 | --[[ 246 | A function more appropriate for serializing objects in a reproduceable way. 247 | --]] 248 | function util.serialize(t) 249 | local memo = {[t] = 0} 250 | local rev_memo = {[0] = t} 251 | local srefs = {} 252 | local result = {} 253 | 254 | -- phase 1: recursively descend the table structure 255 | local n = 0 256 | while rev_memo[n] do 257 | result[n + 1] = write_table_ex(rev_memo[n], memo, rev_memo, srefs, n) 258 | n = n + 1 259 | end 260 | 261 | -- phase 2: reverse order 262 | for i = 1, n*.5 do 263 | local j = n - i + 1 264 | result[i], result[j] = result[j], result[i] 265 | end 266 | 267 | -- phase 3: add all the tricky cyclic stuff 268 | for i, v in ipairs(srefs) do 269 | n = n + 1 270 | result[n] = write_key_value_pair(v[2], v[3], memo, rev_memo, '_[' .. v[1] .. ']') 271 | end 272 | 273 | -- phase 4: add something about returning the main table 274 | if result[n]:sub(1, 5) == '_[0]=' then 275 | result[n] = 'return ' .. result[n]:sub(6) 276 | else 277 | result[n + 1] = 'return _[0]' 278 | end 279 | 280 | -- phase 5: just concatenate everything 281 | result = table.concat(result, '\n') 282 | return n > 1 and 'local _={}\n' .. result or result 283 | end 284 | 285 | function util.map(t, f) 286 | local results = {} 287 | local func = f or tostring 288 | for i,v in ipairs(t) do 289 | table.insert(results, func(v)) 290 | end 291 | return results 292 | end 293 | 294 | function util.slice(t, index) 295 | local result = {} 296 | for i = index, #t do table.insert(result, t[i]) end 297 | return result 298 | end 299 | -------------------------------------------------------------------------------- /src/samples/CreateMemoryRecord.lua: -------------------------------------------------------------------------------- 1 | local parent = getAddressList().getMemoryRecordByDescription("PersonController:Update") 2 | -- return memrec.getType() -- type 11 3 | 4 | local n = getAddressList().createMemoryRecord() 5 | n.setDescription('New Record') 6 | n.appendToEntry(parent) -- also works: n.Parent = parent 7 | n.Type = vtAutoAssembler -- must be set before setting 'Script' 8 | n.Script = [[[enable] 9 | [disable] 10 | ]] 11 | getAddressList().SelectedRecord = n -- select record 12 | n.Active = true 13 | print('new record id:'..tostring(n.id)) 14 | return "DONE!" 15 | 16 | --[[ other things: 17 | 18 | ShowAsHex = true for address types 19 | types can be: 20 | vtDword - for counter 21 | vtQword - for pointer 22 | vtSingle 23 | vtDouble 24 | vtString 25 | 26 | Set 'Selected = true' 27 | Set 'Active = true' 28 | 29 | memrec.Color = 0xff0000 -- blue 30 | 31 | IsGroupHeader - for record that is ONLY group header, maybe create header for script? 32 | 33 | Interesting, you can set DropDownList to StringList, in 'value:description' lines, lets you select options? 34 | 35 | Can use 'DontSave' for temp things 36 | 37 | Can set Collapsed = true or false 38 | 39 | Can set OffsetCount and Offset[index] for pointers 40 | 41 | ]] 42 | 43 | -------------------------------------------------------------------------------- /src/samples/Dev.CT.lua: -------------------------------------------------------------------------------- 1 | -- table script for Dev.CT table 2 | 3 | --[[-------------------------------------------------------------------------------- 4 | Dev table script. Load this into a cheat table in the root directory of this 5 | repository, or into Dev.CT 6 | --]] 7 | 8 | -- only execute once 9 | 10 | DevMenu = DevMenu or {} 11 | 12 | if DevMenu.miTopMenuItem == nil then 13 | -- does not exist, create new menu item second from last (before 'Help') 14 | local mfm=getMainForm().Menu 15 | DevMenu.miTopMenuItem = createMenuItem(mfm) -- create child 16 | DevMenu.miTopMenuItem.Caption = "Dev (MonoHelper)" 17 | mfm.Items.insert(mfm.Items.Count - 1, DevMenu.miTopMenuItem) --add near end before last item ('Help') 18 | else 19 | -- exists already, clear children to recreate 20 | DevMenu.miTopMenuItem.clear() 21 | end 22 | 23 | local mi -- re-used 24 | 25 | mi = createMenuItem(DevMenu.miTopMenuItem) 26 | mi.Caption = "Build and Reload LUA" 27 | mi.OnClick = function() 28 | local all, forms, lua = loadfile(getMainForm().openDialog1.InitialDir.."Build/build.lua")() 29 | loadstring(lua)() 30 | print('Reloaded lua!') 31 | end 32 | mi.Name = 'miDevReloadLua' 33 | DevMenu.miTopMenuItem.Add(mi) 34 | DevMenu.miDevReloadLua = mi 35 | 36 | mi = createMenuItem(DevMenu.miTopMenuItem) 37 | mi.Caption = "Build Only" 38 | mi.OnClick = function() 39 | local all, forms, lua = loadfile(getMainForm().openDialog1.InitialDir.."Build/build.lua")() 40 | showMessage("Output is in Build/output/monohelper.lua, if you've changed the forms, save each one you changed to 'src/forms' and build again") 41 | end 42 | mi.Name = 'miDevBuildLua' 43 | DevMenu.miTopMenuItem.Add(mi) 44 | DevMenu.miDevBuildLua = mi 45 | 46 | -- menu item will build, then copy the file to the CE autorun folder, easy to close and restart CE to see changes 47 | mi = createMenuItem(DevMenu.miTopMenuItem) 48 | mi.Caption = "Build and copy to CE autorun directory" 49 | mi.OnClick = function() 50 | local all, forms, lua = loadfile(getMainForm().openDialog1.InitialDir.."Build/build.lua")() 51 | local path = getCheatEngineDir()..[[\autorun\monohelper.lua]] 52 | local f, err = io.open(path, "w") 53 | if f == nil then return nil, err end 54 | f:write(all) 55 | f:close() 56 | showMessage("Restart CE to see changes") 57 | end 58 | mi.Name = 'miDevBuildToCEAutorunDirectory' 59 | DevMenu.miTopMenuItem.Add(mi) 60 | DevMenu.miDevBuildAutorunLua = mi 61 | 62 | -- menu item will build, then create a new table entry to run the script you 63 | -- can copy to another table 64 | mi = createMenuItem(DevMenu.miTopMenuItem) 65 | mi.Caption = "Build and create table entry" 66 | mi.OnClick = function() 67 | local all, forms, lua = loadfile(getMainForm().openDialog1.InitialDir.."Build/build.lua")() 68 | local aa = "[enable]\r\n${lua}\r\nLaunchMonoDataCollector()\r\n"..aa.."\r\n{$asm}\r\n[disable]\r\n" 69 | local entry = getAddressList().createMemoryRecord() 70 | entry.setDescription("MonoHelper") 71 | entry.Type = vtAutoAssembler -- must be set before setting 'Script' 72 | entry.Script = aa 73 | end 74 | mi.Name = 'miDevBuildToCEAutorunDirectory' 75 | DevMenu.miTopMenuItem.Add(mi) 76 | DevMenu.miDevBuildTableEntry = mi 77 | 78 | -------------------------------------------------------------------------------- /src/samples/Popups.lua: -------------------------------------------------------------------------------- 1 | -- this works for changing the pop-up to have only the new menu item you add! 2 | --[[ this works for changing the pop-up to have only the new menu item you add! 3 | 4 | local mi = createMenuItem(formMonoClass.popupMethods.Items) 5 | mi.Caption = "Click me!" 6 | mi.OnClick = function() 7 | showMessage("You clicked me, silly goose!") 8 | end 9 | mi.name = 'miClickMe' 10 | formMonoClass.popupMethods.Items:clear() 11 | formMonoClass.popupMethods.Items.add(mi) 12 | 13 | --]] 14 | 15 | 16 | --------- TEMP 17 | formMonoClass.popupMethods.Items:clear() 18 | local mi = createMenuItem(formMonoClass.popupMethods.Items) 19 | mi.Caption = "Click me again!" 20 | mi.OnClick = function() 21 | print('you clicked the popup!') 22 | showMessage("You clicked me, silly goose!") 23 | end 24 | mi.name = 'miClickMe' 25 | formMonoClass.popupMethods.Items.add(mi) 26 | 27 | --]] 28 | 29 | formMonoClass.popupMethods.Items.OnClick = function(sender) print('You clicked the popup!') end 30 | formMonoClass.popupMethods.Items.setOnClick(function(sender) print('You clicked the popup!') end) 31 | formMonoClass.popupMethods.OnPopup = function(sender) print('OnPopup!') end 32 | 33 | formMonoClass.listMethods.OnSelectItem = function(sender) 34 | showMessage("You selected something!") 35 | end 36 | 37 | 38 | 39 | --------- set popup menu when it is clicked 40 | formMonoClass.popupMethods.OnPopup = function(sender) 41 | formMonoClass.popupMethods.Items:clear() 42 | 43 | local mi 44 | 45 | mi = createMenuItem(formMonoClass.popupMethods.Items) 46 | mi.Caption = "Say hello" 47 | mi.OnClick = function() 48 | showMessage("Hello, world!") 49 | end 50 | mi.name = 'miSayHello' 51 | formMonoClass.popupMethods.Items.add(mi) 52 | 53 | mi = createMenuItem(formMonoClass.popupMethods.Items) 54 | mi.Caption = "CLICK ME!" 55 | mi.OnClick = function() 56 | showMessage("Thank you so much for clicking me!") 57 | end 58 | mi.name = 'miClickMe' 59 | formMonoClass.popupMethods.Items.add(mi) 60 | end 61 | 62 | -------------------------------------------------------------------------------- /src/samples/old_util.lua: -------------------------------------------------------------------------------- 1 | util = util or {} 2 | 3 | util.loadTextFile = function (name, useTableFile) 4 | if useTableFile then 5 | local tableFile = findTableFile(name) 6 | if not tableFile then return nil, 'Unable to open table file "'..tostring(name)..'"' end 7 | local ss = createStringStream() 8 | ss.Position = 0 -- recommended on wiki: https://wiki.cheatengine.org/index.php?title=Lua:Class:TableFile 9 | ss.CopyFrom(tableFile.Stream, 0) 10 | local text = ss.DataString 11 | ss.destroy() 12 | return text 13 | else 14 | local path = getMainForm().openDialog1.InitialDir..name 15 | local f, err = io.open(path, "r") 16 | -- fall back to table file if disk file error (doesn't exist) 17 | if f == nil then return loadTextFile(name, true) end 18 | local text = f:read("*all") 19 | f:close() 20 | return text 21 | end 22 | end 23 | 24 | --[[ 25 | Save a string to a text file. If useTableFile is true it will be saved as 26 | a TableFile. The directory should be where the cheat file is, it is the 27 | initial directory for the dialog when you are saving your cheat table. 28 | --]] 29 | util.saveTextFile = function(name, text, useTableFile) 30 | if useTableFile then 31 | local tf = findTableFile(name) 32 | if tf ~= nil then 33 | tf.delete() 34 | tf = nil 35 | end 36 | tf = createTableFile(name) 37 | local ss = createStringStream(text) 38 | tf.Stream.CopyFrom(ss, 0) 39 | ss.destroy() 40 | return true 41 | else 42 | local path = getMainForm().saveDialog1.InitialDir..name 43 | local f, err = io.open(path, "w") 44 | if f == nil then return nil, err end 45 | f:write(text) 46 | f:close() 47 | return true 48 | end 49 | end 50 | 51 | --[[ 52 | For mono symbols, you have to surround them in quotes for use in AA 53 | ]] 54 | function util.safeAddress(s) 55 | local tbl = {} 56 | for a in string.gmatch(s, "([^+]+)[+]?") do table.insert(tbl, a) end 57 | local result = '"'..tbl[1]..'"' 58 | if #tbl > 1 then result = result..'+'..tbl[2] end 59 | return result 60 | end 61 | 62 | function util.split(s, separator) 63 | local tbl = {} 64 | for str in string.gmatch(s, "([^"..separator.."]+)") do table.insert(tbl, str) end 65 | return tbl 66 | end 67 | 68 | function util.trim(s) 69 | return (s:gsub("^%s*(.-)%s*$", "%1")) 70 | end 71 | 72 | --[[ 73 | Pretty-print serialize the passed object and return the results 74 | as a string. This does nice indenting and follows metatables. It 75 | is not guaranteed to provide the same object back if the string is 76 | loaded because it just replaces circular references with a string 77 | among other things. It does try to keep tables/arrays on one line 78 | if there is room (120 characters). 79 | --]] 80 | function util.pretty(value, funcs, indent, done, stack) 81 | indent = indent or 0 82 | funcs = funcs or {} 83 | done = done or {} 84 | stack = stack or { {value=value} } -- initialize with passed value, no key 85 | 86 | -- if a 'userdata', use metatable 87 | local l = value 88 | if type(value) == "userdata" then 89 | l = getmetatable(value) 90 | end 91 | 92 | if type(l) == "table" then 93 | -- if we've already serialized the table, return string representation 94 | -- to avoid self-referencing in recursion. Only the first serialization 95 | -- will have the full serialized string 96 | if done[l] then return tostring(l) end 97 | done[l] = true 98 | 99 | local list = {} 100 | 101 | -- here we return "{", the values, and "}" 102 | if (#l > 0) then 103 | -- if we have a count ('#'), then it's just an array of values 104 | for i,v in ipairs(l) do 105 | table.insert(list, util.pretty(v, funcs, indent + 2, done, stack)) 106 | end 107 | else 108 | -- no count, so either empty or object with key/value 109 | -- first we sort the keys 110 | local keys = {} 111 | for k,v in pairs(l) do table.insert(keys, k) end 112 | table.sort(keys) 113 | 114 | local last = {key = "UNKNOWN", value = "NONE"} 115 | table.insert(stack, last) 116 | for i,k in ipairs(keys) do 117 | local propValue = value[k] 118 | last.key = k 119 | last.value = value[k] 120 | for j,f in ipairs(funcs) do 121 | local returnValue = f(value, k, value[k], stack, indent) 122 | if returnValue ~= nil then 123 | propValue = returnValue 124 | break 125 | end 126 | end 127 | table.insert(list, string.format("%s = %s", tostring(k), util.pretty(propValue, funcs, indent+2, done, stack))) 128 | end 129 | table.remove(stack, #stack) 130 | end 131 | 132 | -- now we find the total size, add 200 if there are line breaks 133 | local size = 0 134 | for i,v in ipairs(list) do 135 | size = size + string.len(v) + 2 136 | if string.find(v, "\r\n") then 137 | size = 200 138 | break; 139 | end 140 | end 141 | -- if it's small enough, join with commas 142 | if (size < 120) then 143 | local result = {"{ "} 144 | for i,v in ipairs(list) do 145 | if i > 1 then table.insert(result, ", ") end 146 | table.insert(result, v) 147 | end 148 | table.insert(result, " }") 149 | return table.concat(result); 150 | else 151 | local indentString1 = string.rep(" ", indent) 152 | local indentString2 = string.rep(" ", indent + 2) 153 | local result = {"{\r\n"} 154 | for i,v in ipairs(list) do 155 | table.insert(result, indentString2) 156 | table.insert(result, v) 157 | if (i < #list) then table.insert(result, ",") end 158 | table.insert(result, "\r\n") 159 | end 160 | -- table.insert(result, indentString) 161 | table.insert(result, indentString1) 162 | table.insert(result, "}") 163 | return table.concat(result); 164 | end 165 | end 166 | 167 | if type(value) == "string" then 168 | return string.format("\"%s\"", value) 169 | end 170 | return tostring(value) 171 | end 172 | 173 | 174 | 175 | --[[ 176 | Values and functions used by util.serialize(t) 177 | ---]] 178 | 179 | local oddvals = {[tostring(1/0)] = '1/0', [tostring(-1/0)] = '-1/0', [tostring(-(0/0))] = '-(0/0)', [tostring(0/0)] = '0/0'} 180 | 181 | local kw = {['and'] = true, ['break'] = true, ['do'] = true, ['else'] = true, 182 | ['elseif'] = true, ['end'] = true, ['false'] = true, ['for'] = true, 183 | ['function'] = true, ['goto'] = true, ['if'] = true, ['in'] = true, 184 | ['local'] = true, ['nil'] = true, ['not'] = true, ['or'] = true, 185 | ['repeat'] = true, ['return'] = true, ['then'] = true, ['true'] = true, 186 | ['until'] = true, ['while'] = true} 187 | 188 | local getchr = function(c) 189 | return "\\" .. c:byte() 190 | end 191 | 192 | local make_safe = function(text) 193 | return ("%q"):format(text):gsub('\n', 'n'):gsub("[\128-\255]", getchr) 194 | end 195 | 196 | local write = function(t, memo, rev_memo) 197 | local ty = type(t) 198 | if ty == 'number' then 199 | t = format("%.17g", t) 200 | return oddvals[t] or t 201 | elseif ty == 'boolean' or ty == 'nil' then 202 | return tostring(t) 203 | elseif ty == 'string' then 204 | return make_safe(t) 205 | elseif ty == 'table' or ty == 'function' then 206 | if not memo[t] then 207 | local index = #rev_memo + 1 208 | memo[t] = index 209 | rev_memo[index] = t 210 | end 211 | return '_[' .. memo[t] .. ']' 212 | else 213 | error("Trying to serialize unsupported type " .. ty) 214 | end 215 | end 216 | 217 | local write_key_value_pair = function(k, v, memo, rev_memo, name) 218 | if type(k) == 'string' and k:match '^[_%a][_%w]*$' and not kw[k] then 219 | return (name and name .. '.' or '') .. k ..'=' .. write(v, memo, rev_memo) 220 | else 221 | return (name or '') .. '[' .. write(k, memo, rev_memo) .. ']=' .. write(v, memo, rev_memo) 222 | end 223 | end 224 | 225 | local is_cyclic = function(memo, sub, super) 226 | local m = memo[sub] 227 | local p = memo[super] 228 | return m and p and m < p 229 | end 230 | 231 | local write_table_ex = function(t, memo, rev_memo, srefs, name) 232 | if type(t) == 'function' then 233 | return '_[' .. name .. ']=loadstring' .. make_safe(string.dump(t)) 234 | end 235 | local m = {} 236 | local mi = 1 237 | for i = 1, #t do -- don't use ipairs here, we need the gaps 238 | local v = t[i] 239 | if v == t or is_cyclic(memo, v, t) then 240 | srefs[#srefs + 1] = {name, i, v} 241 | m[mi] = 'nil' 242 | mi = mi + 1 243 | else 244 | m[mi] = write(v, memo, rev_memo) 245 | mi = mi + 1 246 | end 247 | end 248 | for k,v in pairs(t) do 249 | if type(k) ~= 'number' or math.floor(k) ~= k or k < 1 or k > #t then 250 | if v == t or k == t or is_cyclic(memo, v, t) or is_cyclic(memo, k, t) then 251 | srefs[#srefs + 1] = {name, k, v} 252 | else 253 | m[mi] = write_key_value_pair(k, v, memo, rev_memo) 254 | mi = mi + 1 255 | end 256 | end 257 | end 258 | return '_[' .. name .. ']={' .. table.concat(m, ',') .. '}' 259 | end 260 | 261 | 262 | --[[ 263 | A function more appropriate for serializing objects in a reproduceable way. 264 | --]] 265 | function util.serialize(t) 266 | local memo = {[t] = 0} 267 | local rev_memo = {[0] = t} 268 | local srefs = {} 269 | local result = {} 270 | 271 | -- phase 1: recursively descend the table structure 272 | local n = 0 273 | while rev_memo[n] do 274 | result[n + 1] = write_table_ex(rev_memo[n], memo, rev_memo, srefs, n) 275 | n = n + 1 276 | end 277 | 278 | -- phase 2: reverse order 279 | for i = 1, n*.5 do 280 | local j = n - i + 1 281 | result[i], result[j] = result[j], result[i] 282 | end 283 | 284 | -- phase 3: add all the tricky cyclic stuff 285 | for i, v in ipairs(srefs) do 286 | n = n + 1 287 | result[n] = write_key_value_pair(v[2], v[3], memo, rev_memo, '_[' .. v[1] .. ']') 288 | end 289 | 290 | -- phase 4: add something about returning the main table 291 | if result[n]:sub(1, 5) == '_[0]=' then 292 | result[n] = 'return ' .. result[n]:sub(6) 293 | else 294 | result[n + 1] = 'return _[0]' 295 | end 296 | 297 | -- phase 5: just concatenate everything 298 | result = table.concat(result, '\n') 299 | return n > 1 and 'local _={}\n' .. result or result 300 | end 301 | 302 | function util.map(t, f) 303 | local results = {} 304 | local func = f or tostring 305 | for i,v in ipairs(t) do 306 | table.insert(results, func(v)) 307 | end 308 | return results 309 | end 310 | --------------------------------------------------------------------------------