What's new

BattleTech (CE Mono vs. JustDecompile)

SunBeam

RCE Fanatics
Talents
Fearless Donors
Joined
Feb 4, 2018
Messages
743
Likes
401
Points
63
#1
EPISODE 1

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 Unity decompiled outputs versus the decompiled code Cheat Engine's MonoDataCollector returns. For this explanation I will use the below:
  • Cheat Engine 6.7 -- ignore my screenshots showing RC3; you don't need RC3
  • Telerik JustDecompile -- you can download it here
  • BattleTech game
  • BattleTech table found in this post
Alright.

Fire up the game, get in a map. Open Cheat Engine, load table, activate [ Enable ] script, activate Cheat Handler script. Go back in-game and hit Numpad 0 to execute DEBUG_PlayerOneGodMode. Wait, wait.. what did I just do? Rewind, please :)

Let's take them one by one, from JustDecompile perspective, so you understand how I got to what the script just did ;)

Fire-up Telerik JustDecompile. Hit Ctrl+O and choose Assembly-CSharp.dll from your <SteamLibFolder>\BATTLETECH\BattleTech_Data\Managed folder. Once opened, you will see a Search button up-top on the toolbar. Click it and type godmode in the Find What: field. You will see this:



See DEBUG_PlayerOneGodMode reference on second line? Double-click it. You'll be taken here:



What can you see in that picture? Simple:
  • Left tree shows the function, as well as the Namespace and Type Name. 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 - - BattleTech.UI:CombatDebugHUD - - this denotes hierarchy in C++ (a member of).
  • Our function is called DEBUG_PlayerOneGodMode, so it will be appended to the above with a ":" as well (member of).
  • Final result is:
    Code:
    BattleTech.UI:CombatDebugHUD:DEBUG_PlayerOneGodMode
  • We'll see in a bit what's to be done with this.

  • Right side of the window shows you the actual function and its code:
C++:
public void DEBUG_PlayerOneGodMode()
{
    this.SetGodMode(this.combatHUD.Combat.Teams.Find((Team x) => x.GUID == "bf40fd39-ccf9-47c4-94a6-061809681140"));
}
Looking further at the code, we see this function calls-in another: SetGodMode. Click on it and you are taken here:



Transcript:

C++:
private void SetGodMode(Team team)
{
    for (int i = 0; i < team.units.Count; i++)
    {
        AbstractActor item = team.units[i];
        item.StatCollection.ModifyStat<float>("debug", -1, "DamageReductionMultiplierAll", StatCollection.StatOperation.Float_Multiply, 0.1f, -1, true);
        item.StatCollection.ModifyStat<float>("debug", -1, "ReceivedInstabilityMultiplier", StatCollection.StatOperation.Float_Multiply, 0.1f, -1, true);
        item.StatCollection.ModifyStat<bool>("debug", -1, "IgnorePilotInjuries", StatCollection.StatOperation.Set, true, -1, true);
        for (int j = 0; j < item.Weapons.Count; j++)
        {
            Weapon weapon = item.Weapons[j];
            weapon.StatCollection.ModifyStat<float>("debug", -1, "DamagePerShot", StatCollection.StatOperation.Float_Multiply, 10f, -1, true);
            weapon.StatCollection.ModifyStat<float>("debug", -1, "AccuracyModifier", StatCollection.StatOperation.Float_Add, -20f, -1, true);
        }
        item.Combat.MessageCenter.PublishMessage(new FloatieMessage(item.GUID, item.GUID, "GOD MODE", FloatieMessage.MessageNature.Buff));
    }
}

So, what the developer God Mode does in reality is:
  • DamageReductionMultiplierAll is set to its current value * 0.1f
  • ReceivedInstabilityMultiplier is set to current value * 0.1f
  • IgnorePilotInjuries becomes active
  • DamagePerShot is set to current value * 10f
  • AccuracyModifier is set to current value + (-20f)
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 instance pointer to be able to call it. Even though you see a void type of function, this only means it doesn't take-in parameters. However, the instance pointer - also referenced as this - is required.

How do you know you need this and where to find it?

1) You know you need this due to this piece of code:

C++:
public void DEBUG_PlayerOneGodMode()
{
    this.SetGodMode(this.combatHUD.Combat.Teams.Find((Team x) => x.GUID == "bf40fd39-ccf9-47c4-94a6-061809681140"));
}

See this.SetGodMode? See this.combatHUD.Combat.Teams.Find? That's how you know :)

2) To get it you would need to break on a function you know - from testing - is executed in the Namespace:Type Name (just so I use the references from JustDecompile). Most Unity functions come - conveniently - with an Update function :) You'll see in a bit.

