if (mono_enumDomains() == nil) then LaunchMonoDataCollector() end local is64Bit = targetIs64Bit() local ARRAY_SIZE_OFF = is64Bit and 0x18 or 0x10 local ARRAY_DATA_OFF = is64Bit and 0x20 or 0x14 local PTR_SIZE = is64Bit and 8 or 4 local monoTypeToVartypeLookup = { [0x02] = vtByte, -- BOOLEAN [0x03] = vtWord, -- CHAR [0x04] = vtByte, -- I1 [0x05] = vtByte, -- U1 [0x06] = vtWord, -- I2 [0x07] = vtWord, -- U2 [0x08] = vtDword, -- I4 (Int32) [0x09] = vtDword, -- U4 [0x0a] = vtQword, -- I8 [0x0b] = vtQword, -- U8 [0x0c] = vtSingle, -- R4 [0x0d] = vtDouble, -- R8 [0x0e] = vtString, -- STRING [0x0f] = vtPointer, -- PTR [0x12] = vtPointer, -- CLASS [0x14] = vtPointer -- ARRAY } --[[ generic helper functions ]] function printf(fmt, ...) print(fmt:format(...)) end function splitByComma(str) if str == nil or str == "" then return {} end local result = {} for word in string.gmatch(str, "([^,]+)") do table.insert(result, word) end return result end --[[ mono helper functions ]] function dumpFields(class) print("class fields:") for i, v in pairs(mono_class_enumFields(class)) do printf("\tname: %s - static: %s", v.name, tostring(v.isStatic)) end end function getFieldFromName(class, name, shouldBeStatic) local fields = mono_class_enumFields(class) if not fields then return nil end for i, v in pairs(fields) do if v.isStatic == (shouldBeStatic == true) and v.name == name then return v end end print(("could not find field: %s"):format(name)) return nil end --[[ mono class wrappers ]] function MonoArray(addr) if not addr or addr == 0 then error("attempted to instantiate MonoArray with invalid addr") end local classId, className = mono_object_getClass(addr) if not className then error("address did not point to valid object") end if className:sub(-2) ~= "[]" then error("address did not point to array") end local array = { _address = addr, getLength = function(self) return readInteger(self._address + ARRAY_SIZE_OFF) end, getElement = function(self, idx) if idx < 0 or idx >= self:getLength() then error("invalid index") end return readPointer(self._address + ARRAY_DATA_OFF + (idx * PTR_SIZE)) end } setmetatable(array, { __tostring = function() return "Mono array instance" end, __index = function(self, k) local idx = tonumber(k) if idx ~= nil then return self:getElement(idx) end return rawget(self, k) end }) return array end local stringClass = mono_findClass("System", "String") local string_Length = getFieldFromName(stringClass, "_stringLength") local string_FirstChar = getFieldFromName(stringClass, "_firstChar") function MonoStringReader(addr) if not addr or addr == 0 then return nil end local classId, className = mono_object_getClass(addr) if className ~= "String" then error("address did not point to string") end local str = { _address = addr, getLength = function(self) return readInteger(self._address + string_Length.offset) end, getValue = function(self) return readString(self._address + string_FirstChar.offset, self:getLength() * 2, true) end } setmetatable(str, { __tostring = function(self) return self:getValue() end, __index = str }) return str end function MonoFunctionHelper(fnName, fnId, isStatic) local fn = { _name = fnName, _id = fnId, _signatures = {}, _static = isStatic } if type(fnId) == "table" then for i, v in ipairs(fnId) do local sig = mono_method_getSignature(v) table.insert(fn._signatures, {id = v, params = splitByComma(sig)}) end end setmetatable(fn, { __call = function(self, ...) local args = {...} local methodArgs = args local instancePtr = nil if not self._static then if #args == 0 then error("Instance method " .. self._name .. " requires instance pointer") end instancePtr = table.remove(methodArgs, 1) end local activeId = nil if type(self._id) == "table" then for _, candidate in ipairs(self._signatures) do if #candidate.params == #methodArgs then activeId = candidate.id break end end if not activeId then error("No overload for " .. self._name .. " matches arg count " .. #methodArgs) end else activeId = self._id end local paramsInfo = mono_method_get_parameters(activeId) local monoArgs = {} for i = 1, #methodArgs do local pType = paramsInfo.parameters[i].type table.insert(monoArgs, { type = monoTypeToVartypeLookup[pType] or vtPointer, value = methodArgs[i] }) end return mono_invoke_method(mono_enumDomains()[1], activeId, instancePtr, monoArgs) end, __tostring = function(self) return "MonoFunction: " .. self._name end }) return fn end function MonoClass(namespace, name) local classId = mono_findClass(namespace, name) if not classId then error("Could not find class " .. name) end local domain = mono_enumDomains()[1] local staticAddr = mono_class_getStaticFieldAddress(domain, classId) local methodsRaw = mono_class_enumMethods(classId) local methodsSorted = {} for _, v in pairs(methodsRaw) do if methodsSorted[v.name] then if type(methodsSorted[v.name]) ~= "table" then methodsSorted[v.name] = {methodsSorted[v.name]} end table.insert(methodsSorted[v.name], v.method) else methodsSorted[v.name] = v.method end end local methodsWrapped = {} for mName, mId in pairs(methodsSorted) do local checkId = type(mId) == "table" and mId[1] or mId local flags = mono_method_getFlags(checkId) local isStatic = bAnd(flags, 0x0010) ~= 0 methodsWrapped[mName] = MonoFunctionHelper(mName, mId, isStatic) end local classObj = { _id = classId, _name = name, _ns = namespace, _fieldInfo = {}, _staticAddr = staticAddr, _methods = methodsWrapped, getStaticFieldOffset = function(self, name) local field = getFieldFromName(self._id, name, true) return field and field.offset or nil end, getStaticField = function(self, offset) return readPointer(self._staticAddr + offset) end, getMethod = function(self, methodName) local m = self._methods[methodName] if not m then error("Method " .. methodName .. " not found!") end return m end } return setmetatable(classObj, { __index = function(self, key) return self._methods[key] or rawget(self, key) end }) end local typeClass = MonoClass("System", "Type") function Type(typeName) local typeStr = mono_new_string(typeName) local getType = typeClass:getMethod("GetType") local typeRet = getType(typeStr) print(type(typeRet), typeRet) end --[[ game ]] local GameDataClass = MonoClass("", "GameData") local instanceOffset = GameDataClass:getStaticFieldOffset("Instance") printf("GameData class id: %s", tostring(GameDataClass)) printf("GameData.Instance field offset: %x", instanceOffset or 0) local gameDataInstance = GameDataClass:getStaticField(instanceOffset) printf("GameData: %x", gameDataInstance or 0) local alterFunds = GameDataClass:getMethod("CmdAlterFunds") printf("GameData.CmdAlterFunds: %x", alterFunds._id or 0) alterFunds(gameDataInstance, 13371337)