duckstation

duckstation, but archived from the revision just before upstream changed it to a proprietary software project, this version is the libre one
git clone https://git.neptards.moe/u3shit/duckstation.git
Log | Files | Refs | README | LICENSE

rc_client_raintegration.c (20710B)


      1 #include "rc_client_raintegration_internal.h"
      2 
      3 #include "rc_client_internal.h"
      4 
      5 #include "rapi/rc_api_common.h"
      6 
      7 #ifdef RC_CLIENT_SUPPORTS_RAINTEGRATION
      8 
      9 static void rc_client_raintegration_load_dll(rc_client_t* client,
     10     const wchar_t* search_directory, rc_client_callback_t callback, void* callback_userdata)
     11 {
     12   wchar_t sPath[_MAX_PATH];
     13   const int nPathSize = sizeof(sPath) / sizeof(sPath[0]);
     14   rc_client_raintegration_t* raintegration;
     15   int sPathIndex = 0;
     16   DWORD dwAttrib;
     17   HINSTANCE hDLL;
     18 
     19   if (search_directory) {
     20     sPathIndex = swprintf_s(sPath, nPathSize, L"%s\\", search_directory);
     21     if (sPathIndex > nPathSize - 22) {
     22       callback(RC_INVALID_STATE, "search_directory too long", client, callback_userdata);
     23       return;
     24     }
     25   }
     26 
     27 #if defined(_M_X64) || defined(__amd64__)
     28   wcscpy_s(&sPath[sPathIndex], nPathSize - sPathIndex,  L"RA_Integration-x64.dll");
     29   dwAttrib = GetFileAttributesW(sPath);
     30   if (dwAttrib == INVALID_FILE_ATTRIBUTES) {
     31     wcscpy_s(&sPath[sPathIndex], nPathSize - sPathIndex, L"RA_Integration.dll");
     32     dwAttrib = GetFileAttributesW(sPath);
     33   }
     34 #else
     35   wcscpy_s(&sPath[sPathIndex], nPathSize - sPathIndex, L"RA_Integration.dll");
     36   dwAttrib = GetFileAttributesW(sPath);
     37 #endif
     38 
     39   if (dwAttrib == INVALID_FILE_ATTRIBUTES) {
     40     callback(RC_MISSING_VALUE, "RA_Integration.dll not found in search directory", client, callback_userdata);
     41     return;
     42   }
     43 
     44   hDLL = LoadLibraryW(sPath);
     45   if (hDLL == NULL) {
     46     char error_message[512];
     47     const DWORD last_error = GetLastError();
     48     int offset = snprintf(error_message, sizeof(error_message), "Failed to load RA_Integration.dll (%u)", last_error);
     49 
     50     if (last_error != 0) {
     51       LPSTR messageBuffer = NULL;
     52       const DWORD size = FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
     53         NULL, last_error, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&messageBuffer, 0, NULL);
     54 
     55       snprintf(&error_message[offset], sizeof(error_message) - offset, ": %.*s", size, messageBuffer);
     56 
     57       LocalFree(messageBuffer);
     58     }
     59 
     60     callback(RC_ABORTED, error_message, client, callback_userdata);
     61     return;
     62   }
     63 
     64   raintegration = (rc_client_raintegration_t*)rc_buffer_alloc(&client->state.buffer, sizeof(rc_client_raintegration_t));
     65   memset(raintegration, 0, sizeof(*raintegration));
     66   raintegration->hDLL = hDLL;
     67 
     68   raintegration->get_version = (rc_client_raintegration_get_string_func_t)GetProcAddress(hDLL, "_RA_IntegrationVersion");
     69   raintegration->get_host_url = (rc_client_raintegration_get_string_func_t)GetProcAddress(hDLL, "_RA_HostUrl");
     70   raintegration->init_client = (rc_client_raintegration_init_client_func_t)GetProcAddress(hDLL, "_RA_InitClient");
     71   raintegration->init_client_offline = (rc_client_raintegration_init_client_func_t)GetProcAddress(hDLL, "_RA_InitOffline");
     72   raintegration->set_console_id = (rc_client_raintegration_set_int_func_t)GetProcAddress(hDLL, "_RA_SetConsoleID");
     73   raintegration->shutdown = (rc_client_raintegration_action_func_t)GetProcAddress(hDLL, "_RA_Shutdown");
     74 
     75   raintegration->update_main_window_handle = (rc_client_raintegration_hwnd_action_func_t)GetProcAddress(hDLL, "_RA_UpdateHWnd");
     76 
     77   raintegration->get_external_client = (rc_client_raintegration_get_external_client_func_t)GetProcAddress(hDLL, "_Rcheevos_GetExternalClient");
     78   raintegration->get_menu = (rc_client_raintegration_get_menu_func_t)GetProcAddress(hDLL, "_Rcheevos_RAIntegrationGetMenu");
     79   raintegration->activate_menu_item = (rc_client_raintegration_activate_menuitem_func_t)GetProcAddress(hDLL, "_Rcheevos_ActivateRAIntegrationMenuItem");
     80   raintegration->set_write_memory_function = (rc_client_raintegration_set_write_memory_func_t)GetProcAddress(hDLL, "_Rcheevos_SetRAIntegrationWriteMemoryFunction");
     81   raintegration->set_get_game_name_function = (rc_client_raintegration_set_get_game_name_func_t)GetProcAddress(hDLL, "_Rcheevos_SetRAIntegrationGetGameNameFunction");
     82   raintegration->set_event_handler = (rc_client_raintegration_set_event_handler_func_t)GetProcAddress(hDLL, "_Rcheevos_SetRAIntegrationEventHandler");
     83   raintegration->has_modifications = (rc_client_raintegration_get_int_func_t)GetProcAddress(hDLL, "_Rcheevos_HasModifications");
     84   raintegration->get_achievement_state = (rc_client_raintegration_get_achievement_state_func_t)GetProcAddress(hDLL, "_Rcheevos_GetAchievementState");
     85 
     86   if (!raintegration->get_version ||
     87       !raintegration->init_client ||
     88       !raintegration->get_external_client) {
     89     FreeLibrary(hDLL);
     90 
     91     callback(RC_ABORTED, "One or more required exports was not found in RA_Integration.dll", client, callback_userdata);
     92   }
     93   else {
     94     rc_mutex_lock(&client->state.mutex);
     95     client->state.raintegration = raintegration;
     96     rc_mutex_unlock(&client->state.mutex);
     97 
     98     RC_CLIENT_LOG_INFO_FORMATTED(client, "RA_Integration.dll %s loaded", client->state.raintegration->get_version());
     99   }
    100 }
    101 
    102 typedef struct rc_client_version_validation_callback_data_t {
    103   rc_client_t* client;
    104   rc_client_callback_t callback;
    105   void* callback_userdata;
    106   HWND main_window_handle;
    107   char* client_name;
    108   char* client_version;
    109   rc_client_async_handle_t async_handle;
    110 } rc_client_version_validation_callback_data_t;
    111 
    112 int rc_client_version_less(const char* left, const char* right)
    113 {
    114   do {
    115     int left_len = 0;
    116     int right_len = 0;
    117     while (*left && *left == '0')
    118       ++left;
    119     while (left[left_len] && left[left_len] != '.')
    120       ++left_len;
    121     while (*right && *right == '0')
    122       ++right;
    123     while (right[right_len] && right[right_len] != '.')
    124       ++right_len;
    125 
    126     if (left_len != right_len)
    127       return (left_len < right_len);
    128 
    129     while (left_len--) {
    130       if (*left != *right)
    131         return (*left < *right);
    132       ++left;
    133       ++right;
    134     }
    135 
    136     if (*left == '.')
    137       ++left;
    138     if (*right == '.')
    139       ++right;
    140   } while (*left || *right);
    141 
    142   return 0;
    143 }
    144 
    145 static void rc_client_init_raintegration(rc_client_t* client,
    146     rc_client_version_validation_callback_data_t* version_validation_callback_data)
    147 {
    148   rc_client_raintegration_init_client_func_t init_func = client->state.raintegration->init_client;
    149 
    150   if (client->state.raintegration->get_host_url) {
    151     const char* host_url = client->state.raintegration->get_host_url();
    152     if (host_url) {
    153       if (strcmp(host_url, "OFFLINE") != 0) {
    154         if (strcmp(host_url, "https://retroachievements.org") != 0)
    155           rc_client_set_host(client, host_url);
    156       }
    157       else if (client->state.raintegration->init_client_offline) {
    158         init_func = client->state.raintegration->init_client_offline;
    159         RC_CLIENT_LOG_INFO(client, "Initializing in offline mode");
    160       }
    161     }
    162   }
    163 
    164   if (!init_func || !init_func(version_validation_callback_data->main_window_handle,
    165       version_validation_callback_data->client_name,
    166       version_validation_callback_data->client_version)) {
    167     const char* error_message = "RA_Integration initialization failed";
    168 
    169     rc_client_unload_raintegration(client);
    170 
    171     RC_CLIENT_LOG_ERR(client, error_message);
    172     version_validation_callback_data->callback(RC_ABORTED, error_message, client, version_validation_callback_data->callback_userdata);
    173   }
    174   else {
    175     rc_client_external_t* external_client = (rc_client_external_t*)
    176         rc_buffer_alloc(&client->state.buffer, sizeof(*external_client));
    177     memset(external_client, 0, sizeof(*external_client));
    178 
    179     if (!client->state.raintegration->get_external_client(external_client, RC_CLIENT_EXTERNAL_VERSION)) {
    180       const char* error_message = "RA_Integration external client export failed";
    181 
    182       rc_client_unload_raintegration(client);
    183 
    184       RC_CLIENT_LOG_ERR(client, error_message);
    185       version_validation_callback_data->callback(RC_ABORTED, error_message, client, version_validation_callback_data->callback_userdata);
    186     }
    187     else {
    188       /* copy state to the external client */
    189       if (external_client->enable_logging)
    190         external_client->enable_logging(client, client->state.log_level, client->callbacks.log_call);
    191 
    192       if (external_client->set_event_handler)
    193         external_client->set_event_handler(client, client->callbacks.event_handler);
    194       if (external_client->set_read_memory)
    195         external_client->set_read_memory(client, client->callbacks.read_memory);
    196 
    197       if (external_client->set_hardcore_enabled)
    198         external_client->set_hardcore_enabled(rc_client_get_hardcore_enabled(client));
    199       if (external_client->set_unofficial_enabled)
    200         external_client->set_unofficial_enabled(rc_client_get_unofficial_enabled(client));
    201       if (external_client->set_encore_mode_enabled)
    202         external_client->set_encore_mode_enabled(rc_client_get_encore_mode_enabled(client));
    203       if (external_client->set_spectator_mode_enabled)
    204         external_client->set_spectator_mode_enabled(rc_client_get_spectator_mode_enabled(client));
    205 
    206       /* attach the external client and call the callback */
    207       client->state.external_client = external_client;
    208 
    209       client->state.raintegration->hMainWindow = version_validation_callback_data->main_window_handle;
    210       client->state.raintegration->bIsInited = 1;
    211 
    212       version_validation_callback_data->callback(RC_OK, NULL,
    213          client, version_validation_callback_data->callback_userdata);
    214     }
    215   }
    216 }
    217 
    218 static void rc_client_version_validation_callback(const rc_api_server_response_t* server_response, void* callback_data)
    219 {
    220   rc_client_version_validation_callback_data_t* version_validation_callback_data =
    221       (rc_client_version_validation_callback_data_t*)callback_data;
    222   rc_client_t* client = version_validation_callback_data->client;
    223 
    224   if (rc_client_async_handle_aborted(client, &version_validation_callback_data->async_handle)) {
    225     RC_CLIENT_LOG_VERBOSE(client, "Version validation aborted");
    226   }
    227   else {
    228     rc_api_response_t response;
    229     int result;
    230     const char* current_version;
    231     const char* minimum_version = "";
    232 
    233     rc_json_field_t fields[] = {
    234       RC_JSON_NEW_FIELD("Success"),
    235       RC_JSON_NEW_FIELD("Error"),
    236       RC_JSON_NEW_FIELD("MinimumVersion"),
    237     };
    238 
    239     memset(&response, 0, sizeof(response));
    240     rc_buffer_init(&response.buffer);
    241 
    242     result = rc_json_parse_server_response(&response, server_response, fields, sizeof(fields) / sizeof(fields[0]));
    243     if (result == RC_OK) {
    244       if (!rc_json_get_required_string(&minimum_version, &response, &fields[2], "MinimumVersion"))
    245         result = RC_MISSING_VALUE;
    246     }
    247 
    248     if (result != RC_OK) {
    249       RC_CLIENT_LOG_ERR_FORMATTED(client, "Failed to fetch latest integration version: %.*s", server_response->body_length, server_response->body);
    250 
    251       rc_client_unload_raintegration(client);
    252 
    253       version_validation_callback_data->callback(result, rc_error_str(result),
    254           client, version_validation_callback_data->callback_userdata);
    255     }
    256     else {
    257       current_version = client->state.raintegration->get_version();
    258 
    259       if (rc_client_version_less(current_version, minimum_version)) {
    260         char error_message[256];
    261 
    262         rc_client_unload_raintegration(client);
    263 
    264         snprintf(error_message, sizeof(error_message),
    265             "RA_Integration version %s is lower than minimum version %s", current_version, minimum_version);
    266         RC_CLIENT_LOG_WARN(client, error_message);
    267         version_validation_callback_data->callback(RC_ABORTED, error_message, client, version_validation_callback_data->callback_userdata);
    268       }
    269       else {
    270         RC_CLIENT_LOG_INFO_FORMATTED(client, "Validated RA_Integration version %s (minimum %s)", current_version, minimum_version);
    271 
    272         rc_client_init_raintegration(client, version_validation_callback_data);
    273       }
    274     }
    275 
    276     rc_buffer_destroy(&response.buffer);
    277   }
    278 
    279   free(version_validation_callback_data->client_name);
    280   free(version_validation_callback_data->client_version);
    281   free(version_validation_callback_data);
    282 }
    283 
    284 rc_client_async_handle_t* rc_client_begin_load_raintegration(rc_client_t* client,
    285     const wchar_t* search_directory, HWND main_window_handle,
    286     const char* client_name, const char* client_version,
    287     rc_client_callback_t callback, void* callback_userdata)
    288 {
    289   rc_client_version_validation_callback_data_t* callback_data;
    290   rc_api_url_builder_t builder;
    291   rc_api_request_t request;
    292 
    293   if (!client) {
    294     callback(RC_INVALID_STATE, "client is required", client, callback_userdata);
    295     return NULL;
    296   }
    297 
    298   if (!client_name) {
    299     callback(RC_INVALID_STATE, "client_name is required", client, callback_userdata);
    300     return NULL;
    301   }
    302 
    303   if (!client_version) {
    304     callback(RC_INVALID_STATE, "client_version is required", client, callback_userdata);
    305     return NULL;
    306   }
    307 
    308   if (client->state.user != RC_CLIENT_USER_STATE_NONE) {
    309     callback(RC_INVALID_STATE, "Cannot initialize RAIntegration after login", client, callback_userdata);
    310     return NULL;
    311   }
    312 
    313   if (!client->state.raintegration) {
    314     if (!main_window_handle) {
    315       callback(RC_INVALID_STATE, "main_window_handle is required", client, callback_userdata);
    316       return NULL;
    317     }
    318 
    319     rc_client_raintegration_load_dll(client, search_directory, callback, callback_userdata);
    320     if (!client->state.raintegration)
    321       return NULL;
    322   }
    323 
    324   if (client->state.raintegration->get_host_url) {
    325     const char* host_url = client->state.raintegration->get_host_url();
    326     if (host_url && strcmp(host_url, "https://retroachievements.org") != 0 &&
    327                     strcmp(host_url, "OFFLINE") != 0) {
    328       /* if the DLL specifies a custom host, use it */
    329       rc_client_set_host(client, host_url);
    330     }
    331   }
    332 
    333   memset(&request, 0, sizeof(request));
    334   rc_api_url_build_dorequest_url(&request);
    335   rc_url_builder_init(&builder, &request.buffer, 48);
    336   rc_url_builder_append_str_param(&builder, "r", "latestintegration");
    337   request.post_data = rc_url_builder_finalize(&builder);
    338 
    339   callback_data = calloc(1, sizeof(*callback_data));
    340   if (!callback_data) {
    341     callback(RC_OUT_OF_MEMORY, rc_error_str(RC_OUT_OF_MEMORY), client, callback_userdata);
    342     return NULL;
    343   }
    344 
    345   callback_data->client = client;
    346   callback_data->callback = callback;
    347   callback_data->callback_userdata = callback_userdata;
    348   callback_data->client_name = strdup(client_name);
    349   callback_data->client_version = strdup(client_version);
    350   callback_data->main_window_handle = main_window_handle;
    351 
    352   client->callbacks.server_call(&request, rc_client_version_validation_callback, callback_data, client);
    353   return &callback_data->async_handle;
    354 }
    355 
    356 void rc_client_raintegration_update_main_window_handle(rc_client_t* client, HWND main_window_handle)
    357 {
    358   if (client && client->state.raintegration) {
    359     client->state.raintegration->hMainWindow = main_window_handle;
    360 
    361     if (client->state.raintegration->bIsInited &&
    362         client->state.raintegration->update_main_window_handle) {
    363       client->state.raintegration->update_main_window_handle(main_window_handle);
    364     }
    365   }
    366 }
    367 
    368 void rc_client_raintegration_set_write_memory_function(rc_client_t* client, rc_client_raintegration_write_memory_func_t handler)
    369 {
    370   if (client && client->state.raintegration && client->state.raintegration->set_write_memory_function)
    371     client->state.raintegration->set_write_memory_function(client, handler);
    372 }
    373 
    374 void rc_client_raintegration_set_get_game_name_function(rc_client_t* client, rc_client_raintegration_get_game_name_func_t handler)
    375 {
    376   if (client && client->state.raintegration && client->state.raintegration->set_get_game_name_function)
    377     client->state.raintegration->set_get_game_name_function(client, handler);
    378 }
    379 
    380 void rc_client_raintegration_set_event_handler(rc_client_t* client,
    381     rc_client_raintegration_event_handler_t handler)
    382 {
    383   if (client && client->state.raintegration && client->state.raintegration->set_event_handler)
    384     client->state.raintegration->set_event_handler(client, handler);
    385 }
    386 
    387 const rc_client_raintegration_menu_t* rc_client_raintegration_get_menu(const rc_client_t* client)
    388 {
    389   if (!client || !client->state.raintegration ||
    390       !client->state.raintegration->bIsInited ||
    391       !client->state.raintegration->get_menu) {
    392     return NULL;
    393   }
    394 
    395   return client->state.raintegration->get_menu();
    396 }
    397 
    398 void rc_client_raintegration_set_console_id(rc_client_t* client, uint32_t console_id)
    399 {
    400   if (client && client->state.raintegration && client->state.raintegration->set_console_id)
    401     client->state.raintegration->set_console_id(console_id);
    402 }
    403 
    404 int rc_client_raintegration_has_modifications(const rc_client_t* client)
    405 {
    406   if (!client || !client->state.raintegration ||
    407       !client->state.raintegration->bIsInited ||
    408       !client->state.raintegration->has_modifications) {
    409     return 0;
    410   }
    411 
    412   return client->state.raintegration->has_modifications();
    413 }
    414 
    415 int rc_client_raintegration_get_achievement_state(const rc_client_t* client, uint32_t achievement_id)
    416 {
    417   if (!client || !client->state.raintegration ||
    418       !client->state.raintegration->bIsInited ||
    419       !client->state.raintegration->get_achievement_state) {
    420     return RC_CLIENT_RAINTEGRATION_ACHIEVEMENT_STATE_NONE;
    421   }
    422 
    423   return client->state.raintegration->get_achievement_state(achievement_id);
    424 }
    425 
    426 void rc_client_raintegration_rebuild_submenu(rc_client_t* client, HMENU hMenu)
    427 {
    428    HMENU hPopupMenu = NULL;
    429    const rc_client_raintegration_menu_t* menu;
    430 
    431    if (!client || !client->state.raintegration)
    432       return;
    433 
    434    /* destroy the existing menu */
    435    if (client->state.raintegration->hPopupMenu)
    436       DestroyMenu(client->state.raintegration->hPopupMenu);
    437 
    438    /* create the popup menu */
    439    hPopupMenu = CreatePopupMenu();
    440 
    441    menu = rc_client_raintegration_get_menu(client);
    442    if (menu && menu->num_items)
    443    {
    444       const rc_client_raintegration_menu_item_t* menuitem = menu->items;
    445       const rc_client_raintegration_menu_item_t* stop = menu->items + menu->num_items;
    446 
    447       for (; menuitem < stop; ++menuitem)
    448       {
    449          if (menuitem->id == 0)
    450             AppendMenuA(hPopupMenu, MF_SEPARATOR, 0U, NULL);
    451          else
    452          {
    453             UINT flags = MF_STRING;
    454             if (menuitem->checked)
    455                flags |= MF_CHECKED;
    456             if (!menuitem->enabled)
    457                flags |= MF_GRAYED;
    458 
    459             AppendMenuA(hPopupMenu, flags, menuitem->id, menuitem->label);
    460          }
    461       }
    462    }
    463 
    464    /* add/update the item containing the popup menu */
    465    {
    466       int nIndex = GetMenuItemCount(hMenu);
    467       const char* menuText = "&RetroAchievements";
    468       char buffer[64];
    469 
    470       UINT flags = MF_POPUP | MF_STRING;
    471       if (!menu || !menu->num_items)
    472          flags |= MF_GRAYED;
    473 
    474       while (--nIndex >= 0)
    475       {
    476          if (GetMenuStringA(hMenu, nIndex, buffer, sizeof(buffer) - 1, MF_BYPOSITION))
    477          {
    478             if (strcmp(buffer, menuText) == 0)
    479                break;
    480          }
    481       }
    482 
    483       if (nIndex == -1)
    484          AppendMenuA(hMenu, flags, (UINT_PTR)hPopupMenu, menuText);
    485       else
    486          ModifyMenuA(hMenu, nIndex, flags | MF_BYPOSITION, (UINT_PTR)hPopupMenu, menuText);
    487 
    488       if (client->state.raintegration->hMainWindow && GetMenu(client->state.raintegration->hMainWindow) == hMenu)
    489         DrawMenuBar(client->state.raintegration->hMainWindow);
    490    }
    491 
    492    client->state.raintegration->hPopupMenu = hPopupMenu;
    493 }
    494 
    495 void rc_client_raintegration_update_menu_item(const rc_client_t* client, const rc_client_raintegration_menu_item_t* menuitem)
    496 {
    497    if (client && client->state.raintegration && client->state.raintegration->hPopupMenu)
    498    {
    499       UINT flags = MF_STRING;
    500       if (menuitem->checked)
    501          flags |= MF_CHECKED;
    502 
    503       CheckMenuItem(client->state.raintegration->hPopupMenu, menuitem->id, flags | MF_BYCOMMAND);
    504 
    505       flags = (menuitem->enabled) ? MF_ENABLED : MF_GRAYED;
    506       EnableMenuItem(client->state.raintegration->hPopupMenu, menuitem->id, flags | MF_BYCOMMAND);
    507    }
    508 }
    509 
    510 int rc_client_raintegration_activate_menu_item(const rc_client_t* client, uint32_t menu_item_id)
    511 {
    512    if (!client || !client->state.raintegration || !client->state.raintegration->activate_menu_item)
    513       return 0;
    514 
    515    return client->state.raintegration->activate_menu_item(menu_item_id);
    516 }
    517 
    518 void rc_client_unload_raintegration(rc_client_t* client)
    519 {
    520   HINSTANCE hDLL;
    521 
    522   if (!client || !client->state.raintegration)
    523     return;
    524 
    525   RC_CLIENT_LOG_INFO(client, "Unloading RA_Integration")
    526 
    527   if (client->state.external_client && client->state.external_client->destroy)
    528     client->state.external_client->destroy();
    529 
    530   if (client->state.raintegration->shutdown)
    531     client->state.raintegration->shutdown();
    532 
    533   rc_mutex_lock(&client->state.mutex);
    534   hDLL = client->state.raintegration->hDLL;
    535   client->state.raintegration = NULL;
    536   client->state.external_client = NULL;
    537   rc_mutex_unlock(&client->state.mutex);
    538 
    539   if (hDLL)
    540     FreeLibrary(hDLL);
    541 }
    542 
    543 #endif /* RC_CLIENT_SUPPORTS_RAINTEGRATION */