Skip to content

rc_client_raintegration

Jamiras edited this page Mar 3, 2024 · 12 revisions

RAIntegration is the toolkit used to create and test achievements for the RetroAchievements platform. This page should help guide you through integrating it into an emulator that is already built to use rc_client.

If you want to integrate with the toolkit without implementing rc_client, see the legacy RAIntegration guide.

Loading the DLL

RAIntegration is provided via a DLL that can be loaded at runtime. The first thing you'll need to do is extend the initialization method to load the DLL.

#ifdef RC_CLIENT_SUPPORTS_RAINTEGRATION
static void load_integration_callback(int result, const char* error_message, rc_client_t* client, void* userdata)
{
  // If DLL not present, do nothing. User can still play without the toolkit.
  if (result != RC_MISSING_VALUE)
  {
    // If not successful, just report the error and bail.
    if (result != RC_OK)
    {
      show_message("Login failed: %s", message);
      return;
    }

    // DLL was loaded.
    // TODO: hook up menu and dll event handler
  }

  // Things are ready to load a game. If the DLL was initialized, calling rc_client_begin_load_game will be redirected
  // through the DLL so the toolkit has access to the game data. Similarly, things like rc_create_leaderboard_list will
  // be redirected through the DLL to reflect any local changes made by the user.
}
#endif

void initialize_retroachievements_client(void)
{
  ...

#ifdef RC_CLIENT_SUPPORTS_RAINTEGRATION
  // attempt to load the integration DLL from the directory containing the main client executable
  // in x64 build, will look for RA_Integration-x64.dll, then RA_Integration.dll.
  // in non-x64 build, will only look for RA_Integration.dll
  {
    wchar_t szFilePath[MAX_PATH];
    GetModuleFileNameW(NULL, szFilePath, MAX_PATH);
    PathRemoveFileSpecW(szFilePath);

    rc_client_begin_load_raintegration(g_client, szFilePath, g_hWnd,
        "MyClient", "1.0", load_integration_callback, NULL);
  }
#endif
}

Notice the use of #ifdef RC_CLIENT_SUPPORTS_RAINTEGRATION? This ensures the code can still be built on non-Windows platforms where the DLL cannot be supported. As such, you must define RC_CLIENT_SUPPORTS_RAINTEGRATION when building the code (both the client code and the rcheevos code) to enable the functionality. The DLL is currently very heavily tied to Windows, so you should only define RC_CLIENT_SUPPORTS_RAINTEGRATION in Windows builds.

Hooking up the menu

Once you've got the DLL loading, you need to add the RetroAchievements menu somewhere in the program. If you're using the WINAPI to build your Window, there's a handy helper function that you can use.

static void load_integration_callback(int result, const char* error_message, rc_client_t* client, void* userdata)
{
  ...

    // DLL was loaded.
    // TODO: hook up dll event handler
    rc_client_raintegration_rebuild_submenu(g_client, GetMenu(g_hWnd));
  }

  ...
}

You also need to handle when the menu items are selected. Find the code where the program is handling the existing menu items and add something similar to this:

    ...
#ifdef RC_CLIENT_SUPPORTS_RAINTEGRATION
    else if (rc_client_raintegration_activate_menu_item(LOWORD(wParam)))
    {
      return 0; // DLL menu item was handled
    }
 #endif
    ...

If you're using a frontend other than Windows directly (like QT), you can call rc_client_raintegration_get_menu and convert those objects into a UI elements appropriate for your selected UI framework. Activating the menu items should still call rc_client_raintegration_activate_menu_item with the menu item's ID.

At this point, you should be able to open the tool windows from the menu.

Handling RAIntegration-specific Events

There are a few events specific to the DLL that the client needs to handle.

