mirror of
				https://github.com/RetroDECK/Duckstation.git
				synced 2025-04-10 19:15:14 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			1044 lines
		
	
	
		
			36 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			1044 lines
		
	
	
		
			36 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| #include "RA_Interface.h"
 | |
| 
 | |
| #include <winhttp.h>
 | |
| #include <cassert>
 | |
| #include <stdexcept>
 | |
| #include <string>
 | |
| 
 | |
| #ifndef CCONV
 | |
| #define CCONV __cdecl
 | |
| #endif
 | |
| 
 | |
| // Initialization
 | |
| static const char*  (CCONV* _RA_IntegrationVersion)() = nullptr;
 | |
| static const char*  (CCONV* _RA_HostName)() = nullptr;
 | |
| static const char*  (CCONV* _RA_HostUrl)() = nullptr;
 | |
| static int          (CCONV* _RA_InitI)(HWND hMainWnd, int nConsoleID, const char* sClientVer) = nullptr;
 | |
| static int          (CCONV* _RA_InitOffline)(HWND hMainWnd, int nConsoleID, const char* sClientVer) = nullptr;
 | |
| static int          (CCONV* _RA_InitClient)(HWND hMainWnd, const char* sClientName, const char* sClientVer) = nullptr;
 | |
| static int          (CCONV* _RA_InitClientOffline)(HWND hMainWnd, const char* sClientName, const char* sClientVer) = nullptr;
 | |
| static void         (CCONV* _RA_InstallSharedFunctions)(int(*)(), void(*)(), void(*)(), void(*)(), void(*)(char*), void(*)(), void(*)(const char*)) = nullptr;
 | |
| static void         (CCONV* _RA_SetForceRepaint)(int bEnable) = nullptr;
 | |
| static HMENU        (CCONV* _RA_CreatePopupMenu)() = nullptr;
 | |
| static int          (CCONV* _RA_GetPopupMenuItems)(RA_MenuItem*) = nullptr;
 | |
| static void         (CCONV* _RA_InvokeDialog)(LPARAM nID) = nullptr;
 | |
| static void         (CCONV* _RA_SetUserAgentDetail)(const char* sDetail);
 | |
| static void         (CCONV* _RA_AttemptLogin)(int bBlocking) = nullptr;
 | |
| static int          (CCONV* _RA_SetConsoleID)(unsigned int nConsoleID) = nullptr;
 | |
| static void         (CCONV* _RA_ClearMemoryBanks)() = nullptr;
 | |
| static void         (CCONV* _RA_InstallMemoryBank)(int nBankID, RA_ReadMemoryFunc* pReader, RA_WriteMemoryFunc* pWriter, int nBankSize) = nullptr;
 | |
| static void         (CCONV* _RA_InstallMemoryBankBlockReader)(int nBankID, RA_ReadMemoryBlockFunc* pReader) = nullptr;
 | |
| static int          (CCONV* _RA_Shutdown)() = nullptr;
 | |
| // Overlay
 | |
| static int          (CCONV* _RA_IsOverlayFullyVisible)() = nullptr;
 | |
| static void         (CCONV* _RA_SetPaused)(int bIsPaused) = nullptr;
 | |
| static void         (CCONV* _RA_NavigateOverlay)(ControllerInput* pInput) = nullptr;
 | |
| static void         (CCONV* _RA_UpdateHWnd)(HWND hMainHWND);
 | |
| // Game Management
 | |
| static unsigned int (CCONV* _RA_IdentifyRom)(const BYTE* pROM, unsigned int nROMSize) = nullptr;
 | |
| static unsigned int (CCONV* _RA_IdentifyHash)(const char* sHash) = nullptr;
 | |
| static void         (CCONV* _RA_ActivateGame)(unsigned int nGameId) = nullptr;
 | |
| static int          (CCONV* _RA_OnLoadNewRom)(const BYTE* pROM, unsigned int nROMSize) = nullptr;
 | |
| static int          (CCONV* _RA_ConfirmLoadNewRom)(int bQuitting) = nullptr;
 | |
| // Runtime Functionality
 | |
| static void         (CCONV* _RA_DoAchievementsFrame)() = nullptr;
 | |
| static void         (CCONV* _RA_SuspendRepaint)() = nullptr;
 | |
| static void         (CCONV* _RA_ResumeRepaint)() = nullptr;
 | |
| static void         (CCONV* _RA_UpdateAppTitle)(const char* pMessage) = nullptr;
 | |
| static const char*  (CCONV* _RA_UserName)() = nullptr;
 | |
| static int          (CCONV* _RA_HardcoreModeIsActive)(void) = nullptr;
 | |
| static int          (CCONV* _RA_WarnDisableHardcore)(const char* sActivity) = nullptr;
 | |
| static void         (CCONV* _RA_OnReset)() = nullptr;
 | |
| static void         (CCONV* _RA_OnSaveState)(const char* sFilename) = nullptr;
 | |
| static void         (CCONV* _RA_OnLoadState)(const char* sFilename) = nullptr;
 | |
| static int          (CCONV* _RA_CaptureState)(char* pBuffer, int nBufferSize) = nullptr;
 | |
| static void         (CCONV* _RA_RestoreState)(const char* pBuffer) = nullptr;
 | |
| 
 | |
| static HINSTANCE g_hRADLL = nullptr;
 | |
| 
 | |
| void RA_AttemptLogin(int bBlocking)
 | |
| {
 | |
|     if (_RA_AttemptLogin != nullptr)
 | |
|         _RA_AttemptLogin(bBlocking);
 | |
| }
 | |
| 
 | |
| const char* RA_UserName(void)
 | |
| {
 | |
|     if (_RA_UserName != nullptr)
 | |
|         return _RA_UserName();
 | |
| 
 | |
|     return "";
 | |
| }
 | |
| 
 | |
| void RA_NavigateOverlay(ControllerInput* pInput)
 | |
| {
 | |
|     if (_RA_NavigateOverlay != nullptr)
 | |
|         _RA_NavigateOverlay(pInput);
 | |
| }
 | |
| 
 | |
| void RA_UpdateRenderOverlay(HDC hDC, ControllerInput* pInput, float fDeltaTime, RECT* prcSize, bool Full_Screen, bool Paused)
 | |
| {
 | |
|     if (_RA_NavigateOverlay != nullptr)
 | |
|         _RA_NavigateOverlay(pInput);
 | |
| }
 | |
| 
 | |
| int RA_IsOverlayFullyVisible(void)
 | |
| {
 | |
|     if (_RA_IsOverlayFullyVisible != nullptr)
 | |
|         return _RA_IsOverlayFullyVisible();
 | |
| 
 | |
|     return 0;
 | |
| }
 | |
| 
 | |
| void RA_UpdateHWnd(HWND hMainWnd)
 | |
| {
 | |
|     if (_RA_UpdateHWnd != nullptr)
 | |
|         _RA_UpdateHWnd(hMainWnd);
 | |
| }
 | |
| 
 | |
| unsigned int RA_IdentifyRom(BYTE* pROMData, unsigned int nROMSize)
 | |
