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