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

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 }