Removing "combat slowdown" feature from RimWorld

Started by RawCode, November 05, 2015, 09:45:01 AM

Previous topic - Next topic

RawCode

Disclaimer:
black arts, version\platform dependant

I saw questions about "boring" combat slowdown zillion times, also I saw zillion explanations of game design by Tynan, still, if people asks, someone should give them what they want.

Generic reverse engineering method is "observation", actually "observation" is generic method of humanity, all known technology invented by observation of some kind or
related to results of previous observations.

First action we will perform is observation, we will force game into desired state and track clearly visible changes.
You can use this simple and yet not popular method for anything.

Most clear change is "stroked out" fast and very fast speeds, you just can't miss this change.

With some knowledge of unity API and (god bless) google we likely to discover DrawLineHorizontal method that perform "line rendering".
Also you can use "time" and or "speed" keys and or just bruteforce entire source of game (my favorite method, but it extremely time and energy consuming).

That method can be found inside TimeControls class that  performs most actions related to speed control.
But not every action, few things moved to other locations.

Condition related to drawing line is "Find.TickManager.slower.ForcedNormalSpeed".
That "condition" is boolean property of TimeSlower class.

From perspective of CLI property is instance method with name "get_ForcedNormalSpeed".
That method is compiler generated and hidden from view, also, due to control pass and inline limits, property is slower then direct access of field, that will be inlined.

ONLY action done by that method is returning or setting specific variable, nothing more, nothing less.

Most simple way to "remove" slowdown is changing that method into unconditional return of desired value.
But in our case we will redirect variable IO into different variable, this will allow to hook "slowdown", enable and disable it at any moment.

Probably i should bind hotkey for it or add menu checkbox, later...

All methods are stored in memory (obviously) in form of platform specific opcodes, in case of our target platform (windows64) opcodes are x86 assembler.
IL codes also stored, but, they used only for initial method compilation and later are ignored.

Its possible to change IL and force recompilation, but this will be explained later.

Invocation of GetFunctionPointer() over MethodInfo expose native pointer to x86 of that method.
Memory allocated for methods marked RWX ever after compilation is complete, this allows to rewrite method code at runtime.

payload of getter method is simple: loading variable reference into EAX, dereferencing EAX and returning result.

Variable reference is "hardcoded" into method and stored as part of opcode, it can be changed right here without any side effects.
Variable reference of "normal" types is exposed by "&" operator without any magic.

If you want reference to "managed type" you can read it from bytecode, calculate from class pool or just invoke __makeref undocumented IL code.
Under normal conditions you should never do it with managed types, you can screw garbage collection.

Lets begin:

            Log.Warning("x86 override test 2");
            Log.Warning("this mod will override TimeSlower.ForcedNormalSpeed");

            //getting pointer to method
            //double check binding flags, in other case game will fail with NPE
            byte* mpx_1 = (byte*)typeof(TimeSlower).GetMethod("get_ForcedNormalSpeed", BindingFlags.Instance | BindingFlags.Public).MethodHandle.GetFunctionPointer().ToPointer();

            //lazy init
            int ttz = 0;

            //read reference of our controller variable
            fixed(bool* key = &TIMESLOWEROVERRIDE)

            //cast reference into int
            ttz = (int)key;

            //just debug
            Log.Warning(ttz.ToString("X2"));

            //convert reference into byte array, this required to "reverse" bytes
            byte[] bytes = BitConverter.GetBytes(ttz);


            *(mpx_1 + 0) = 0xB8; //XOR MOV RAW
            *(mpx_1 + 1) = bytes[0]; //RAW WORD
            *(mpx_1 + 2) = bytes[1];
            *(mpx_1 + 3) = bytes[2];
            *(mpx_1 + 4) = bytes[3];

            *(mpx_1 + 5) = 0x8b; //XOR DEREFERENCE
            *(mpx_1 + 6) = 0;   //into itself
            *(mpx_1 + 7) = 0x90; //padding
            *(mpx_1 + 8) = 0x90;

            *(mpx_1 + 9) = 0xC3; //return

            Log.Warning("override complete");
            Log.Warning("have a nice day!");


Invoking given code at any moment will redirect time slowdown control into TIMESLOWEROVERRIDE variable making methods SignalForceNormalSpeed and SignalForceNormalSpeedShort used by engine obsolete.








Fluffy (l2032)

This is extremely interesting stuff, but can this be adapted to a more generic example? Ideally I'ld like to be able to detour any method into another method (provided ofcourse that the signatures match).

The Cities: Skylines modding community uses a very similar detour method described here;
https://github.com/sschoener/cities-skylines-detour

The main part of the method is this;

                // R11 is volatile.
                byte* sitePtr = (byte*)site.ToPointer();
                *sitePtr = 0x49; // mov r11, target
                *( sitePtr + 1 ) = 0xBB;
                *( (ulong*)( sitePtr + 2 ) ) = (ulong)target.ToInt64();
                *( sitePtr + 10 ) = 0x41; // jmp r11
                *( sitePtr + 11 ) = 0xFF;
                *( sitePtr + 12 ) = 0xE3;


Which is extremely similar to the code you posted. I'ld like to adapt this to RW, but there's a few obvious differences (64 vs. 32 bit being the primary one). My attempts have led me to this variant;


        private static void PatchJumpTo( IntPtr site, IntPtr target )
        {
            unsafe
            {
                //getting pointer to method
                //double check binding flags, in other case game will fail with NPE
                byte* mpx_1 = (byte*)site.ToPointer();

                //maybe?
                int ttz = target.ToInt32();
               
                //just debug
                Log.Warning( ttz.ToString( "X2" ) );

                //convert reference into byte array, this required to "reverse" bytes
                byte[] bytes = BitConverter.GetBytes(ttz);
               
                *( mpx_1 + 0 ) = 0xB8; //XOR MOV RAW
                *( mpx_1 + 1 ) = bytes[0]; //RAW WORD
                *( mpx_1 + 2 ) = bytes[1];
                *( mpx_1 + 3 ) = bytes[2];
                *( mpx_1 + 4 ) = bytes[3];

                *( mpx_1 + 5 ) = 0x8b; //XOR DEREFERENCE
                *( mpx_1 + 6 ) = 0;   //into itself
                *( mpx_1 + 7 ) = 0x90; //padding
                *( mpx_1 + 8 ) = 0x90;

                *( mpx_1 + 9 ) = 0xC3; //return
            }
        }

Which is basically your code with a convenience wrapper;

        public static void RedirectCalls( MethodInfo from, MethodInfo to )
        {
            // GetFunctionPointer enforces compilation of the method.
            var fptr1 = from.MethodHandle.GetFunctionPointer();
            var fptr2 = to.MethodHandle.GetFunctionPointer();

            PatchJumpTo( fptr1, fptr2 );
            // We could also use:
            //RedirectCall(from, to);
        }


I've tried testing this, and the good news is that it doesn't give errors. The bad news is that it does break the method (I've tested with detouring Log.Message, which doesn't work anymore after detouring), and undoing the detour (i.e. calling the detour again with from/to reversed) doesn't restore the original.

I've wrapped the whole thing into a small mod, see attachment.

I, and I assume the rest of the modding community, would be VERY obliged if you could have a look, and maybe even fix this to work with arbitrary methods?


[attachment deleted due to age]

RawCode

Method hooks is not "new and original research", anything about this will be similar.

Sebastian Schöner have advanced version of methods (compared to my code) i will review both projects and probably will make something more useful.

Thx for link, it really helps.

Issue with your code:

It try to read bool located at @IntPtr target@ address.
Return is always false, no other actions are preformed.

Also, no matter how well code is done, "hook" is irreversible and final action as long as original method body is not saved somewhere.

Invoking method again in reverse order will cause method to invoke itself and crash CLI with stackoverflow.

TheGentlmen


Fluffy (l2032)

Quote from: RawCode on November 06, 2015, 09:16:02 AM
Method hooks is not "new and original research", anything about this will be similar.

Sebastian Schöner have advanced version of methods (compared to my code) i will review both projects and probably will make something more useful.

Thx for link, it really helps.

Issue with your code:

It try to read bool located at @IntPtr target@ address.
Return is always false, no other actions are preformed.
I kinda figured that, problem is I can't read hex, and have no idea what I'm doing :p.

Also, no matter how well code is done, "hook" is irreversible and final action as long as original method body is not saved somewhere.
We're just changing pointers though right? Couldn't we store the original pointer somewhere?