Time to move to Cheat Engine; remember you've loaded the table and executed Numpad 0. 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:
  • Cheat Engine > Memory Viewer
  • Ctrl+G >
    Code:
    BattleTech.UI:CombatDebugHUD:DEBUG_PlayerOneGodMode - OR - BattleTech.UI:CombatDebugHUD:SetGodMode
  • OK
(before executing the functions)

DEBUG_PlayerOneGodMode:



SetGodMode:



(after executing the functions)

DEBUG_PlayerOneGodMode:



SetGodMode:



OK, OK.. but what about the instance pointer? We'll get it from debugging BattleTech.UI:CombatDebugHUD:Update. So head there in Cheat Engine's Memory Viewer via Ctrl+G and set a breakpoint at the function's prologue:



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 instance pointer is in RCX. 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 :)

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

Then a function that would use the two above:

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

So up to this point you have the tools you need to understand a void 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:

C++:
item.StatCollection.ModifyStat<float>("debug", -1, "DamageReductionMultiplierAll", StatCollection.StatOperation.Float_Multiply, 0.1f, -1, true);

In Cheat Engine's ASM:

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



But, but.. how do I know which is which? Well.. it's called debugging + sense of observation. 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 rcx with always be the this pointer - - the instance pointer - - so start from rdx onward:

C++:
item.StatCollection.ModifyStat<float>("debug", -1, "DamageReductionMultiplierAll", StatCollection.StatOperation.Float_Multiply, 0.1f, -1, true);

item.StatCollection.ModifyStat<float> is a function; its JustDecompile ASM reference is here:

CEA:
B8192494 - 49 BB D05719B800000000 - mov r11,BattleTech:StatCollection:ModifyStat { [EC8B4855] }
B819249E - 41 FF D3              - call r11

First parameter of this function is a string - "debug"; in ASM it should be a buffer referenced somewhere (see rdx):

CEA:
B8192461 - BA 50AF3A67           - mov edx,673AAF50 { [0862A2E0] }



Second parameter is -1. This is dead obvious marked here (see r8):

CEA:
B8192466 - 49 B8 FFFFFFFFFFFFFFFF - mov r8,FFFFFFFFFFFFFFFF { -1 }

Third parameter is another string - "DamageReductionMultiplierAll"; in ASM it should be a buffer referenced somewhere (see r9):

CEA:
B8192470 - 49 B9 601F4B7501000000 - mov r9,00000001754B1F60 { [0862A2E0] }



Next-up, we have these:

C++:
StatCollection.StatOperation.Float_Multiply, 0.1f, -1, true

In JustDecompile, if you click on Float_Multiply you'll be taken to this location; care to count with me? :D



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

So Float_Multiply is 0xC in that list, right? :) Good. Let's continue; next-up is a float value - 0.1 - followed by a -1 and a true. 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:

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 }

Cheat Engine actually shows us where the 0.1 float reference is. It is first converted into a double-precision float (see CVTSS2SD instruction here), then to a single-precision float (see CVTSD2SS instruction here). 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:

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<float> function
B819249E - 41 FF D3              - call r11 // called here

And for the grand finale, let's check the stack before call r11 happens:



Am hoping this was clear enough to help you modify the God Mode function to your liking :p

BR,
Sun
 

SunBeam

RCE Fanatics
Talents
Fearless Donors
Joined
Feb 4, 2018
Messages
743
Likes
401
Points
63
#4
Well, people complained there aren't that many tutorials and CE is dying. There you go :p To hell with video tutorials I say! :D
 
Joined
May 2, 2018
Messages
6
Likes
4
Points
3
#5
You can't tutorial years of dev experience. There are more moving pieces in these things then you can think of at one time. And this is stuff even most devs will find challenging. If you really want to learn CE you need to learn software development.
 

SunBeam

RCE Fanatics
Talents
Fearless Donors
Joined
Feb 4, 2018
Messages
743
Likes
401
Points
63
#6
EPISODE 2