| {
 | |
|     if (_RA_IdentifyRom != nullptr)
 | |
|         return _RA_IdentifyRom(pROMData, nROMSize);
 | |
| 
 | |
|     return 0;
 | |
| }
 | |
| 
 | |
| unsigned int RA_IdentifyHash(const char* sHash)
 | |
| {
 | |
|     if (_RA_IdentifyHash!= nullptr)
 | |
|         return _RA_IdentifyHash(sHash);
 | |
| 
 | |
|     return 0;
 | |
| }
 | |
| 
 | |
| void RA_ActivateGame(unsigned int nGameId)
 | |
| {
 | |
|     if (_RA_ActivateGame != nullptr)
 | |
|         _RA_ActivateGame(nGameId);
 | |
| }
 | |
| 
 | |
| void RA_OnLoadNewRom(BYTE* pROMData, unsigned int nROMSize)
 | |
| {
 | |
|     if (_RA_OnLoadNewRom != nullptr)
 | |
|         _RA_OnLoadNewRom(pROMData, nROMSize);
 | |
| }
 | |
| 
 | |
| void RA_ClearMemoryBanks(void)
 | |
| {
 | |
|     if (_RA_ClearMemoryBanks != nullptr)
 | |
|         _RA_ClearMemoryBanks();
 | |
| }
 | |
| 
 | |
| void RA_InstallMemoryBank(int nBankID, RA_ReadMemoryFunc pReader, RA_WriteMemoryFunc pWriter, int nBankSize)
 | |
| {
 | |
|     if (_RA_InstallMemoryBank != nullptr)
 | |
|         _RA_InstallMemoryBank(nBankID, pReader, pWriter, nBankSize);
 | |
| }
 | |
| 
 | |
| void RA_InstallMemoryBankBlockReader(int nBankID, RA_ReadMemoryBlockFunc pReader)
 | |
| {
 | |
|     if (_RA_InstallMemoryBankBlockReader != nullptr)
 | |
|         _RA_InstallMemoryBankBlockReader(nBankID, pReader);
 | |
| }
 | |
| 
 | |
| HMENU RA_CreatePopupMenu(void)
 | |
| {
 | |
|     return (_RA_CreatePopupMenu != nullptr) ? _RA_CreatePopupMenu() : nullptr;
 | |
| }
 | |
| 
 | |
| int RA_GetPopupMenuItems(RA_MenuItem *pItems)
 | |
| {
 | |
|     return (_RA_GetPopupMenuItems != nullptr) ? _RA_GetPopupMenuItems(pItems) : 0;
 | |
| }
 | |
| 
 | |
| void RA_UpdateAppTitle(const char* sCustomMsg)
 | |
| {
 | |
|     if (_RA_UpdateAppTitle != nullptr)
 | |
|         _RA_UpdateAppTitle(sCustomMsg);
 | |
| }
 | |
| 
 | |
| void RA_HandleHTTPResults(void)
 | |
| {
 | |
| }
 | |
| 
 | |
| int RA_ConfirmLoadNewRom(int bIsQuitting)
 | |
| {
 | |
|     return _RA_ConfirmLoadNewRom ? _RA_ConfirmLoadNewRom(bIsQuitting) : 1;
 | |
| }
 | |
| 
 | |
| void RA_InvokeDialog(LPARAM nID)
 | |
| {
 | |
|     if (_RA_InvokeDialog != nullptr)
 | |
|         _RA_InvokeDialog(nID);
 | |
| }
 | |
| 
 | |
| void RA_SetPaused(bool bIsPaused)
 | |
| {
 | |
|     if (_RA_SetPaused != nullptr)
 | |
|         _RA_SetPaused(bIsPaused);
 | |
| }
 | |
| 
 | |
| void RA_OnLoadState(const char* sFilename)
 | |
| {
 | |
|     if (_RA_OnLoadState != nullptr)
 | |
|         _RA_OnLoadState(sFilename);
 | |
| }
 | |
| 
 | |
| void RA_OnSaveState(const char* sFilename)
 | |
| {
 | |
|     if (_RA_OnSaveState != nullptr)
 | |
|         _RA_OnSaveState(sFilename);
 | |
| }
 | |
| 
 | |
| int RA_CaptureState(char* pBuffer, int nBufferSize)
 | |
| {
 | |
|     if (_RA_CaptureState != nullptr)
 | |
|         return _RA_CaptureState(pBuffer, nBufferSize);
 | |
| 
 | |
|     return 0;
 | |
| }
 | |
| 
 | |
| void RA_RestoreState(const char* pBuffer)
 | |
| {
 | |
|     if (_RA_RestoreState != nullptr)
 | |
|         _RA_RestoreState(pBuffer);
 | |
| }
 | |
| 
 | |
| void RA_OnReset(void)
 | |
| {
 | |
|     if (_RA_OnReset != nullptr)
 | |
|         _RA_OnReset();
 | |
| }
 | |
| 
 | |
| void RA_DoAchievementsFrame(void)
 | |
| {
 | |
|     if (_RA_DoAchievementsFrame != nullptr)
 | |
|         _RA_DoAchievementsFrame();
 | |
| }
 | |
| 
 | |
| void RA_SetForceRepaint(int bEnable)
 | |
| {
 | |
|     if (_RA_SetForceRepaint != nullptr)
 | |
|         _RA_SetForceRepaint(bEnable);
 | |
| }
 | |
| 
 | |
| void RA_SuspendRepaint(void)
 | |
| {
 | |
|   if (_RA_SuspendRepaint != nullptr)
 | |
|     _RA_SuspendRepaint();
 | |
| }
 | |
| 
 | |
| void RA_ResumeRepaint(void)
 | |
| {
 | |
|   if (_RA_ResumeRepaint != nullptr)
 | |
|     _RA_ResumeRepaint();
 | |
| }
 | |
| 
 | |
| void RA_SetConsoleID(unsigned int nConsoleID)
 | |
| {
 | |
|     if (_RA_SetConsoleID != nullptr)
 | |
|         _RA_SetConsoleID(nConsoleID);
 | |
| }
 | |
| 
 | |
| int RA_HardcoreModeIsActive(void)
 | |
| {
 | |
|     return (_RA_HardcoreModeIsActive != nullptr) ? _RA_HardcoreModeIsActive() : 0;
 | |
| }
 | |
| 
 | |
| int RA_WarnDisableHardcore(const char* sActivity)
 | |
| {
 | |
|     // If Hardcore mode not active, allow the activity.
 | |
|     if (!RA_HardcoreModeIsActive())
 | |
|         return 1;
 | |
| 
 | |
|     // DLL function will display a yes/no dialog. If the user chooses yes, the DLL will disable hardcore mode, and the activity can proceed.
 | |
|     if (_RA_WarnDisableHardcore != nullptr)
 | |
|         return _RA_WarnDisableHardcore(sActivity);
 | |
| 
 | |
|     // We cannot disable hardcore mode, so just warn the user and prevent the activity.
 | |
|     std::string sMessage;
 | |
|     sMessage = "You cannot " + std::string(sActivity) + " while Hardcore mode is active.";
 | |
|     MessageBoxA(nullptr, sMessage.c_str(), "Warning", MB_OK | MB_ICONWARNING);
 | |
|     return 0;
 | |
| }
 | |
