Hello folks,
Thought I'd give this a shot, since there's a ton of people out there not mastering (or at least understanding) the concept of matching whatever they see in [I]Unity[/I] decompiled outputs versus the decompiled code Cheat Engine's [I]MonoDataCollector[/I] returns. For this explanation I will use the below:
[LIST]
[*][B]Cheat Engine 6.7[/B] -- ignore my screenshots showing RC3; you don't need RC3
[*][B]Telerik JustDecompile[/B] -- you can download it [URL='https://www.telerik.com/products/decompiler.aspx']here[/URL]
[*][B]BattleTech[/B] game
[*][B]BattleTech table[/B] found in [URL='https://fearlessrevolution.com/threads/battletech.6603/#post-43753']this[/URL] post
[/LIST]
Alright.
Fire up the game, get in a map. Open Cheat Engine, load table, activate [B][ Enable ][/B] script, activate [B]Cheat Handler[/B] script. Go back in-game and hit [B]Numpad 0[/B] to execute [B]DEBUG_PlayerOneGodMode[/B]. Wait, wait.. what did I just do? Rewind, please :)
Let's take them one by one, from [B]JustDecompile[/B] perspective, so you understand how I got to what the script just did ;)
Fire-up Telerik JustDecompile. Hit Ctrl+O and choose [B]Assembly-CSharp.dll[/B] from your [I]
[IMG]https://i.imgur.com/iXWPTYU.png[/IMG]
See [B]DEBUG_PlayerOneGodMode[/B] reference on second line? Double-click it. You'll be taken here:
[IMG]https://i.imgur.com/QymRkUj.png[/IMG]
What can you see in that picture? Simple:
[LIST]
[*]Left tree shows the function, as well as the [I]Namespace[/I] and [I]Type Name[/I]. Why are these important? Because we will make use of them to get to this function in Cheat Engine.
[*]Based on the above, we can concatenate these to a string with ":" as separator - - [COLOR=rgb(85, 57, 130)]BattleTech.UI[/COLOR]:[COLOR=rgb(26, 188, 156)]CombatDebugHUD[/COLOR] - - this denotes hierarchy in C++ (a member of).
[*][COLOR=null]Our function is called DEBUG_PlayerOneGodMode, so it will be appended to the above with a ":" as well (member of).[/COLOR]
[*][COLOR=null]Final result is:[/COLOR]
[COLOR=null][code]BattleTech.UI:CombatDebugHUD:DEBUG_PlayerOneGodMode[/code][/COLOR]
[*][COLOR=null]We'll see in a bit what's to be done with this.[/COLOR]
[*]Right side of the window shows you the actual function and its code:
[/LIST]
[code=cpp]
public void DEBUG_PlayerOneGodMode()
{
this.SetGodMode(this.combatHUD.Combat.Teams.Find((Team x) => x.GUID == "bf40fd39-ccf9-47c4-94a6-061809681140"));
}
[/code]
Looking further at the code, we see this function calls-in another: [B]SetGodMode[/B]. Click on it and you are taken here:
[IMG]https://i.imgur.com/GXKsusO.png[/IMG]
Transcript:
[code=cpp]
private void SetGodMode(Team team)
{
for (int i = 0; i < team.units.Count; i++)
{
AbstractActor item = team.units[i];
item.StatCollection.ModifyStat
item.StatCollection.ModifyStat
item.StatCollection.ModifyStat
for (int j = 0; j < item.Weapons.Count; j++)
{
Weapon weapon = item.Weapons[j];
weapon.StatCollection.ModifyStat
weapon.StatCollection.ModifyStat
}
item.Combat.MessageCenter.PublishMessage(new FloatieMessage(item.GUID, item.GUID, "GOD MODE", FloatieMessage.MessageNature.Buff));
}
}
[/code]
So, what the [I]developer[/I] God Mode does in reality is:
[LIST]
[*][B]DamageReductionMultiplierAll[/B] is set to its [I]current value * 0.1f[/I]
[*][B]ReceivedInstabilityMultiplier[/B] is set to [I]current value * 0.1f[/I]
[*][B]IgnorePilotInjuries[/B] becomes [I]active[/I]
[*][B]DamagePerShot[/B] is set to [I]current value * 10f[/I]
[*][B]AccuracyModifier[/B] is set to [I]current value + (-20f)[/I]
[/LIST]
This happens for all Mechs in player's team.
Now that you've seen what's going on, next logical question would be: "OK, but how do I run this in Cheat Engine? I want to activate the function!". The logical answer would be create a thread to call in this function - DEBUG_PlayerOneGodMode - and execute it. All would be nice and dandy, but once you manage doing that and are able to call it, you'll notice game crashes. Why? Because you also need an [I]instance pointer[/I] to be able to call it. Even though you see a [I]void[/I] type of function, this only means it doesn't take-in parameters. However, the instance pointer - also referenced as [I]this[/I] - is required.
How do you know you need this and where to find it?
[B]1)[/B] You know you need this due to this piece of code:
[code=cpp]
public void DEBUG_PlayerOneGodMode()
{
this.SetGodMode(this.combatHUD.Combat.Teams.Find((Team x) => x.GUID == "bf40fd39-ccf9-47c4-94a6-061809681140"));
}
[/code]
See [B]this.[/B]SetGodMode? See [B]this.[/B]combatHUD.Combat.Teams.Find? That's how you know :)
[B]2)[/B] To get it you would need to break on a function you know - from testing - is executed in the [B]Namespace:Type Name[/B] (just so I use the references from JustDecompile). Most Unity functions come - conveniently - with an [B]Update [/B]function :) You'll see in a bit.
Time to move to Cheat Engine; remember you've loaded the table and executed [B]Numpad 0[/B]. If you've not done it, do it now; I'll explain why it is important for this tutorial: although Cheat Engine is able to decompile Unity code, it will not also execute functions to retrieve symbols. So, all code in the two functions - DEBUG_PlayerOneGodMode, SetGodMode - will show no actual symbolicals.
To get to these functions we'll make use of what I mentioned earlier:
[LIST]
[*]Cheat Engine > Memory Viewer
[*]Ctrl+G >
[COLOR=null][code]BattleTech.UI:CombatDebugHUD:DEBUG_PlayerOneGodMode - OR - BattleTech.UI:CombatDebugHUD:SetGodMode[/code][/COLOR]
[*][COLOR=null][COLOR=null][COLOR=null]OK[/COLOR][/COLOR][/COLOR]
[/LIST]
(before [I]executing [/I]the functions)
DEBUG_PlayerOneGodMode:
[IMG]https://i.imgur.com/Tz26mV2.png[/IMG]
SetGodMode:
[IMG]https://i.imgur.com/0dPssTj.png[/IMG]
(after [I]executing [/I]the functions)
DEBUG_PlayerOneGodMode:
[IMG]https://i.imgur.com/4XUpp7h.png[/IMG]
SetGodMode:
[IMG]https://i.imgur.com/8ADF4fk.png[/IMG]
OK, OK.. but what about the [I]instance pointer[/I]? We'll get it from debugging [COLOR=null][COLOR=null]BattleTech.UI:CombatDebugHUD:[B]Update[/B]. So head there in Cheat Engine's Memory Viewer via Ctrl+G and set a breakpoint at the function's prologue:[/COLOR][/COLOR]
[IMG]https://i.imgur.com/bspBxf1.png[/IMG]
Note that once you set breakpoint there, Mono gets un-linked. You'll know this has happened when you don't see symbols anymore :) Do your stuff, then activate it again in main CE window (Mono > Activate mono features). Reason why this happens is the mono thread makes use of your hardware breakpoints.
Your [I]instance pointer[/I] is in [B]RCX[/B]. In my case, 0x0000000142C77380.
What you can do at this point - - as I did - - is to hook the Update function and store this pointer to a static location. Then make use of it in your thread code. Another thing to note is your CreateThread needs to be attached/detached to/from the mono thread. How can this be done? See the code in the table :)
[code=CEA]
mono_thAttach:
sub rsp,28
// attach thread to mono domain
call mono.mono_get_root_domain
mov rcx,rax
call mono.mono_thread_attach
mov [self],rax
add rsp,28
ret
mono_thDetach:
sub rsp,28
// detach thread from mono domain
mov rcx,[self]
call mono.mono_thread_detach
add rsp,28
ret
[/code]
Then a function that would use the two above:
[code=CEA]
AddFunds_do:
sub rsp,28
call mono_thAttach
mov rcx,[pSimGameState]
test rcx,rcx
je short @f
mov rdx,[dwFundsAmount]
xor r8,r8
mov r9d,1
call _AddFunds
@@:
call mono_thDetach
add rsp,28
ret
[/code]
So up to this point you have the tools you need to understand a [I]void[/I] type (no parameters) Unity function and what it needs to be run from a thread in Cheat Engine. Now that you've also executed the DEBUG_PlayerOneGodMode function, thus implicitly the SetGodMode function, let's take a look at how to interpret it from JustDecompile to the ASM code you see in Memory Viewer.
Another reason for creating this tutorial was the various people nagging me to alter some parameters SetGodMode changed that they didn't like, making the game too easy or dull (e.g.: DamagePerShot too high).
Let's see; I'll take the first parameter for a spin, the rest should be easy once you get this one:
[code=cpp]
item.StatCollection.ModifyStat
[/code]
In Cheat Engine's ASM:
[code=CEA]
B8192400 - 55 - push rbp
B8192401 - 48 8B EC - mov rbp,rsp
B8192404 - 56 - push rsi
B8192405 - 57 - push rdi
B8192406 - 41 55 - push r13
B8192408 - 41 56 - push r14
B819240A - 41 57 - push r15
B819240C - 48 83 EC 28 - sub rsp,28 { 40 }
B8192410 - 48 8B F2 - mov rsi,rdx
B8192413 - 45 33 ED - xor r13d,r13d
B8192416 - E9 ED020000 - jmp BattleTech.UI:CombatDebugHUD:SetGodMode+308
B819241B - 48 8D 64 24 00 - lea rsp,[rsp+00]
B8192420 - 48 8B 46 48 - mov rax,[rsi+48]
B8192424 - 48 8B C8 - mov rcx,rax
B8192427 - 49 8B D5 - mov rdx,r13
B819242A - 48 83 EC 20 - sub rsp,20 { 32 }
B819242E - 83 38 00 - cmp dword ptr [rax],00 { 0 }
B8192431 - 49 BB C032EC0800000000 - mov r11,System.Collections.Generic:List`1:get_Item { [EC8B4855] }
B819243B - 41 FF D3 - call r11
B819243E - 48 83 C4 20 - add rsp,20 { 32 }
B8192442 - 48 8B F8 - mov rdi,rax
B8192445 - 48 8B C7 - mov rax,rdi
B8192448 - 48 8B C8 - mov rcx,rax
B819244B - 83 39 00 - cmp dword ptr [rcx],00 { 0 }
B819244E - 48 8B 40 58 - mov rax,[rax+58]
B8192452 - F3 0F10 05 06030000 - movss xmm0,[BattleTech.UI:CombatDebugHUD:SetGodMode+360] { [0.10] }
B819245A - F3 0F5A C0 - cvtss2sd xmm0,xmm0
B819245E - 48 8B C8 - mov rcx,rax
B8192461 - BA 50AF3A67 - mov edx,673AAF50 { [0862A2E0] }
B8192466 - 49 B8 FFFFFFFFFFFFFFFF - mov r8,FFFFFFFFFFFFFFFF { -1 }
B8192470 - 49 B9 601F4B7501000000 - mov r9,00000001754B1F60 { [0862A2E0] }
B819247A - 6A 01 - push 01 { 1 }
B819247C - 6A FF - push -01 { 255 }
B819247E - 48 83 EC 08 - sub rsp,08 { 8 }
B8192482 - F2 0F5A E8 - cvtsd2ss xmm5,xmm0
B8192486 - F3 0F11 2C 24 - movss [rsp],xmm5
B819248B - 6A 0C - push 0C { 12 }
B819248D - 48 83 EC 20 - sub rsp,20 { 32 }
B8192491 - 83 38 00 - cmp dword ptr [rax],00 { 0 }
B8192494 - 49 BB D05719B800000000 - mov r11,BattleTech:StatCollection:ModifyStat { [EC8B4855] }
B819249E - 41 FF D3 - call r11
B81924A1 - 48 83 C4 40 - add rsp,40 { 64 }
B81924A5 - 83 3F 00 - cmp dword ptr [rdi],00 { 0 }
B81924A8 - 48 8B 47 58 - mov rax,[rdi+58]
B81924AC - F3 0F10 05 9C020000 - movss xmm0,[BattleTech.UI:CombatDebugHUD:SetGodMode+350] { [0.10] }
B81924B4 - F3 0F5A C0 - cvtss2sd xmm0,xmm0
B81924B8 - 48 8B C8 - mov rcx,rax
B81924BB - BA 50AF3A67 - mov edx,673AAF50 { [0862A2E0] }
B81924C0 - 49 B8 FFFFFFFFFFFFFFFF - mov r8,FFFFFFFFFFFFFFFF { -1 }
B81924CA - 41 B9 4015447C - mov r9d,7C441540 { [0862A2E0] }
B81924D0 - 6A 01 - push 01 { 1 }
B81924D2 - 6A FF - push -01 { 255 }
B81924D4 - 48 83 EC 08 - sub rsp,08 { 8 }
B81924D8 - F2 0F5A E8 - cvtsd2ss xmm5,xmm0
B81924DC - F3 0F11 2C 24 - movss [rsp],xmm5
B81924E1 - 6A 0C - push 0C { 12 }
B81924E3 - 48 83 EC 20 - sub rsp,20 { 32 }
B81924E7 - 83 38 00 - cmp dword ptr [rax],00 { 0 }
B81924EA - 49 BB D05719B800000000 - mov r11,BattleTech:StatCollection:ModifyStat { [EC8B4855] }
B81924F4 - 41 FF D3 - call r11
[/code]
[IMG]https://i.imgur.com/bxCHSDU.png[/IMG]
But, but.. how do I know which is which? Well.. it's called [B]debugging + sense of observation[/B]. It's something you'll have to learn in your own free time.
Let's take them one by one, for the first piece of code (as I was saying), in the order of how x64 registers are used for the parameters of a function (rcx, rdx, r8, r9, etc.). Just keep in mind [I]rcx[/I] with always be the [I]this[/I] pointer - - the instance pointer - - so start from [I]rdx[/I] onward:
[code=cpp]
item.StatCollection.ModifyStat
[/code]
[I]item.StatCollection.ModifyStat
[code=CEA]
B8192494 - 49 BB D05719B800000000 - mov r11,BattleTech:StatCollection:ModifyStat { [EC8B4855] }
B819249E - 41 FF D3 - call r11
[/code]
First parameter of this function is a string - [I]"debug"[/I]; in ASM it should be a buffer referenced somewhere (see [B]rdx[/B]):
[code=CEA]
B8192461 - BA 50AF3A67 - mov edx,673AAF50 { [0862A2E0] }
[/code]
[IMG]https://i.imgur.com/1dT8V6h.png[/IMG]
Second parameter is -1. This is dead obvious marked here (see [B]r8[/B]):
[code=CEA]
B8192466 - 49 B8 FFFFFFFFFFFFFFFF - mov r8,FFFFFFFFFFFFFFFF { -1 }
[/code]
Third parameter is another string - [I]"DamageReductionMultiplierAll"[/I]; in ASM it should be a buffer referenced somewhere (see [B]r9[/B]):
[code=CEA]
B8192470 - 49 B9 601F4B7501000000 - mov r9,00000001754B1F60 { [0862A2E0] }
[/code]
[IMG]https://i.imgur.com/6CDo1Ss.png[/IMG]
Next-up, we have these:
[code=cpp]
StatCollection.StatOperation.Float_Multiply, 0.1f, -1, true
[/code]
In JustDecompile, if you click on [B]Float_Multiply[/B] you'll be taken to this location; care to count with me? :D
[IMG]https://i.imgur.com/R03eLEu.png[/IMG]
[code]
Set, //0
Int_Add, //1
Int_Subtract, //2
Int_Multiply, //3
Int_Divide, //4
Int_Divide_Denom, //5
Int_Mod, //6
Int_Multiply_Float, //7
Int_Divide_Float, //8
Int_Divide_Denom_Float, //9
Float_Add, //A
Float_Subtract, //B
Float_Multiply, //C
Float_Divide, //D
Float_Divide_Denom, //E
Float_Multiply_Int, //F
Float_Divide_Int, //10
Float_Divide_Denom_Int, //11
String_Append, //12
String_Prepend, //13
Bitflag_SetBit, //14
Bitflag_FlipBit, //15
Bitflag_Combine //16
[/code]
So [I]Float_Multiply[/I] is 0xC in that list, right? :) Good. Let's continue; next-up is a float value - 0.1 - followed by a -1 and a [I]true[/I]. The true is translated as 1. Just so you know. So what we need to find in the ASM code is the right sequence for these 4:
[code=CEA]
B8192452 - F3 0F10 05 06030000 - movss xmm0,[BattleTech.UI:CombatDebugHUD:SetGodMode+360] { [0.10] }
B819245A - F3 0F5A C0 - cvtss2sd xmm0,xmm0
..
..
B819247A - 6A 01 - push 01 { 1 }
B819247C - 6A FF - push -01 { 255 }
B819247E - 48 83 EC 08 - sub rsp,08 { 8 }
B8192482 - F2 0F5A E8 - cvtsd2ss xmm5,xmm0
B8192486 - F3 0F11 2C 24 - movss [rsp],xmm5
B819248B - 6A 0C - push 0C { 12 }
[/code]
Cheat Engine actually shows us where the 0.1 float reference is. It is first converted into a double-precision float (see CVTSS2SD instruction [URL='https://www.felixcloutier.com/x86/CVTSS2SD.html']here[/URL]), then to a single-precision float (see CVTSD2SS instruction [URL='https://www.felixcloutier.com/x86/CVTSD2SS.html']here[/URL]). Then comes the push 1, push -1, storing the MMX conversion to stack and the push C (remember Float_Multiply?).
Let's read it now, from top to bottom:
[code=CEA]
B8192452 - F3 0F10 05 06030000 - movss xmm0,[BattleTech.UI:CombatDebugHUD:SetGodMode+360] { [0.10] } // 0.1f
B819245A - F3 0F5A C0 - cvtss2sd xmm0,xmm0
B819245E - 48 8B C8 - mov rcx,rax
B8192461 - BA 50AF3A67 - mov edx,673AAF50 { [0862A2E0] } // "debug" string
B8192466 - 49 B8 FFFFFFFFFFFFFFFF - mov r8,FFFFFFFFFFFFFFFF { -1 } // -1
B8192470 - 49 B9 601F4B7501000000 - mov r9,00000001754B1F60 { [0862A2E0] } // "DamageReductionMultiplierAll" string
B819247A - 6A 01 - push 01 { 1 } // true
B819247C - 6A FF - push -01 { 255 } // -1
B819247E - 48 83 EC 08 - sub rsp,08 { 8 }
B8192482 - F2 0F5A E8 - cvtsd2ss xmm5,xmm0
B8192486 - F3 0F11 2C 24 - movss [rsp],xmm5 // 0.1f written here
B819248B - 6A 0C - push 0C { 12 } // StatCollection.StatOperation.Float_Multiply
B819248D - 48 83 EC 20 - sub rsp,20 { 32 }
B8192491 - 83 38 00 - cmp dword ptr [rax],00 { 0 }
B8192494 - 49 BB D05719B800000000 - mov r11,BattleTech:StatCollection:ModifyStat { [EC8B4855] } // item.StatCollection.ModifyStat
B819249E - 41 FF D3 - call r11 // called here
[/code]
And for the grand finale, let's check the stack before [I]call r11 [/I]happens:
[IMG]https://i.imgur.com/cPFtjrS.png[/IMG]
Am hoping this was clear enough to help you modify the God Mode function to your liking :p
BR,
Sun