Difference between revisions of "Tutorial:Mono:Basic"

From Cheat Engine
Jump to navigation Jump to search
Line 24: Line 24:
 
it needs to be called after attaching to the process (after the Mono features are initialized).
 
it needs to be called after attaching to the process (after the Mono features are initialized).
 
<pre>--[============================================================[
 
<pre>--[============================================================[
Process : Cuphead.exe
+
    Process         : Cuphead.exe
Game Version : 5.6.2.10718
+
    Game Version   : 5.6.2.10718
CE Version : 6.7
+
    CE Version     : 6.7
 
]============================================================]--
 
]============================================================]--
 
PROCESS_NAME = 'Cuphead.exe'
 
PROCESS_NAME = 'Cuphead.exe'
Line 36: Line 36:
 
local autoAttachTimer = nil
 
local autoAttachTimer = nil
 
local function autoAttachTimer_tick(timer)
 
local function autoAttachTimer_tick(timer)
if autoAttachTimerTickMax > 0 and autoAttachTimerTicks >= autoAttachTimerTickMax then
+
    if autoAttachTimerTickMax > 0 and autoAttachTimerTicks >= autoAttachTimerTickMax then
timer.destroy()
+
        timer.destroy()
end
+
    end
if getProcessIDFromProcessName(PROCESS_NAME) ~= nil then
+
    if getProcessIDFromProcessName(PROCESS_NAME) ~= nil then
timer.destroy()
+
        timer.destroy()
--openProcess(PROCESS_NAME)
+
        --openProcess(PROCESS_NAME)
mono_OpenProcess(getProcessIDFromProcessName(PROCESS_NAME))
+
        mono_OpenProcess(getProcessIDFromProcessName(PROCESS_NAME))
local InjectedMono = LaunchMonoDataCollector()
+
        local InjectedMono = LaunchMonoDataCollector()
if InjectedMono and InjectedMono ~= 0 then
+
        if InjectedMono and InjectedMono ~= 0 then
print(string.format('Mono Features Enabled: %X', InjectedMono))
+
            print(string.format('Mono Features Enabled: %X', InjectedMono))
end
+
        end
end
+
    end
autoAttachTimerTicks = autoAttachTimerTicks + 1
+
    autoAttachTimerTicks = autoAttachTimerTicks + 1
 
end
 
end
 
autoAttachTimer = createTimer(MainForm)
 
autoAttachTimer = createTimer(MainForm)
 
autoAttachTimer.Interval = autoAttachTimerInterval
 
autoAttachTimer.Interval = autoAttachTimerInterval
 
autoAttachTimer.OnTimer = autoAttachTimer_tick
 
autoAttachTimer.OnTimer = autoAttachTimer_tick
 +
</pre>
 +
Or We could just use an Auto Assembler script and nest other scripts under it, and we would only need to check for ''process'' and initialize the Mono features.
 +
<pre>[ENABLE]
 +
{$lua}
 +
if syntaxcheck then return end
 +
if process and readInteger(process) ~= 0 then
 +
    mono_initialize()
 +
    LaunchMonoDataCollector()
 +
else
 +
    local msg = 'No process detected.'
 +
    print(msg)
 +
    error(msg)
 +
end
 +
{$asm}
 +
[DISABLE]
 
</pre>
 
</pre>
  
Line 90: Line 105:
 
So how do we JIT the code,  
 
So how do we JIT the code,  
 
we can use '''[[Mono:Lua:mono_findMethod|mono_findMethod]]''' with '''[[Mono:Lua:mono_compile_method|mono_compile_method]]'''.  
 
we can use '''[[Mono:Lua:mono_findMethod|mono_findMethod]]''' with '''[[Mono:Lua:mono_compile_method|mono_compile_method]]'''.  
 +
  
 
=== Finding the Namespace ===
 
=== Finding the Namespace ===
Line 96: Line 112:
 
[[File:Mono.Tutorial.MonoDissector.02.png||Mono Dissector form|border]]
 
[[File:Mono.Tutorial.MonoDissector.02.png||Mono Dissector form|border]]
  
 +
==== Finding the Namespace with script ====
 
Or we can use [[Mono:Lua:mono_getJitInfo|mono_getJitInfo]] with [[Mono:Lua:mono_method_getClass|mono_method_getClass]] in the Lua Engine to get the ''Namespace''.
 
Or we can use [[Mono:Lua:mono_getJitInfo|mono_getJitInfo]] with [[Mono:Lua:mono_method_getClass|mono_method_getClass]] in the Lua Engine to get the ''Namespace''.
 
<pre>
 
<pre>
Line 103: Line 120:
 
</pre>
 
</pre>
 
But this returns an empty Namespace, and it turns out ''[[Mono:Lua:mono_findMethod|mono_findMethod]]'' will tend to work with an empty Namespace.
 
But this returns an empty Namespace, and it turns out ''[[Mono:Lua:mono_findMethod|mono_findMethod]]'' will tend to work with an empty Namespace.
 +
: Note: '''Most stuff in a Mono games will be in the ''Assembly-CSharp'' Namespace.'''
  
  
 