| 
 | |
| void RA_DisableHardcore()
 | |
| {
 | |
|     // passing nullptr to _RA_WarnDisableHardcore will just disable hardcore mode without prompting.
 | |
|     if (_RA_WarnDisableHardcore != nullptr)
 | |
|         _RA_WarnDisableHardcore(nullptr);
 | |
| }
 | |
| 
 | |
| static size_t DownloadToFile(char* pData, size_t nDataSize, void* pUserData)
 | |
| {
 | |
|     FILE* file = (FILE*)pUserData;
 | |
|     return fwrite(pData, 1, nDataSize, file);
 | |
| }
 | |
| 
 | |
| typedef struct DownloadBuffer
 | |
| {
 | |
|     char* pBuffer;
 | |
|     size_t nBufferSize;
 | |
|     size_t nOffset;
 | |
| } DownloadBuffer;
 | |
| 
 | |
| static size_t DownloadToBuffer(char* pData, size_t nDataSize, void* pUserData)
 | |
| {
 | |
|     DownloadBuffer* pBuffer = (DownloadBuffer*)pUserData;
 | |
|     const size_t nRemaining = pBuffer->nBufferSize - pBuffer->nOffset;
 | |
|     if (nDataSize > nRemaining)
 | |
|         nDataSize = nRemaining;
 | |
| 
 | |
|     if (nDataSize > 0)
 | |
|     {
 | |
|         memcpy(pBuffer->pBuffer + pBuffer->nOffset, pData, nDataSize);
 | |
|         pBuffer->nOffset += nDataSize;
 | |
|     }
 | |
| 
 | |
|     return nDataSize;
 | |
| }
 | |
| 
 | |
| typedef size_t (DownloadFunc)(char* pData, size_t nDataSize, void* pUserData);
 | |
| 
 | |
| static BOOL DoBlockingHttpCall(const char* sHostUrl, const char* sRequestedPage, const char* sPostData,
 | |
|   DownloadFunc fnDownload, void* pDownloadUserData, DWORD* pBytesRead, DWORD* pStatusCode)
 | |
| {
 | |
|     BOOL bResults = FALSE, bSuccess = FALSE;
 | |
|     HINTERNET hSession = nullptr, hConnect = nullptr, hRequest = nullptr;
 | |
|     size_t nHostnameLen;
 | |
| 
 | |
|     WCHAR wBuffer[1024];
 | |
|     size_t nTemp;
 | |
|     DWORD nBytesAvailable = 0;
 | |
|     DWORD nBytesToRead = 0;
 | |
|     DWORD nBytesFetched = 0;
 | |
|     (*pBytesRead) = 0;
 | |
| 
 | |
|     INTERNET_PORT nPort = INTERNET_DEFAULT_HTTP_PORT;
 | |
|     const char* sHostName = sHostUrl;
 | |
|     if (_strnicmp(sHostName, "http://", 7) == 0)
 | |
|     {
 | |
|         sHostName += 7;
 | |
|     }
 | |
|     else if (_strnicmp(sHostName, "https://", 8) == 0)
 | |
|     {
 | |
|         sHostName += 8;
 | |
|         nPort = INTERNET_DEFAULT_HTTPS_PORT;
 | |
|     }
 | |
| 
 | |
|     const char* sPort = strchr(sHostName, ':');
 | |
|     if (sPort)
 | |
|     {
 | |
|         nHostnameLen = sPort - sHostName;
 | |
|         nPort = atoi(sPort + 1);
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|         nHostnameLen = strlen(sHostName);
 | |
|     }
 | |
| 
 | |
|     // Use WinHttpOpen to obtain a session handle.
 | |
|     hSession = WinHttpOpen(L"RetroAchievements Client Bootstrap",
 | |
|         WINHTTP_ACCESS_TYPE_DEFAULT_PROXY,
 | |
|         WINHTTP_NO_PROXY_NAME,
 | |
|         WINHTTP_NO_PROXY_BYPASS, 0);
 | |
| 
 | |
|     // Specify an HTTP server.
 | |
|     if (hSession == nullptr)
 | |
|     {
 | |
|         *pStatusCode = GetLastError();
 | |
|     }
 | |
|     else
 | |
|     {
 | |
| #if defined(_MSC_VER) && _MSC_VER >= 1400
 | |
|         mbstowcs_s(&nTemp, wBuffer, sizeof(wBuffer) / sizeof(wBuffer[0]), sHostName, nHostnameLen);
 | |
| #else
 | |
|         nTemp = mbstowcs(wBuffer, sHostName, nHostnameLen);
 | |
| #endif
 | |
| 
 | |
|         if (nTemp > 0)
 | |
|         {
 | |
|             hConnect = WinHttpConnect(hSession, wBuffer, nPort, 0);
 | |
|         }
 | |
| 
 | |
|         // Create an HTTP Request handle.
 | |
|         if (hConnect == nullptr)
 | |
|         {
 | |
|             *pStatusCode = GetLastError();
 | |
|         }
 | |
|         else
 | |
|         {
 | |
| #if defined(_MSC_VER) && _MSC_VER >= 1400
 | |
|             mbstowcs_s(&nTemp, wBuffer, sizeof(wBuffer) / sizeof(wBuffer[0]), sRequestedPage, strlen(sRequestedPage) + 1);
 | |
| #else
 | |
|             nTemp = mbstowcs(wBuffer, sRequestedPage, strlen(sRequestedPage) + 1);
 | |
| #endif
 | |
| 
 | |
|             hRequest = WinHttpOpenRequest(hConnect,
 | |
|                 sPostData ? L"POST" : L"GET",
 | |
|                 wBuffer,
 | |
|                 nullptr,
 | |
|                 WINHTTP_NO_REFERER,
 | |
|                 WINHTTP_DEFAULT_ACCEPT_TYPES,
 | |
|                 (nPort == INTERNET_DEFAULT_HTTPS_PORT) ? WINHTTP_FLAG_SECURE : 0);
 | |
| 
 | |
|             // Send a Request.
 | |
|             if (hRequest == nullptr)
 | |
|             {
 | |
|                 *pStatusCode = GetLastError();
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 if (sPostData)
 | |
|                 {
 | |
|                     const size_t nPostDataLength = strlen(sPostData);
 | |
|                     bResults = WinHttpSendRequest(hRequest,
 | |
|                         L"Content-Type: application/x-www-form-urlencoded",
 | |
|                         0, (LPVOID)sPostData, (DWORD)nPostDataLength, (DWORD)nPostDataLength, 0);
 | |
|                 }
 | |
|                 else
 | |
|                 {
 | |
|                     bResults = WinHttpSendRequest(hRequest,
 | |
|                         L"Content-Type: application/x-www-form-urlencoded",
 | |
|                         0, WINHTTP_NO_REQUEST_DATA, 0, 0, 0);
 | |
|                 }
 | |
| 
 | |
|                 if (!bResults || !WinHttpReceiveResponse(hRequest, nullptr))
 | |
|                 {
 | |
|                     *pStatusCode = GetLastError();
 | |
|                 }
 | |
|                 else
 | |
|                 {
 | |
|                     char buffer[16384];
 | |
|                     DWORD dwSize = sizeof(DWORD);
 | |
|                     WinHttpQueryHeaders(hRequest, WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER, WINHTTP_HEADER_NAME_BY_INDEX, pStatusCode, &dwSize, WINHTTP_NO_HEADER_INDEX);
 | |
| 
 | |
|                     bSuccess = TRUE;
 | |
|                     do
 | |
|                     {
 | |
|                         nBytesAvailable = 0;
 | |
|                         WinHttpQueryDataAvailable(hRequest, &nBytesAvailable);
 | |
|                         if (nBytesAvailable == 0)
 | |
|                             break;
 | |
| 
 | |
|                         do
 | |
|                         {
 | |
|                             if (nBytesAvailable > sizeof(buffer))
 | |
|                                 nBytesToRead = sizeof(buffer);
 | |
|                             else
 | |
|                                 nBytesToRead = nBytesAvailable;
 | |
| 
 | |
|                             nBytesFetched = 0;
 | |
|                             if (WinHttpReadData(hRequest, buffer, nBytesToRead, &nBytesFetched))
 | |
|                             {
 | |
|                                 size_t nBytesWritten = fnDownload(buffer, nBytesFetched, pDownloadUserData);
 | |
|                                 if (nBytesWritten < nBytesFetched)
 | |
|                                 {
 | |
|                                     if (*pStatusCode == 200)
 | |
|                                         *pStatusCode = 998;
 | |
| 
 | |
|                                     bSuccess = FALSE;
 | |
|                                     break;
 | |
|                                 }
 | |
| 
 | |
|                                 (*pBytesRead) += (DWORD)nBytesWritten;
 | |
|                                 nBytesAvailable -= nBytesFetched;
 | |
|                             }
 | |
|                             else
 | |
|                             {
 | |
|                                 if (*pStatusCode == 200)
 | |
|                                     *pStatusCode = GetLastError();
 | |
| 
 | |
|                                 bSuccess = FALSE;
 | |
|                                 break;
 | |
|                             }
 | |
|                         } while (nBytesAvailable > 0);
 | |
|                     } while (TRUE);
 | |
|                 }
 | |
| 
 | |
|                 WinHttpCloseHandle(hRequest);
 | |
|             }
 | |
| 
 | |
|             WinHttpCloseHandle(hConnect);
 | |
|         }
 | |
| 
 | |
|         WinHttpCloseHandle(hSession);
 | |
|     }
 | |
| 
 | |
|     return bSuccess;
 | |
| }
 | |