There are times in life when you take a step back and see the bigger picture. I was going to update the table last night (which I'm gonna do anyway), then I thought to myself "why not extend the tutorial?" So here goes.

If you check my table here, you will notice I use hooks to get the instance pointers needed for some of the functions in the Cheat Handler. Considering the code runs in a thread, therefore requires no per-se interaction from the user - just using the designed hotkey to execute it - what if we don't rely on hooks at all? ;) The reason I am mentioning this is game updates have the tendency to break these hooks, as the prologue of the functions often changes (or are JIT-ed differently). To explain it even better, see this practical example in my 1.2 table, which, as you reported, crashes on latest version. Let's see why:

Code:
[ENABLE]

define( CombatDebugHUD_Update, "BattleTech.UI:CombatDebugHUD:Update" )
define( SimGameState_Update, "BattleTech:SimGameState:Update" )

..
..

//***************************
//*          Hooks          *
//***************************

//get .this Instance pointer ;)

CombatDebugHUD_Update:
jmp CombatDebugHUD_Update_h
CombatDebugHUD_Update_b:

SimGameState_Update:
jmp SimGameState_Update_h
db 90 90 90 90 90
SimGameState_Update_b:

..
..

CombatDebugHUD_Update_h:
mov [pCombatDebugHUD],rcx
CombatDebugHUD_Update_o:
readmem( CombatDebugHUD_Update, 15 )
jmp CombatDebugHUD_Update_b

SimGameState_Update_h:
mov [pSimGameState],rcx
SimGameState_Update_o:
readmem( SimGameState_Update, 19 )
jmp SimGameState_Update_b

..
..

[DISABLE]

CombatDebugHUD_Update:
readmem( CombatDebugHUD_Update_o, 15 )

SimGameState_Update:
readmem( SimGameState_Update_o, 19 )

..
..

If we go to CombatDebugHUD_Update, which in reality is BattleTech.UI:CombatDebugHUD:Update, we see this:

Code:
543BA8D0 - 55                    - push rbp
543BA8D1 - 48 8B EC              - mov rbp,rsp
543BA8D4 - 53                    - push rbx
543BA8D5 - 56                    - push rsi
543BA8D6 - 57                    - push rdi
543BA8D7 - 41 54                 - push r12
543BA8D9 - 41 55                 - push r13
543BA8DB - 41 56                 - push r14
543BA8DD - 41 57                 - push r15
543BA8DF - 48 83 EC 28           - sub rsp,28
543BA8E3 - 48 89 4D A8           - mov [rbp-58],rcx

Keep in mind CE will most likely create a 14-bytes jump when allocation is not done close to game module. In this case, the Assembly-CSharp.dll is dynamically allocated, so there are 99% chances you won't be able to create 5-bytes jumps. What I mean with this is when you write "jmp Hook" to your allocated memory space, CE can do this:

Code:
E8 xx xx xx xx - JMP Hook
or this:
Code:
FF 25 xx xx xx xx xx xx xx xx xx xx xx xx - jmp Hook

We rule out the possibility of 5-bytes JMPs; let's focus on 14-bytes.

Back to my explanation. We're at BattleTech.UI:CombatDebugHUD:Update with displayed ASM:

Code:
543BA8D0 - 55                    - push rbp
543BA8D1 - 48 8B EC              - mov rbp,rsp
543BA8D4 - 53                    - push rbx
543BA8D5 - 56                    - push rsi
543BA8D6 - 57                    - push rdi
543BA8D7 - 41 54                 - push r12
543BA8D9 - 41 55                 - push r13
543BA8DB - 41 56                 - push r14
543BA8DD - 41 57                 - push r15
543BA8DF - 48 83 EC 28           - sub rsp,28
543BA8E3 - 48 89 4D A8           - mov [rbp-58],rcx

In order to produce a JMP, CE will overwrite 14 bytes at the prologue of our function. So this:

Code:
CombatDebugHUD_Update:
jmp CombatDebugHUD_Update_h
produces this:
Code:
543BA8D0 - FF25 00000000 99037B5FF77F0000 - jmp 7FF75F7B0399
543BA8DE - 57                    - push rdi
543BA8DF - 48 83 EC 28           - sub rsp,28

So code from 543BA8D0 (prologue) down to 543BA8DE was replaced with a long JMP. All nice and dandy, but.. from our cave, we want to return to "push rdi" instruction, so game resumes execution after having passed through our cave. If you count 14 bytes from top going down, you got:

Code:
55 48 8B EC 53 56 57 41 54 41 55 41 56 41

As you can see the 0x41 byte is part of a 2-bytes instruction. So CE will break it with the JMP:

Code:
543BA8DD - 41 57                 - push r15

So it wouldn't be correct the return JMP to point to 543BA8DE, because it would execute an invalid instruction. If you check my code, I am indicating exactly that:

Code:
CombatDebugHUD_Update:
jmp CombatDebugHUD_Update_h
CombatDebugHUD_Update_b:

..
..

CombatDebugHUD_Update_h:
..
..
jmp CombatDebugHUD_Update_b

By assigning the CombatDebugHUD_Update_b label right under jmp CombatDebugHUD_Update_h, I am telling the JMP in the cave to land right below it to the invalid instruction: jmp CombatDebugHUD_Update_b. That's why 1.2 table crashes with latest version of the game. And that's why you should avoid methods like these unless you intend to update the table with each freakin' game update (because the Mono code is JIT-ed differently, having different sizes or containing a different order of the instructions). In short, your AOBs (if you use them) will certainly break as well.

So.. to avoid this, I thought of not actually using a hook. In some occasions, you can fetch a pointer without the need to use hooks. Which is what I'm going to show you in the next episode ;)

BR,
Sun
 

Top Bottom