Announcement

Collapse
No announcement yet.

[Tutorial] Assigning values to const variables

Collapse
X
 
  • Filter
  • Time
  • Show
Clear All
new posts

    [Tutorial] Assigning values to const variables

    This tutorial will teach you how to be the boss and assign values to const variables despite the compiler's unwillingness to do so. It assumes some knowledge of UnrealScript and programming in general (as we will be writing an external program to hack).

    The tutorial is mostly based on this work: Unreal Under the Hood: UnrealScript Bytecode Decompiler, which, in turn, is based on previous reverse-engineering work by many other people. Give most of the appreciation to those people, not me.

    Let's start.
    To be practical, I will first show an example why it's necessary at all. Suppose we want to create a mutator that messages players when Damage Amplifier becomes available. A brute-force approach would be to constantly poll the pickup factory until its state becomes 'Pickup'. The solution is mostly fine, but it's not effective. It will either consume a lot of CPU cycles, or be rather inaccurate (it can even miss the event, if someone pickups it just after the spawn).
    If you ever launched the editor, you know that UT3 has something called Kismet. Among other things, there's a event called "Pickup status change" that you can attach to pickup factories. It fires when pickup becomes available and when it's taken.

    Click image for larger version

Name:	pickup_status_change.gif
Views:	1
Size:	6.0 KB
ID:	3253810

    With that said, perhaps we can try to create small Kismet sequence in runtime that will do what we want? Let's do this.

    First, create a subclass of SeqEvent_PickupStatusChange that calls a delegate function when activated.

    Code:
    class SeqEvent_PickupStatusChange_Delegate extends SeqEvent_PickupStatusChange;
    
    event Activated() {
        OnActivated(PickupFactory(Originator), Pawn(Instigator));
        
        // reset active status so it can be activated again
        // usually, it's handled by parent sequence
        // but on maps where there's no default sequence,
        // we use a fake one, which doesn't "tick" its children
        bActive = false;
    }
    
    delegate OnActivated(PickupFactory ThisFactory, Pawn EventInstigator);
    Simple and straightforward. Next, the mutator code (also pretty simple):
    Code:
    class EffTheConstVariables_Mut extends UTMutator;
    
    function InitMutator(string Options, out string ErrorMessage)
    {
        super.InitMutator(Options, ErrorMessage);
    
        AttachSequenceObjectsToPickups();
    }
    
    function OnPickupStatusChange(PickupFactory F, Pawn EventInstigator) {
        if (EventInstigator == None) {
            // it's available
            WorldInfo.Game.Broadcast(None, "UDamage is now available!"); 
        } else {
            // someone picked it up
        }
    }
    
    function AttachSequenceObjectsToPickups() {
        local UTPickupFactory_UDamage Factory;
        local SeqEvent_PickupStatusChange_Delegate PSC;
    
        foreach WorldInfo.AllActors(class'UTPickupFactory_UDamage', Factory) {
            PSC = new(None) class'SeqEvent_PickupStatusChange_Delegate';
            PSC.Originator = Factory;
            PSC.OnActivated = OnPickupStatusChange;
            Factory.GeneratedEvents.AddItem(PSC);
        }
    }
    Let's try our new mutator in-game. Ooops. As soon as the powerup spawns, game will crash.

    Click image for larger version