| 
 | |
| static BOOL IsNetworkError(DWORD nStatusCode)
 | |
| {
 | |
|     switch (nStatusCode)
 | |
|     {
 | |
|         case 12002: // timeout
 | |
|         case 12007: // dns lookup failed
 | |
|         case 12017: // handle closed before request completed
 | |
|         case 12019: // handle not initialized
 | |
|         case 12028: // data not available at this time
 | |
|         case 12029: // handshake failed
 | |
|         case 12030: // connection aborted
 | |
|         case 12031: // connection reset
 | |
|         case 12032: // explicit request to retry
 | |
|         case 12152: // response could not be parsed, corrupt?
 | |
|         case 12163: // lost connection during request
 | |
|             return TRUE;
 | |
| 
 | |
|         default:
 | |
|             return FALSE;
 | |
|     }
 | |
| }
 | |
| 
 | |
| static BOOL DoBlockingHttpCallWithRetry(const char* sHostUrl, const char* sRequestedPage, const char* sPostData,
 | |
|   char pBufferOut[], unsigned int nBufferOutSize, DWORD* pBytesRead, DWORD* pStatusCode)
 | |
| {
 | |
|     int nRetries = 4;
 | |
|     do
 | |
|     {
 | |
|         DownloadBuffer downloadBuffer;
 | |
|         memset(&downloadBuffer, 0, sizeof(downloadBuffer));
 | |
|         downloadBuffer.pBuffer = pBufferOut;
 | |
|         downloadBuffer.nBufferSize = nBufferOutSize;
 | |
| 
 | |
|         if (DoBlockingHttpCall(sHostUrl, sRequestedPage, sPostData, DownloadToBuffer, &downloadBuffer, pBytesRead, pStatusCode) != FALSE)
 | |
|             return TRUE;
 | |
| 
 | |
|         if (!IsNetworkError(*pStatusCode))
 | |
|             return FALSE;
 | |
| 
 | |
|         --nRetries;
 | |
|     } while (nRetries);
 | |
| 
 | |
|     return FALSE;
 | |
| }
 | |
| 
 | |
| static BOOL DoBlockingHttpCallWithRetry(const char* sHostUrl, const char* sRequestedPage, const char* sPostData,
 | |
|   FILE* pFile, DWORD* pBytesRead, DWORD* pStatusCode)
 | |
| {
 | |
|   int nRetries = 4;
 | |
|   do
 | |
|   {
 | |
|       fseek(pFile, 0, SEEK_SET);
 | |
|       if (DoBlockingHttpCall(sHostUrl, sRequestedPage, sPostData, DownloadToFile, pFile, pBytesRead, pStatusCode) != FALSE)
 | |
|         return TRUE;
 | |
| 
 | |
|       if (!IsNetworkError(*pStatusCode))
 | |
|         return FALSE;
 | |
| 
 | |
|       --nRetries;
 | |
|   } while (nRetries);
 | |
| 
 | |
|   return FALSE;
 | |
| }
 | |
| 
 | |
| #ifndef RA_UTEST
 | |
| static std::wstring GetIntegrationPath()
 | |
| {
 | |
|     wchar_t sBuffer[2048];
 | |
|     DWORD iIndex = GetModuleFileNameW(0, sBuffer, 2048);
 | |
|     while (iIndex > 0 && sBuffer[iIndex - 1] != '\\' && sBuffer[iIndex - 1] != '/')
 | |
|         --iIndex;
 | |
| 
 | |
| #if defined(_MSC_VER) && _MSC_VER >= 1400
 | |
|     wcscpy_s(&sBuffer[iIndex], sizeof(sBuffer)/sizeof(sBuffer[0]) - iIndex, L"RA_Integration.dll");
 | |
| #else
 | |
|     wcscpy(&sBuffer[iIndex], L"RA_Integration.dll");
 | |
| #endif
 | |
| 
 | |
|     return std::wstring(sBuffer);
 | |
| }
 | |
| #endif
 | |
| 
 | |
| #if 0
 | |
| 
 | |
| static void FetchIntegrationFromWeb(char* sLatestVersionUrl, DWORD* pStatusCode)
 | |
