I got bored again so I decided to come back to Far Cry 2 to see if it’s any fun. As someone that doesn’t have the energy to learn anything drawing related (dx/opengl) I decided that I’d go the usual route of CLI-based commands. The in-game console can be launched using the tilde (~) key and you can issue a limited set of commands. Using the data structures/functions below you can replace the default callback function with your custom one in order to add custom commands for your hax.

NOTE: Since there isn’t any data on this game (SDK/PDB/etc.) I’m naming data structures with names I deem fit.

struct __declspec(align(4)) IConsole

{

 __int32 N00000001[0x1A];

 char EnableConsoleWrite;

 __int16 mIsOpen;

 char Padding1;

 __int32 N0000001[0X90];

};

IConsole represents the in-game console. There’s 1 instance of this class which is a global variable located at:

.data:11555980

You can build a wrapper class that uses singleton pattern to properly rebuild this class or just use the pointer directly.

Every time a command is entered the game takes the input, formats it and sends it to the callback function for parsing, filtering etc. The address of this callback function is at:

.code:0x102955E0

Pseudo-code for the callback function (Hexrays):

void __thiscall OnCommandEntered(IConsole *this, CommandDescriptor *commandDescriptor)

{

  IConsole *pConsole; // ebp@1

  unsigned int src_; // esi@8

  _BYTE *v4; // ecx@8

  unsigned int v5; // eax@11

  unsigned int v6; // esi@13

  unsigned int v7; // eax@13

  unsigned int v8; // eax@13

  int v9; // ST10_4@15

  unsigned int v10; // ST0C_4@15

  unsigned int srcLength; // eax@18

  const char *v12; // eax@18

  const char *v13; // esi@18

  int v14; // eax@20

  int v15; // edi@22

  int v16; // edi@24

  int v17; // eax@27

  int v18; // eax@27

  const char *i; // eax@27

  void *v20; // eax@32

  int v21; // ST10_4@34

  void *v22; // ST0C_4@34

  int v23; // esi@35

  char *v24; // eax@35

  CommandDescriptor *v25; // ST14_4@35

  wchar_t *v26; // eax@35

  int v27; // eax@38

  wchar_t *v28; // eax@39

  char *v29; // eax@41

  const char *v30; // eax@44

  const char *result; // eax@45

  const char *v32; // eax@46

  _BYTE *v33; // eax@46

  const char *v34; // eax@49

  int v35; // edi@49

  signed int v36; // ebp@51

  unsigned int v37; // eax@51

  const char **v38; // ebx@51

  const char *v39; // ecx@52

  const void *v40; // edx@57

  const char *v41; // ecx@60

  char *v42; // ecx@64

  int v43; // eax@67

  int v44; // ecx@70

  int v45; // eax@70

  wchar_t *v46; // eax@71

  char *v47; // eax@74

  char *commandStr; // eax@80

  char searchCharacter; // [sp+10h] [bp-150h]@1

  int v50; // [sp+14h] [bp-14Ch]@21

  bool v51; // [sp+1Bh] [bp-145h]@48

  int a1; // [sp+1Ch] [bp-144h]@1

  unsigned int src; // [sp+20h] [bp-140h]@5

  int v54; // [sp+30h] [bp-130h]@6

  unsigned int v55; // [sp+34h] [bp-12Ch]@4

  void *v56; // [sp+38h] [bp-128h]@1

  char *userCommandStr; // [sp+3Ch] [bp-124h]@1

  int v58; // [sp+4Ch] [bp-114h]@1

  unsigned int v59; // [sp+50h] [bp-110h]@1

  int v60; // [sp+54h] [bp-10Ch]@48

  unsigned int v61; // [sp+58h] [bp-108h]@48

  int v62; // [sp+5Ch] [bp-104h]@48

  char v63; // [sp+60h] [bp-100h]@26

  char *v64; // [sp+64h] [bp-FCh]@32

  int v65; // [sp+74h] [bp-ECh]@34

  unsigned int v66; // [sp+78h] [bp-E8h]@32

  int descriptor_1; // [sp+7Ch] [bp-E4h]@20

  IConsole *pConsole1; // [sp+98h] [bp-C8h]@1

  int (__stdcall **v69)(int, int); // [sp+9Ch] [bp-C4h]@35

  wchar_t *fmt; // [sp+A0h] [bp-C0h]@35

  int v71; // [sp+A4h] [bp-BCh]@78

  int *v72; // [sp+A8h] [bp-B8h]@78

  int v73; // [sp+ACh] [bp-B4h]@78

  unsigned int v74; // [sp+B4h] [bp-ACh]@35

  int descriptor; // [sp+B8h] [bp-A8h]@8

  char v76; // [sp+D4h] [bp-8Ch]@21

  char v77; // [sp+F0h] [bp-70h]@27

  int v78; // [sp+10Ch] [bp-54h]@27

  char v79; // [sp+128h] [bp-38h]@27

  char v80; // [sp+144h] [bp-1Ch]@27

 

  pConsole = this;

  pConsole1 = this;

  sub_100033D0(&a1, commandDescriptor);         // parses the descriptor?

  v56 = &unk_10F239D1;

  v59 = 15;

  searchCharacter = 0;

  v58 = 0;

  std::char_traits<char>::assign(&userCommandStr, &searchCharacter);// ZeroMemory

  if ( !sub_1028F9E0(pConsole, &a1) )

  {

    if ( !v54 )

    {

LABEL_85:

      sub_10096AC0(&v56);

      sub_10096AC0(&a1);

      return;

    }

    CommandDescriptor::CommandDescriptor(&descriptor, &version);// Constructor for a2

    sub_10002850(&descriptor, commandDescriptor, 0, 0xFFFFFFFF);

    sub_10292EE0(pConsole, &descriptor);

    src_ = src;

    v4 = src;

    if ( v55 < 0x10 )

      v4 = &src;

    if ( *v4 == 35 )

    {

      v5 = src;

      if ( v55 < 0x10 )

        v5 = &src;

      v6 = v5 + 1;

      v7 = std::char_traits<char>::length(v5 + 1);

      sub_10002D40(&a1, v6, v7);

      v8 = src;

      if ( v55 < 0x10 )

        v8 = &src;

      v9 = v54;

      v10 = v8;

      sub_102A7350();

      sub_102A7C90(v10, v9, 0);

      goto LABEL_84;

    }

    if ( v55 < 0x10 )

      src_ = &src;

    srcLength = std::char_traits<char>::length(src_);

    sub_10002D40(&a1, src_, srcLength);

    searchCharacter = 32;

    v12 = ParseCommand(&a1, &searchCharacter, 0x100000000ui64);

    v13 = v12;

    if ( v12 == -1 )

    {

      sub_10002B00(&v56, &a1, 0, 0xFFFFFFFF);

    }

    else

    {

      v14 = sub_10003110(&descriptor_1, 0, v12);

      sub_10002B00(&v56, v14, 0, 0xFFFFFFFF);

      sub_10096AC0(&descriptor_1);

    }

    sub_10742C00(&v56);

    sub_10002CC0(&v76);

    sub_10290810(&v50, &v76);

    if ( v50 != pConsole->N00000012 )

    {

      v15 = v50 + 40;

      if ( sub_1028F7D0(pConsole, v50 + 40) )

      {

        sub_10002B00(&a1, &a1, 0, 0xFFFFFFFF);

        sub_10294370(pConsole, v15, &a1);

        goto EXIT_FUNCTION;

      }

    }

    sub_10290190(&v50, &v56);

    v16 = v50;

    if ( v50 != pConsole->N00000018 )

    {

      v50 = *(v50 + 40);

      if ( sub_1028F7D0(pConsole, v50) )

      {

        sub_10047090(&v63);

        sub_100032C0(v16 + 12);

        if ( v13 != -1 )

        {

          v17 = sub_10003110(&descriptor_1, v13 + 1, -1);

          sub_100032C0(v17);

          sub_10096AC0(&descriptor_1);

          sub_10003300(&descriptor_1, filename);

          sub_10003300(&v78, "=\"");

          sub_100031F0(&v77, &v78);

          sub_100031F0(&v80, &a1);

          v18 = sub_100031F0(&v79, &descriptor_1);

          sub_100032C0(v18);

          sub_10096AC0(&v79);

          sub_10096AC0(&v80);

          sub_10096AC0(&v77);

          sub_10096AC0(&v78);

          sub_10096AC0(&descriptor_1);

          searchCharacter = 32;

          for ( i = ParseCommand(&a1, &searchCharacter, 0x100000000ui64);

                i != -1;

                i = ParseCommand(&a1, &searchCharacter, 0x100000000ui64) )

          {

            sub_100BC060(&a1, i, 1u);

            searchCharacter = 32;

          }

          if ( AreStringsEqual(&a1, "?") || AreStringsEqual(&a1, "help") )

          {

            v27 = sub_10290660(&v77);

            if ( *(v27 + 24) < 8u )

              v28 = (v27 + 4);

            else

              v28 = *(v27 + 4);

            CommandDescriptor::CommandDescriptor(&descriptor_1, v28);

            v29 = userCommandStr;

            if ( v59 < 0x10 )

              v29 = &userCommandStr;

            sub_10293700(pConsole, &descriptor_1, v29);

            sub_10002930(&descriptor_1);

            sub_10002930(&v77);

            sub_10096AC0(&v63);

            goto EXIT_FUNCTION;

          }

          if ( v54 )

          {

            v20 = v64;

            if ( v66 < 0x10 )

              v20 = &v64;

            v21 = v65;

            v22 = v20;

            sub_102A7350();

            sub_102A7C90(v22, v21, 0);

          }

        }

        v23 = v50;

        v24 = sub_102ADAC0(v50);

        sub_10003300(&v78, v24);

        CommandDescriptor::CommandDescriptor(&descriptor_1, L" = ");

        v25 = Descriptor::Descriptor(&v79, &v78);

        Descriptor::Descriptor(&v80, v23 + 4);

        sub_10191620(&v77, &descriptor_1);

        sub_10191620(&v69, v25);

        sub_10002930(&v77);

        sub_10002930(&v80);

        sub_10002930(&v79);

        sub_10002930(&descriptor_1);

        sub_10096AC0(&v78);

        v26 = fmt;

        if ( v74 < 8 )

          v26 = &fmt;

        WriteToConsole(pIConsole, 0, v26);

        sub_10002930(&v69);

        sub_10096AC0(&v63);

EXIT_FUNCTION:

        sub_10096AC0(&v76);

LABEL_84:

        sub_10002930(&descriptor);

        goto LABEL_85;

      }

    }

    searchCharacter = 63;

    v30 = ParseCommand(&a1, &searchCharacter, '\x01\0\0\0\0');

    searchCharacter = 0;

    if ( v30 + 1 == 0 )

    {

      searchCharacter = 33;

      result = ParseCommand(&a1, &searchCharacter, '\x01\0\0\0\0');

      searchCharacter = 1;

      if ( result + 1 == 0 )

      {

        if ( commandDescriptor->CopySize < 8u )

          commandStr = &commandDescriptor->UserCommand;

        else

          commandStr = commandDescriptor->UserCommand;

        WriteToConsole(pIConsole, '\0', L"Unknown command: %s", commandStr);

        goto EXIT_FUNCTION;

      }

    }

    v32 = sub_10AD7180(&a1, " ", '\0');

    sub_10003110(&v63, '\0', v32);

    v33 = src;

    if ( v55 < 0x10 )

      v33 = &src;

    v51 = *v33 == 63;

    v60 = '\0';

    v61 = '\0';

    v62 = 0;

    sub_10295300(&v60, 1);

    v50 = '\0';

    if ( v61 <= '\0' )

    {

LABEL_78:

      v72 = &v61;

      fmt = &v60;

      v71 = 0;

      v73 = 4;

      v69 = &off_10DC385C;

      sub_10225F00(&v69);

      sub_10096AC0(&v63);

      goto EXIT_FUNCTION;

    }

    while ( 1 )

    {

      v34 = v64;

      v35 = *(v60 + 4 * v50) + 4;

      if ( v66 < 0x10 )

        v34 = &v64;

      v36 = strlen(v34);

      v37 = *(*(v60 + 4 * v50) + 28);

      v38 = (*(v60 + 4 * v50) + 8);

      if ( v37 < 0x10 )

        v39 = (*(v60 + 4 * v50) + 8);

      else

        v39 = *v38;

      if ( strlen(v39) < v36 )

        goto LABEL_77;

      if ( !v51 )

      {

        if ( searchCharacter )

        {

          v42 = v64;

          if ( v66 < 0x10 )

            v42 = &v64;

          if ( v37 < 0x10 )

            v43 = *(v60 + 4 * v50) + 8;

          else

            v43 = *v38;

          if ( !sub_1018DAD0(v43, v42) )

            goto LABEL_77;

        }

        else

        {

          v40 = v64;

          if ( v66 < 0x10 )

            v40 = &v64;

          if ( v37 < 0x10 )

            v41 = (*(v60 + 4 * v50) + 8);

          else

            v41 = *v38;

          if ( memicmp(v41, v40, v36) )

            goto LABEL_77;

        }

      }

      v44 = *(v60 + 4 * v50);

      v45 = sub_10290660(&v77);

      if ( *(v45 + 24) < 8u )

        v46 = (v45 + 4);

      else

        v46 = *(v45 + 4);

      CommandDescriptor::CommandDescriptor(&descriptor_1, v46);

      if ( *(v35 + 24) < 0x10u )

        v47 = (v35 + 4);

      else

        v47 = *v38;

      sub_10293700(pConsole1, &descriptor_1, v47);

      sub_10002930(&descriptor_1);

      sub_10002930(&v77);

LABEL_77:

      if ( ++v50 >= v61 )

        goto LABEL_78;

    }

  }

  if ( v59 >= 0x10 )

    sub_10419130(userCommandStr);

  v59 = 15;

  searchCharacter = 0;

  v58 = 0;

  std::char_traits<char>::assign(&userCommandStr, &searchCharacter);

  if ( v55 >= 0x10 )

    sub_10419130(src);

  v55 = 15;

  searchCharacter = 0;

  v54 = 0;

  std::char_traits<char>::assign(&src, &searchCharacter);

}

The function takes a pointer to a data structure I named CommandDescriptor. This contains information such as the actual string, length of the string and some other stuff that I’ll act like I understand. CommandDescriptor:

struct CommandDescriptor

{

 int field_0;

 char *UserCommand;

 int field_8;

 int field_C;

 int field_10;

 int CommandLength;

 int CopySize;

};

If you want to implement your own commands then just hook/redirect the callback function and add in your own parsing, filtering code. A nice way to implement this would be to use an std::map that maps strings (the command) to a second callback function to be called for that specific command.

Enjoy (:

Bonus:

The version of the console is printed in a different position inside of the console. In order to print out in any position on the screen you can reverse the way it’s printed. This is at:

.text:104C262F