TimZaman / dotaservice

DotaService is a service to play Dota 2 through gRPC
Other
114 stars 19 forks source link

Botcpp Work #26

Closed Nostrademous closed 5 years ago

Nostrademous commented 5 years ago

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 commented 5 years ago

Do you really need to call new though? That would surprise me. One interesting fact is that even if we have the .so and the symbols in place, the builtin/lua bot seems to take precedence. This is why i was wondering if we somehow need to register that we are taking over conttrol

On Fri, Dec 21, 2018, 21:56 Nostrademous <notifications@github.com wrote:

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()

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 { } }

— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub https://github.com/TimZaman/dotaservice/issues/26, or mute the thread https://github.com/notifications/unsubscribe-auth/AHXSRB2rePi6ntjWHZKNkT3rYVWd3HQHks5u7Ur7gaJpZM4ZfFPn .

Nostrademous commented 5 years ago

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 commented 5 years ago

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 commented 5 years ago

interesting observation indeed.

On Sat, Dec 22, 2018 at 12:22 AM Nostrademous notifications@github.com wrote:

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;

— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/TimZaman/dotaservice/issues/26#issuecomment-449522066, or mute the thread https://github.com/notifications/unsubscribe-auth/AHXSRLtsKo7wVA8O6fHW8Wsar9IO2c_Vks5u7W0ggaJpZM4ZfFPn .

Nostrademous commented 5 years ago

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 commented 5 years ago

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 commented 5 years ago

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 commented 5 years ago

Any updates from your two RE guys?

TimZaman commented 5 years ago

nope :(

TimZaman commented 5 years ago

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 commented 5 years ago

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 commented 5 years ago

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.