| {
 | |
|     DWORD nBytesRead = 0;
 | |
|     const wchar_t* sDownloadFilename = L"RA_Integration.download";
 | |
|     const wchar_t* sFilename = L"RA_Integration.dll";
 | |
|     const wchar_t* sOldFilename = L"RA_Integration.old";
 | |
| 
 | |
| #if defined(_MSC_VER) && _MSC_VER >= 1400
 | |
|     FILE* pf;
 | |
|     errno_t nErr = _wfopen_s(&pf, sDownloadFilename, L"wb");
 | |
| #else
 | |
|     FILE* pf = _wfopen(sDownloadFilename, L"wb");
 | |
| #endif
 | |
| 
 | |
|     if (!pf)
 | |
|     {
 | |
| #if defined(_MSC_VER) && _MSC_VER >= 1400
 | |
|         wchar_t sErrBuffer[2048];
 | |
|         _wcserror_s(sErrBuffer, sizeof(sErrBuffer) / sizeof(sErrBuffer[0]), nErr);
 | |
| 
 | |
|         std::wstring sErrMsg = std::wstring(L"Unable to write ") + sOldFilename + L"\n" + sErrBuffer;
 | |
| #else
 | |
|         std::wstring sErrMsg = std::wstring(L"Unable to write ") + sOldFilename + L"\n" + _wcserror(errno);
 | |
| #endif
 | |
| 
 | |
|         MessageBoxW(nullptr, sErrMsg.c_str(), L"Error", MB_OK | MB_ICONERROR);
 | |
|         return;
 | |
|     }
 | |
| 
 | |
|     char* pSplit = sLatestVersionUrl + 8; /* skip over protocol */
 | |
|     while (*pSplit != '/')
 | |
|     {
 | |
|         if (!*pSplit)
 | |
|         {
 | |
|             *pStatusCode = 997;
 | |
|             return;
 | |
|         }
 | |
|         ++pSplit;
 | |
|     }
 | |
|     *pSplit++ = '\0';
 | |
| 
 | |
|     if (DoBlockingHttpCallWithRetry(sLatestVersionUrl, pSplit, nullptr, pf, &nBytesRead, pStatusCode))
 | |
|     {
 | |
|         fclose(pf);
 | |
| 
 | |
|         /* wait up to one second for the DLL to actually be released - sometimes it's not immediate */
 | |
|         for (int i = 0; i < 10; i++)
 | |
|         {
 | |
|             if (GetModuleHandleW(sFilename) == nullptr)
 | |
|                 break;
 | |
| 
 | |
|             Sleep(100);
 | |
|         }
 | |
| 
 | |
|         // delete the last old dll if it's still present
 | |
|         DeleteFileW(sOldFilename);
 | |
| 
 | |
|         // if there's a dll present, rename it
 | |
|         if (GetFileAttributesW(sFilename) != INVALID_FILE_ATTRIBUTES &&
 | |
|             !MoveFileW(sFilename, sOldFilename))
 | |
|         {
 | |
|             MessageBoxW(nullptr, L"Could not rename old dll", L"Error", MB_OK | MB_ICONERROR);
 | |
|         }
 | |
|         // rename the download to be the dll
 | |
|         else if (!MoveFileW(sDownloadFilename, sFilename))
 | |
|         {
 | |
|             MessageBoxW(nullptr, L"Could not rename new dll", L"Error", MB_OK | MB_ICONERROR);
 | |
|         }
 | |
| 
 | |
|         // delete the old dll
 | |
|         DeleteFileW(sOldFilename);
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|         fclose(pf);
 | |
|     }
 | |
| }
 | |
| 
 | |
| #endif
 | |
| 
 | |
| //Returns the last Win32 error, in string format. Returns an empty string if there is no error.
 | |
| static std::string GetLastErrorAsString()
 | |
| {
 | |
|     //Get the error message, if any.
 | |
|     DWORD errorMessageID = ::GetLastError();
 | |
|     if (errorMessageID == 0)
 | |
|         return "No error message has been recorded";
 | |
| 
 | |
|     LPSTR messageBuffer = nullptr;
 | |
|     size_t size = FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
 | |
|         nullptr, errorMessageID, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&messageBuffer, 0, nullptr);
 | |
| 
 | |
|     std::string message(messageBuffer, size);
 | |
| 
 | |
|     //Free the buffer.
 | |
|     LocalFree(messageBuffer);
 | |
| 
 | |
|     return message;
 | |
| }
 | |
| 
 | |
| static const char* CCONV _RA_InstallIntegration()
 | |
| {
 | |
|     SetErrorMode(0);
 | |
| 
 | |
|     std::wstring sIntegrationPath = GetIntegrationPath();
 | |
| 
 | |
|     DWORD dwAttrib = GetFileAttributesW(sIntegrationPath.c_str());
 | |
|     if (dwAttrib == INVALID_FILE_ATTRIBUTES)
 | |
|         return "0.0";
 | |
| 
 | |
|     g_hRADLL = LoadLibraryW(sIntegrationPath.c_str());
 | |
|     if (g_hRADLL == nullptr)
 | |
|     {
 | |
|         char buffer[1024];
 | |
|         sprintf_s(buffer, 1024, "Could not load RA_Integration.dll: %d\n%s\n", ::GetLastError(), GetLastErrorAsString().c_str());
 | |
|         MessageBoxA(nullptr, buffer, "Warning", MB_OK | MB_ICONWARNING);
 | |
| 
 | |
|         return "0.0";
 | |
|     }
 | |
| 
 | |
|     //	Install function pointers one by one
 | |
| 
 | |
|     _RA_IntegrationVersion = (const char* (CCONV*)())                                GetProcAddress(g_hRADLL, "_RA_IntegrationVersion");
 | |
|     _RA_HostName = (const char* (CCONV*)())                                          GetProcAddress(g_hRADLL, "_RA_HostName");
 | |
|     _RA_HostUrl = (const char* (CCONV*)())                                           GetProcAddress(g_hRADLL, "_RA_HostUrl");
 | |
|     _RA_InitI = (int(CCONV*)(HWND, int, const char*))                                GetProcAddress(g_hRADLL, "_RA_InitI");
 | |
|     _RA_InitOffline = (int(CCONV*)(HWND, int, const char*))                          GetProcAddress(g_hRADLL, "_RA_InitOffline");
 | |
|     _RA_InitClient = (int(CCONV*)(HWND, const char*, const char*))                   GetProcAddress(g_hRADLL, "_RA_InitClient");
 | |
|     _RA_InitClientOffline = (int(CCONV*)(HWND, const char*, const char*))            GetProcAddress(g_hRADLL, "_RA_InitClientOffline");
 | |
|     _RA_InstallSharedFunctions = (void(CCONV*)(int(*)(), void(*)(), void(*)(), void(*)(), void(*)(char*), void(*)(), void(*)(const char*))) GetProcAddress(g_hRADLL, "_RA_InstallSharedFunctionsExt");
 | |
|     _RA_SetForceRepaint = (void(CCONV*)(int))                                        GetProcAddress(g_hRADLL, "_RA_SetForceRepaint");
 | |
|     _RA_CreatePopupMenu = (HMENU(CCONV*)(void))                                      GetProcAddress(g_hRADLL, "_RA_CreatePopupMenu");
 | |
|     _RA_GetPopupMenuItems = (int(CCONV*)(RA_MenuItem*))                              GetProcAddress(g_hRADLL, "_RA_GetPopupMenuItems");
 | |
|     _RA_InvokeDialog = (void(CCONV*)(LPARAM))                                        GetProcAddress(g_hRADLL, "_RA_InvokeDialog");
 | |
|     _RA_SetUserAgentDetail = (void(CCONV*)(const char*))                             GetProcAddress(g_hRADLL, "_RA_SetUserAgentDetail");
 | |
|     _RA_AttemptLogin = (void(CCONV*)(int))                                           GetProcAddress(g_hRADLL, "_RA_AttemptLogin");
 | |
|     _RA_SetConsoleID = (int(CCONV*)(unsigned int))                                   GetProcAddress(g_hRADLL, "_RA_SetConsoleID");
 | |
|     _RA_ClearMemoryBanks = (void(CCONV*)())                                          GetProcAddress(g_hRADLL, "_RA_ClearMemoryBanks");
 | |
|     _RA_InstallMemoryBank = (void(CCONV*)(int, RA_ReadMemoryFunc*, RA_WriteMemoryFunc*, int)) GetProcAddress(g_hRADLL, "_RA_InstallMemoryBank");
 | |
|     _RA_InstallMemoryBankBlockReader = (void(CCONV*)(int, RA_ReadMemoryBlockFunc*))  GetProcAddress(g_hRADLL, "_RA_InstallMemoryBankBlockReader");
 | |
|     _RA_Shutdown = (int(CCONV*)())                                                   GetProcAddress(g_hRADLL, "_RA_Shutdown");
 | |
|     _RA_IsOverlayFullyVisible = (int(CCONV*)())                                      GetProcAddress(g_hRADLL, "_RA_IsOverlayFullyVisible");
 | |
|     _RA_SetPaused = (void(CCONV*)(int))                                              GetProcAddress(g_hRADLL, "_RA_SetPaused");
 | |
|     _RA_NavigateOverlay = (void(CCONV*)(ControllerInput*))                           GetProcAddress(g_hRADLL, "_RA_NavigateOverlay");
 | |
|     _RA_UpdateHWnd = (void(CCONV*)(HWND))                                            GetProcAddress(g_hRADLL, "_RA_UpdateHWnd");
 | |
|     _RA_IdentifyRom = (unsigned int(CCONV*)(const BYTE*, unsigned int))              GetProcAddress(g_hRADLL, "_RA_IdentifyRom");
 | |
|     _RA_IdentifyHash = (unsigned int(CCONV*)(const char*))                           GetProcAddress(g_hRADLL, "_RA_IdentifyHash");
 | |
|     _RA_ActivateGame = (void(CCONV*)(unsigned int))                                  GetProcAddress(g_hRADLL, "_RA_ActivateGame");
 | |
|     _RA_OnLoadNewRom = (int(CCONV*)(const BYTE*, unsigned int))                      GetProcAddress(g_hRADLL, "_RA_OnLoadNewRom");
 | |
|     _RA_ConfirmLoadNewRom = (int(CCONV*)(int))                                       GetProcAddress(g_hRADLL, "_RA_ConfirmLoadNewRom");
 | |
|     _RA_DoAchievementsFrame = (void(CCONV*)())                                       GetProcAddress(g_hRADLL, "_RA_DoAchievementsFrame");
 | |
|     _RA_SuspendRepaint = (void(CCONV*)())                                            GetProcAddress(g_hRADLL, "_RA_SuspendRepaint");
 | |
|     _RA_ResumeRepaint = (void(CCONV*)())                                             GetProcAddress(g_hRADLL, "_RA_ResumeRepaint");
 | |
|     _RA_UpdateAppTitle = (void(CCONV*)(const char*))                                 GetProcAddress(g_hRADLL, "_RA_UpdateAppTitle");
 | |
|     _RA_UserName = (const char* (CCONV*)())                                          GetProcAddress(g_hRADLL, "_RA_UserName");
 | |
|     _RA_HardcoreModeIsActive = (int(CCONV*)())                                       GetProcAddress(g_hRADLL, "_RA_HardcoreModeIsActive");
 | |
|     _RA_WarnDisableHardcore = (int(CCONV*)(const char*))                             GetProcAddress(g_hRADLL, "_RA_WarnDisableHardcore");
 | |
|     _RA_OnReset = (void(CCONV*)())                                                   GetProcAddress(g_hRADLL, "_RA_OnReset");
 | |
|     _RA_OnSaveState = (void(CCONV*)(const char*))                                    GetProcAddress(g_hRADLL, "_RA_OnSaveState");
 | |
|     _RA_OnLoadState = (void(CCONV*)(const char*))                                    GetProcAddress(g_hRADLL, "_RA_OnLoadState");
 | |
|     _RA_CaptureState = (int(CCONV*)(char*, int))                                     GetProcAddress(g_hRADLL, "_RA_CaptureState");
 | |
|     _RA_RestoreState = (void(CCONV*)(const char*))                                   GetProcAddress(g_hRADLL, "_RA_RestoreState");
 | |
| 
 | |
|     return _RA_IntegrationVersion ? _RA_IntegrationVersion() : "0.0";
 | |
| }
 | |
| 
 | |
| static void GetJsonField(const char* sJson, const char* sField, char *pBuffer, size_t nBufferSize)
 | |
| {
 | |
|     const size_t nFieldSize = strlen(sField);
 | |
|     const char* pValue;
 | |
| 
 | |
|     *pBuffer = 0;
 | |
|     do
 | |
|     {
 | |
|         const char* pScan = strstr(sJson, sField);
 | |
|         if (!pScan)
 | |
|             return;
 | |
| 
 | |
|         if (pScan[-1] != '"' || pScan[nFieldSize] != '"')
 | |
|         {
 | |
|             sJson = pScan + 1;
 | |
|             continue;
 | |
|         }
 | |
| 
 | |
|         pScan += nFieldSize + 1;
 | |
|         while (*pScan == ':' || isspace(*pScan))
 | |
|             ++pScan;
 | |
|         if (*pScan != '"')
 | |
|             return;
 | |
| 
 | |
|         pValue = ++pScan;
 | |
|         while (*pScan != '"')
 | |
|         {
 | |
|             if (!*pScan)
 | |
|                 return;
 | |
| 
 | |
|             ++pScan;
 | |
|         }
 | |
| 
 | |
|         while (pValue < pScan && nBufferSize > 1)
 | |
|         {
 | |
|             if (*pValue == '\\')
 | |
|                 ++pValue;
 | |
| 
 | |
|             *pBuffer++ = *pValue++;
 | |
|             nBufferSize--;
 | |
|         }
 | |
| 
 | |
|         *pBuffer = '\0';
 | |
|         return;
 | |
| 
 | |
|     } while (1);
 | |
| }
 | |
| 
 | |
| static unsigned long long ParseVersion(const char* sVersion)
 | |