=== JIT the method ===
 
=== JIT the method ===
Then we can setup a script like this
+
You can JIT a method in the [[Mono:MonoDissect|Mono Dissect Form]] by right clicking on a ''selected method''.<br>
 +
[[File:Mono.Tutorial.MonoDissector.03.png||JIT context menu item|border]]
 +
 
 +
This will JIT the method and open the memory viewer at the methods start.<br>
 +
[[File:Mono.Tutorial.MonoDissector.03.JITed.png||JITed method|border]]
 +
 
 +
==== Scripted JITting ====
 +
Or we can setup a script to JIT the method.
 
<pre>{$STRICT}
 
<pre>{$STRICT}
 
define(bytes,89 47 60)
 
define(bytes,89 47 60)
Line 114: Line 139:
 
{$lua}
 
{$lua}
 
if syntaxcheck then return end
 
if syntaxcheck then return end
local mId = mono_findMethod('Assembly-CSharp', 'PlayerStatsManager', 'TakeDamage')
+
if LaunchMonoDataCollector() ~= 0 then
--local mId = mono_findMethod('', 'PlayerStatsManager', 'TakeDamage') ---- This also works
+
    local mId = mono_findMethod('Assembly-CSharp', 'PlayerStatsManager', 'TakeDamage')
mono_compile_method(mId)
+
    --local mId = mono_findMethod('', 'PlayerStatsManager', 'TakeDamage') ---- This also works
 +
    mono_compile_method(mId)
 +
end
 
{$asm}
 
{$asm}
  
Line 124: Line 151:
 
   nop
 
   nop
 
   nop
 
   nop
 
  
 
////
 
////
Line 133: Line 159:
 
   // mov [edi+60],eax
 
   // mov [edi+60],eax
 
</pre>
 
</pre>
Now with this we can just enable the script after the Mono features have been enabled.
+
With this we can just enable the script after the Mono features have been enabled.
  
  
Line 147: Line 173:
 
{$lua}
 
{$lua}
 
if syntaxcheck then return end
 
if syntaxcheck then return end
local mId = mono_findMethod('Assembly-CSharp', 'PlayerStatsManager', 'TakeDamage')
+
if LaunchMonoDataCollector() ~= 0 then
--local mId = mono_findMethod('', 'PlayerStatsManager', 'TakeDamage') ---- This also works
+
    local mId = mono_findMethod('Assembly-CSharp', 'PlayerStatsManager', 'TakeDamage')
mono_compile_method(mId)
+
    --local mId = mono_findMethod('', 'PlayerStatsManager', 'TakeDamage') ---- This also works
 +
    mono_compile_method(mId)
 +
end
 
{$asm}
 
{$asm}
  
Line 155: Line 183:
 
PlayerStatsManager:TakeDamage:
 
PlayerStatsManager:TakeDamage:
 
   ret
 
   ret
 
  
 
////
 
////
Line 166: Line 193:
  
  
 +
<!-- == Find stuff with [[Mono:MonoDissect|Mono Dissect Form]] == -->
 
{{MonoSeeAlso}}
 
{{MonoSeeAlso}}

Revision as of 20:59, 5 May 2018


So what are the Cheat Engine mono features?

What is Mono?
Mono is a free and open-source project. Created to build an ECMA (European Computer Manufacturers Association) standard-compliant .NET Framework compatible set of tools. Including a C# compiler and a Common Language Runtime with just-in-time (JIT) compilation.
Side Note: The logo of Mono is a stylized monkey's face, mono being Spanish for monkey.

The Cheat Engine Mono feature are basically tools to help in Mono games. They can offer a different way to create and/or use cheats.


Let's setup infinite health in a Mono game. I'll be using Cuphead.


Attaching to the process

So if you attach to a Mono game then Cheat Engine initializes the Mono features and there will be a new Mono menu item in the Cheat Engine main form. But you'll notice if you use openProcess in some Lua script, the Mono menu item doesn't show up. With Mono we want to us mono_OpenProcess to open the process or use mono_initialize to initialize the Mono features. The main thing to note is it takes a process ID (number), not a process name (string). Then we just need to use LaunchMonoDataCollector to activate the Mono features, it needs to be called after attaching to the process (after the Mono features are initialized).

--[============================================================[
    Process         : Cuphead.exe
    Game Version    : 5.6.2.10718
    CE Version      : 6.7
]============================================================]--
PROCESS_NAME = 'Cuphead.exe'
GAME_TITLE = 'Cuphead'
-- GAME_VERSION = '5.6.2.10718'
local autoAttachTimerInterval = 100
local autoAttachTimerTicks = 0
local autoAttachTimerTickMax = 5000
local autoAttachTimer = nil
local function autoAttachTimer_tick(timer)
    if autoAttachTimerTickMax > 0 and autoAttachTimerTicks >= autoAttachTimerTickMax then
        timer.destroy()
    end
    if getProcessIDFromProcessName(PROCESS_NAME) ~= nil then
        timer.destroy()
        --openProcess(PROCESS_NAME)
        mono_OpenProcess(getProcessIDFromProcessName(PROCESS_NAME))
        local InjectedMono = LaunchMonoDataCollector()
        if InjectedMono and InjectedMono ~= 0 then
            print(string.format('Mono Features Enabled: %X', InjectedMono))
        end
    end
    autoAttachTimerTicks = autoAttachTimerTicks + 1