Invoking method again in reverse order will cause method to invoke itself and crash CLI with stackoverflow.
I hadn't thought of that, but now you say it thats actually kindof obvious. MethodInfo on the detoured method would just return the new method, replacing it with itself?

RawCode

QuoteI kinda figured that, problem is I can't read hex, and have no idea what I'm doing :p.

hex (x86 assembly) stuff
http://ref.x86asm.net/geek.html

+

this "convert" hex into a bit more readable form
https://www.onlinedisassembler.com/odaweb/


+
this sample code that allows to "read" code of method (in VS you can just hit assembly view)

MethodInfo INFO_1 = typeof(MainClass).GetMethod ("dummy_call_to_return", BindingFlags.Static | BindingFlags.Public);
MethodInfo INFO_2 = typeof(MainClass).GetMethod ("get_REPLACEMENT", BindingFlags.Static | BindingFlags.Public);

byte *POINTER_1 = (byte*)INFO_1. MethodHandle.GetFunctionPointer().ToPointer();
byte *POINTER_2 = (byte*)INFO_2. MethodHandle.GetFunctionPointer().ToPointer();

int TMP = 0;
int I = 0;

for (;;)
{
//this line will dereference pointer + 1\2\3\4\5 into value stored by given pointer
TMP = *(POINTER_1 + I);
I++;
//print value in hex form
Console.WriteLine (TMP.ToString ("X2"));

//this line is harmful and will cause major issue time to time
//it will stop execution if hit "return" opcode
//sadly, it does not track is  "opcode" actually opcode and will return if hit c3 as part of pointer or value
if (TMP == 0xC3)
break;
}


QuoteWe're just changing pointers though right? Couldn't we store the original pointer somewhere?

When assembly is loaded, CLI generates stub for every method in given assembly, EVERY, no matter is method can be invoked or not.
If methodA *can* invoke methodB, it will be stored inside methodB reference list.
Ever if method is behind if(true)return; it still *can* be invoked and will be processed.

Stub is simple function that pass method_jit_info (IL bytecode + metadata) into compiler, nothing more, nothing less.

Changing opcode of stub function and replacing method_jit_info reference with other method_jit_info allows to change code of methods that not yet compiled.
Also you can change method_jit_info itself.

After compilation is finished, compiler enumerate reference list of method and replace all references to stub method with reference to real method.
This modification done to OTHER methods.

This can be observed by watching memory of application, also VS2013 feature "assembly view" that can be used to track this feature step by step.

If method not yet compiled - no problem, compiler perform "have compiled image" check and wont emit stub for methods that have compiled image.

If methodA invokes methodB that "makes people cry" there are multiple options to "make people scream":

1) You modify methodB directly (most simple method, provided with article).
1.1) You can modify method stub, replacing it with jump to other method, this will cause CLI to skip compilation and leave references to *stub* in code.
1.2) If method allocation size is sufficient, you don't need to "jump" you can just copy over code you want (inline it).
2) You modify methodA and all other methods that can invoke methodB and replace reference to methodB with methodC
3) You modify CLI itself and add "invocation pipeline \ redirection list" into internal routine.

1) unsafe pointer operations, lossy process, irreversible, unmanaged.
2) dll import, clean, lossless, reversible, managed, singletron.
3) completely native implementation, managed, allows multiple injections into single method.

1st option is dirty hack, multiple mods using it will cause nothing but trouble.
2nd option is clean, but singletron, if modA replaced methodA there is no way around it and no other mod may perform same operation.

3rd option is "how such things should be done" - also it will keep memory image of all methods intact and allows advanced features like "do not invoke blabla.blabla(int)"

in order to call other method or jump to other method (c# use near only, long not test probably works):

1) Get pointer of current method
2) Get pointer of target method

3) Calculate relative offset from current to target method, can be negative, in such case special rules to store it apply, cos it normally overflow "signed integer" type that c# use by default

3) Calculate relative offset from beginning of method to jump\call instruction

4) After offsets are calculated, your invocation in preseudo code will looks

0000 save stack
0008 load params
nop
nop
0010 jump
0014 forward from this location (jump is +0010 relative to method offset) 90 words (word == 4 bytes)
0018 leave subroutine
0022 return

managing stack and passing params is hard part, since CLI perform some actions "behind scene"

TheGentlmen