| {
 | |
|     char* pPart;
 | |
| 
 | |
|     unsigned long long major = strtoul(sVersion, &pPart, 10);
 | |
|     if (*pPart == '.')
 | |
|         ++pPart;
 | |
| 
 | |
|     unsigned long long minor = strtoul(pPart, &pPart, 10);
 | |
|     if (*pPart == '.')
 | |
|         ++pPart;
 | |
| 
 | |
|     unsigned long long patch = strtoul(pPart, &pPart, 10);
 | |
|     if (*pPart == '.')
 | |
|         ++pPart;
 | |
| 
 | |
|     unsigned long long revision = strtoul(pPart, &pPart, 10);
 | |
| 
 | |
|     // 64-bit max signed value is 9223 37203 68547 75807
 | |
|     unsigned long long version = (major * 100000) + minor;
 | |
|     version = (version * 100000) + patch;
 | |
|     version = (version * 100000) + revision;
 | |
|     return version;
 | |
| }
 | |
| 
 | |
| static void RA_InitCommon(HWND hMainHWND, int nEmulatorID, const char* sClientName, const char* sClientVersion)
 | |
| {
 | |
|     char sVerInstalled[32];
 | |
| #if defined(_MSC_VER) && _MSC_VER >= 1400
 | |
|     strcpy_s(sVerInstalled, sizeof(sVerInstalled), _RA_InstallIntegration());
 | |
| #else
 | |
|     strcpy(sVerInstalled, _RA_InstallIntegration());
 | |
| #endif
 | |
| 
 | |
|     char sHostUrl[256] = "";
 | |
|     if (_RA_HostUrl != nullptr)
 | |
|     {
 | |
| #if defined(_MSC_VER) && _MSC_VER >= 1400
 | |
|         strcpy_s(sHostUrl, sizeof(sHostUrl), _RA_HostUrl());
 | |
| #else
 | |
|         strcpy(sHostUrl, _RA_HostUrl());
 | |
| #endif
 | |
|     }
 | |
|     else if (_RA_HostName != nullptr)
 | |
|     {
 | |
|       sprintf_s(sHostUrl, "http://%s", _RA_HostName());
 | |
|     }
 | |
| 
 | |
|     if (!sHostUrl[0])
 | |
|     {
 | |
| #if defined(_MSC_VER) && _MSC_VER >= 1400
 | |
|         strcpy_s(sHostUrl, sizeof(sHostUrl), "http://retroachievements.org");
 | |
| #else
 | |
|         strcpy(sHostUrl, "http://retroachievements.org");
 | |
| #endif
 | |
|     }
 | |
|     else if (_RA_InitOffline != nullptr && strcmp(sHostUrl, "http://OFFLINE") == 0)
 | |
|     {
 | |
|         if (sClientName == nullptr)
 | |
|             _RA_InitOffline(hMainHWND, nEmulatorID, sClientVersion);
 | |
|         else
 | |
|             _RA_InitClientOffline(hMainHWND, sClientName, sClientVersion);
 | |
|         return;
 | |
|     }
 | |
| 
 | |
|     DWORD nBytesRead = 0;
 | |
|     DWORD nStatusCode = 0;
 | |
|     char buffer[1024];
 | |
|     ZeroMemory(buffer, 1024);
 | |
| 
 | |
|     if (DoBlockingHttpCallWithRetry(sHostUrl, "dorequest.php", "r=latestintegration", buffer, sizeof(buffer), &nBytesRead, &nStatusCode) == FALSE)
 | |
|     {
 | |
|         if (_RA_InitOffline != nullptr)
 | |
|         {
 | |
|             sprintf_s(buffer, sizeof(buffer), "Cannot access %s (status code %u)\nWorking offline.", sHostUrl, nStatusCode);
 | |
|             MessageBoxA(hMainHWND, buffer, "Warning", MB_OK | MB_ICONWARNING);
 | |
| 
 | |
|             _RA_InitOffline(hMainHWND, nEmulatorID, sClientVersion);
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             sprintf_s(buffer, sizeof(buffer), "Cannot access %s (status code %u)\nPlease try again later.", sHostUrl, nStatusCode);
 | |
|             MessageBoxA(hMainHWND, buffer, "Warning", MB_OK | MB_ICONWARNING);
 | |
| 
 | |
|             RA_Shutdown();
 | |
|         }
 | |
|         return;
 | |
|     }
 | |
| 
 | |
|     /* remove trailing zeros from client version */
 | |
|     char* ptr = sVerInstalled + strlen(sVerInstalled);
 | |
|     while (ptr[-1] == '0' && ptr[-2] == '.' && (ptr - 2) > sVerInstalled)
 | |
|         ptr -= 2;
 | |
|     *ptr = '\0';
 | |
|     if (strchr(sVerInstalled, '.') == NULL)
 | |
|     {
 | |
|         *ptr++ = '.';
 | |
|         *ptr++ = '0';
 | |
|         *ptr = '\0';
 | |
|     }
 | |
| 
 | |
|     char sLatestVersionUrl[256];
 | |
|     char sVersionBuffer[32];
 | |
|     GetJsonField(buffer, "MinimumVersion", sVersionBuffer, sizeof(sVersionBuffer));
 | |
|     const unsigned long long nMinimumDLLVer = ParseVersion(sVersionBuffer);
 | |
| 
 | |
|     GetJsonField(buffer, "LatestVersion", sVersionBuffer, sizeof(sVersionBuffer));
 | |
|     const unsigned long long nLatestDLLVer = ParseVersion(sVersionBuffer);
 | |
| 
 | |
| #if defined(_M_X64) || defined(__amd64__)
 | |
|     GetJsonField(buffer, "LatestVersionUrlX64", sLatestVersionUrl, sizeof(sLatestVersionUrl));
 | |
| #else
 | |
|     GetJsonField(buffer, "LatestVersionUrl", sLatestVersionUrl, sizeof(sLatestVersionUrl));
 | |
| #endif
 | |
| 
 | |
|     if (nLatestDLLVer == 0 || !sLatestVersionUrl[0])
 | |
|     {
 | |
|         /* NOTE: repurposing sLatestVersionUrl for the error message */
 | |
|         GetJsonField(buffer, "Error", sLatestVersionUrl, sizeof(sLatestVersionUrl));
 | |
|         if (sLatestVersionUrl[0])
 | |
|             sprintf_s(buffer, sizeof(buffer), "Failed to fetch latest integration version.\n\n%s", sLatestVersionUrl);
 | |
|         else
 | |
|             sprintf_s(buffer, sizeof(buffer), "The latest integration check did not return a valid response.");
 | |
| 
 | |
|         MessageBoxA(hMainHWND, buffer, "Error", MB_OK | MB_ICONERROR);
 | |
|         RA_Shutdown();
 | |
|         return;
 | |
|     }
 | |
| 
 | |
|     int nMBReply = 0;
 | |
|     unsigned long long nVerInstalled = ParseVersion(sVerInstalled);
 | |
|     if (nVerInstalled < nMinimumDLLVer)
 | |
|     {
 | |
|         RA_Shutdown(); // Unhook the DLL so we can replace it.
 | |
| 
 | |
|         if (nVerInstalled == 0)
 | |
|         {
 | |
|             sprintf_s(buffer, sizeof(buffer), "Install RetroAchievements toolset?\n\n"
 | |
|                 "In order to earn achievements you must download the toolset library.");
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             sprintf_s(buffer, sizeof(buffer), "Upgrade RetroAchievements toolset?\n\n"
 | |
|                 "A required upgrade to the toolset is available. If you don't upgrade, you won't be able to earn achievements.\n\n"
 | |
|                 "Latest Version: %s\nInstalled Version: %s", sVersionBuffer, sVerInstalled);
 | |
|         }
 | |
| 
 | |
|         nMBReply = MessageBoxA(hMainHWND, buffer, "Warning", MB_YESNO | MB_ICONWARNING);
 | |
|     }
 | |
|     else if (nVerInstalled < nLatestDLLVer)
 | |
|     {
 | |
|         sprintf_s(buffer, sizeof(buffer), "Upgrade RetroAchievements toolset?\n\n"
 | |
|             "An optional upgrade to the toolset is available.\n\n"
 | |
|             "Latest Version: %s\nInstalled Version: %s", sVersionBuffer, sVerInstalled);
 | |
| 
 | |
|         nMBReply = MessageBoxA(hMainHWND, buffer, "Warning", MB_YESNO | MB_ICONWARNING);
 | |
| 
 | |
|         if (nMBReply == IDYES)
 | |
|             RA_Shutdown(); // Unhook the DLL so we can replace it.
 | |
|     }
 | |
| 
 | |
|     if (nMBReply == IDYES)
 | |
|     {
 | |
|         //FetchIntegrationFromWeb(sLatestVersionUrl, &nStatusCode);
 | |
|         nStatusCode = 0;
 | |
| 
 | |
|         if (nStatusCode == 200)
 | |
|             nVerInstalled = ParseVersion(_RA_InstallIntegration());
 | |
| 
 | |
|         if (nVerInstalled < nLatestDLLVer)
 | |
|         {
 | |
|             sprintf_s(buffer, sizeof(buffer), "Failed to update toolset (status code %u).", nStatusCode);
 | |
|             MessageBoxA(hMainHWND, buffer, "Error", MB_OK | MB_ICONERROR);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     if (nVerInstalled < nMinimumDLLVer)
 | |
|     {
 | |
|         RA_Shutdown();
 | |
| 
 | |
|         sprintf_s(buffer, sizeof(buffer), "%s toolset is required to earn achievements.", nVerInstalled == 0 ? "The" : "A newer");
 | |
|         MessageBoxA(hMainHWND, buffer, "Warning", MB_OK | MB_ICONWARNING);
 | |
|     }
 | |
|     else if (sClientName == nullptr)
 | |
|     {
 | |
|         if (!_RA_InitI(hMainHWND, nEmulatorID, sClientVersion))
 | |
|             RA_Shutdown();
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|         if (!_RA_InitClient(hMainHWND, sClientName, sClientVersion))
 | |
|             RA_Shutdown();
 | |
|     }
 | |
| }
 | |
| 
 | |
| void RA_Init(HWND hMainHWND, int nEmulatorID, const char* sClientVersion)
 | |
| {
 | |
|     RA_InitCommon(hMainHWND, nEmulatorID, nullptr, sClientVersion);
 | |
| }
 | |
| 
 | |
| void RA_InitClient(HWND hMainHWND, const char* sClientName, const char* sClientVersion)
 | |
| {
 | |
|     RA_InitCommon(hMainHWND, -1, sClientName, sClientVersion);
 | |
| }
 | |
| 
 | |
| void RA_SetUserAgentDetail(const char* sDetail)
 | |
| {
 | |
|     if (_RA_SetUserAgentDetail != nullptr)
 | |
|         _RA_SetUserAgentDetail(sDetail);
 | |
| }
 | |
| 
 | |
| void RA_InstallSharedFunctions(int(*)(void), void(*fpCauseUnpause)(void), void(*fpCausePause)(void), void(*fpRebuildMenu)(void), void(*fpEstimateTitle)(char*), void(*fpResetEmulation)(void), void(*fpLoadROM)(const char*))
 | |
| {
 | |
|     if (_RA_InstallSharedFunctions != nullptr)
 | |
|         _RA_InstallSharedFunctions(nullptr, fpCauseUnpause, fpCausePause, fpRebuildMenu, fpEstimateTitle, fpResetEmulation, fpLoadROM);
 | |
| }
 | |
| 
 | |
| void RA_Shutdown()
 | |
| {
 | |
|     //	Call shutdown on toolchain
 | |
|     if (_RA_Shutdown != nullptr)
 | |
|     {
 | |
|       _RA_Shutdown();
 | |
|     }
 | |
| 
 | |
|     //	Clear func ptrs
 | |
|     _RA_IntegrationVersion = nullptr;
 | |
|     _RA_HostName = nullptr;
 | |
|     _RA_HostUrl = nullptr;
 | |
|     _RA_InitI = nullptr;
 | |
|     _RA_InitOffline = nullptr;
 | |
|     _RA_InitClient = nullptr;
 | |
|     _RA_InitClientOffline = nullptr;
 | |
|     _RA_InstallSharedFunctions = nullptr;
 | |
|     _RA_SetForceRepaint = nullptr;
 | |
|     _RA_CreatePopupMenu = nullptr;
 | |
|     _RA_GetPopupMenuItems = nullptr;
 | |
|     _RA_InvokeDialog = nullptr;
 | |
|     _RA_SetUserAgentDetail = nullptr;
 | |
|     _RA_AttemptLogin = nullptr;
 | |
|     _RA_SetConsoleID = nullptr;
 | |
|     _RA_ClearMemoryBanks = nullptr;
 | |
|     _RA_InstallMemoryBank = nullptr;
 | |
|     _RA_InstallMemoryBankBlockReader = nullptr;
 | |
|     _RA_Shutdown = nullptr;
 | |
|     _RA_IsOverlayFullyVisible = nullptr;
 | |
|     _RA_SetPaused = nullptr;
 | |
|     _RA_NavigateOverlay = nullptr;
 | |
|     _RA_UpdateHWnd = nullptr;
 | |
|     _RA_IdentifyRom = nullptr;
 | |
|     _RA_IdentifyHash = nullptr;
 | |
|     _RA_ActivateGame = nullptr;
 | |
|     _RA_OnLoadNewRom = nullptr;
 | |
|     _RA_ConfirmLoadNewRom = nullptr;
 | |
|     _RA_DoAchievementsFrame = nullptr;
 | |
|     _RA_SuspendRepaint = nullptr;
 | |
|     _RA_ResumeRepaint = nullptr;
 | |
|     _RA_UpdateAppTitle = nullptr;
 | |
|     _RA_UserName = nullptr;
 | |
|     _RA_HardcoreModeIsActive = nullptr;
 | |
|     _RA_WarnDisableHardcore = nullptr;
 | |
|     _RA_OnReset = nullptr;
 | |
|     _RA_OnSaveState = nullptr;
 | |
|     _RA_OnLoadState = nullptr;
 | |
|     _RA_CaptureState = nullptr;
 | |
|     _RA_RestoreState = nullptr;
 | |
| 
 | |
|     /* unload the DLL */
 | |
|     if (g_hRADLL)
 | |
|     {
 | |
|         FreeLibrary(g_hRADLL);
 | |
|         g_hRADLL = nullptr;
 | |
|     }
 | |
| }
 | 
