mirror of https://github.com/stenzek/duckstation
				
				
				
			
			You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
	
	
		
			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;
 | 
						|
    }
 | 
						|
}
 |