static void raintegration_event_handler(const rc_client_raintegration_event_t* event, rc_client_t* client)
{
   switch (event->type)
   {
      case RC_CLIENT_RAINTEGRATION_EVENT_MENUITEM_CHECKED_CHANGED:
         // The checked state of one of the menu items has changed and should be reflected in the UI.
         // Call the handy helper function if the menu was created by rc_client_raintegration_rebuild_submenu.
         rc_client_raintegration_update_menu_item(client, event->menu_item);
         break;
      case RC_CLIENT_RAINTEGRATION_EVENT_PAUSE:
         // The toolkit has hit a breakpoint and wants to pause the emulator. Do so.
         pause_emulator();
         break;
      case RC_CLIENT_RAINTEGRATION_EVENT_HARDCORE_CHANGED:
         // Hardcore mode has been changed (either directly by the user, or disabled through the use of the tools).
         // The frontend doesn't necessarily need to know that this value changed, they can still query it whenever
         // it's appropriate, but the event lets the frontend do things like enable/disable rewind or cheats.
         handle_hardcore_changed();
         break;
      default:
         log_message("Unsupported raintegration event %u\n", event->type);
         break;
   }
}

static void load_integration_callback(int result, const char* error_message, rc_client_t* client, void* userdata)
{
  ...

    // DLL was loaded.
    rc_client_raintegration_set_event_handler(g_client, raintegration_event_handler);
    rc_client_raintegration_rebuild_submenu(g_client, GetMenu(g_hWnd));
  }

  ...
}

Memory Modification

We also need to allow the toolkit to directly modify memory. This works in a very similar manner to reading memory.

static void raintegration_write_memory_handler(uint32_t address, uint8_t* buffer,
                                               uint32_t num_bytes, rc_client_t* client)
{
  uint32_t real_address = convert_retroachievements_address_to_real_address(address);
  return emulator_write_memory(real_address, buffer, num_bytes);
}

static void load_integration_callback(int result, const char* error_message, rc_client_t* client, void* userdata)
{
  ...

    // DLL was loaded.
    rc_client_raintegration_set_event_handler(g_client, raintegration_event_handler);
    rc_client_raintegration_set_write_memory_function(g_client, raintegration_write_memory_handler);
    rc_client_raintegration_rebuild_submenu(g_client, GetMenu(g_hWnd));
  }

  ...
}

Handling Unrecognized Games

When a user loads a game that the server doesn't recognize, the toolkit will present them with a dialog that allows them to select the game from the supported games list and play the game in a compatibility testing mode. This dialog asks the frontend for a description of the game to help the user find an appropriate match. The description is usually just the filename without the path or extension.

static void raintegration_get_game_name_handler(char* buffer, uint32_t buffer_size, rc_client_t* client)
{
   snprintf(buffer, buffer_size, get_filename(g_loaded_game));
   remove_extension(buffer);
}

static void load_integration_callback(int result, const char* error_message, rc_client_t* client, void* userdata)
{
  ...

    // DLL was loaded.
    rc_client_raintegration_set_event_handler(g_client, raintegration_event_handler);
    rc_client_raintegration_set_write_memory_function(g_client, raintegration_write_memory_handler);
    rc_client_raintegration_set_get_game_name_function(g_client, raintegration_get_game_name_handler);
    rc_client_raintegration_rebuild_submenu(g_client, GetMenu(g_hWnd));
  }

  ...
}

Preventing Loss of Changes

Before unloading a game, the client should check for modifications and prompt the user whether or not to proceed with unloading the game.

int raintegration_confirm_unload_game()
{
  if (rc_client_raintegration_has_modifications(g_client)) {
    if (!confirm("Are you sure you want to close this game? Uncommitted modifications will be lost."))
      return 0;
  }

  rc_client_unload_game(g_client);
  return 1;
}

Other Things to Consider

If, for whatever reason, you destroy the main window and create a new one, you should let the DLL know by calling rc_client_raintegration_update_main_window_handle.

rcheevos

rc_client

Integration guide

client

user

game

processing

rc_client_raintegration

Integration guide

rc_runtime

rhash

rapi

common

user

runtime

info

Clone this wiki locally