Name:	no_root_sequence.jpg
Views:	1
Size:	21.0 KB
ID:	3253811

    The error is as follows:
    Code:
    Critical: appError called:
    Critical: Assertion failed: RootSeq [File:.\Src\UnSequence.cpp] [Line: 678]
    No root sequence for SeqEvent_PickupStatusChange_Delegate Transient.SeqEvent_PickupStatusChange_Delegate_0, NO PARENT
    Stack:
    SequenceObject class has a property called ParentSequence. It's a const one, so we can't reall modify it from UnrealScript. If you try that, you'll simply get a compilation error. Or maybe we actually can?

    Let's change the mutator code a bit. Add a new function and modify the existing one:
    Code:
    static function Test(SequenceObject Seq, SequenceObject NewParent) {
        NewParent = Seq.ParentSequence;
    }
    
    function AttachSequenceObjectsToPickups() {
        local UTPickupFactory_UDamage Factory;
        local SeqEvent_PickupStatusChange_Delegate PSC;
        local Sequence FakeParent;
    
        FakeParent = new(None) class'Sequence';
    
        foreach WorldInfo.AllActors(class'UTPickupFactory_UDamage', Factory) {
            PSC = new(None) class'SeqEvent_PickupStatusChange_Delegate';
            PSC.Originator = Factory;
            PSC.OnActivated = OnPickupStatusChange;
            Test(Seq, FakeParent);
            Factory.GeneratedEvents.AddItem(PSC);
        }
    }
    The function Test is almost the thing we want, except we're doing the reversed assignment. All we need to do is to flip this assignment's operands. In order to do that, we will be modifying the UnrealScript bytecode directly!

    Unreal packages contain header, name table, export table and actual data. They also contain several other tables, but they're not needed for our purposes. I won't go into gory details, though. If you wish, just read the source code of the aforementioned UnHood program (or the scripts below, for they are based on that program, anyway).

    We will be using Python programming language (Python 3, to be precise) from now on. It's easy to learn and powerful enough. Put the following files in some directory and launch an interactive Python interpreter from there:
    https://raw.githubusercontent.com/WG...nary_reader.py
    https://raw.githubusercontent.com/WG...al_bytecode.py
    https://raw.githubusercontent.com/WG...eal_package.py

    unreal_package module defines everything that's necessary to read Unreal packages. UnrealPackage class, when called with file argument, reads package, including its header, name table and import/export tables. find_function_bytecode function looks up a function in the export table by name. The export table entry contains the offset and size of the function itself within the package. Small function header is located there, and bytecode follows.

    Code:
    > py -3
    Python 3.3.2 (v3.3.2:d047928ae3f6, May 16 2013, 00:06:53) [MSC v.1600 64 bit (AMD64)] on win32
    Type "help", "copyright", "credits" or "license" for more information.
    >>> import unreal_package
    >>> f = open(r"C:\Users\WGH\Documents\My Games\Unreal Tournament 3\UTGame\Published\CookedPC\Script\EffTheConstVariables.u", "r+b")
    >>> package = unreal_package.UnrealPackage(f)
    >>> package.find_function_bytecode(["Default__EffTheConstVariables_Mut", "Test"])
    (5186, 23)
    At this point, we have offset and size of the function's bytecode. Let's read it. unreal_bytecode module supports only some of the known opcodes. You can consult the UnHood project for the full list, but we don't need all of them for such a simple function.
    Code:
    >>> f.seek(5186)
    5186
    >>> import unreal_bytecode
    >>> bytecode = unreal_bytecode.BytecodeReader(f)
    >>> bytecode[0]
    <unreal_bytecode.Opcodes.Op_Let object at 0x00000000026195F8>
    >>> bytecode[0].left
    <unreal_bytecode.Opcodes.Op_LocalVariable object at 0x0000000002619FD0>
    >>> bytecode[0].right
    <unreal_bytecode.Opcodes.Op_Context object at 0x0000000002608C18>
    >>> bytecode[0].right.context
    <unreal_bytecode.Opcodes.Op_LocalVariable object at 0x00000000026081D0>
    >>> bytecode[0].right.value
    <unreal_bytecode.Opcodes.Op_InstanceVariable object at 0x0000000002608278>
    So far, bytecode looks like this: Let(local_var(...), context(local_var(...), instance_var(...))). We can infer that in place of the ellipsises there are our variables: Let(local_var(NewParent), context(local_var(Seq), instance_var(ParentSequence))).
    Let's look at the next instructions:
    Code:
    >>> bytecode[1]
    <unreal_bytecode.Opcodes.Op_Return object at 0x0000000002623390>
    >>> bytecode[2]
    <unreal_bytecode.Opcodes.Op_EndOfScript object at 0x0000000002623400>
    >>> bytecode[3]
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
      File ".\unreal_bytecode.py", line 110, in __getitem__
        return self._bytecodes[index]
    IndexError: list index out of range
    That's it. We have Let, Return and EndOfScript opcodes, which matches what we wrote in the functino. All we need to do now is to swap the arguments of that Let opcode and write it back to the file.
    Code:
    >>> bytecode[0].swap()
    >>> changed_bytecode = bytecode.serialize()
    >>> len(changed_bytecode)
    23
    >>> f.seek(5186)
    5186
    >>> f.write(changed_bytecode)
    23
    >>> f.close()
    >>> ^Z
    And now we're done. Let's run the game and see if it works. And it does!

    Click image for larger version

Name:	it_works.jpg
Views:	1
Size:	44.2 KB
ID:	3253812

    So, I have demonstrated how to assign const variables, and that it can be useful to build Kismet sequences in runtime. And I think there might be even more useful applications.

    Exercise for the reader: find a way to break 'private' access specifier.
Working...
X