Unity Hacking via Python

tfigment

Novice Cheater
Apr 15, 2017
21
5
3
#1
After creating several scripts for Cheat Engine for Unity games in the past I decided I'd like to pursue a different approach as it was nearly impossible to maintain them when the game changed and finally got something working that I like well enough to share. Since its not Cheat Engine, I'm posting generic details here in the General Gamehacking thread.

If you do not already know how to program then if you do not already have a game specific set of scripts then this might be advanced material.

In any case, I really wanted something scriptable and something that can inspect anything in the game and change it. Ultimately I figured out how to compile IronPython for Mono that Unity uses and compiled something that loads into general Unity games and apps I've tried so far. I use a thirdparty plugin called IPA from Eusth (More details can be found here https://github.com/Eusth/IPA) to instrument the game and load plugins it was created for one game but works for any Unity game I've ever tried.

Prerequisites:
Runs on Windows x86 and x64 architectures. Only on standalone unity installs so no silverlight or web clients. And currently I have different builds for Unity 4 and 5 as that is what I have encountered most.

To install:
Download and unpack the console zip file from github file into the game folder. The plugin requires IPA to be installed so I've created additional downloads that include a recent version but you can download separately from here.

Drag the game exe over ipa.exe to instrument it. Note that this changes the dlls in the Managed folder. This enables the console assemblies to be loaded by the game (and any other plugins you might have). There are other ways to do this that might be simpler and less invasive but this is really simple once you used it once or twice. It makes a backup of the changed dlls so you can revert the changes if you dont like it.

