Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Botcpp Work #26

Closed
Nostrademous opened this issue Dec 21, 2018 · 12 comments
Closed

Botcpp Work #26

Nostrademous opened this issue Dec 21, 2018 · 12 comments

Comments

@Nostrademous
Copy link
Collaborator

Nostrademous commented Dec 21, 2018

This Issue/Post is mostly for tracking things tried. Below compiles and doesn't crash, but doesn't take an action.

What I put in my Act()

CMsgBotWorldState_Action_MoveToLocation * mtl_pb; 
CMsgBotWorldState_Vector * loc_pb; 

extern "C" void Act(int team_id, CMsgBotWorldState_Actions * actions_pb) {
    // Act seems to be called practically _exactly_ after Observe is called.
    // Since it is called once per team, all team-decisions need to be made here. That means
    // that we need to communicate all actions. Probably that means we need to return the actions
    // protobuf somehow. I think returning the protobuffer itself, from this function makes
    // the most sense.
    // This call is fully blocking the entire game, so possible they are indeed waiting for a 
    // synchronous return.

    cout << "Act\t" << (void*)actions_pb << endl;

    //if (game_state == 4 or game_state == 5) {
    if (actions_pb) {
        //CMsgBotWorldState_Actions * actions_pb = new CMsgBotWorldState_Actions();
        actions_pb = new CMsgBotWorldState_Actions();
        
        mtl_pb = new CMsgBotWorldState_Action_MoveToLocation();
        loc_pb = new CMsgBotWorldState_Vector();
        loc_pb->set_x(0.0);
        loc_pb->set_y(0.0);
        loc_pb->set_z(0.0);
        mtl_pb->set_allocated_location(loc_pb);
        mtl_pb->add_units(test_id);
        
        actions_pb->set_dota_time(dtime + 0.1);
        actions_pb->set_extradata("EXTRA");
        CMsgBotWorldState_Action * action_pb = actions_pb->add_actions();
        action_pb->set_actiontype(CMsgBotWorldState_Action_Type_DOTA_UNIT_ORDER_MOVE_TO_POSITION);
        action_pb->set_player(4);
        action_pb->set_actionid(0);
        action_pb->set_allocated_movetolocation(mtl_pb);
        
        CMsgBotWorldState_Actions_OceanAnnotation * oa_pb = new CMsgBotWorldState_Actions_OceanAnnotation();
        
        CMsgBotWorldState_Actions_OceanAnnotation_Hero * hero_pb = new CMsgBotWorldState_Actions_OceanAnnotation_Hero();
        hero_pb->set_playerid(4);
        
        oa_pb->add_heroes();
        actions_pb->set_allocated_oceanannotation(oa_pb);
        
        std::string s;
        google::protobuf::TextFormat::PrintToString(*actions_pb, &s);
        cout << "Our Message:\n" << s << endl;
    }
    return;
}

What I see in dotaservice:

dota_time: 43.8411026
actions {
  actionType: DOTA_UNIT_ORDER_MOVE_TO_POSITION
  player: 4
  actionID: 0
  moveToLocation {
    units: 5
    location {
      x: 0
      y: 0
      z: 0
    }
  }
}
extraData: "EXTRA"
oceanAnnotation {
  heroes {
  }
}
@TimZaman
Copy link
Owner

TimZaman commented Dec 21, 2018 via email

@Nostrademous
Copy link
Collaborator Author

Nostrademous commented Dec 21, 2018

No, I don't have to call new I guess and it should be the same. I am not sure what the pointer points too though. Could be an uninitialized pointer so better be safe. Honestly, I'm not sure it isn't passing a CMsgBotWorldState * instead of a _Actions. Perhaps even something else.

Regarding the "interesting fact". Yeah, I noticed this as well as I noted it in the other issue when I posted the function sequence. Basically it does:

1) collect world data (that effectively populate the World State protobuf data) 
-- the collection is spread amongst many functions is same (or similar) order 
-- to the Messages as presented int he protobuf
2) Call a function that calls our DL functions 
-- this happens regardless if we loaded the DL, just if we didn't the `if (Observe)` 
-- and `if (Act)` won't be true so those functions won't be called.
3) Call the Lua Execution Engine which in turn invokes the Think() function of every player. 
-- We gutted the functionality of that by putting our bot_generic.lua() with an empty 
-- Think(), except for Nevermore of course.

Here were the actual calls that were literally one after the other in sequence:
     callDynamicallyLoadedLibrary(a1, v28, (__m128)time_delta);
     execLuaBotThink(a1, "TeamThink", *(_QWORD *)&a1->gap354[975], 0LL, 0LL);

@Nostrademous
Copy link
Collaborator Author

Also, a few things to consider - we are basing a lot of the _Action* stuff from a public repo where you found the protobuf definition, but it's the internet - no guarantee it's correct. Now, I did find all the structures as "strings" in the libservice library so they are probably correct, but worth considering what are facts versus assumptions.

Also, the CMsgBotWorldState protobuf is not necessarily complete. The *_Actions should really be referenced somewhere and if you check the bottom the index values are not in complete sequence, index values of 9, 18 and 19 are missing. It is possible that 9 is a repeated .CMsgBotWorldState.Actions actions = 9;

@TimZaman
Copy link
Owner

TimZaman commented Dec 21, 2018 via email

@Nostrademous
Copy link
Collaborator Author

Here is another possible signature for Observe and Act using Hopperapp Disassembler.

image

It thinks Observe is void Observe(arg1, arg2, 0x10000, (void*)Observe, 0x1) and Act is char * Act(arg1)

@Nostrademous
Copy link
Collaborator Author

Also, another note. It looks like they are using google::protobuf::message_lite for protobufs

0x10000 is the max size of a serialized world state

@Nostrademous
Copy link
Collaborator Author

Running that code in a debugger shows that the decompiler is wrong. 0x10000 never exists, not sure why the debugger is confused.

x86_64 is a bit annoying as args to functions are passed via registers and not on the stack like x86, so it's harder to guess what's valid and what's just a trashed/leftover register value.

The only thing I have discovered thus far is that there is data at CMsgBotWorldState index 9 in the protobuf. Not sure what it is yet, but looks like a pointer to another structure.

Regarding signatures at this point I'm pretty sure:
void Observe(uint32_t teamID, const CMsgBotWorldState &ws)
void Act(uint32_t teamID)
are correct.

Which begs the question of how do we set actions.

@Nostrademous
Copy link
Collaborator Author

Any updates from your two RE guys?

@TimZaman
Copy link
Owner

TimZaman commented Jan 7, 2019

nope :(

@TimZaman
Copy link
Owner

TimZaman commented Jan 7, 2019

One thing that i noticed was interesting (and annoying!) is that the worldstate is being send out as:

  1. radiant worldstate message sent
  2. radiant bot invoked
  3. dire worldstate message sent
  4. dire bot invoked

@Nostrademous
Copy link
Collaborator Author

I don't believe you set the console parameter -botworldstatesocket_threaded which might affect it.

I remember Chris Carollo saying at one time that if two "players" (one dire, one radiant) did an action affecting the same target at the same time (meaning in the same frame) it would be randomly decided which one gets applied first rather than always giving preference to Radiant (i.e., a spell being cast on a target that is activating BKB at the same time). I don't see how his statement would be true if your above message always stood true (unless... actions are 'invoked' but not resolved until a step #5 sync step or something).

@Nostrademous
Copy link
Collaborator Author

Finally, I did dig some more in late December and found that there is an index 9 in the protobuf that has a value.

It is a pointer to something (you can modify the protobuf to have a value here:
https://github.com/TimZaman/dotaservice/blob/master/dotaservice/protos/dota_gcmessages_common_bot_script.proto#L638)

example:
optional uint32 unknown_9 = 9;

And print it in Observe(). It will have a value. If you dereference that value as a list of 32-bit integers you will have more data. Don't know what it is, but if you set the unknown_9 value to 0x0 it crashes the system.. meaning it does have "value" and "use" somewhere.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants