Skip to content

Conversation

@Detanup01
Copy link
Owner

Fix SteamInternal_CreateInterface throw when called by NOT Client related interface version

@otavepto
Copy link
Contributor

otavepto commented Nov 22, 2025

I would recommend returning a nullptr with some debug message if the interface name doesn't start with "SteamClient" instead of falling back to the generic interface getter.

Thanks to the weird design of Ghidra I can't export the decompilation in a portable format, but I'll try to explain how steam haneles it.

This is how the original function looks like in steamclient (same on Linux/Windows)

CreateInterface()
undefined8 CreateInterface(char *param_1,undefined4 *param_2)

{
  SteamItf_t **ppSVar1;
  int iVar2;
  undefined8 uVar3;
  SteamItf_t *pSVar4;
  
  pSVar4 = PTR_steamclient_itf_list;
  if (PTR_steamclient_itf_list != (SteamItf_t *)0x0) {
    do {
      iVar2 = strcmp(pSVar4->itf_ver,param_1);
      if (iVar2 == 0) {
        if (param_2 != (undefined4 *)0x0) {
          *param_2 = 0;
        }
                    /* WARNING: Could not recover jumptable at 0x0214c51c. Too many branches */
                    /* WARNING: Treating indirect jump as call */
        uVar3 = (*(code *)pSVar4->factory)();
        return uVar3;
      }
      ppSVar1 = &pSVar4->prev;
      pSVar4 = *ppSVar1;
    } while (*ppSVar1 != (SteamItf_t *)0x0);
  }
  if (param_2 != (undefined4 *)0x0) {
    *param_2 = 1;
  }
  return 0;
}

As you can see, it loops over a linked list and attempts to do a strcmp just like the emu, what we care about is this global pointer PTR_steamclient_itf_list

Based on reversing this structure, this is how I defined it in Ghidra

pic image

If you search for all refs to this global pointer and look for the places where it is written, you'll find only 1 place where it is modified.

pic image

If we follow this function, this is how it looks like after cleanup

code to add a node
void add_steamclient_itf(SteamItf_t *itf_node,void *factory,char *ver_name)
{
  SteamItf_t *pSVar1;
  
  itf_node->itf_ver = ver_name;
  itf_node->factory = factory;
  pSVar1 = itf_node;
  itf_node->prev = PTR_steamclient_itf_list;
  PTR_steamclient_itf_list = pSVar1;
  return;
}

So, just the usual function which adds a node to the linked list.
And, if you find all refs to this node appending function, you'll land at this code

code to init linked list
void _INIT_230(void)

{
  FUN_029c4f20(&DAT_02b974a0);
  FUN_00df1180(FUN_029c5700,&DAT_02b974a0,&PTR_LOOP_02b50500);
  _DAT_02b97478 = 0xffffffff;
  _DAT_02b97480 = 0xffffffff;
  _DAT_02b97460 = 0xffffffffffffffff;
  _DAT_02b97468 = 0x1ffffffff;
  FUN_01220730(&DAT_02b97490,0,0);
  _DAT_02b97470 = DAT_02b97490;
  FUN_00df1180(FUN_0121dec0,&DAT_02b97460,&PTR_LOOP_02b50500);
  add_steamclient_itf(&DAT_02b97440,FUN_0121aed0,"SteamClient006");
  add_steamclient_itf(&DAT_02b97420,FUN_0121aee0,"SteamClient007");
  add_steamclient_itf(&DAT_02b97400,FUN_0121aef0,"SteamClient008");
  add_steamclient_itf(&DAT_02b973e0,FUN_0121af00,"SteamClient009");
  add_steamclient_itf(&DAT_02b973c0,FUN_0121af10,"SteamClient010");
  add_steamclient_itf(&DAT_02b973a0,FUN_0121af20,"SteamClient011");
  add_steamclient_itf(&DAT_02b97380,FUN_0121af30,"SteamClient012");
  add_steamclient_itf(&DAT_02b97360,FUN_0121af40,"SteamClient013");
  add_steamclient_itf(&DAT_02b97340,FUN_0121af50,"SteamClient014");
  add_steamclient_itf(&DAT_02b97320,FUN_0121af60,"SteamClient015");
  add_steamclient_itf(&DAT_02b97300,FUN_0121af70,"SteamClient016");
  add_steamclient_itf(&DAT_02b972e0,FUN_0121af80,"SteamClient017");
  add_steamclient_itf(&DAT_02b972c0,FUN_0121af90,"SteamClient018");
  add_steamclient_itf(&DAT_02b972a0,FUN_0121afa0,"SteamClient019");
  add_steamclient_itf(&DAT_02b97280,FUN_0121afb0,"SteamClient020");
  add_steamclient_itf(&DAT_02b97260,steamclient_factory_SteamClient021,"SteamClient021");
  add_steamclient_itf(&DAT_02b97240,steamclient_factory_SteamClient022,"SteamClient022");
  return;
}

The weird name _INIT_xxx is GCC's constructor-style .so library entry function, so just a static function defined somewhere and called.

The other refs to this node appending function register some internal interfaces

code to init linked list (2)
add_steamclient_itf(&DAT_02b9d220,FUN_01555ca0,"CLIENTENGINE_INTERFACE_VERSION005");
add_steamclient_itf(&DAT_02b9d200,FUN_01551ac0,"IVALIDATE001");
add_steamclient_itf(&DAT_02bb7420,FUN_01d93900,"p2pvoicesingleton002");
add_steamclient_itf(&DAT_02bb7bb0,FUN_01d9b1b0,"p2pvoice002");

So the eventual conclusion is that real steam will return nullptr in case the interface name didn't match any of these.

I could be wrong or missing some important detail, so take my words with a grain of salt :/
A real test against steam would be the definitive answer.

Edit: tested returning null and the game falls back to calling SteamInternal_FindOrCreateUserInterface as it should have in the first place :/

@Detanup01
Copy link
Owner Author

Will change tomorrow (or if you already have stuff feel free to push in this branch)

Added debug print statement for interface check.
@Detanup01 Detanup01 merged commit 15f6e17 into dev Nov 26, 2025
64 checks passed
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

Successfully merging this pull request may close these issues.

3 participants