To use:
Press the Ctrl+` (backquote) keys at the same time. For international users you can change the console startup options from the Plugins\Console\Console.ini file if you dont have easy access to the backtick or prefer a different key. There is also an option to open on start or to start hidden and run a startup script which is useful if there is a GUI cheat window script for the game.

You can close the console using ^Z (Control-Z) + Enter or run quit(). I will note that once the console is closed after opening I cannot restart the console and have it work properly so that is disabled. Games will close if you close the console using the X button.

From here you can manipulate anything in the game if you know how to find it. Most games usually have some class that is the root of everything that you can use to get access to more interesting stuff though not always easily. You can use Reflector, dotPeek or dnSpy to search through the game assemblies to find useful stuff. For Unity, most of the good stuff is in Assembly-CSharp.dll.

Notes:
I should note that manipulating Unity objects directly from the Console can crash the game so I've introduced a library called coroutine to help run python functions properly in unity. If you put too much in a function then unity block and you never see anything so its best to only use the unity wrapper for short operations. You can look at the test.py library in the download for details.

Also you cannot create MonoBehaviour derived classes directly in Python so I had to create wrapper classes that call back to python classes.

You can use the behaviors to take things to the next level and custom user interfaces in Unity which will run arbitrary python code. This is finally where the real payoff is as you can bind keys or buttons to scripts that do useful work. I'll post some specific game examples elsewhere to show this.

You can see an example of using the Unity gui for Cosmic Star Heroine here.

Examples:
Code:
>>> import UnityEngine
>>> print UnityEngine.Application.unityVersion
5.3.5f1
Code:
"""
Create centered window that has a couple of buttons for testing
"""
def test():
    import unity_util
    import UnityEngine
    from UnityEngine import GUI, GUILayout, Screen, Rect, Input, KeyCode
    import System
    
    # delete all of the old stuff
    unity_util.clean_behaviors()
    
    class DemoMessage():
        def __init__(self):
            self.color = UnityEngine.Color.white
            self.counter = 1
            self.options = System.Array.CreateInstance(UnityEngine.GUILayoutOption, 0)
            self.show_buttons = False
            self.gameObject = None # will be assigned if exists as member
            self.component = None # will be assigned if exists as member
            self.visible = True
            #print "Called when object is created"
            
        def Update(self):
            # Update is called less so better place to check keystate than OnGUI
            if Input.GetKeyDown(KeyCode.F8):
                # unity sucks for checking meta keys
                ctrl, alt, shift = unity_util.metakey_state()
                if ctrl and not alt and not shift:
                    self.visible = not self.visible
                    
        def OnGUI(self):
            if self.visible:
                self.counter = self.counter + 1
                origColor = GUI.color
                try:
                    GUI.BeginGroup(Rect (Screen.width / 2 - 50, Screen.height / 2 - 50, 400, 400))
                    GUI.color = self.color
                    with GUILayout.VerticalScope("Group", GUI.skin.window):
                        GUILayout.Label(str(self.counter))
                        self.show_buttons = GUILayout.Toggle(self.show_buttons, "Buttons")
                        if self.show_buttons:
                            if GUILayout.Button("Click me"):
                                print "clicked"
                            if GUILayout.Button("Close"):
                                if self.gameObject: 
                                    UnityEngine.Object.DestroyObject(self.gameObject)
                except:
                    GUI.color = origColor
                GUI.EndGroup()
            
        def __del__(self):
            #print "Called when object is garbage collected"
            pass
    
    return unity_util.create_gui_behavior(DemoMessage)
Downloads:
https://github.com/TheHologram/unity-console/releases
 

Cryptek

What is cheating?
Apr 17, 2017
1
0
1
#2
As someone who almost exclusively hacks unity games now-a-days, this is awesome. Thank you so much!

Can't wait to dive in with it.
 

tfigment

Novice Cheater
Apr 15, 2017
21
5
3
#4
Started one but game didn't pique my interest or sustain it in any case. Might try again. The base stuff works and its a matter of reversing the code and writing a user interface. Wasn't really getting feedback on these console releases so wasn't sure there was any actual interest as there are CE tables that do quite a bit. I generally do these for myself and then release but if there is interest I will sometimes go a little further. BTW, I liked how you set god mode text (assuming that is not built in).
 

SunBeam

Trouble-Maker
Talents
Feb 4, 2018
564
299
63
#5
Yeah, it's built in. Print function can also use be used separately.

I'd be interested mostly for learning purposes, as I've been tracking your article and doing those examples myself since last year. Have encountered some issues along the way (console working in a quirky way) and I think I also PMed you about it :) But a lot of shit has happened in-between, you were also not that active, but now that I see you're still doing this and responded, I would like to get back on track and learn this systematically :)

It's true the game is pretty linear, yet my sole interest is the py coding behind the console and logic of correspondences: Unity-console object referencing vs. what you'd see in Mono or a decompiler (Telerik's JustDecompile).

Let me know if there's need to spike your interest in a different way :)

P.S.: I'll move this in PMs, if need be; sorry for the off-topic.
 

tfigment

Novice Cheater
Apr 15, 2017
21
5
3
#6
Yeah, it's built in. Print function can also use be used separately.

I'd be interested mostly for learning purposes, as I've been tracking your article and doing those examples myself since last year. Have encountered some issues along the way (console working in a quirky way) and I think I also PMed you about it :) But a lot of shit has happened in-between, you were also not that active, but now that I see you're still doing this and responded, I would like to get back on track and learn this systematically :)

It's true the game is pretty linear, yet my sole interest is the py coding behind the console and logic of correspondences: Unity-console object referencing vs. what you'd see in Mono or a decompiler (Telerik's JustDecompile).

Let me know if there's need to spike your interest in a different way :)

P.S.: I'll move this in PMs, if need be; sorry for the off-topic.
I don't mind off-topic as long as its pleasant conversation but maybe more appropriate in the original Unity-Console thread. Anyway you're an administrator :) . I'm not trying to post this stuff to make money or anything so I dont really do marketing so my stuff sometimes falls of a cliff in terms or reaction. Just love the technical aspect and the challenge more than anything. I hardly play the games I hack these days as this is far more fun. I'm active when I can be but have limited time so generally unresponsive to inquiries. I'm not sure I've seen PMs but maybe I was not actually logging in. I think the forum upgrade probably wiped any PMs at least I dont see any. But dont check emails and stuff (even from my mother). I'm such a bad person :) .

I'm happy to continue any conversation in the open so that other people can learn if they are interested.
 

tfigment

Novice Cheater
Apr 15, 2017
21
5
3
#8
So what are your questions about objects? In general MonoObject and MonoClass are the basis for nearly everything in the .net land in the mono implementation. IronPython will wrap the .NET object with a thin layer but normally you wont really notice.

Unity uses at least in older version a fork of mono 2 that is rather old. I matched it up at one point but I've since forgot which version was closest. I would recommend downloading the oldest version of mono 2.x that you can find from the internet.

MonoObject is in mono/metadata/object.h but really everything is in MonoClass which hold the class functionality like properties and methods. MonoObject just has a reference to a vtable which holds the MonoClass reference. MonoClass is in mostly in mono/metadata/class-internals.h. From the C structures you can infer offsets for pointers that are useful for CheatEngine for example. I used this knowledge with arrays and stuff when I wrote some lua code for the mono scripts that are part of CE. Much of the layer that the lua scripts interact with are these monoclass and monoobject pointers.

But in general I would recommend staying very far away from using pointers directly as it is probably fragile. If you can get access to the functions then you can use mono correctly and get reference counts working and what not.

.NET is then a layer of code which makes it easier to walk through the MonoClass. I believe the MonoObject is the same thing that you get access to in .NET in terms of memory locations.

With Unity comes some extra challenges as their object and component .net stuff layer on this and become even more difficult to access via pointers. The Components require basically calling the mono libraries and pointer math becomes hard. If you haven't messed with that stuff then this may sound like more greek than it already is.

Anyway this stuff is really why I decided to the IronPython route since I didn't really want to keep going that low all of the time and was just too much effort for too little payback.
 

SunBeam

Trouble-Maker
Talents
Feb 4, 2018
564
299
63
#9
Let's say I will make an attempt at mimic-ing what you've done for your tutorial game, but in BattleTech. With some simple feature such as adding Funds/Morale at ship screen. I have the ASM scripts wrote out like so:

CEA:
AddMorale_do:
sub rsp,28
call mono_thAttach
call _getInstance
mov rcx,rax
call _getSimulation
test rax,rax
je short @f
  mov rcx,rax
  mov rdx,[dwMoraleAmount]
  xor r8,r8
  call _AddMorale
@@:
call mono_thDetach
add rsp,28
ret

AddFunds_do:
sub rsp,28
call mono_thAttach
call _getInstance
mov rcx,rax
call _getSimulation
test rax,rax
je short @f
  mov rcx,rax
  mov rdx,[dwFundsAmount]
  xor r8,r8
  mov r9d,1
  call _AddFunds
@@:
call mono_thDetach
add rsp,28
ret

Will try to call BattleTech:GameInstance:get_Simulation and then BattleTech:SimGameState:AddMorale or BattleTech:SimGameState:AddFunds from console, fiddling with parameters and calling convention. That's what I'd be interested in. But we talk after I've succeeded ;) Want to have done testing before opening my mouth again :D

BR,
Sun
 

SunBeam

Trouble-Maker
Talents
Feb 4, 2018
564
299
63
#10
@tfigment: After having patched BATTLETECH.exe with the console files you've referenced, this is what happens on launch:



And this is the folder set-up:



Any idea? Game's Unity is x64. Might have to do with that?
 

tfigment

Novice Cheater
Apr 15, 2017
21
5
3
#11
@tfigment: After having patched BATTLETECH.exe with the console files you've referenced, this is what happens on launch:


And this is the folder set-up:



Any idea? Game's Unity is x64. Might have to do with that?

You likely copied the Console.ini from PoE2. That is this problem. I have a number of Preload.Assemblies listed that do not apply for battletech (the ones that start with oei). Remove those from the preload and it should start.

Out of caution. you should remove the SceneChange.Script.Py as for first attempt at loading as it will try to show the cheat window and fail due to similar mismatches. Basically you will need to author a script in the lib folder to call here as the launch boot script. I will usually stub out the showWindow script and have it return immediately before any imports.

Edit: Now I remember this one did have some other issues. Mostly around the loading of folders in the Plugins folder that I had to work around. If you startup then it might hang with the logo before the start menu. Its failing to load a native dll due to it not being on the path.

Here is a build that sort of works. Its just a bootstrap build that shows the Cheat window but doesn't do anything.

You have to launch the game with the Start.bat file which works around the path problem. This is really kind of annoying and I'm not sure the precise cause and will likely need code change to fix the windows SetDllDirectory which is probably the root cause of my problems.

Link: https://mega.nz/#!8YFhBSCL
Key: !gL4SLFDH0BhWlh9NM7mVD1qPq5rlzjv0VcCHsUgb3Eg
Post automatically merged:

Let's say I will make an attempt at mimic-ing what you've done for your tutorial game, but in BattleTech. With some simple feature such as adding Funds/Morale at ship screen. I have the ASM scripts wrote out like so:

Will try to call BattleTech:GameInstance:get_Simulation and then BattleTech:SimGameState:AddMorale or BattleTech:SimGameState:AddFunds from console, fiddling with parameters and calling convention. That's what I'd be interested in. But we talk after I've succeeded ;) Want to have done testing before opening my mouth again :D

BR,
Sun
Accessing Funds and Morale
Python:
import BattleTech
BattleTech.UnityGameInstance.Instance.Game.Simulation.Funds
BattleTech.UnityGameInstance.Instance.Game.Simulation.Morale
BattleTech.UnityGameInstance.Instance.Game.Simulation.AddFunds(100000)
BattleTech.UnityGameInstance.Instance.Game.Simulation.AddMorale(10, None)
 
Last edited:

SunBeam

Trouble-Maker
Talents
Feb 4, 2018
564
299
63
#12
Yeah. Game crashes, a second instance is launched, but stays open this time around.
 
Top Bottom