Duckstation/dep/rainterface/RA_Interface.cpp
2023-08-21 22:38:55 +10:00

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;
}
}