RA_Interface.cpp (36636B)
1 #include "RA_Interface.h" 2 3 #include <winhttp.h> 4 #include <cassert> 5 #include <stdexcept> 6 #include <string> 7 8 #ifndef CCONV 9 #define CCONV __cdecl 10 #endif 11 12 // Initialization 13 static const char* (CCONV* _RA_IntegrationVersion)() = nullptr; 14 static const char* (CCONV* _RA_HostName)() = nullptr; 15 static const char* (CCONV* _RA_HostUrl)() = nullptr; 16 static int (CCONV* _RA_InitI)(HWND hMainWnd, int nConsoleID, const char* sClientVer) = nullptr; 17 static int (CCONV* _RA_InitOffline)(HWND hMainWnd, int nConsoleID, const char* sClientVer) = nullptr; 18 static int (CCONV* _RA_InitClient)(HWND hMainWnd, const char* sClientName, const char* sClientVer) = nullptr; 19 static int (CCONV* _RA_InitClientOffline)(HWND hMainWnd, const char* sClientName, const char* sClientVer) = nullptr; 20 static void (CCONV* _RA_InstallSharedFunctions)(int(*)(), void(*)(), void(*)(), void(*)(), void(*)(char*), void(*)(), void(*)(const char*)) = nullptr; 21 static void (CCONV* _RA_SetForceRepaint)(int bEnable) = nullptr; 22 static HMENU (CCONV* _RA_CreatePopupMenu)() = nullptr; 23 static int (CCONV* _RA_GetPopupMenuItems)(RA_MenuItem*) = nullptr; 24 static void (CCONV* _RA_InvokeDialog)(LPARAM nID) = nullptr; 25 static void (CCONV* _RA_SetUserAgentDetail)(const char* sDetail); 26 static void (CCONV* _RA_AttemptLogin)(int bBlocking) = nullptr; 27 static int (CCONV* _RA_SetConsoleID)(unsigned int nConsoleID) = nullptr; 28 static void (CCONV* _RA_ClearMemoryBanks)() = nullptr; 29 static void (CCONV* _RA_InstallMemoryBank)(int nBankID, RA_ReadMemoryFunc* pReader, RA_WriteMemoryFunc* pWriter, int nBankSize) = nullptr; 30 static void (CCONV* _RA_InstallMemoryBankBlockReader)(int nBankID, RA_ReadMemoryBlockFunc* pReader) = nullptr; 31 static int (CCONV* _RA_Shutdown)() = nullptr; 32 // Overlay 33 static int (CCONV* _RA_IsOverlayFullyVisible)() = nullptr; 34 static void (CCONV* _RA_SetPaused)(int bIsPaused) = nullptr; 35 static void (CCONV* _RA_NavigateOverlay)(ControllerInput* pInput) = nullptr; 36 static void (CCONV* _RA_UpdateHWnd)(HWND hMainHWND); 37 // Game Management 38 static unsigned int (CCONV* _RA_IdentifyRom)(const BYTE* pROM, unsigned int nROMSize) = nullptr; 39 static unsigned int (CCONV* _RA_IdentifyHash)(const char* sHash) = nullptr; 40 static void (CCONV* _RA_ActivateGame)(unsigned int nGameId) = nullptr; 41 static int (CCONV* _RA_OnLoadNewRom)(const BYTE* pROM, unsigned int nROMSize) = nullptr; 42 static int (CCONV* _RA_ConfirmLoadNewRom)(int bQuitting) = nullptr; 43 // Runtime Functionality 44 static void (CCONV* _RA_DoAchievementsFrame)() = nullptr; 45 static void (CCONV* _RA_SuspendRepaint)() = nullptr; 46 static void (CCONV* _RA_ResumeRepaint)() = nullptr; 47 static void (CCONV* _RA_UpdateAppTitle)(const char* pMessage) = nullptr; 48 static const char* (CCONV* _RA_UserName)() = nullptr; 49 static int (CCONV* _RA_HardcoreModeIsActive)(void) = nullptr; 50 static int (CCONV* _RA_WarnDisableHardcore)(const char* sActivity) = nullptr; 51 static void (CCONV* _RA_OnReset)() = nullptr; 52 static void (CCONV* _RA_OnSaveState)(const char* sFilename) = nullptr; 53 static void (CCONV* _RA_OnLoadState)(const char* sFilename) = nullptr; 54 static int (CCONV* _RA_CaptureState)(char* pBuffer, int nBufferSize) = nullptr; 55 static void (CCONV* _RA_RestoreState)(const char* pBuffer) = nullptr; 56 57 static HINSTANCE g_hRADLL = nullptr; 58 59 void RA_AttemptLogin(int bBlocking) 60 { 61 if (_RA_AttemptLogin != nullptr) 62 _RA_AttemptLogin(bBlocking); 63 } 64 65 const char* RA_UserName(void) 66 { 67 if (_RA_UserName != nullptr) 68 return _RA_UserName(); 69 70 return ""; 71 } 72 73 void RA_NavigateOverlay(ControllerInput* pInput) 74 { 75 if (_RA_NavigateOverlay != nullptr) 76 _RA_NavigateOverlay(pInput); 77 } 78 79 void RA_UpdateRenderOverlay(HDC hDC, ControllerInput* pInput, float fDeltaTime, RECT* prcSize, bool Full_Screen, bool Paused) 80 { 81 if (_RA_NavigateOverlay != nullptr) 82 _RA_NavigateOverlay(pInput); 83 } 84 85 int RA_IsOverlayFullyVisible(void) 86 { 87 if (_RA_IsOverlayFullyVisible != nullptr) 88 return _RA_IsOverlayFullyVisible(); 89 90 return 0; 91 } 92 93 void RA_UpdateHWnd(HWND hMainWnd) 94 { 95 if (_RA_UpdateHWnd != nullptr) 96 _RA_UpdateHWnd(hMainWnd); 97 } 98 99 unsigned int RA_IdentifyRom(BYTE* pROMData, unsigned int nROMSize) 100 { 101 if (_RA_IdentifyRom != nullptr) 102 return _RA_IdentifyRom(pROMData, nROMSize); 103 104 return 0; 105 } 106 107 unsigned int RA_IdentifyHash(const char* sHash) 108 { 109 if (_RA_IdentifyHash!= nullptr) 110 return _RA_IdentifyHash(sHash); 111 112 return 0; 113 } 114 115 void RA_ActivateGame(unsigned int nGameId) 116 { 117 if (_RA_ActivateGame != nullptr) 118 _RA_ActivateGame(nGameId); 119 } 120 121 void RA_OnLoadNewRom(BYTE* pROMData, unsigned int nROMSize) 122 { 123 if (_RA_OnLoadNewRom != nullptr) 124 _RA_OnLoadNewRom(pROMData, nROMSize); 125 } 126 127 void RA_ClearMemoryBanks(void) 128 { 129 if (_RA_ClearMemoryBanks != nullptr) 130 _RA_ClearMemoryBanks(); 131 } 132 133 void RA_InstallMemoryBank(int nBankID, RA_ReadMemoryFunc pReader, RA_WriteMemoryFunc pWriter, int nBankSize) 134 { 135 if (_RA_InstallMemoryBank != nullptr) 136 _RA_InstallMemoryBank(nBankID, pReader, pWriter, nBankSize); 137 } 138 139 void RA_InstallMemoryBankBlockReader(int nBankID, RA_ReadMemoryBlockFunc pReader) 140 { 141 if (_RA_InstallMemoryBankBlockReader != nullptr) 142 _RA_InstallMemoryBankBlockReader(nBankID, pReader); 143 } 144 145 HMENU RA_CreatePopupMenu(void) 146 { 147 return (_RA_CreatePopupMenu != nullptr) ? _RA_CreatePopupMenu() : nullptr; 148 } 149 150 int RA_GetPopupMenuItems(RA_MenuItem *pItems) 151 { 152 return (_RA_GetPopupMenuItems != nullptr) ? _RA_GetPopupMenuItems(pItems) : 0; 153 } 154 155 void RA_UpdateAppTitle(const char* sCustomMsg) 156 { 157 if (_RA_UpdateAppTitle != nullptr) 158 _RA_UpdateAppTitle(sCustomMsg); 159 } 160 161 void RA_HandleHTTPResults(void) 162 { 163 } 164 165 int RA_ConfirmLoadNewRom(int bIsQuitting) 166 { 167 return _RA_ConfirmLoadNewRom ? _RA_ConfirmLoadNewRom(bIsQuitting) : 1; 168 } 169 170 void RA_InvokeDialog(LPARAM nID) 171 { 172 if (_RA_InvokeDialog != nullptr) 173 _RA_InvokeDialog(nID); 174 } 175 176 void RA_SetPaused(bool bIsPaused) 177 { 178 if (_RA_SetPaused != nullptr) 179 _RA_SetPaused(bIsPaused); 180 } 181 182 void RA_OnLoadState(const char* sFilename) 183 { 184 if (_RA_OnLoadState != nullptr) 185 _RA_OnLoadState(sFilename); 186 } 187 188 void RA_OnSaveState(const char* sFilename) 189 { 190 if (_RA_OnSaveState != nullptr) 191 _RA_OnSaveState(sFilename); 192 } 193 194 int RA_CaptureState(char* pBuffer, int nBufferSize) 195 { 196 if (_RA_CaptureState != nullptr) 197 return _RA_CaptureState(pBuffer, nBufferSize); 198 199 return 0; 200 } 201 202 void RA_RestoreState(const char* pBuffer) 203 { 204 if (_RA_RestoreState != nullptr) 205 _RA_RestoreState(pBuffer); 206 } 207 208 void RA_OnReset(void) 209 { 210 if (_RA_OnReset != nullptr) 211 _RA_OnReset(); 212 } 213 214 void RA_DoAchievementsFrame(void) 215 { 216 if (_RA_DoAchievementsFrame != nullptr) 217 _RA_DoAchievementsFrame(); 218 } 219 220 void RA_SetForceRepaint(int bEnable) 221 { 222 if (_RA_SetForceRepaint != nullptr) 223 _RA_SetForceRepaint(bEnable); 224 } 225 226 void RA_SuspendRepaint(void) 227 { 228 if (_RA_SuspendRepaint != nullptr) 229 _RA_SuspendRepaint(); 230 } 231 232 void RA_ResumeRepaint(void) 233 { 234 if (_RA_ResumeRepaint != nullptr) 235 _RA_ResumeRepaint(); 236 } 237 238 void RA_SetConsoleID(unsigned int nConsoleID) 239 { 240 if (_RA_SetConsoleID != nullptr) 241 _RA_SetConsoleID(nConsoleID); 242 } 243 244 int RA_HardcoreModeIsActive(void) 245 { 246 return (_RA_HardcoreModeIsActive != nullptr) ? _RA_HardcoreModeIsActive() : 0; 247 } 248 249 int RA_WarnDisableHardcore(const char* sActivity) 250 { 251 // If Hardcore mode not active, allow the activity. 252 if (!RA_HardcoreModeIsActive()) 253 return 1; 254 255 // DLL function will display a yes/no dialog. If the user chooses yes, the DLL will disable hardcore mode, and the activity can proceed. 256 if (_RA_WarnDisableHardcore != nullptr) 257 return _RA_WarnDisableHardcore(sActivity); 258 259 // We cannot disable hardcore mode, so just warn the user and prevent the activity. 260 std::string sMessage; 261 sMessage = "You cannot " + std::string(sActivity) + " while Hardcore mode is active."; 262 MessageBoxA(nullptr, sMessage.c_str(), "Warning", MB_OK | MB_ICONWARNING); 263 return 0; 264 } 265 266 void RA_DisableHardcore() 267 { 268 // passing nullptr to _RA_WarnDisableHardcore will just disable hardcore mode without prompting. 269 if (_RA_WarnDisableHardcore != nullptr) 270 _RA_WarnDisableHardcore(nullptr); 271 } 272 273 static size_t DownloadToFile(char* pData, size_t nDataSize, void* pUserData) 274 { 275 FILE* file = (FILE*)pUserData; 276 return fwrite(pData, 1, nDataSize, file); 277 } 278 279 typedef struct DownloadBuffer 280 { 281 char* pBuffer; 282 size_t nBufferSize; 283 size_t nOffset; 284 } DownloadBuffer; 285 286 static size_t DownloadToBuffer(char* pData, size_t nDataSize, void* pUserData) 287 { 288 DownloadBuffer* pBuffer = (DownloadBuffer*)pUserData; 289 const size_t nRemaining = pBuffer->nBufferSize - pBuffer->nOffset; 290 if (nDataSize > nRemaining) 291 nDataSize = nRemaining; 292 293 if (nDataSize > 0) 294 { 295 memcpy(pBuffer->pBuffer + pBuffer->nOffset, pData, nDataSize); 296 pBuffer->nOffset += nDataSize; 297 } 298 299 return nDataSize; 300 } 301 302 typedef size_t (DownloadFunc)(char* pData, size_t nDataSize, void* pUserData); 303 304 static BOOL DoBlockingHttpCall(const char* sHostUrl, const char* sRequestedPage, const char* sPostData, 305 DownloadFunc fnDownload, void* pDownloadUserData, DWORD* pBytesRead, DWORD* pStatusCode) 306 { 307 BOOL bResults = FALSE, bSuccess = FALSE; 308 HINTERNET hSession = nullptr, hConnect = nullptr, hRequest = nullptr; 309 size_t nHostnameLen; 310 311 WCHAR wBuffer[1024]; 312 size_t nTemp; 313 DWORD nBytesAvailable = 0; 314 DWORD nBytesToRead = 0; 315 DWORD nBytesFetched = 0; 316 (*pBytesRead) = 0; 317 318 INTERNET_PORT nPort = INTERNET_DEFAULT_HTTP_PORT; 319 const char* sHostName = sHostUrl; 320 if (_strnicmp(sHostName, "http://", 7) == 0) 321 { 322 sHostName += 7; 323 } 324 else if (_strnicmp(sHostName, "https://", 8) == 0) 325 { 326 sHostName += 8; 327 nPort = INTERNET_DEFAULT_HTTPS_PORT; 328 } 329 330 const char* sPort = strchr(sHostName, ':'); 331 if (sPort) 332 { 333 nHostnameLen = sPort - sHostName; 334 nPort = atoi(sPort + 1); 335 } 336 else 337 { 338 nHostnameLen = strlen(sHostName); 339 } 340 341 // Use WinHttpOpen to obtain a session handle. 342 hSession = WinHttpOpen(L"RetroAchievements Client Bootstrap", 343 WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, 344 WINHTTP_NO_PROXY_NAME, 345 WINHTTP_NO_PROXY_BYPASS, 0); 346 347 // Specify an HTTP server. 348 if (hSession == nullptr) 349 { 350 *pStatusCode = GetLastError(); 351 } 352 else 353 { 354 #if defined(_MSC_VER) && _MSC_VER >= 1400 355 mbstowcs_s(&nTemp, wBuffer, sizeof(wBuffer) / sizeof(wBuffer[0]), sHostName, nHostnameLen); 356 #else 357 nTemp = mbstowcs(wBuffer, sHostName, nHostnameLen); 358 #endif 359 360 if (nTemp > 0) 361 { 362 hConnect = WinHttpConnect(hSession, wBuffer, nPort, 0); 363 } 364 365 // Create an HTTP Request handle. 366 if (hConnect == nullptr) 367 { 368 *pStatusCode = GetLastError(); 369 } 370 else 371 { 372 #if defined(_MSC_VER) && _MSC_VER >= 1400 373 mbstowcs_s(&nTemp, wBuffer, sizeof(wBuffer) / sizeof(wBuffer[0]), sRequestedPage, strlen(sRequestedPage) + 1); 374 #else 375 nTemp = mbstowcs(wBuffer, sRequestedPage, strlen(sRequestedPage) + 1); 376 #endif 377 378 hRequest = WinHttpOpenRequest(hConnect, 379 sPostData ? L"POST" : L"GET", 380 wBuffer, 381 nullptr, 382 WINHTTP_NO_REFERER, 383 WINHTTP_DEFAULT_ACCEPT_TYPES, 384 (nPort == INTERNET_DEFAULT_HTTPS_PORT) ? WINHTTP_FLAG_SECURE : 0); 385 386 // Send a Request. 387 if (hRequest == nullptr) 388 { 389 *pStatusCode = GetLastError(); 390 } 391 else 392 { 393 if (sPostData) 394 { 395 const size_t nPostDataLength = strlen(sPostData); 396 bResults = WinHttpSendRequest(hRequest, 397 L"Content-Type: application/x-www-form-urlencoded", 398 0, (LPVOID)sPostData, (DWORD)nPostDataLength, (DWORD)nPostDataLength, 0); 399 } 400 else 401 { 402 bResults = WinHttpSendRequest(hRequest, 403 L"Content-Type: application/x-www-form-urlencoded", 404 0, WINHTTP_NO_REQUEST_DATA, 0, 0, 0); 405 } 406 407 if (!bResults || !WinHttpReceiveResponse(hRequest, nullptr)) 408 { 409 *pStatusCode = GetLastError(); 410 } 411 else 412 { 413 char buffer[16384]; 414 DWORD dwSize = sizeof(DWORD); 415 WinHttpQueryHeaders(hRequest, WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER, WINHTTP_HEADER_NAME_BY_INDEX, pStatusCode, &dwSize, WINHTTP_NO_HEADER_INDEX); 416 417 bSuccess = TRUE; 418 do 419 { 420 nBytesAvailable = 0; 421 WinHttpQueryDataAvailable(hRequest, &nBytesAvailable); 422 if (nBytesAvailable == 0) 423 break; 424 425 do 426 { 427 if (nBytesAvailable > sizeof(buffer)) 428 nBytesToRead = sizeof(buffer); 429 else 430 nBytesToRead = nBytesAvailable; 431 432 nBytesFetched = 0; 433 if (WinHttpReadData(hRequest, buffer, nBytesToRead, &nBytesFetched)) 434 { 435 size_t nBytesWritten = fnDownload(buffer, nBytesFetched, pDownloadUserData); 436 if (nBytesWritten < nBytesFetched) 437 { 438 if (*pStatusCode == 200) 439 *pStatusCode = 998; 440 441 bSuccess = FALSE; 442 break; 443 } 444 445 (*pBytesRead) += (DWORD)nBytesWritten; 446 nBytesAvailable -= nBytesFetched; 447 } 448 else 449 { 450 if (*pStatusCode == 200) 451 *pStatusCode = GetLastError(); 452 453 bSuccess = FALSE; 454 break; 455 } 456 } while (nBytesAvailable > 0); 457 } while (TRUE); 458 } 459 460 WinHttpCloseHandle(hRequest); 461 } 462 463 WinHttpCloseHandle(hConnect); 464 } 465 466 WinHttpCloseHandle(hSession); 467 } 468 469 return bSuccess; 470 } 471 472 static BOOL IsNetworkError(DWORD nStatusCode) 473 { 474 switch (nStatusCode) 475 { 476 case 12002: // timeout 477 case 12007: // dns lookup failed 478 case 12017: // handle closed before request completed 479 case 12019: // handle not initialized 480 case 12028: // data not available at this time 481 case 12029: // handshake failed 482 case 12030: // connection aborted 483 case 12031: // connection reset 484 case 12032: // explicit request to retry 485 case 12152: // response could not be parsed, corrupt? 486 case 12163: // lost connection during request 487 return TRUE; 488 489 default: 490 return FALSE; 491 } 492 } 493 494 static BOOL DoBlockingHttpCallWithRetry(const char* sHostUrl, const char* sRequestedPage, const char* sPostData, 495 char pBufferOut[], unsigned int nBufferOutSize, DWORD* pBytesRead, DWORD* pStatusCode) 496 { 497 int nRetries = 4; 498 do 499 { 500 DownloadBuffer downloadBuffer; 501 memset(&downloadBuffer, 0, sizeof(downloadBuffer)); 502 downloadBuffer.pBuffer = pBufferOut; 503 downloadBuffer.nBufferSize = nBufferOutSize; 504 505 if (DoBlockingHttpCall(sHostUrl, sRequestedPage, sPostData, DownloadToBuffer, &downloadBuffer, pBytesRead, pStatusCode) != FALSE) 506 return TRUE; 507 508 if (!IsNetworkError(*pStatusCode)) 509 return FALSE; 510 511 --nRetries; 512 } while (nRetries); 513 514 return FALSE; 515 } 516 517 static BOOL DoBlockingHttpCallWithRetry(const char* sHostUrl, const char* sRequestedPage, const char* sPostData, 518 FILE* pFile, DWORD* pBytesRead, DWORD* pStatusCode) 519 { 520 int nRetries = 4; 521 do 522 { 523 fseek(pFile, 0, SEEK_SET); 524 if (DoBlockingHttpCall(sHostUrl, sRequestedPage, sPostData, DownloadToFile, pFile, pBytesRead, pStatusCode) != FALSE) 525 return TRUE; 526 527 if (!IsNetworkError(*pStatusCode)) 528 return FALSE; 529 530 --nRetries; 531 } while (nRetries); 532 533 return FALSE; 534 } 535 536 #ifndef RA_UTEST 537 static std::wstring GetIntegrationPath() 538 { 539 wchar_t sBuffer[2048]; 540 DWORD iIndex = GetModuleFileNameW(0, sBuffer, 2048); 541 while (iIndex > 0 && sBuffer[iIndex - 1] != '\\' && sBuffer[iIndex - 1] != '/') 542 --iIndex; 543 544 #if defined(_MSC_VER) && _MSC_VER >= 1400 545 wcscpy_s(&sBuffer[iIndex], sizeof(sBuffer)/sizeof(sBuffer[0]) - iIndex, L"RA_Integration.dll"); 546 #else 547 wcscpy(&sBuffer[iIndex], L"RA_Integration.dll"); 548 #endif 549 550 return std::wstring(sBuffer); 551 } 552 #endif 553 554 #if 0 555 556 static void FetchIntegrationFromWeb(char* sLatestVersionUrl, DWORD* pStatusCode) 557 { 558 DWORD nBytesRead = 0; 559 const wchar_t* sDownloadFilename = L"RA_Integration.download"; 560 const wchar_t* sFilename = L"RA_Integration.dll"; 561 const wchar_t* sOldFilename = L"RA_Integration.old"; 562 563 #if defined(_MSC_VER) && _MSC_VER >= 1400 564 FILE* pf; 565 errno_t nErr = _wfopen_s(&pf, sDownloadFilename, L"wb"); 566 #else 567 FILE* pf = _wfopen(sDownloadFilename, L"wb"); 568 #endif 569 570 if (!pf) 571 { 572 #if defined(_MSC_VER) && _MSC_VER >= 1400 573 wchar_t sErrBuffer[2048]; 574 _wcserror_s(sErrBuffer, sizeof(sErrBuffer) / sizeof(sErrBuffer[0]), nErr); 575 576 std::wstring sErrMsg = std::wstring(L"Unable to write ") + sOldFilename + L"\n" + sErrBuffer; 577 #else 578 std::wstring sErrMsg = std::wstring(L"Unable to write ") + sOldFilename + L"\n" + _wcserror(errno); 579 #endif 580 581 MessageBoxW(nullptr, sErrMsg.c_str(), L"Error", MB_OK | MB_ICONERROR); 582 return; 583 } 584 585 char* pSplit = sLatestVersionUrl + 8; /* skip over protocol */ 586 while (*pSplit != '/') 587 { 588 if (!*pSplit) 589 { 590 *pStatusCode = 997; 591 return; 592 } 593 ++pSplit; 594 } 595 *pSplit++ = '\0'; 596 597 if (DoBlockingHttpCallWithRetry(sLatestVersionUrl, pSplit, nullptr, pf, &nBytesRead, pStatusCode)) 598 { 599 fclose(pf); 600 601 /* wait up to one second for the DLL to actually be released - sometimes it's not immediate */ 602 for (int i = 0; i < 10; i++) 603 { 604 if (GetModuleHandleW(sFilename) == nullptr) 605 break; 606 607 Sleep(100); 608 } 609 610 // delete the last old dll if it's still present 611 DeleteFileW(sOldFilename); 612 613 // if there's a dll present, rename it 614 if (GetFileAttributesW(sFilename) != INVALID_FILE_ATTRIBUTES && 615 !MoveFileW(sFilename, sOldFilename)) 616 { 617 MessageBoxW(nullptr, L"Could not rename old dll", L"Error", MB_OK | MB_ICONERROR); 618 } 619 // rename the download to be the dll 620 else if (!MoveFileW(sDownloadFilename, sFilename)) 621 { 622 MessageBoxW(nullptr, L"Could not rename new dll", L"Error", MB_OK | MB_ICONERROR); 623 } 624 625 // delete the old dll 626 DeleteFileW(sOldFilename); 627 } 628 else 629 { 630 fclose(pf); 631 } 632 } 633 634 #endif 635 636 //Returns the last Win32 error, in string format. Returns an empty string if there is no error. 637 static std::string GetLastErrorAsString() 638 { 639 //Get the error message, if any. 640 DWORD errorMessageID = ::GetLastError(); 641 if (errorMessageID == 0) 642 return "No error message has been recorded"; 643 644 LPSTR messageBuffer = nullptr; 645 size_t size = FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, 646 nullptr, errorMessageID, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&messageBuffer, 0, nullptr); 647 648 std::string message(messageBuffer, size); 649 650 //Free the buffer. 651 LocalFree(messageBuffer); 652 653 return message; 654 } 655 656 static const char* CCONV _RA_InstallIntegration() 657 { 658 SetErrorMode(0); 659 660 std::wstring sIntegrationPath = GetIntegrationPath(); 661 662 DWORD dwAttrib = GetFileAttributesW(sIntegrationPath.c_str()); 663 if (dwAttrib == INVALID_FILE_ATTRIBUTES) 664 return "0.0"; 665 666 g_hRADLL = LoadLibraryW(sIntegrationPath.c_str()); 667 if (g_hRADLL == nullptr) 668 { 669 char buffer[1024]; 670 sprintf_s(buffer, 1024, "Could not load RA_Integration.dll: %d\n%s\n", ::GetLastError(), GetLastErrorAsString().c_str()); 671 MessageBoxA(nullptr, buffer, "Warning", MB_OK | MB_ICONWARNING); 672 673 return "0.0"; 674 } 675 676 // Install function pointers one by one 677 678 _RA_IntegrationVersion = (const char* (CCONV*)()) GetProcAddress(g_hRADLL, "_RA_IntegrationVersion"); 679 _RA_HostName = (const char* (CCONV*)()) GetProcAddress(g_hRADLL, "_RA_HostName"); 680 _RA_HostUrl = (const char* (CCONV*)()) GetProcAddress(g_hRADLL, "_RA_HostUrl"); 681 _RA_InitI = (int(CCONV*)(HWND, int, const char*)) GetProcAddress(g_hRADLL, "_RA_InitI"); 682 _RA_InitOffline = (int(CCONV*)(HWND, int, const char*)) GetProcAddress(g_hRADLL, "_RA_InitOffline"); 683 _RA_InitClient = (int(CCONV*)(HWND, const char*, const char*)) GetProcAddress(g_hRADLL, "_RA_InitClient"); 684 _RA_InitClientOffline = (int(CCONV*)(HWND, const char*, const char*)) GetProcAddress(g_hRADLL, "_RA_InitClientOffline"); 685 _RA_InstallSharedFunctions = (void(CCONV*)(int(*)(), void(*)(), void(*)(), void(*)(), void(*)(char*), void(*)(), void(*)(const char*))) GetProcAddress(g_hRADLL, "_RA_InstallSharedFunctionsExt"); 686 _RA_SetForceRepaint = (void(CCONV*)(int)) GetProcAddress(g_hRADLL, "_RA_SetForceRepaint"); 687 _RA_CreatePopupMenu = (HMENU(CCONV*)(void)) GetProcAddress(g_hRADLL, "_RA_CreatePopupMenu"); 688 _RA_GetPopupMenuItems = (int(CCONV*)(RA_MenuItem*)) GetProcAddress(g_hRADLL, "_RA_GetPopupMenuItems"); 689 _RA_InvokeDialog = (void(CCONV*)(LPARAM)) GetProcAddress(g_hRADLL, "_RA_InvokeDialog"); 690 _RA_SetUserAgentDetail = (void(CCONV*)(const char*)) GetProcAddress(g_hRADLL, "_RA_SetUserAgentDetail"); 691 _RA_AttemptLogin = (void(CCONV*)(int)) GetProcAddress(g_hRADLL, "_RA_AttemptLogin"); 692 _RA_SetConsoleID = (int(CCONV*)(unsigned int)) GetProcAddress(g_hRADLL, "_RA_SetConsoleID"); 693 _RA_ClearMemoryBanks = (void(CCONV*)()) GetProcAddress(g_hRADLL, "_RA_ClearMemoryBanks"); 694 _RA_InstallMemoryBank = (void(CCONV*)(int, RA_ReadMemoryFunc*, RA_WriteMemoryFunc*, int)) GetProcAddress(g_hRADLL, "_RA_InstallMemoryBank"); 695 _RA_InstallMemoryBankBlockReader = (void(CCONV*)(int, RA_ReadMemoryBlockFunc*)) GetProcAddress(g_hRADLL, "_RA_InstallMemoryBankBlockReader"); 696 _RA_Shutdown = (int(CCONV*)()) GetProcAddress(g_hRADLL, "_RA_Shutdown"); 697 _RA_IsOverlayFullyVisible = (int(CCONV*)()) GetProcAddress(g_hRADLL, "_RA_IsOverlayFullyVisible"); 698 _RA_SetPaused = (void(CCONV*)(int)) GetProcAddress(g_hRADLL, "_RA_SetPaused"); 699 _RA_NavigateOverlay = (void(CCONV*)(ControllerInput*)) GetProcAddress(g_hRADLL, "_RA_NavigateOverlay"); 700 _RA_UpdateHWnd = (void(CCONV*)(HWND)) GetProcAddress(g_hRADLL, "_RA_UpdateHWnd"); 701 _RA_IdentifyRom = (unsigned int(CCONV*)(const BYTE*, unsigned int)) GetProcAddress(g_hRADLL, "_RA_IdentifyRom"); 702 _RA_IdentifyHash = (unsigned int(CCONV*)(const char*)) GetProcAddress(g_hRADLL, "_RA_IdentifyHash"); 703 _RA_ActivateGame = (void(CCONV*)(unsigned int)) GetProcAddress(g_hRADLL, "_RA_ActivateGame"); 704 _RA_OnLoadNewRom = (int(CCONV*)(const BYTE*, unsigned int)) GetProcAddress(g_hRADLL, "_RA_OnLoadNewRom"); 705 _RA_ConfirmLoadNewRom = (int(CCONV*)(int)) GetProcAddress(g_hRADLL, "_RA_ConfirmLoadNewRom"); 706 _RA_DoAchievementsFrame = (void(CCONV*)()) GetProcAddress(g_hRADLL, "_RA_DoAchievementsFrame"); 707 _RA_SuspendRepaint = (void(CCONV*)()) GetProcAddress(g_hRADLL, "_RA_SuspendRepaint"); 708 _RA_ResumeRepaint = (void(CCONV*)()) GetProcAddress(g_hRADLL, "_RA_ResumeRepaint"); 709 _RA_UpdateAppTitle = (void(CCONV*)(const char*)) GetProcAddress(g_hRADLL, "_RA_UpdateAppTitle"); 710 _RA_UserName = (const char* (CCONV*)()) GetProcAddress(g_hRADLL, "_RA_UserName"); 711 _RA_HardcoreModeIsActive = (int(CCONV*)()) GetProcAddress(g_hRADLL, "_RA_HardcoreModeIsActive"); 712 _RA_WarnDisableHardcore = (int(CCONV*)(const char*)) GetProcAddress(g_hRADLL, "_RA_WarnDisableHardcore"); 713 _RA_OnReset = (void(CCONV*)()) GetProcAddress(g_hRADLL, "_RA_OnReset"); 714 _RA_OnSaveState = (void(CCONV*)(const char*)) GetProcAddress(g_hRADLL, "_RA_OnSaveState"); 715 _RA_OnLoadState = (void(CCONV*)(const char*)) GetProcAddress(g_hRADLL, "_RA_OnLoadState"); 716 _RA_CaptureState = (int(CCONV*)(char*, int)) GetProcAddress(g_hRADLL, "_RA_CaptureState"); 717 _RA_RestoreState = (void(CCONV*)(const char*)) GetProcAddress(g_hRADLL, "_RA_RestoreState"); 718 719 return _RA_IntegrationVersion ? _RA_IntegrationVersion() : "0.0"; 720 } 721 722 static void GetJsonField(const char* sJson, const char* sField, char *pBuffer, size_t nBufferSize) 723 { 724 const size_t nFieldSize = strlen(sField); 725 const char* pValue; 726 727 *pBuffer = 0; 728 do 729 { 730 const char* pScan = strstr(sJson, sField); 731 if (!pScan) 732 return; 733 734 if (pScan[-1] != '"' || pScan[nFieldSize] != '"') 735 { 736 sJson = pScan + 1; 737 continue; 738 } 739 740 pScan += nFieldSize + 1; 741 while (*pScan == ':' || isspace(*pScan)) 742 ++pScan; 743 if (*pScan != '"') 744 return; 745 746 pValue = ++pScan; 747 while (*pScan != '"') 748 { 749 if (!*pScan) 750 return; 751 752 ++pScan; 753 } 754 755 while (pValue < pScan && nBufferSize > 1) 756 { 757 if (*pValue == '\\') 758 ++pValue; 759 760 *pBuffer++ = *pValue++; 761 nBufferSize--; 762 } 763 764 *pBuffer = '\0'; 765 return; 766 767 } while (1); 768 } 769 770 static unsigned long long ParseVersion(const char* sVersion) 771 { 772 char* pPart; 773 774 unsigned long long major = strtoul(sVersion, &pPart, 10); 775 if (*pPart == '.') 776 ++pPart; 777 778 unsigned long long minor = strtoul(pPart, &pPart, 10); 779 if (*pPart == '.') 780 ++pPart; 781 782 unsigned long long patch = strtoul(pPart, &pPart, 10); 783 if (*pPart == '.') 784 ++pPart; 785 786 unsigned long long revision = strtoul(pPart, &pPart, 10); 787 788 // 64-bit max signed value is 9223 37203 68547 75807 789 unsigned long long version = (major * 100000) + minor; 790 version = (version * 100000) + patch; 791 version = (version * 100000) + revision; 792 return version; 793 } 794 795 static void RA_InitCommon(HWND hMainHWND, int nEmulatorID, const char* sClientName, const char* sClientVersion) 796 { 797 char sVerInstalled[32]; 798 #if defined(_MSC_VER) && _MSC_VER >= 1400 799 strcpy_s(sVerInstalled, sizeof(sVerInstalled), _RA_InstallIntegration()); 800 #else 801 strcpy(sVerInstalled, _RA_InstallIntegration()); 802 #endif 803 804 char sHostUrl[256] = ""; 805 if (_RA_HostUrl != nullptr) 806 { 807 #if defined(_MSC_VER) && _MSC_VER >= 1400 808 strcpy_s(sHostUrl, sizeof(sHostUrl), _RA_HostUrl()); 809 #else 810 strcpy(sHostUrl, _RA_HostUrl()); 811 #endif 812 } 813 else if (_RA_HostName != nullptr) 814 { 815 sprintf_s(sHostUrl, "http://%s", _RA_HostName()); 816 } 817 818 if (!sHostUrl[0]) 819 { 820 #if defined(_MSC_VER) && _MSC_VER >= 1400 821 strcpy_s(sHostUrl, sizeof(sHostUrl), "http://retroachievements.org"); 822 #else 823 strcpy(sHostUrl, "http://retroachievements.org"); 824 #endif 825 } 826 else if (_RA_InitOffline != nullptr && strcmp(sHostUrl, "http://OFFLINE") == 0) 827 { 828 if (sClientName == nullptr) 829 _RA_InitOffline(hMainHWND, nEmulatorID, sClientVersion); 830 else 831 _RA_InitClientOffline(hMainHWND, sClientName, sClientVersion); 832 return; 833 } 834 835 DWORD nBytesRead = 0; 836 DWORD nStatusCode = 0; 837 char buffer[1024]; 838 ZeroMemory(buffer, 1024); 839 840 if (DoBlockingHttpCallWithRetry(sHostUrl, "dorequest.php", "r=latestintegration", buffer, sizeof(buffer), &nBytesRead, &nStatusCode) == FALSE) 841 { 842 if (_RA_InitOffline != nullptr) 843 { 844 sprintf_s(buffer, sizeof(buffer), "Cannot access %s (status code %u)\nWorking offline.", sHostUrl, nStatusCode); 845 MessageBoxA(hMainHWND, buffer, "Warning", MB_OK | MB_ICONWARNING); 846 847 _RA_InitOffline(hMainHWND, nEmulatorID, sClientVersion); 848 } 849 else 850 { 851 sprintf_s(buffer, sizeof(buffer), "Cannot access %s (status code %u)\nPlease try again later.", sHostUrl, nStatusCode); 852 MessageBoxA(hMainHWND, buffer, "Warning", MB_OK | MB_ICONWARNING); 853 854 RA_Shutdown(); 855 } 856 return; 857 } 858 859 /* remove trailing zeros from client version */ 860 char* ptr = sVerInstalled + strlen(sVerInstalled); 861 while (ptr[-1] == '0' && ptr[-2] == '.' && (ptr - 2) > sVerInstalled) 862 ptr -= 2; 863 *ptr = '\0'; 864 if (strchr(sVerInstalled, '.') == NULL) 865 { 866 *ptr++ = '.'; 867 *ptr++ = '0'; 868 *ptr = '\0'; 869 } 870 871 char sLatestVersionUrl[256]; 872 char sVersionBuffer[32]; 873 GetJsonField(buffer, "MinimumVersion", sVersionBuffer, sizeof(sVersionBuffer)); 874 const unsigned long long nMinimumDLLVer = ParseVersion(sVersionBuffer); 875 876 GetJsonField(buffer, "LatestVersion", sVersionBuffer, sizeof(sVersionBuffer)); 877 const unsigned long long nLatestDLLVer = ParseVersion(sVersionBuffer); 878 879 #if defined(_M_X64) || defined(__amd64__) 880 GetJsonField(buffer, "LatestVersionUrlX64", sLatestVersionUrl, sizeof(sLatestVersionUrl)); 881 #else 882 GetJsonField(buffer, "LatestVersionUrl", sLatestVersionUrl, sizeof(sLatestVersionUrl)); 883 #endif 884 885 if (nLatestDLLVer == 0 || !sLatestVersionUrl[0]) 886 { 887 /* NOTE: repurposing sLatestVersionUrl for the error message */ 888 GetJsonField(buffer, "Error", sLatestVersionUrl, sizeof(sLatestVersionUrl)); 889 if (sLatestVersionUrl[0]) 890 sprintf_s(buffer, sizeof(buffer), "Failed to fetch latest integration version.\n\n%s", sLatestVersionUrl); 891 else 892 sprintf_s(buffer, sizeof(buffer), "The latest integration check did not return a valid response."); 893 894 MessageBoxA(hMainHWND, buffer, "Error", MB_OK | MB_ICONERROR); 895 RA_Shutdown(); 896 return; 897 } 898 899 int nMBReply = 0; 900 unsigned long long nVerInstalled = ParseVersion(sVerInstalled); 901 if (nVerInstalled < nMinimumDLLVer) 902 { 903 RA_Shutdown(); // Unhook the DLL so we can replace it. 904 905 if (nVerInstalled == 0) 906 { 907 sprintf_s(buffer, sizeof(buffer), "Install RetroAchievements toolset?\n\n" 908 "In order to earn achievements you must download the toolset library."); 909 } 910 else 911 { 912 sprintf_s(buffer, sizeof(buffer), "Upgrade RetroAchievements toolset?\n\n" 913 "A required upgrade to the toolset is available. If you don't upgrade, you won't be able to earn achievements.\n\n" 914 "Latest Version: %s\nInstalled Version: %s", sVersionBuffer, sVerInstalled); 915 } 916 917 nMBReply = MessageBoxA(hMainHWND, buffer, "Warning", MB_YESNO | MB_ICONWARNING); 918 } 919 else if (nVerInstalled < nLatestDLLVer) 920 { 921 sprintf_s(buffer, sizeof(buffer), "Upgrade RetroAchievements toolset?\n\n" 922 "An optional upgrade to the toolset is available.\n\n" 923 "Latest Version: %s\nInstalled Version: %s", sVersionBuffer, sVerInstalled); 924 925 nMBReply = MessageBoxA(hMainHWND, buffer, "Warning", MB_YESNO | MB_ICONWARNING); 926 927 if (nMBReply == IDYES) 928 RA_Shutdown(); // Unhook the DLL so we can replace it. 929 } 930 931 if (nMBReply == IDYES) 932 { 933 //FetchIntegrationFromWeb(sLatestVersionUrl, &nStatusCode); 934 nStatusCode = 0; 935 936 if (nStatusCode == 200) 937 nVerInstalled = ParseVersion(_RA_InstallIntegration()); 938 939 if (nVerInstalled < nLatestDLLVer) 940 { 941 sprintf_s(buffer, sizeof(buffer), "Failed to update toolset (status code %u).", nStatusCode); 942 MessageBoxA(hMainHWND, buffer, "Error", MB_OK | MB_ICONERROR); 943 } 944 } 945 946 if (nVerInstalled < nMinimumDLLVer) 947 { 948 RA_Shutdown(); 949 950 sprintf_s(buffer, sizeof(buffer), "%s toolset is required to earn achievements.", nVerInstalled == 0 ? "The" : "A newer"); 951 MessageBoxA(hMainHWND, buffer, "Warning", MB_OK | MB_ICONWARNING); 952 } 953 else if (sClientName == nullptr) 954 { 955 if (!_RA_InitI(hMainHWND, nEmulatorID, sClientVersion)) 956 RA_Shutdown(); 957 } 958 else 959 { 960 if (!_RA_InitClient(hMainHWND, sClientName, sClientVersion)) 961 RA_Shutdown(); 962 } 963 } 964 965 void RA_Init(HWND hMainHWND, int nEmulatorID, const char* sClientVersion) 966 { 967 RA_InitCommon(hMainHWND, nEmulatorID, nullptr, sClientVersion); 968 } 969 970 void RA_InitClient(HWND hMainHWND, const char* sClientName, const char* sClientVersion) 971 { 972 RA_InitCommon(hMainHWND, -1, sClientName, sClientVersion); 973 } 974 975 void RA_SetUserAgentDetail(const char* sDetail) 976 { 977 if (_RA_SetUserAgentDetail != nullptr) 978 _RA_SetUserAgentDetail(sDetail); 979 } 980 981 void RA_InstallSharedFunctions(int(*)(void), void(*fpCauseUnpause)(void), void(*fpCausePause)(void), void(*fpRebuildMenu)(void), void(*fpEstimateTitle)(char*), void(*fpResetEmulation)(void), void(*fpLoadROM)(const char*)) 982 { 983 if (_RA_InstallSharedFunctions != nullptr) 984 _RA_InstallSharedFunctions(nullptr, fpCauseUnpause, fpCausePause, fpRebuildMenu, fpEstimateTitle, fpResetEmulation, fpLoadROM); 985 } 986 987 void RA_Shutdown() 988 { 989 // Call shutdown on toolchain 990 if (_RA_Shutdown != nullptr) 991 { 992 _RA_Shutdown(); 993 } 994 995 // Clear func ptrs 996 _RA_IntegrationVersion = nullptr; 997 _RA_HostName = nullptr; 998 _RA_HostUrl = nullptr; 999 _RA_InitI = nullptr; 1000 _RA_InitOffline = nullptr; 1001 _RA_InitClient = nullptr; 1002 _RA_InitClientOffline = nullptr; 1003 _RA_InstallSharedFunctions = nullptr; 1004 _RA_SetForceRepaint = nullptr; 1005 _RA_CreatePopupMenu = nullptr; 1006 _RA_GetPopupMenuItems = nullptr; 1007 _RA_InvokeDialog = nullptr; 1008 _RA_SetUserAgentDetail = nullptr; 1009 _RA_AttemptLogin = nullptr; 1010 _RA_SetConsoleID = nullptr; 1011 _RA_ClearMemoryBanks = nullptr; 1012 _RA_InstallMemoryBank = nullptr; 1013 _RA_InstallMemoryBankBlockReader = nullptr; 1014 _RA_Shutdown = nullptr; 1015 _RA_IsOverlayFullyVisible = nullptr; 1016 _RA_SetPaused = nullptr; 1017 _RA_NavigateOverlay = nullptr; 1018 _RA_UpdateHWnd = nullptr; 1019 _RA_IdentifyRom = nullptr; 1020 _RA_IdentifyHash = nullptr; 1021 _RA_ActivateGame = nullptr; 1022 _RA_OnLoadNewRom = nullptr; 1023 _RA_ConfirmLoadNewRom = nullptr; 1024 _RA_DoAchievementsFrame = nullptr; 1025 _RA_SuspendRepaint = nullptr; 1026 _RA_ResumeRepaint = nullptr; 1027 _RA_UpdateAppTitle = nullptr; 1028 _RA_UserName = nullptr; 1029 _RA_HardcoreModeIsActive = nullptr; 1030 _RA_WarnDisableHardcore = nullptr; 1031 _RA_OnReset = nullptr; 1032 _RA_OnSaveState = nullptr; 1033 _RA_OnLoadState = nullptr; 1034 _RA_CaptureState = nullptr; 1035 _RA_RestoreState = nullptr; 1036 1037 /* unload the DLL */ 1038 if (g_hRADLL) 1039 { 1040 FreeLibrary(g_hRADLL); 1041 g_hRADLL = nullptr; 1042 } 1043 }