end
autoAttachTimer = createTimer(MainForm)
autoAttachTimer.Interval = autoAttachTimerInterval
autoAttachTimer.OnTimer = autoAttachTimer_tick

Or We could just use an Auto Assembler script and nest other scripts under it, and we would only need to check for process and initialize the Mono features.

[ENABLE]
{$lua}
if syntaxcheck then return end
if process and readInteger(process) ~= 0 then
    mono_initialize()
    LaunchMonoDataCollector()
else
    local msg = 'No process detected.'
    print(msg)
    error(msg)
end
{$asm}
[DISABLE]


Working with Mono in scripts

So let's say we already found health with traditional value scanning. If you have the Mono features enabled and setup an injection script then you will see an addresses like this "PlayerStatsManager:TakeDamage+ABC" instead of just a game plus offset address.
Take Damage Code

So if we setup a script we can use the Mono address to make if more robust to deal with game updates better.

{$STRICT}
define(bytes,89 47 60)
////
//// ------------------------------ ENABLE ------------------------------
[ENABLE]
assert(PlayerStatsManager:TakeDamage+8A, bytes)
PlayerStatsManager:TakeDamage+8A:
  nop
  nop
  nop
////
//// ------------------------------ DISABLE ------------------------------
[DISABLE]
PlayerStatsManager:TakeDamage+8A:
  db bytes
  // mov [edi+60],eax

But if we try to enable this script before the player takes damage, then you will find it won't enable. So let's fix that.

So fist we need to understand why scripts using Mono addresses don't enable before some given action. Well in short, Mono uses a JIT compiler.

Just-in-time (JIT) compilation (a.k.a.: dynamic translation or run-time compilation), involves compilation during execution of a program (at run time) rather than prior to execution.

So to get the game to JIT (compile) the code we need to preform some action, for the above code we have to take damage. So how do we JIT the code, we can use mono_findMethod with mono_compile_method.


Finding the Namespace

To do that we need the Namespace, Class, and Method names, we have the Class and Method in the above code. We can use the find tool of the Mono Dissect Form to find the Namespace by searching for the class name.
Mono Dissector form
Mono Dissector form

Finding the Namespace with script

Or we can use mono_getJitInfo with mono_method_getClass in the Lua Engine to get the Namespace.

local mID = mono_getJitInfo(getAddress('PlayerStatsManager:TakeDamage')).method
local cID = mono_method_getClass(mID)
print('Namespace: ', '"' .. mono_class_getNamespace(cID) .. '"')

But this returns an empty Namespace, and it turns out mono_findMethod will tend to work with an empty Namespace.

Note: Most stuff in a Mono games will be in the Assembly-CSharp Namespace.


JIT the method

You can JIT a method in the Mono Dissect Form by right clicking on a selected method.
JIT context menu item

This will JIT the method and open the memory viewer at the methods start.
JITed method

Scripted JITting

Or we can setup a script to JIT the method.

{$STRICT}
define(bytes,89 47 60)
////
//// ------------------------------ ENABLE ------------------------------
[ENABLE]
{$lua}
if syntaxcheck then return end
if LaunchMonoDataCollector() ~= 0 then
    local mId = mono_findMethod('Assembly-CSharp', 'PlayerStatsManager', 'TakeDamage')
    --local mId = mono_findMethod('', 'PlayerStatsManager', 'TakeDamage') ---- This also works
    mono_compile_method(mId)
end
{$asm}

assert(PlayerStatsManager:TakeDamage+8A, bytes)
PlayerStatsManager:TakeDamage+8A:
  nop
  nop
  nop

////
//// ------------------------------ DISABLE ------------------------------
[DISABLE]
PlayerStatsManager:TakeDamage+8A:
  db bytes
  // mov [edi+60],eax

With this we can just enable the script after the Mono features have been enabled.


Make method only return

We could also try to just make the method return with out doing any thing.
Take Damage Code start
Take Damage Code return

{$STRICT}
define(bytes,55)
////
//// ------------------------------ ENABLE ------------------------------
[ENABLE]
{$lua}
if syntaxcheck then return end
if LaunchMonoDataCollector() ~= 0 then
    local mId = mono_findMethod('Assembly-CSharp', 'PlayerStatsManager', 'TakeDamage')
    --local mId = mono_findMethod('', 'PlayerStatsManager', 'TakeDamage') ---- This also works
    mono_compile_method(mId)
end
{$asm}

assert(PlayerStatsManager:TakeDamage, bytes)
PlayerStatsManager:TakeDamage:
  ret

////
//// ------------------------------ DISABLE ------------------------------
[DISABLE]
PlayerStatsManager:TakeDamage:
  db bytes
  // push ebp


See also