diff --git a/3rdparty/rainterface/.gitignore b/3rdparty/rainterface/.gitignore new file mode 100644 index 0000000000..5761abcfdf --- /dev/null +++ b/3rdparty/rainterface/.gitignore @@ -0,0 +1 @@ +*.o diff --git a/3rdparty/rainterface/LICENSE b/3rdparty/rainterface/LICENSE new file mode 100644 index 0000000000..296dc5caec --- /dev/null +++ b/3rdparty/rainterface/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 RetroAchievements.org + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/3rdparty/rainterface/RA_Consoles.h b/3rdparty/rainterface/RA_Consoles.h new file mode 100644 index 0000000000..59839b64f8 --- /dev/null +++ b/3rdparty/rainterface/RA_Consoles.h @@ -0,0 +1,84 @@ +#ifndef RA_CONSOLES_H +#define RA_CONSOLES_H + +/* this list should match the list in rcheevos/include/rconsoles.h */ +enum ConsoleID +{ + UnknownConsoleID = 0, + MegaDrive = 1, + N64 = 2, + SNES = 3, + GB = 4, + GBA = 5, + GBC = 6, + NES = 7, + PCEngine = 8, + SegaCD = 9, + Sega32X = 10, + MasterSystem = 11, + PlayStation = 12, + Lynx = 13, + NeoGeoPocket = 14, + GameGear = 15, + GameCube = 16, + Jaguar = 17, + DS = 18, + WII = 19, + WIIU = 20, + PlayStation2 = 21, + Xbox = 22, + MagnavoxOdyssey = 23, + PokemonMini = 24, + Atari2600 = 25, + MSDOS = 26, + Arcade = 27, + VirtualBoy = 28, + MSX = 29, + C64 = 30, + ZX81 = 31, + Oric = 32, + SG1000 = 33, + VIC20 = 34, + Amiga = 35, + AtariST = 36, + AmstradCPC = 37, + AppleII = 38, + Saturn = 39, + Dreamcast = 40, + PSP = 41, + CDi = 42, + ThreeDO = 43, + Colecovision = 44, + Intellivision = 45, + Vectrex = 46, + PC8800 = 47, + PC9800 = 48, + PCFX = 49, + Atari5200 = 50, + Atari7800 = 51, + X68K = 52, + WonderSwan = 53, + CassetteVision = 54, + SuperCassetteVision = 55, + NeoGeoCD = 56, + FairchildChannelF = 57, + FMTowns = 58, + ZXSpectrum = 59, + GameAndWatch = 60, + NokiaNGage = 61, + Nintendo3DS = 62, + Supervision = 63, + SharpX1 = 64, + Tic80 = 65, + ThomsonTO8 = 66, + PC6000 = 67, + Pico = 68, + MegaDuck = 69, + Zeebo = 70, + Arduboy = 71, + WASM4 = 72, + + NumConsoleIDs +}; + +#endif /* !RA_CONSOLES_H */ diff --git a/3rdparty/rainterface/RA_Emulators.h b/3rdparty/rainterface/RA_Emulators.h new file mode 100644 index 0000000000..ebaa702336 --- /dev/null +++ b/3rdparty/rainterface/RA_Emulators.h @@ -0,0 +1,23 @@ +#ifndef RA_EMULATORS_H +#define RA_EMULATORS_H + +enum EmulatorID +{ + RA_Gens = 0, + RA_Project64 = 1, + RA_Snes9x = 2, + RA_VisualboyAdvance = 3, + RA_Nester = 4, + RA_FCEUX = 5, + RA_PCE = 6, + RA_Libretro = 7, + RA_Meka = 8, + RA_QUASI88 = 9, + RA_AppleWin = 10, + RA_Oricutron = 11, + + NumEmulatorIDs, + UnknownEmulator = NumEmulatorIDs +}; + +#endif /* !RA_EMULATORS_H */ diff --git a/3rdparty/rainterface/RA_Interface.cpp b/3rdparty/rainterface/RA_Interface.cpp new file mode 100644 index 0000000000..2152538500 --- /dev/null +++ b/3rdparty/rainterface/RA_Interface.cpp @@ -0,0 +1,1051 @@ +#include "RA_Interface.h" + +#include +#include +#include +#include + +#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) + { +#ifdef __cplusplus + try { +#endif + _RA_Shutdown(); +#ifdef __cplusplus + } + catch (std::runtime_error&) { + } +#endif + } + + // 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; + } +} diff --git a/3rdparty/rainterface/RA_Interface.h b/3rdparty/rainterface/RA_Interface.h new file mode 100644 index 0000000000..763ff90c75 --- /dev/null +++ b/3rdparty/rainterface/RA_Interface.h @@ -0,0 +1,386 @@ +#ifndef RA_INTERFACE_H +#define RA_INTERFACE_H + +#include /* HWND */ + +#ifdef __cplusplus +extern "C" { +#endif + +/****************************** + * Initialization * + ******************************/ + +/** + * Loads and initializes the DLL. + * + * Must be called before using any of the other functions. Will automatically download the DLL + * if not found, or prompt the user to upgrade if a newer version is available + * + * @param hMainHWND the handle of the main window + * @param nEmulatorID the unique idenfier of the emulator + * @param sClientVersion the current version of the emulator (will be validated against the minimum version for the specified emulator ID) + */ +extern void RA_Init(HWND hMainHWND, int nEmulatorID, const char* sClientVersion); + +/** + * Loads and initializes the DLL. + * + * Must be called before using any of the other functions. Will automatically download the DLL + * if not found, or prompt the user to upgrade if a newer version is available + * + * @param hMainHWND the handle of the main window + * @param sClientName the name of the client, displayed in the title bar and included in the User-Agent for API calls + * @param sClientVersion the current version of the client + */ +extern void RA_InitClient(HWND hMainHWND, const char* sClientName, const char* sClientVersion); + +/** + * Defines callbacks that the DLL can use to interact with the client. + * + * @param fpUnusedIsActive [no longer used] returns non-zero if a game is running + * @param fpCauseUnpause unpauses the emulator + * @param fpCausePause pauses the emulator + * @param fpRebuildMenu notifies the client that the popup menu has changed (@see RA_CreatePopupMenu) + * @param fpEstimateTitle gets a short description for the game being loaded (parameter is a 256-byte buffer to fill) + * @param fpResetEmulator resets the emulator + * @param fpLoadROM [currently unused] tells the emulator to load a specific game + */ +extern void RA_InstallSharedFunctions(int (*fpUnusedIsActive)(void), + void (*fpCauseUnpause)(void), void (*fpCausePause)(void), void (*fpRebuildMenu)(void), + void (*fpEstimateTitle)(char*), void (*fpResetEmulator)(void), void (*fpLoadROM)(const char*)); + +/** + * Tells the DLL to use UpdateWindow instead of InvalidateRect when the UI needs to be repainted. This is primarily + * necessary when integrating with an emulator using the SDL library as it keeps the message queue flooded so the + * InvalidateRect messages never get turned into WM_PAINT messages. + * + * @param bEnable non-zero if InvalidateRect calls should be replaced with UpdateWindow + */ +extern void RA_SetForceRepaint(int bEnable); + +/** + * Creates a popup menu that can be appended to the main menu of the emulator. + * + * @return handle to the menu. if not attached to the program menu, caller must destroy it themselves. + */ +extern HMENU RA_CreatePopupMenu(void); + +/* Resource values for menu items - needed by MFC ON_COMMAND_RANGE macros or WM_COMMAND WndProc handlers + * they're not all currently used, allowing additional items without forcing recompilation of the emulators + */ +#define IDM_RA_MENUSTART 1700 +#define IDM_RA_MENUEND 1739 + +typedef struct RA_MenuItem +{ + LPCWSTR sLabel; + LPARAM nID; + int bChecked; +} RA_MenuItem; + +/** + * Gets items for building a popup menu. + * + * @param pItems Pre-allocated array to populate [should contain space for at least 32 items] + * @return Number of items populated in the items array + */ +extern int RA_GetPopupMenuItems(RA_MenuItem *pItems); + +/** + * Called when a menu item in the popup menu is selected. + * + * @param nID the ID of the menu item (will be between IDM_RA_MENUSTART and IDM_RA_MENUEND) + */ +extern void RA_InvokeDialog(LPARAM nID); + +/** + * Provides additional information to include in the User-Agent string for API calls. + * + * This is primarily used to identify dependencies or configurations (such as libretro core versions) + * + * @param sDetail the additional information to include + */ +extern void RA_SetUserAgentDetail(const char* sDetail); + +/** + * Attempts to log in to the retroachievements.org site. + * + * Prompts the user for their login credentials and performs the login. If they've previously logged in and have + * chosen to store their credentials, the login occurs without prompting. + * + * @param bBlocking if zero, control is returned to the calling process while the login request is processed by the server. + */ +extern void RA_AttemptLogin(int bBlocking); + +/** + * Specifies the console associated to the emulator. + * + * May be called just before loading a game if the emulator supports multiple consoles. + * + * @param nConsoleID the unique identifier of the console associated to the game being loaded. + */ +extern void RA_SetConsoleID(unsigned int nConsoleID); + +/** + * Resets memory references created by previous calls to RA_InstallMemoryBank. + */ +extern void RA_ClearMemoryBanks(void); + +typedef unsigned char (RA_ReadMemoryFunc)(unsigned int nAddress); +typedef void (RA_WriteMemoryFunc)(unsigned int nAddress, unsigned char nValue); +/** + * Exposes a block of memory to the DLL. + * + * The blocks of memory expected by the DLL are unique per console ID. To identify the correct map for a console, + * view the consoleinfo.c file in the rcheevos repository. + * + * @param nBankID the index of the bank to update. will replace any existing bank at that index. + * @param pReader a function to read from the bank. parameter is the offset within the bank to read from. + * @param pWriter a function to write to the bank. parameters are the offset within the bank to write to and an 8-bit value to write. + * @param nBankSize the size of the bank. + */ +extern void RA_InstallMemoryBank(int nBankID, RA_ReadMemoryFunc pReader, RA_WriteMemoryFunc pWriter, int nBankSize); + +typedef unsigned int (RA_ReadMemoryBlockFunc)(unsigned int nAddress, unsigned char* pBuffer, unsigned int nBytes); +extern void RA_InstallMemoryBankBlockReader(int nBankID, RA_ReadMemoryBlockFunc pReader); + +/** + * Deinitializes and unloads the DLL. + */ +extern void RA_Shutdown(void); + + + +/****************************** + * Overlay * + ******************************/ + +/** + * Determines if the overlay is fully visible. + * + * Precursor check before calling RA_NavigateOverlay + * + * @return non-zero if the overlay is fully visible, zero if it is not. + */ +extern int RA_IsOverlayFullyVisible(void); + +/** + * Called to show or hide the overlay. + * + * @param bIsPaused true to show the overlay, false to hide it. + */ +extern void RA_SetPaused(bool bIsPaused); + +struct ControllerInput +{ + int m_bUpPressed; + int m_bDownPressed; + int m_bLeftPressed; + int m_bRightPressed; + int m_bConfirmPressed; /* Usually C or A */ + int m_bCancelPressed; /* Usually B */ + int m_bQuitPressed; /* Usually Start */ +}; + +/** + * Passes controller input to the overlay. + * + * Does nothing if the overlay is not fully visible. + * + * @param pInput pointer to a ControllerInput structure indicating which inputs are active. + */ +extern void RA_NavigateOverlay(struct ControllerInput* pInput); + +/** + * [deprecated] Updates the overlay for a single frame. + * + * This function just calls RA_NavigateOverlay. Updating and rendering is now handled internally to the DLL. + */ +extern void RA_UpdateRenderOverlay(HDC, struct ControllerInput* pInput, float, RECT*, bool, bool); + +/** + * Updates the handle to the main window. + * + * The main window handle is used to anchor the overlay. If the client recreates the handle as the result of switching + * from windowed mode to full screen, or for any other reason, it should call this to reattach the overlay. + * + * @param hMainHWND the new handle of the main window + */ +extern void RA_UpdateHWnd(HWND hMainHWND); + + + +/****************************** + * Game Management * + ******************************/ + +/** + * Identifies the game associated to a block of memory. + * + * The block of memory is the fully buffered game file. If more complicated identification is required, the caller + * needs to link against rcheevos/rhash directly to generate the hash, and call RA_IdentifyHash with the result. + * Can be called when switching discs to ensure the additional discs are still associated to the loaded game. + * + * @param pROMData the contents of the game file + * @param nROMSize the size of the game file + * @return the unique identifier of the game, 0 if no association available. + */ +extern unsigned int RA_IdentifyRom(BYTE* pROMData, unsigned int nROMSize); + +/** + * Identifies the game associated to a pre-generated hash. + * + * Used when the hash algorithm is something other than full file. + * Can be called when switching discs to ensure the additional discs are still associated to the loaded game. + * + * @param sHash the hash generated by rcheevos/rhash + * @return the unique identifier of the game, 0 if no association available. + */ +extern unsigned int RA_IdentifyHash(const char* sHash); + +/** + * Fetches all retroachievements related data for the specified game. + * + * @param nGameId the unique identifier of the game to activate + */ +extern void RA_ActivateGame(unsigned int nGameId); + +/** + * Identifies and activates the game associated to a block of memory. + * + * Functions as a call to RA_IdentifyRom followed by a call to RA_ActivateGame. + * + * @param pROMData the contents of the game file + * @param nROMSize the size of the game file + */ +extern void RA_OnLoadNewRom(BYTE* pROMData, unsigned int nROMSize); + +/** + * Called before unloading the game to allow the user to save any changes they might have. + * + * @param bIsQuitting non-zero to change the messaging to indicate the emulator is closing. + * @return zero to abort the unload. non-zero to continue. + */ +extern int RA_ConfirmLoadNewRom(int bIsQuitting); + + + +/****************************** + * Runtime Functionality * + ******************************/ + +/** + * Does all achievement-related processing for a single frame. + */ +extern void RA_DoAchievementsFrame(void); + +/** + * Temporarily disables forced updating of tool windows. + * + * Primarily used while fast-forwarding. + */ +extern void RA_SuspendRepaint(void); + +/** + * Resumes forced updating of tool windows. + */ +extern void RA_ResumeRepaint(void); + +/** + * [deprecated] Used to be used to ensure the asynchronous server calls are processed on the UI thread. + * That's all managed within the DLL now. Calling this function does nothing. + */ +extern void RA_HandleHTTPResults(void); + +/** + * Adds flavor text to the application title bar. + * + * Application title bar is managed by the DLL. Value will be "ClientName - Version - Flavor Text - Username" + * + * @param sCustomMessage the flavor text to include in the title bar. + */ +extern void RA_UpdateAppTitle(const char* sCustomMessage); + +/** + * Get the user name of the currently logged in user. + * + * @return user name of the currently logged in user, empty if no user is logged in. + */ +const char* RA_UserName(void); + +/** + * Determines if the user is currently playing with hardcore enabled. + * + * The client should disable any features that would give the player an unfair advantage if this returns non-zero. + * Things like loading states, using cheats, modifying RAM, disabling rendering layers, viewing decoded tilemaps, etc. + * + * @return non-zero if hardcore mode is currently active. + */ +extern int RA_HardcoreModeIsActive(void); + +/** + * Warns the user they're about to do something that will disable hardcore mode. + * + * @param sActivity what the user is about to do (i.e. "load a state"). + * @return non-zero if the user disabled hardcore and the activity is allowed. + * zero if the user declined to disable hardcore and the activity should be aborted. + */ +extern int RA_WarnDisableHardcore(const char* sActivity); + +/** + * Disables hardcore mode without prompting or notifying the user. + * + * Should only be called if the client does its own prompting/notification. + * Typically used when an activity cannot be aborted. + */ +extern void RA_DisableHardcore(void); + +/** + * Notifies the DLL that the game has been reset. + * + * Disables active leaderboards and resets hit counts on all active achievements. + */ +extern void RA_OnReset(void); + +/** + * Notifies the DLL that a save state has been created. + * + * Creates a .rap file next to the state file that contains achievement-related information for the save state. + * + * @param sFilename full path to the save state file. + */ +extern void RA_OnSaveState(const char* sFilename); + +/** + * Notifies the DLL that a save state has been loaded. + * + * Loads the .rap file next to the state file that contains achievement-related information for the save state being loaded. + * + * @param sFilename full path to the save state file. + */ +extern void RA_OnLoadState(const char* sFilename); + +/** + * Captures the current state of the achievement runtime for inclusion in a save state. + * + * @param pBuffer buffer to write achievement state information to + * @param nBufferSize the size of the buffer + * @return the number of bytes needed to capture the achievement state. if less than nBufferSize, pBuffer + * will not be populated. the function should be called again with a larger buffer. + */ +extern int RA_CaptureState(char* pBuffer, int nBufferSize); + +/** + * Restores the state of the achievement runtime from a previously captured state. + * + * @param pBuffer buffer containing previously serialized achievement state information + */ +extern void RA_RestoreState(const char* pBuffer); + + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif // !RA_INTERFACE_H diff --git a/3rdparty/rainterface/README.md b/3rdparty/rainterface/README.md new file mode 100644 index 0000000000..9fb572bd82 --- /dev/null +++ b/3rdparty/rainterface/README.md @@ -0,0 +1,5 @@ +# RAInterface + +This code is intended to be loaded into another repository as a submodule. + +An emulator should include RA_Interface.cpp in its build and link against winhttp.lib. Then, the emulator can be modified to call the hooks provided in RA_Interface.cpp at appropriate times to integrate with the RetroAchievements server via the RA_Integration.dll. See wiki for more details. diff --git a/3rdparty/rainterface/rainterface.vcxproj b/3rdparty/rainterface/rainterface.vcxproj new file mode 100644 index 0000000000..3879e983cc --- /dev/null +++ b/3rdparty/rainterface/rainterface.vcxproj @@ -0,0 +1,48 @@ + + + + + + {95DD0A0C-D14D-4CFF-A593-820EF26EFCC8} + + + + StaticLibrary + $(DefaultPlatformToolset) + MultiByte + true + true + false + + + + + + + + + + + + + + AllRules.ruleset + + + + %(PreprocessorDefinitions) + $(ProjectDir)%(AdditionalIncludeDirectories) + TurnOffAllWarnings + + + + + + + + + + + + + \ No newline at end of file diff --git a/3rdparty/rainterface/rainterface.vcxproj.filters b/3rdparty/rainterface/rainterface.vcxproj.filters new file mode 100644 index 0000000000..e276966113 --- /dev/null +++ b/3rdparty/rainterface/rainterface.vcxproj.filters @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/PCSX2_qt.sln b/PCSX2_qt.sln index a93d6d254c..649b686162 100644 --- a/PCSX2_qt.sln +++ b/PCSX2_qt.sln @@ -59,6 +59,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "cpuinfo", "3rdparty\cpuinfo EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "rcheevos", "3rdparty\rcheevos\rcheevos.vcxproj", "{6D5B5AD9-1525-459B-939F-A5E1082AF6B3}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "rainterface", "3rdparty\rainterface\rainterface.vcxproj", "{95DD0A0C-D14D-4CFF-A593-820EF26EFCC8}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug AVX2|x64 = Debug AVX2|x64 @@ -393,6 +395,18 @@ Global {6D5B5AD9-1525-459B-939F-A5E1082AF6B3}.Release AVX2|x64.Build.0 = Release|x64 {6D5B5AD9-1525-459B-939F-A5E1082AF6B3}.Release|x64.ActiveCfg = Release|x64 {6D5B5AD9-1525-459B-939F-A5E1082AF6B3}.Release|x64.Build.0 = Release|x64 + {95DD0A0C-D14D-4CFF-A593-820EF26EFCC8}.Debug AVX2|x64.ActiveCfg = Debug|x64 + {95DD0A0C-D14D-4CFF-A593-820EF26EFCC8}.Debug AVX2|x64.Build.0 = Debug|x64 + {95DD0A0C-D14D-4CFF-A593-820EF26EFCC8}.Debug|x64.ActiveCfg = Debug|x64 + {95DD0A0C-D14D-4CFF-A593-820EF26EFCC8}.Debug|x64.Build.0 = Debug|x64 + {95DD0A0C-D14D-4CFF-A593-820EF26EFCC8}.Devel AVX2|x64.ActiveCfg = Devel|x64 + {95DD0A0C-D14D-4CFF-A593-820EF26EFCC8}.Devel AVX2|x64.Build.0 = Devel|x64 + {95DD0A0C-D14D-4CFF-A593-820EF26EFCC8}.Devel|x64.ActiveCfg = Devel|x64 + {95DD0A0C-D14D-4CFF-A593-820EF26EFCC8}.Devel|x64.Build.0 = Devel|x64 + {95DD0A0C-D14D-4CFF-A593-820EF26EFCC8}.Release AVX2|x64.ActiveCfg = Release|x64 + {95DD0A0C-D14D-4CFF-A593-820EF26EFCC8}.Release AVX2|x64.Build.0 = Release|x64 + {95DD0A0C-D14D-4CFF-A593-820EF26EFCC8}.Release|x64.ActiveCfg = Release|x64 + {95DD0A0C-D14D-4CFF-A593-820EF26EFCC8}.Release|x64.Build.0 = Release|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -421,6 +435,7 @@ Global {A4323327-3F2B-4271-83D9-7F9A3C66B6B2} = {78EBE642-7A4D-4EA7-86BE-5639C6646C38} {7E183337-A7E9-460C-9D3D-568BC9F9BCC1} = {78EBE642-7A4D-4EA7-86BE-5639C6646C38} {6D5B5AD9-1525-459B-939F-A5E1082AF6B3} = {78EBE642-7A4D-4EA7-86BE-5639C6646C38} + {95DD0A0C-D14D-4CFF-A593-820EF26EFCC8} = {78EBE642-7A4D-4EA7-86BE-5639C6646C38} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {0BC474EA-3628-45D3-9DBC-E22D0B7E0F77}