From 7f3e27a3ef2b1b3b4b81aea567c5b9dd7ffe21f5 Mon Sep 17 00:00:00 2001 From: mika-n <35538525+mika-n@users.noreply.github.com> Date: Mon, 18 May 2020 16:50:09 +0300 Subject: [PATCH] Fixed number of gears value. Fixed multimonitor/virtual desktop issue (preview image was not drawn in the correct place. Maybe it now works. Cannot test this myself). Fixed bug in preview image generation if FixUp.VSyncActive=0 was set. Fixed bug showing "car1/car2/car3/etc" name if the RBRCIT installed car was actually one of the original car models (ie. without NGP description file). Fixed a bug if there was a dot in a car model name (the plugin showed the car name wrong). Added new options: ScreenshotFileType=PNG | BMP. ScreenshotAPIType=0 | 1 (0=DirectX framebuffer data in a screenshot, 1=GDI screen data in a screenshot). Some Win7 PCs seemed to have issues with DX screenshots (wrong background colors). Added NGPCarMenu\CustomCarSpecs.ini file to support original car models and any other non-NGP related custom car model. --- src/CreateReleaseZip.cmd | 14 +- src/CustomCarSpecs.ini | 82 +++++ src/D3D9Helpers.cpp | 366 +++++++++++++++++++---- src/D3D9Helpers.h | 20 +- src/NGPCarMenu.cpp | 531 ++++++++++++++++++++++----------- src/NGPCarMenu.h | 16 +- src/NGPCarMenu.ini | 21 +- src/NGPCarMenu.rc | 8 +- src/NGPCarMenu.vcxproj | 3 + src/NGPCarMenu.vcxproj.filters | 3 + src/RBRAPI.cpp | 32 +- src/RBRAPI.h | 6 +- 12 files changed, 852 insertions(+), 250 deletions(-) create mode 100644 src/CustomCarSpecs.ini diff --git a/src/CreateReleaseZip.cmd b/src/CreateReleaseZip.cmd index 19b2cee..2b47028 100644 --- a/src/CreateReleaseZip.cmd +++ b/src/CreateReleaseZip.cmd @@ -1,5 +1,11 @@ @echo off +rem +rem Quick and dirty "tool" to package a new NGPCarMenu_.zip release file. +rem Remember to do "clean - rebuild all" with Release build in VC++ before packaging the release ZIP file. +rem + + SET APPNAME=NGPCarMenu SET VERSIONTAG=%~1 SET RELEASE_FOLDER=Release\VER_%VERSIONTAG% @@ -40,16 +46,18 @@ mkdir "%RELEASE_FOLDER%\Plugins\%APPNAME%\" mkdir "%RELEASE_FOLDER%\Plugins\%APPNAME%\preview\1920x1080\" mkdir "%RELEASE_FOLDER%\Plugins\%APPNAME%\preview\1366x768\" +rem Dummy files because 7Zip tool would ignore empty folders +type NUL > "%RELEASE_FOLDER%\Plugins\%APPNAME%\preview\1920x1080\carImages.txt" +type NUL > "%RELEASE_FOLDER%\Plugins\%APPNAME%\preview\1366x768\carImages.txt" + copy "Release\%APPNAME%.dll" "%RELEASE_FOLDER%\Plugins\" copy "%APPNAME%.ini" "%RELEASE_FOLDER%\Plugins\" copy "%APPNAME%.rpl" "%RELEASE_FOLDER%\Replays\" +copy "CustomCarSpecs.ini" "%RELEASE_FOLDER%\Plugins\%APPNAME%\" copy "..\LicenseText.txt" "%RELEASE_FOLDER%\Plugins\%APPNAME%\" copy "..\ReadMe.md" "%RELEASE_FOLDER%\Plugins\%APPNAME%\" copy "..\ReadMe.md" "%RELEASE_FOLDER%\Plugins\%APPNAME%\ReadMe.txt" -type NUL > "%RELEASE_FOLDER%\Plugins\%APPNAME%\preview\1920x1080\carImages.txt" -type NUL > "%RELEASE_FOLDER%\Plugins\%APPNAME%\preview\1366x768\carImages.txt" - PUSHD "%RELEASE_FOLDER%\" del "..\%RELEASE_PKG%" "%ZIP_TOOL%" a -r -tzip "..\%RELEASE_PKG%" *.* diff --git a/src/CustomCarSpecs.ini b/src/CustomCarSpecs.ini new file mode 100644 index 0000000..0f4bfec --- /dev/null +++ b/src/CustomCarSpecs.ini @@ -0,0 +1,82 @@ +; +; NGPCarMenu - Car specs of the original RBR cars. +; +; If RBRCIT/NGP is used to install original car models then Physics\\ folder doesn't have a model description file. +; In that case this plugin use this file to look for custom spec information for those cars (or any other custom non-NGP car model). +; +; By default here are the origial RBR cars, but you can add any custom car here if NGP model doesn't have a car spec information for the car. +; Well, to be exact. NGP carList.ini file does have this information for the original cars EXCEPT the cat FIA Category information. This file adds the FIA category information, +; but at the same time this can be used to add this information for other non-NGP custom car model names. +; + +[Car_1] +name=Citroen Xsara +cat=WRC 2.0 +year=2004 +weight=1400 +power=300@5500 +trans=4WD + + +[Car_2] +name=Hyundai Accent +cat=WRC 2.0 +year=2004 +weight=1400 +power=300@5300 +trans=4WD + + +[Car_3] +name=MG ZR Super 1600 +cat=Super 1600 +year=2004 +weight=1150 +power=215@8450 +trans=2WD + + +[Car_4] +name=Mitsubishi Lancer Evo VII +cat=WRC 2.0 +year=2004 +weight=1400 +power=300@5500 +trans=4WD + + +[Car_5] +name=Peugeot 206 +cat=WRC 2.0 +year=2004 +weight=1400 +power=300@5250 +trans=4WD + + +[Car_6] +name=Subaru Impreza 2003 +cat=WRC 2.0 +year=2004 +weight=1400 +power=300@5500 +trans=4WD + + +[Car_7] +name=Toyota Corolla +cat=WRC 2.0 +year=2004 +weight=1400 +power=300@5250 +trans=4WD + + +[Car_8] +name=Subaru Impreza 2000 +cat=WRC 2.0 +year=2004 +weight=1400 +power=280@6400 +trans=4WD + diff --git a/src/D3D9Helpers.cpp b/src/D3D9Helpers.cpp index 7091d80..8974361 100644 --- a/src/D3D9Helpers.cpp +++ b/src/D3D9Helpers.cpp @@ -26,8 +26,14 @@ #include // vector #include // wstringstream #include // fs::exists +#include // std::ifstream and ofstream #include // IWICxx image funcs +#include // PathRemoveFileSpec + +#include +#include +#pragma comment(lib,"gdiplus.lib") #include #pragma comment(lib, "d3d9.lib") @@ -130,6 +136,18 @@ inline std::string _ToString(const std::wstring& s) return std::string{ buf.get() }; } + +inline void _ToLowerCase(std::string& s) +{ + transform(s.begin(), s.end(), s.begin(), ::tolower); +} + +inline void _ToLowerCase(std::wstring& s) +{ + transform(s.begin(), s.end(), s.begin(), ::tolower); +} + + // // Split "12 34 54 45" string and populate RECT struct with splitted values // @@ -173,14 +191,51 @@ bool _StringToRect(const std::wstring& s, RECT* outRect, const wchar_t separator //--------------------------------------------------------------------------------------------------------------- -#if USE_DEBUG == 1 //----------------------------------------------------------------------------------------------------------------------------- // Debug printout functions. Not used in release build. // -FILE* g_fpDebugLogFile = nullptr; -void DebugPrintCloseFile(); +std::string g_sLogFileName; +FILE* g_fpLogFile = nullptr; + +void DebugOpenFile(bool bOverwriteFile = false) +{ + try + { + if (g_fpLogFile == nullptr) + { + if (g_sLogFileName.empty()) + { + char szModulePath[_MAX_PATH]; + ::GetModuleFileName(NULL, szModulePath, sizeof(szModulePath)); + ::PathRemoveFileSpec(szModulePath); + + g_sLogFileName = szModulePath; + g_sLogFileName = g_sLogFileName + "\\Plugins\\NGPCarMenu\\NGPCarMenu.log"; + +#ifndef USE_DEBUG + // Release build creates always a new empty logfile when the logfile is opened for the first time during a process run + bOverwriteFile = true; +#endif + } + + // Overwrite or append the logfile + fopen_s(&g_fpLogFile, g_sLogFileName.c_str(), (bOverwriteFile ? "w+t" : "a+t")); + } + } + catch (...) + { + // Do nothing + } +} + +void DebugCloseFile() +{ + FILE* fpLogFile = g_fpLogFile; + g_fpLogFile = nullptr; + if (fpLogFile != nullptr) fclose(fpLogFile); +} void DebugPrintFunc(LPCSTR lpszFormat, ...) { @@ -200,14 +255,15 @@ void DebugPrintFunc(LPCSTR lpszFormat, ...) if (_vsnprintf_s(szTxtBuf, sizeof(szTxtBuf) - 1, lpszFormat, args) <= 0) szTxtBuf[0] = '\0'; - if (g_fpDebugLogFile == nullptr) - fopen_s(&g_fpDebugLogFile, "NGPCarMenu.log", "a+t"); + if (g_fpLogFile == nullptr) + DebugOpenFile(); - if (g_fpDebugLogFile) + if (g_fpLogFile) { - fprintf(g_fpDebugLogFile, szTxtTimeStampBuf); - fprintf(g_fpDebugLogFile, szTxtBuf); - fprintf(g_fpDebugLogFile, "\n"); + fprintf(g_fpLogFile, szTxtTimeStampBuf); + fprintf(g_fpLogFile, szTxtBuf); + fprintf(g_fpLogFile, "\n"); + DebugCloseFile(); } } catch (...) @@ -218,21 +274,52 @@ void DebugPrintFunc(LPCSTR lpszFormat, ...) va_end(args); } -void DebugPrintEmptyFile() +void DebugPrintFunc(LPCWSTR lpszFormat, ...) { - FILE* fpDebugLogFile = nullptr; - DebugPrintCloseFile(); - fopen_s(&fpDebugLogFile, "NGPCarMenu.log", "w"); - if (fpDebugLogFile != nullptr) fclose(fpDebugLogFile); + va_list args; + va_start(args, lpszFormat); + + SYSTEMTIME t; + char szTxtTimeStampBuf[32]; + WCHAR szTxtBuf[1024]; + + try + { + GetLocalTime(&t); + if (GetTimeFormat(LOCALE_USER_DEFAULT, 0, &t, "hh:mm:ss ", (LPSTR)szTxtTimeStampBuf, sizeof(szTxtTimeStampBuf) - 1) <= 0) + szTxtTimeStampBuf[0] = '\0'; + + if (_vsnwprintf_s(szTxtBuf, sizeof(szTxtBuf) - 1, lpszFormat, args) <= 0) + szTxtBuf[0] = L'\0'; + + if (g_fpLogFile == nullptr) + DebugOpenFile(); + + if (g_fpLogFile) + { + fprintf(g_fpLogFile, szTxtTimeStampBuf); + fwprintf(g_fpLogFile, szTxtBuf); + fprintf(g_fpLogFile, "\n"); + DebugCloseFile(); + } + } + catch (...) + { + // Do nothing + } + + va_end(args); } -void DebugPrintCloseFile() +void DebugClearFile() { - FILE* fpDebugLogFile = g_fpDebugLogFile; - g_fpDebugLogFile = nullptr; - if (fpDebugLogFile != nullptr) fclose(fpDebugLogFile); + DebugCloseFile(); + DebugOpenFile(true); } + +#if USE_DEBUG == 1 + void DebugDumpBuffer(byte* pBuffer, int iPreOffset = 0, int iBytesToDump = 64) { char txtBuffer[64]; @@ -253,42 +340,190 @@ void DebugDumpBuffer(byte* pBuffer, int iPreOffset = 0, int iBytesToDump = 64) } } -void DebugDumpBufferToScreen(byte* pBuffer, int iPreOffset = 0, int iBytesToDump = 64, int posX = 850, int posY = 1) +#endif // USE_DEBUG + + + +//--------------------------------------------------------------------------------------------------------------- +HRESULT D3D9SavePixelsToFile32bppPBGRA(UINT width, UINT height, UINT stride, LPBYTE pixels, const std::wstring& filePath, const GUID& format, WICPixelFormatGUID pixelFormat); + +// Return GDI+ encoder classid. +// GetEncoderClsid method borrowed from Windows documentation (https://docs.microsoft.com/en-us/windows/win32/gdiplus/-gdiplus-retrieving-the-class-identifier-for-an-encoder-use) +int GdiPlusGetEncoderClsid(const WCHAR* format, CLSID* pClsid) { - WCHAR txtBuffer[64]; + UINT num = 0; // number of image encoders + UINT size = 0; // size of the image encoder array in bytes - D3DRECT rec = { posX, posY, posX + 440, posY + ((iBytesToDump / 8) * 20) }; - g_pRBRIDirect3DDevice9->Clear(1, &rec, D3DCLEAR_TARGET, D3DCOLOR_ARGB(255, 50, 50, 50), 0, 0); + Gdiplus::ImageCodecInfo* pImageCodecInfo = NULL; + Gdiplus::GetImageEncodersSize(&num, &size); + if (size == 0) + return -1; // Failure - byte* pBuffer2 = pBuffer - iPreOffset; - for (int idx = 0; idx < iBytesToDump; idx += 8) + pImageCodecInfo = (Gdiplus::ImageCodecInfo*)(malloc(size)); + if (pImageCodecInfo == NULL) + return -1; // Failure + + Gdiplus::GetImageEncoders(num, size, pImageCodecInfo); + for (UINT j = 0; j < num; ++j) { - int iAppendPos = 0; + if (wcscmp(pImageCodecInfo[j].MimeType, format) == 0) + { + *pClsid = pImageCodecInfo[j].Clsid; + free(pImageCodecInfo); + return j; // Success + } + } - iAppendPos += swprintf_s(txtBuffer + iAppendPos, COUNT_OF_ITEMS(txtBuffer) - iAppendPos, L"%04x ", idx); - for (int idx2 = 0; idx2 < 8; idx2++) - iAppendPos += swprintf_s(txtBuffer + iAppendPos, COUNT_OF_ITEMS(txtBuffer) - iAppendPos, L"%02x ", (byte)pBuffer2[idx + idx2]); + free(pImageCodecInfo); + return -1; // Failure +} - iAppendPos += swprintf_s(txtBuffer + iAppendPos, COUNT_OF_ITEMS(txtBuffer) - iAppendPos, L" | "); - for (int idx2 = 0; idx2 < 8; idx2++) - iAppendPos += swprintf_s(txtBuffer + iAppendPos, COUNT_OF_ITEMS(txtBuffer) - iAppendPos, L"%c", (pBuffer2[idx + idx2] < 32 || pBuffer2[idx + idx2] > 126) ? L'.' : pBuffer2[idx + idx2]); +// Save screenshot file using GDI services instead of DirectX services +HRESULT D3D9SavePixelsToFileGDI(const HWND hAppWnd, RECT wndCaptureRect, const std::wstring& outputFileName, const GUID& format) +{ + HRESULT hResult; + + LPSTR pBuf = nullptr; + HDC hdcAppWnd = nullptr; + HDC hdcMemory = nullptr; + HBITMAP hbmAppWnd = nullptr; + HANDLE hDIB = INVALID_HANDLE_VALUE; + + BITMAP hAppWndBitmap; + BITMAPINFOHEADER bmpInfoHeader; + BITMAPFILEHEADER bmpFileHeader; + + Gdiplus::Bitmap* gdiBitmap = nullptr; + + hResult = S_OK; + + try + { + hdcAppWnd = GetDC(hAppWnd); + if (hdcAppWnd) hdcMemory = CreateCompatibleDC(hdcAppWnd); + + if (!hdcMemory || !hdcAppWnd) hResult = E_INVALIDARG; + + if (SUCCEEDED(hResult) && wndCaptureRect.right == 0 && wndCaptureRect.bottom == 0 && wndCaptureRect.left == 0 && wndCaptureRect.right == 0) + { + // Capture rect all zeros. Take a screenshot of the whole D3D9 client window area (ie. RBR game content without WinOS wnd decorations) + if (GetClientRect(hAppWnd, &wndCaptureRect) == false) + hResult = E_INVALIDARG; + } + + if (SUCCEEDED(hResult)) hbmAppWnd = CreateCompatibleBitmap(hdcAppWnd, wndCaptureRect.right - wndCaptureRect.left, wndCaptureRect.bottom - wndCaptureRect.top); + if (!hbmAppWnd) hResult = E_INVALIDARG; + + HGDIOBJ hgdiResult = SelectObject(hdcMemory, hbmAppWnd); + if(hgdiResult == nullptr || hgdiResult == HGDI_ERROR) + hResult = E_INVALIDARG; + + if (!BitBlt(hdcMemory, 0, 0, wndCaptureRect.right - wndCaptureRect.left, wndCaptureRect.bottom - wndCaptureRect.top, hdcAppWnd, wndCaptureRect.left, wndCaptureRect.top, SRCCOPY | CAPTUREBLT)) + hResult = E_INVALIDARG; + + ZeroMemory(&hAppWndBitmap, sizeof(BITMAP)); + if (SUCCEEDED(hResult) && GetObject(hbmAppWnd, sizeof(BITMAP), &hAppWndBitmap) == 0) + hResult = E_INVALIDARG; + + ZeroMemory(&bmpInfoHeader, sizeof(BITMAPINFOHEADER)); + bmpInfoHeader.biSize = sizeof(BITMAPINFOHEADER); + bmpInfoHeader.biWidth = hAppWndBitmap.bmWidth; + bmpInfoHeader.biHeight = hAppWndBitmap.bmHeight; + bmpInfoHeader.biPlanes = 1; + bmpInfoHeader.biBitCount = hAppWndBitmap.bmBitsPixel; // 32; + bmpInfoHeader.biCompression = BI_RGB; - pFontDebug->DrawText(posX, (idx / 8) * 20 + posY, D3DCOLOR_ARGB(255, 240, 250, 0), txtBuffer, 0); - //pFontDebug->DrawText(posX, (idx / 8) * 20 + posY, D3DCOLOR_ARGB(255, 240, 250, 0), txtBuffer, 0); + DWORD dwBmpSize = ((hAppWndBitmap.bmWidth * bmpInfoHeader.biBitCount + 31) / 32) * 4 * hAppWndBitmap.bmHeight; + + if (SUCCEEDED(hResult)) hDIB = GlobalAlloc(GHND, dwBmpSize); + if (hDIB == nullptr) hResult = E_INVALIDARG; + + if (SUCCEEDED(hResult)) pBuf = (LPSTR)GlobalLock(hDIB); + if (pBuf == nullptr) hResult = E_INVALIDARG; + + if (SUCCEEDED(hResult) && GetDIBits(hdcAppWnd, hbmAppWnd, 0, (UINT)hAppWndBitmap.bmHeight, pBuf, (BITMAPINFO*)&bmpInfoHeader, DIB_RGB_COLORS) == 0) + hResult = E_INVALIDARG; + + if (SUCCEEDED(hResult)) + // Remove potentially existing output file + ::_wremove(outputFileName.c_str()); + + // BMP or PNG output file format + if (SUCCEEDED(hResult) && format == GUID_ContainerFormatBmp) + { + // Save BMP file + std::ofstream outFile(outputFileName, std::ofstream::binary | std::ios::out); + if (outFile) + { + bmpFileHeader.bfOffBits = (DWORD)sizeof(BITMAPFILEHEADER) + (DWORD)sizeof(BITMAPINFOHEADER); + bmpFileHeader.bfSize = dwBmpSize + sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER); + bmpFileHeader.bfType = 0x4D42; // BM file type tag in little endian mode + + outFile.write((LPCSTR)&bmpFileHeader, sizeof(BITMAPFILEHEADER)); + outFile.write((LPCSTR)&bmpInfoHeader, sizeof(BITMAPINFOHEADER)); + outFile.write(pBuf, dwBmpSize); + outFile.flush(); + outFile.close(); + } + } + else if (SUCCEEDED(hResult)) + { + // Save PNG file + CLSID encoderClsid; + Gdiplus::GdiplusStartupInput gdiplusStartupInput; + ULONG_PTR gdiplusToken = 0; + + try + { + Gdiplus::GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL); + + if (gdiplusToken != 0 && GdiPlusGetEncoderClsid(L"image/png", &encoderClsid) >= 0) + { + BITMAPINFO binfo; + ZeroMemory(&binfo, sizeof(binfo)); + binfo.bmiHeader = bmpInfoHeader; + binfo.bmiHeader.biSize = sizeof(binfo); + binfo.bmiHeader.biSizeImage = dwBmpSize; + + gdiBitmap = Gdiplus::Bitmap::FromBITMAPINFO(&binfo, pBuf); + if (gdiBitmap != nullptr) + gdiBitmap->Save(outputFileName.c_str(), &encoderClsid); + } + } + catch(...) + { + LogPrint("ERROR D3D9SavePixelsToFileGDI. %s GDI failed to create file", outputFileName.c_str()); + } + + SAFE_DELETE(gdiBitmap); + if(gdiplusToken != 0) Gdiplus::GdiplusShutdown(gdiplusToken); + } + } + catch (...) + { + hResult = E_INVALIDARG; + LogPrint("ERROR D3D9SavePixelsToFileGDI. %s failed to create the file", outputFileName.c_str()); } -} -#endif // USE_DEBUG + if (hDIB != INVALID_HANDLE_VALUE) + { + GlobalUnlock(hDIB); + GlobalFree(hDIB); + } + if (hbmAppWnd) DeleteObject(hbmAppWnd); + if (hdcMemory) DeleteObject(hdcMemory); + if (hdcAppWnd) ReleaseDC(hAppWnd, hdcAppWnd); + return hResult; +} -//--------------------------------------------------------------------------------------------------------------- // // Save bitmap buffer (taken from D3D9 surface) as PNG file // -HRESULT D3D9SavePixelsToFile32bppPBGRA(UINT width, UINT height, UINT stride, LPBYTE pixels, const std::wstring& filePath, const GUID& format) +HRESULT D3D9SavePixelsToFile32bppPBGRA(UINT width, UINT height, UINT stride, LPBYTE pixels, const std::wstring& filePath, const GUID& format, WICPixelFormatGUID pixelFormat) { if (!pixels || filePath.empty()) return E_INVALIDARG; @@ -300,7 +535,8 @@ HRESULT D3D9SavePixelsToFile32bppPBGRA(UINT width, UINT height, UINT stride, LPB IWICBitmapEncoder* encoder = nullptr; IWICBitmapFrameEncode* frame = nullptr; IWICStream* stream = nullptr; - GUID pf = GUID_WICPixelFormat32bppPBGRA; + //GUID pf = GUID_WICPixelFormat32bppPBGRA; // DX9 surface bitmap format + //GUID pf = GUID_WICPixelFormat24bppBGR; ¨ // GDI bitmap format try { @@ -321,7 +557,7 @@ HRESULT D3D9SavePixelsToFile32bppPBGRA(UINT width, UINT height, UINT stride, LPB if (SUCCEEDED(hResult)) hResult = frame->Initialize(nullptr); if (SUCCEEDED(hResult)) hResult = frame->SetSize(width, height); - if (SUCCEEDED(hResult)) hResult = frame->SetPixelFormat(&pf); + if (SUCCEEDED(hResult)) hResult = frame->SetPixelFormat(&pixelFormat); if (SUCCEEDED(hResult)) hResult = frame->WritePixels(height, stride, stride * height, pixels); if (SUCCEEDED(hResult)) hResult = frame->Commit(); @@ -345,7 +581,8 @@ HRESULT D3D9SavePixelsToFile32bppPBGRA(UINT width, UINT height, UINT stride, LPB // -// Take D3D9 screenshot of RBR screen (the whole game screen or specified capture rectangle only) and save it as PNG file +// Take D3D9 screenshot of RBR screen (the whole game screen or specified capture rectangle only) and save it as PNG file. +// If the outputFileName extension defines the file format (.PNG or .BMP) // HRESULT D3D9SaveScreenToFile(const LPDIRECT3DDEVICE9 pD3Device, const HWND hAppWnd, RECT wndCaptureRect, const std::wstring& outputFileName) { @@ -358,42 +595,63 @@ HRESULT D3D9SaveScreenToFile(const LPDIRECT3DDEVICE9 pD3Device, const HWND hAppW D3DLOCKED_RECT rc; UINT pitch; - if (pD3Device == nullptr || outputFileName.empty()) + GUID cf; // GUID_ContainerFormatPng or GUID_ContainerFormatBmp + + if (/*pD3Device == nullptr ||*/ outputFileName.empty()) return E_INVALIDARG; try { + cf = GUID_ContainerFormatPng; + if (fs::path(outputFileName).extension() == ".bmp") + cf = GUID_ContainerFormatBmp; + + // If DX9 device is NULL then save the output image file using GDI bitmap data instead of using DX9 framebuffer data + if (pD3Device == nullptr) + return D3D9SavePixelsToFileGDI(hAppWnd, wndCaptureRect, outputFileName, cf); + + // Reading bitmap via DirectX frame buffers + DebugPrint("D3D9SaveScreenToFile DirectX buffer"); + //d3d = Direct3DCreate9(D3D_SDK_VERSION); //d3d->GetAdapterDisplayMode(adapter, &mode) pD3Device->GetDirect3D(&d3d); hResult = d3d->GetAdapterDisplayMode(D3DADAPTER_DEFAULT, &mode); - if (hResult == S_OK) hResult = pD3Device->CreateOffscreenPlainSurface(mode.Width, mode.Height, D3DFMT_A8R8G8B8, D3DPOOL_SYSTEMMEM, &surface, nullptr); + if (SUCCEEDED(hResult)) hResult = pD3Device->CreateOffscreenPlainSurface(mode.Width, mode.Height, D3DFMT_A8R8G8B8, D3DPOOL_SCRATCH /*D3DPOOL_SYSTEMMEM*/, &surface, nullptr); - if (wndCaptureRect.right == 0 && wndCaptureRect.bottom == 0 && wndCaptureRect.left == 0 && wndCaptureRect.right == 0) + if (SUCCEEDED(hResult) && wndCaptureRect.right == 0 && wndCaptureRect.bottom == 0 && wndCaptureRect.left == 0 && wndCaptureRect.right == 0) { // Capture rect all zeros. Take a screenshot of the whole D3D9 client window area (ie. RBR game content without WinOS wnd decorations) - GetClientRect(hAppWnd, &wndCaptureRect); - MapWindowPoints(hAppWnd, NULL, (LPPOINT)&wndCaptureRect, 2); + if(!GetClientRect(hAppWnd, &wndCaptureRect)) + // MapWindowPoints(hAppWnd, NULL, (LPPOINT)&wndCaptureRect, 2); + //else + hResult = E_INVALIDARG; } + if (SUCCEEDED(hResult)) MapWindowPoints(hAppWnd, NULL, (LPPOINT)&wndCaptureRect, 2); + // Compute the required bitmap buffer size and allocate it rc.Pitch = 4; - if (hResult == S_OK) hResult = surface->LockRect(&rc, &wndCaptureRect, 0); + if (SUCCEEDED(hResult)) hResult = surface->LockRect(&rc, &wndCaptureRect, /*0*/ D3DLOCK_NO_DIRTY_UPDATE | D3DLOCK_NOSYSLOCK | D3DLOCK_READONLY); pitch = rc.Pitch; - if (hResult == S_OK) hResult = surface->UnlockRect(); - screenshotBuffer = new BYTE[pitch * (wndCaptureRect.bottom - wndCaptureRect.top) /* img height */]; + if (SUCCEEDED(hResult)) hResult = surface->UnlockRect(); - if (hResult == S_OK) hResult = pD3Device->GetFrontBufferData(0, surface); - // fullscreen screenshot + if (SUCCEEDED(hResult)) screenshotBuffer = new BYTE[pitch * (wndCaptureRect.bottom - wndCaptureRect.top) /* img height */]; + if(screenshotBuffer == nullptr) hResult = E_INVALIDARG; + + if (SUCCEEDED(hResult)) hResult = pD3Device->GetFrontBufferData(0, surface); + + // DX fullscreen screenshot (Note! Doesn't work, so run RBR in DX9 window mode, RichardBurnsRally.ini fullsreen=false and use Fixup plugin to set fullscreen wnd mode) // g_pRBRIDirect3DDevice9->GetBackBuffer(0, 0, D3DBACKBUFFER_TYPE_MONO, &surface) // Copy frame to byte buffer - if (hResult == S_OK) hResult = surface->LockRect(&rc, &wndCaptureRect, D3DLOCK_READONLY | D3DLOCK_NOSYSLOCK); - if (hResult == S_OK) CopyMemory(screenshotBuffer, rc.pBits, rc.Pitch * (wndCaptureRect.bottom - wndCaptureRect.top) /* img height */); - if (hResult == S_OK) hResult = surface->UnlockRect(); + if (SUCCEEDED(hResult)) hResult = surface->LockRect(&rc, &wndCaptureRect, D3DLOCK_NO_DIRTY_UPDATE | D3DLOCK_NOSYSLOCK | D3DLOCK_READONLY ); + if (SUCCEEDED(hResult)) CopyMemory(screenshotBuffer, rc.pBits, rc.Pitch * (wndCaptureRect.bottom - wndCaptureRect.top) /* img height */); + if (SUCCEEDED(hResult)) hResult = surface->UnlockRect(); + + if (SUCCEEDED(hResult)) hResult = D3D9SavePixelsToFile32bppPBGRA((wndCaptureRect.right - wndCaptureRect.left) /* img width */, (wndCaptureRect.bottom - wndCaptureRect.top) /* img height */, pitch, screenshotBuffer, outputFileName, cf, GUID_WICPixelFormat32bppPBGRA); - if (hResult == S_OK) hResult = D3D9SavePixelsToFile32bppPBGRA((wndCaptureRect.right - wndCaptureRect.left) /* img width */, (wndCaptureRect.bottom - wndCaptureRect.top) /* img height */, pitch, screenshotBuffer, outputFileName, GUID_ContainerFormatPng); //SavePixelsToFile32bppPBGRA(..., GUID_ContainerFormatDds) //SavePixelsToFile32bppPBGRA(..., GUID_ContainerFormatJpeg) } diff --git a/src/D3D9Helpers.h b/src/D3D9Helpers.h index 0490e72..349077f 100644 --- a/src/D3D9Helpers.h +++ b/src/D3D9Helpers.h @@ -36,22 +36,18 @@ #undef USE_DEBUG #endif - #if USE_DEBUG == 1 -#include "D3D9Font\D3DFont.h" - #define DebugPrint DebugPrintFunc // DEBUG version to dump logfile messages #else #define DebugPrint // RELEASE version of DebugPrint doing nothing #endif -#if USE_DEBUG == 1 -extern CD3DFont* pFontDebug; +#define LogPrint DebugPrintFunc // LogPrint prints out a txt message both in retail and debug builds -extern void DebugPrintCloseFile(); -extern void DebugPrintEmptyFile(); +extern void DebugCloseFile(); +extern void DebugClearFile(); extern void DebugPrintFunc(LPCSTR lpszFormat, ...); -#endif +extern void DebugPrintFunc(LPCWSTR lpszFormat, ...); //------------------------------------------------------------------------------------------------ @@ -71,6 +67,8 @@ extern void _Trim(std::wstring & s); // Trim wstring (in-place, modify the s) extern std::wstring _ToWString(const std::string & s); // Convert std::string to std::wstring extern std::string _ToString(const std::wstring & s); // Convert std::wstring to std:string +extern inline void _ToLowerCase(std::string & s); // Convert string to lowercase letters (in-place, so the original str in the parameter is converted) +extern inline void _ToLowerCase(std::wstring & s); // Convert wstring to lowercase letters (in-place) extern bool _StringToRect(const std::wstring & s, RECT * outRect, const wchar_t separatorChar = L' '); // String in "0 50 200 400" format is converted as RECT struct value @@ -114,8 +112,8 @@ typedef CUSTOM_VERTEX_3D PCUSTOM_VERTEX_3D; typedef struct { - IDirect3DTexture9* pTexture; // D3D9 texture (fex loaded from a PNG file) - CUSTOM_VERTEX_TEX_2D vertexes2D[4]; // Rectangle vertex at specified pixel position and size + IDirect3DTexture9* pTexture; // D3D9 texture (fex loaded from a PNG or BMP file) + CUSTOM_VERTEX_TEX_2D vertexes2D[4]; // Rectangle vertex at specified pixel position and size SIZE imgSize; // The original size of the loaded PNG file image } IMAGE_TEXTURE; // RBR car preview picture (texture and rect vertex) typedef IMAGE_TEXTURE* PIMAGE_TEXTURE; @@ -136,7 +134,7 @@ extern void D3D9DrawVertex2D(const LPDIRECT3DDEVICE9 pD3Device, const LPDIREC // https://stackoverflow.com/questions/30021274/capture-screen-using-directx (Simon Mourier) // Borrowed code snippets about converting DX9 surface to bitmap array and then converting and saving in PNG/DDS file format. Modified to work with RBR plugin. // -// Contains also code snippets about creating an empty D3D9 surface and then populating it with bitmap data coming from, for example, external PNG file (Thanks Ivan for an idea of using empty surface at first). +// Contains also code snippets about creating an empty D3D9 surface and then populating it with bitmap data coming from, for example, external PNG/BMP file (Thanks Ivan for an idea of using empty surface at first). // The code and idea borrowed from https://stackoverflow.com/questions/16172508/how-can-i-create-texture-in-directx-9-without-d3dx9 (Ivan Aksamentov). // Modified the code snipped to work with RBR plugin. // diff --git a/src/NGPCarMenu.cpp b/src/NGPCarMenu.cpp index c12ba17..b6572a6 100644 --- a/src/NGPCarMenu.cpp +++ b/src/NGPCarMenu.cpp @@ -28,7 +28,7 @@ #include // fs::directory_iterator #include // std::ifstream - +#include // std::chrono::steady_clock #include // GUID_ContainerFormatPng #include "NGPCarMenu.h" @@ -46,10 +46,10 @@ tRBRDirectXBeginScene Func_OrigRBRDirectXBeginScene = nullptr; // Re-routed bui tRBRDirectXEndScene Func_OrigRBRDirectXEndScene = nullptr; #if USE_DEBUG == 1 -CD3DFont* pFontDebug = nullptr; +CD3DFont* g_pFontDebug = nullptr; #endif -CD3DFont* pFontCarSpecCustom = nullptr; +CD3DFont* g_pFontCarSpecCustom = nullptr; CNGPCarMenu* g_pRBRPlugin = nullptr; // The one and only RBRPlugin instance @@ -102,10 +102,18 @@ RBRCarSelectionMenuEntry g_RBRCarSelectionMenuEntry[8] = { }; -// Menu item names (custom plugin menu) -char* g_RBRPluginMenu[3] = { +// +// Menu item command ID and names (custom plugin menu). The ID should match the g_RBRPluginMenu array index (0...n) +// +#define C_MENUCMD_RELOAD 0 +#define C_MENUCMD_CREATEOPTION 1 +#define C_MENUCMD_IMAGEOPTION 2 +#define C_MENUCMD_CREATE 3 + +char* g_RBRPluginMenu[4] = { "RELOAD car images" // Clear cached car images to force re-loading of new images ,"Create option" // CreateOptions + ,"Image option" // CreateOptions ,"CREATE car images" // Create new car images (all or only missing car iamges) }; @@ -115,6 +123,42 @@ char* g_RBRPluginMenu_CreateOptions[2] = { ,"All car images" }; +// Image output option +char* g_RBRPluginMenu_ImageOptions[2] = { + "PNG" + ,"BMP" +}; + + +//----------------------------------------------------------------------------------------------------------------------------- +#if USE_DEBUG == 1 +void DebugDumpBufferToScreen(byte* pBuffer, int iPreOffset = 0, int iBytesToDump = 64, int posX = 850, int posY = 1) +{ + WCHAR txtBuffer[64]; + + D3DRECT rec = { posX, posY, posX + 440, posY + ((iBytesToDump / 8) * 20) }; + g_pRBRIDirect3DDevice9->Clear(1, &rec, D3DCLEAR_TARGET, D3DCOLOR_ARGB(255, 50, 50, 50), 0, 0); + + byte* pBuffer2 = pBuffer - iPreOffset; + for (int idx = 0; idx < iBytesToDump; idx += 8) + { + int iAppendPos = 0; + + iAppendPos += swprintf_s(txtBuffer + iAppendPos, COUNT_OF_ITEMS(txtBuffer) - iAppendPos, L"%04x ", idx); + for (int idx2 = 0; idx2 < 8; idx2++) + iAppendPos += swprintf_s(txtBuffer + iAppendPos, COUNT_OF_ITEMS(txtBuffer) - iAppendPos, L"%02x ", (byte)pBuffer2[idx + idx2]); + + iAppendPos += swprintf_s(txtBuffer + iAppendPos, COUNT_OF_ITEMS(txtBuffer) - iAppendPos, L" | "); + + for (int idx2 = 0; idx2 < 8; idx2++) + iAppendPos += swprintf_s(txtBuffer + iAppendPos, COUNT_OF_ITEMS(txtBuffer) - iAppendPos, L"%c", (pBuffer2[idx + idx2] < 32 || pBuffer2[idx + idx2] > 126) ? L'.' : pBuffer2[idx + idx2]); + + g_pFontDebug->DrawText(posX, (idx / 8) * 20 + posY, D3DCOLOR_ARGB(255, 240, 250, 0), txtBuffer, 0); + //pFontDebug->DrawText(posX, (idx / 8) * 20 + posY, D3DCOLOR_ARGB(255, 240, 250, 0), txtBuffer, 0); + } +} +#endif + //----------------------------------------------------------------------------------------------------------------------------- BOOL APIENTRY DllMain( HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) @@ -125,14 +169,11 @@ BOOL APIENTRY DllMain( HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpRese IPlugin* RBR_CreatePlugin( IRBRGame* pGame ) { #if USE_DEBUG == 1 - DebugPrintEmptyFile(); + DebugClearFile(); #endif - DebugPrint("--------------------------------------------\nEnter RBR_CreatePlugin"); + DebugPrint("--------------------------------------------\nRBR_CreatePlugin"); if (g_pRBRPlugin == nullptr) g_pRBRPlugin = new CNGPCarMenu(pGame); - - DebugPrint("Exit RBR_CreatePlugin"); - return g_pRBRPlugin; } @@ -165,6 +206,9 @@ CNGPCarMenu::CNGPCarMenu(IRBRGame* pGame) m_pGame = pGame; m_iMenuSelection = 0; m_iMenuCreateOption = 0; + m_iMenuImageOption = 0; + + m_screenshotAPIType = C_SCREENSHOTAPITYPE_DIRECTX; RefreshSettingsFromPluginINIFile(); InitCarSpecData(); @@ -184,10 +228,10 @@ CNGPCarMenu::~CNGPCarMenu(void) if (gtcDirect3DEndScene != nullptr) delete gtcDirect3DEndScene; #if USE_DEBUG == 1 - if (pFontDebug != nullptr) delete pFontDebug; + if (g_pFontDebug != nullptr) delete g_pFontDebug; #endif - if (pFontCarSpecCustom != nullptr) delete pFontCarSpecCustom; + if (g_pFontCarSpecCustom != nullptr) delete g_pFontCarSpecCustom; SAFE_RELEASE(m_screenshotCroppingRectVertexBuffer); ClearCachedCarPreviewImages(); @@ -195,6 +239,7 @@ CNGPCarMenu::~CNGPCarMenu(void) if (g_pRBRPlugin == this) g_pRBRPlugin = nullptr; DebugPrint("Exit CNGPCarMenu.Destructor"); + DebugCloseFile(); } void CNGPCarMenu::ClearCachedCarPreviewImages() @@ -212,6 +257,8 @@ void CNGPCarMenu::RefreshSettingsFromPluginINIFile() { WCHAR szResolutionText[16]; + DebugPrint("Enter CNGPCarMenu.RefreshSettingsFromPluginINIFile"); + CSimpleIniW pluginINIFile; std::wstring sTextValue; @@ -221,11 +268,12 @@ void CNGPCarMenu::RefreshSettingsFromPluginINIFile() this->m_screenshotReplayFileName = pluginINIFile.GetValue(L"Default", L"ScreenshotReplay", L""); _Trim(this->m_screenshotReplayFileName); + RBRAPI_RefreshWndRect(); swprintf_s(szResolutionText, COUNT_OF_ITEMS(szResolutionText), L"%dx%d", g_rectRBRWndClient.right, g_rectRBRWndClient.bottom); this->m_screenshotPath = pluginINIFile.GetValue(L"Default", L"ScreenshotPath", L""); _Trim(this->m_screenshotPath); - if (this->m_screenshotPath.length() >= 2 && this->m_screenshotPath[0] != '\\' && this->m_screenshotPath[1] != ':') + if (this->m_screenshotPath.length() >= 2 && this->m_screenshotPath[0] != '\\' && this->m_screenshotPath[1] != ':' && g_rectRBRWndClient.right > 0 && g_rectRBRWndClient.bottom > 0) { this->m_screenshotPath = this->m_sRBRRootDirW + L"\\" + this->m_screenshotPath + L"\\" + szResolutionText; @@ -237,8 +285,7 @@ void CNGPCarMenu::RefreshSettingsFromPluginINIFile() } catch (...) { - // Do nothing. - // TODO. Add some error logging to release build + LogPrint("ERROR CNGPCarMenu.RefreshSettingsFromPluginINIFile. %s folder creation failed", this->m_screenshotPath.c_str()); } } @@ -259,8 +306,56 @@ void CNGPCarMenu::RefreshSettingsFromPluginINIFile() sTextValue = pluginINIFile.GetValue(szResolutionText, L"CarSelectRightBlackBar", L""); _StringToRect(sTextValue, &this->m_carSelectRightBlackBarRect); + sTextValue = pluginINIFile.GetValue(L"Default", L"ScreenshotAPIType", L"0"); + this->m_screenshotAPIType = std::stoi(sTextValue); + + // Screenshot image format option. One of the values in g_RBRPluginMenu_ImageOptions array (the default is the item in the first index) + this->m_iMenuImageOption = 0; + sTextValue = pluginINIFile.GetValue(L"Default", L"ScreenshotFileType", L""); + _Trim(sTextValue); + for (int idx = 0; idx < COUNT_OF_ITEMS(g_RBRPluginMenu_ImageOptions); idx++) + { + std::string sOptionValue; + std::wstring wsOptionValue; + sOptionValue = g_RBRPluginMenu_ImageOptions[idx]; + wsOptionValue = _ToWString(sOptionValue); + + if (_wcsnicmp(sTextValue.c_str(), wsOptionValue.c_str(), wsOptionValue.length()) == 0) + { + this->m_iMenuImageOption = idx; + break; + } + } + m_sMenuStatusText1 = _ToString(m_screenshotPath); m_sMenuStatusText2 = _ToString(std::wstring(szResolutionText)) + " native resolution detected"; + + DebugPrint("Exit CNGPCarMenu.RefreshSettingsFromPluginINIFile"); +} + +void CNGPCarMenu::SaveSettingsToPluginINIFile() +{ + // Screenshot image format option. One of the values in g_RBRPluginMenu_ImageOptions array (the default is the item in the first index) + std::string sIniFileName; + std::string sOptionValue; + std::wstring wsOptionValue; + CSimpleIniW pluginINIFile; + + try + { + sIniFileName = CNGPCarMenu::m_sRBRRootDir + "\\Plugins\\NGPCarMenu.ini"; + pluginINIFile.LoadFile(sIniFileName.c_str()); + + sOptionValue = g_RBRPluginMenu_ImageOptions[this->m_iMenuImageOption]; + wsOptionValue = _ToWString(sOptionValue); + + pluginINIFile.SetValue(L"Default", L"ScreenshotFileType", wsOptionValue.c_str()); + pluginINIFile.SaveFile(sIniFileName.c_str()); + } + catch (...) + { + LogPrint("ERROR CNGPCarMenu.SaveSettingsToPluginINIFile. %s INI writing failed", sIniFileName.c_str()); + } } @@ -275,7 +370,14 @@ void CNGPCarMenu::RefreshSettingsFromPluginINIFile() // void CNGPCarMenu::InitCarSpecData() { - CSimpleIniW* ngpCarListINIFile = nullptr; + DebugPrint("Enter CNGPCarMenu.InitCarSpecData"); + + CSimpleIniW ngpCarListINIFile; + CSimpleIniW customCarSpecsINIFile; + + CSimpleIniW stockCarListINIFile; + std::wstring sStockCarModelName; + static const char* szPhysicsCarFolder[8] = { "s_i2003" , "m_lancer", "t_coroll", "h_accent", "p_206", "s_i2000", "c_xsara", "mg_zr" }; std::string sPath; @@ -283,32 +385,61 @@ void CNGPCarMenu::InitCarSpecData() try { - int iNumOfGears = -1; + int iNumOfGears; - ngpCarListINIFile = new CSimpleIniW(); if (fs::exists(m_rbrCITCarListFilePath)) - ngpCarListINIFile->LoadFile(m_rbrCITCarListFilePath.c_str()); + ngpCarListINIFile.LoadFile(m_rbrCITCarListFilePath.c_str()); else // Add warning about missing RBRCIT carList.ini file wcsncpy_s(g_RBRCarSelectionMenuEntry[0].wszCarPhysicsCustomTxt, (m_rbrCITCarListFilePath + L" missing. Cannot show car specs").c_str(), COUNT_OF_ITEMS(g_RBRCarSelectionMenuEntry[0].wszCarPhysicsCustomTxt)); + // Load std RBR cars list and custom carSpec file (fex original car specs are set here). These are used if the phystics\ doesn't have NGP car description file + customCarSpecsINIFile.LoadFile((m_sRBRRootDirW + L"\\Plugins\\NGPCarMenu\\CustomCarSpecs.ini").c_str()); + stockCarListINIFile.LoadFile((m_sRBRRootDirW + L"\\cars\\Cars.ini").c_str()); + for (int idx = 0; idx < 8; idx++) { + iNumOfGears = -1; sPath = CNGPCarMenu::m_sRBRRootDir + "\\physics\\" + szPhysicsCarFolder[idx]; - if (InitCarSpecDataFromPhysicsFile(sPath, &g_RBRCarSelectionMenuEntry[idx], &iNumOfGears)) - InitCarSpecDataFromNGPFile(ngpCarListINIFile, &g_RBRCarSelectionMenuEntry[idx], iNumOfGears); + + if (!InitCarSpecDataFromPhysicsFile(sPath, &g_RBRCarSelectionMenuEntry[idx], &iNumOfGears)) + { + // Desc file of NGP car models missing in physics\ folder. Check if this is a stock original car model (ie. those don't have NGP desc file) + stockCarListINIFile.LoadFile((m_sRBRRootDirW + L"\\cars\\Cars.ini").c_str()); + + WCHAR wszStockCarINISection[8]; + swprintf_s(wszStockCarINISection, COUNT_OF_ITEMS(wszStockCarINISection), L"Car0%d", RBRAPI_MenuIdxToCarID(idx)); + + sStockCarModelName = stockCarListINIFile.GetValue(wszStockCarINISection, L"CarName", L""); + _Trim(sStockCarModelName); + + if (sStockCarModelName.length() >= 4 && sStockCarModelName[sStockCarModelName.length() - 3] == L'(' && sStockCarModelName[sStockCarModelName.length() - 1] == L')') + { + sStockCarModelName.erase(sStockCarModelName.length() - 3); + _Trim(sStockCarModelName); + } + + if(!sStockCarModelName.empty()) + wcsncpy_s(g_RBRCarSelectionMenuEntry[idx].wszCarModel, sStockCarModelName.c_str(), COUNT_OF_ITEMS(g_RBRCarSelectionMenuEntry[idx].wszCarModel)); + } + + // NGP car model or stock car model lookup to RBCIT/carList/carList.ini file. If the car specs are missing then try to use NGPCarMenu custom carspec INI file + if (!InitCarSpecDataFromNGPFile(&ngpCarListINIFile, &g_RBRCarSelectionMenuEntry[idx], iNumOfGears)) + if (InitCarSpecDataFromNGPFile(&customCarSpecsINIFile, &g_RBRCarSelectionMenuEntry[idx], iNumOfGears)) + g_RBRCarSelectionMenuEntry[idx].wszCarPhysics3DModel[0] = L'\0'; // Clear warning about missing NGP car desc file } } catch (const fs::filesystem_error& ex) { - DebugPrint("ERROR in InitCarSpecData. %s", ex.what()); + LogPrint("ERROR CNGPCarMenu.InitCarSpecData. %s %s", m_rbrCITCarListFilePath.c_str(), ex.what()); } catch (...) { // Hmmm... Something went wrong + LogPrint("ERROR CNGPCarMenu.InitCarSpecData. %s reading failed", m_rbrCITCarListFilePath.c_str()); } - SAFE_DELETE(ngpCarListINIFile); + DebugPrint("Exit CNGPCarMenu.InitCarSpecData"); } @@ -317,6 +448,8 @@ void CNGPCarMenu::InitCarSpecData() // bool CNGPCarMenu::InitCarSpecDataFromPhysicsFile(const std::string &folderName, PRBRCarSelectionMenuEntry pRBRCarSelectionMenuEntry, int* outNumOfGears) { + DebugPrint("Enter CNGPCarMenu.InitCarSpecDataFromPhysicsFile"); + const fs::path fsFolderName(folderName); std::wstring wfsFileName; @@ -350,6 +483,8 @@ bool CNGPCarMenu::InitCarSpecDataFromPhysicsFile(const std::string &folderName, try { *outNumOfGears = StrToIntW(sTextLine.substr(14).c_str()); + if (*outNumOfGears > 2) + *outNumOfGears -= 2; // Minus reverse and neutral gears because NGP data includes those in NumberOfGears value } catch (...) { @@ -360,10 +495,10 @@ bool CNGPCarMenu::InitCarSpecDataFromPhysicsFile(const std::string &folderName, } } - // Find all files without file extensions and see it the file is the NGP car model file + // Find all files without file extensions and see it the file is the NGP car model file (revison/specification date/3d model keyword in the beginning of line) for (const auto& entry : fs::directory_iterator(fsFolderName)) { - if (entry.is_regular_file() && entry.path().extension().compare("") == 0) + if (entry.is_regular_file() && entry.path().extension().compare(".lsp") != 0 ) { fsFileName = entry.path().filename().string(); //DebugPrint(fsFileName.c_str()); @@ -437,20 +572,22 @@ bool CNGPCarMenu::InitCarSpecDataFromPhysicsFile(const std::string &folderName, { std::wstring wFolderName = _ToWString(folderName); // Show warning that RBRCIT/NGP carModel file is missing - wcsncpy_s(pRBRCarSelectionMenuEntry->wszCarPhysics3DModel, (wFolderName + L"\\ NGP file missing").c_str(), COUNT_OF_ITEMS(pRBRCarSelectionMenuEntry->wszCarPhysics3DModel)); + wcsncpy_s(pRBRCarSelectionMenuEntry->wszCarPhysics3DModel, (wFolderName + L"\\ NGP model description file missing").c_str(), COUNT_OF_ITEMS(pRBRCarSelectionMenuEntry->wszCarPhysics3DModel)); } - } catch (const fs::filesystem_error& ex) { - DebugPrint("ERROR in InitCarSpecDataFromPhysicsFile. %s", ex.what()); + DebugPrint("ERROR CNGPCarMenu.InitCarSpecDataFromPhysicsFile. %s %s", folderName.c_str(), ex.what()); bResult = FALSE; } catch(...) { + LogPrint("ERROR CNGPCarMenu.InitCarSpecDataFromPhysicsFile. %s folder doesn't have or error while reading NGP model spec file", folderName.c_str()); bResult = FALSE; } + DebugPrint("Exit CNGPCarMenu.InitCarSpecDataFromPhysicsFile"); + return bResult; } @@ -461,8 +598,12 @@ bool CNGPCarMenu::InitCarSpecDataFromPhysicsFile(const std::string &folderName, // bool CNGPCarMenu::InitCarSpecDataFromNGPFile(CSimpleIniW* ngpCarListINIFile, PRBRCarSelectionMenuEntry pRBRCarSelectionMenuEntry, int numOfGears) { + bool bResult = FALSE; + + DebugPrint("Enter CNGPCarMenu.InitCarSpecDataFromNGPFile"); + if (ngpCarListINIFile == nullptr || pRBRCarSelectionMenuEntry->wszCarModel[0] == '\0') - return FALSE; + return bResult; try { @@ -487,15 +628,7 @@ bool CNGPCarMenu::InitCarSpecDataFromNGPFile(CSimpleIniW* ngpCarListINIFile, PRB //DebugPrint(iter->pItem); wszIniItemValue = ngpCarListINIFile->GetValue(iter->pItem, L"cat", nullptr); - //int len = wcstombs(pRBRCarSelectionMenuEntry->szCarCategory, szIniItemValue, COUNT_OF_ITEMS(pRBRCarSelectionMenuEntry->szCarCategory)); - wcstombs_s(/*&len*/ nullptr, pRBRCarSelectionMenuEntry->szCarCategory, sizeof(pRBRCarSelectionMenuEntry->szCarCategory), wszIniItemValue, _TRUNCATE); - - /*if (len == COUNT_OF_ITEMS(pRBRCarSelectionMenuEntry->szCarCategory)) - // Make sure the converted string is NULL terminated in case of truncation - pRBRCarSelectionMenuEntry->szCarCategory[--len] = '\0'; - */ - - //DebugPrint(szIniItemValue); + wcstombs_s(nullptr, pRBRCarSelectionMenuEntry->szCarCategory, sizeof(pRBRCarSelectionMenuEntry->szCarCategory), wszIniItemValue, _TRUNCATE); wszIniItemValue = ngpCarListINIFile->GetValue(iter->pItem, L"year", nullptr); wcsncpy_s(pRBRCarSelectionMenuEntry->wszCarYear, wszIniItemValue, COUNT_OF_ITEMS(pRBRCarSelectionMenuEntry->wszCarYear)); @@ -513,6 +646,7 @@ bool CNGPCarMenu::InitCarSpecDataFromNGPFile(CSimpleIniW* ngpCarListINIFile, PRB else wcsncpy_s(pRBRCarSelectionMenuEntry->wszCarTrans, wszIniItemValue, COUNT_OF_ITEMS(pRBRCarSelectionMenuEntry->wszCarTrans)); + bResult = TRUE; break; } @@ -524,10 +658,13 @@ bool CNGPCarMenu::InitCarSpecDataFromNGPFile(CSimpleIniW* ngpCarListINIFile, PRB catch (...) { // Hmmm...Something went wrong. - return FALSE; + LogPrint("ERROR CNGPCarMenu.InitCarSpecDataFromNGPFile. carList.ini INI file reading error"); + bResult = FALSE; } - return TRUE; + DebugPrint("Exit CNGPCarMenu.InitCarSpecDataFromNGPFile"); + + return bResult;; } @@ -596,10 +733,15 @@ int CNGPCarMenu::GetNextScreenshotCarID(int currentCarID) if (!m_screenshotPath.empty() && g_RBRCarSelectionMenuEntry[RBRAPI_MapCarIDToMenuIdx(currentCarID)].wszCarModel[0] != '\0') { + // If CreateOption is "only missing car images" then check the existence of output img file if (m_iMenuCreateOption == 0) { + std::string imgExtension = "."; + imgExtension = imgExtension + g_RBRPluginMenu_ImageOptions[m_iMenuImageOption]; + _ToLowerCase(imgExtension); + // If the output PNG preview image file already exists then don't re-generate the screenshot (menu option "Only missing car images") - outputFileName = m_screenshotPath + L"\\" + g_RBRCarSelectionMenuEntry[RBRAPI_MapCarIDToMenuIdx(currentCarID)].wszCarModel + L".png"; + outputFileName = m_screenshotPath + L"\\" + g_RBRCarSelectionMenuEntry[RBRAPI_MapCarIDToMenuIdx(currentCarID)].wszCarModel + _ToWString(imgExtension); if (fs::exists(outputFileName)) continue; } @@ -658,12 +800,12 @@ bool CNGPCarMenu::PrepareScreenshotReplayFile(int carID) } catch (const fs::filesystem_error& ex) { - DebugPrint("ERROR in PrepareScreenshotReplayFile. %s", ex.what()); + LogPrint("ERROR CNGPCarMenu::PrepareScreenshotReplayFile. carid=%d %s", carID, ex.what()); return FALSE; } catch (...) { - DebugPrint("ERROR in PrepareScreenshotReplayFile"); + LogPrint("ERROR CNGPCarMenu::PrepareScreenshotReplayFile. carid=%d", carID); return FALSE; } } @@ -686,29 +828,25 @@ const char* CNGPCarMenu::GetName(void) g_pRBRIDirect3DDevice9->GetCreationParameters(&d3dCreationParameters); g_hRBRWnd = d3dCreationParameters.hFocusWindow; - GetWindowRect(g_hRBRWnd, &g_rectRBRWnd); // Window size and position (including potential WinOS window decorations) - GetClientRect(g_hRBRWnd, &g_rectRBRWndClient); // The size or the D3D9 client area (without window decorations and left-top always 0) - CopyRect(&g_rectRBRWndMapped, &g_rectRBRWndClient); - MapWindowPoints(g_hRBRWnd, NULL, (LPPOINT)&g_rectRBRWndMapped, 2); // The client area mapped as physical screen position (left-top relative to screen) - + RBRAPI_RefreshWndRect(); // Pointer 0x493980 -> rbrHwnd? Can it be used to re-route WM messages to our own windows handler and this way to "listen" RBR key presses? RefreshSettingsFromPluginINIFile(); // Initialize D3D9 compatible font classes #if USE_DEBUG == 1 - pFontDebug = new CD3DFont(L"Courier New", 11, 0); - pFontDebug->InitDeviceObjects(g_pRBRIDirect3DDevice9); - pFontDebug->RestoreDeviceObjects(); + g_pFontDebug = new CD3DFont(L"Courier New", 11, 0); + g_pFontDebug->InitDeviceObjects(g_pRBRIDirect3DDevice9); + g_pFontDebug->RestoreDeviceObjects(); #endif int iFontSize = 12; if (g_rectRBRWndClient.bottom < 600) iFontSize = 7; else if (g_rectRBRWndClient.bottom < 768) iFontSize = 9; - pFontCarSpecCustom = new CD3DFont(L"Trebuchet MS", iFontSize, 0); - pFontCarSpecCustom->InitDeviceObjects(g_pRBRIDirect3DDevice9); - pFontCarSpecCustom->RestoreDeviceObjects(); + g_pFontCarSpecCustom = new CD3DFont(L"Trebuchet MS", iFontSize, 0); + g_pFontCarSpecCustom->InitDeviceObjects(g_pRBRIDirect3DDevice9); + g_pFontCarSpecCustom->RestoreDeviceObjects(); if (g_pRBRGameConfig == NULL) g_pRBRGameConfig = (PRBRGameConfig) *(DWORD*)(0x007EAC48); if (g_pRBRGameMode == NULL) g_pRBRGameMode = (PRBRGameMode) * (DWORD*)(0x007EAC48); @@ -807,6 +945,8 @@ void CNGPCarMenu::DrawResultsUI(void) // void CNGPCarMenu::DrawFrontEndPage(void) { + char szTextBuf[128]; // This should be enough to hold the longest g_RBRPlugin menu string + the logest g_RBRPluginMenu_xxxOptions option string + // Draw blackout (coordinates specify the 'window' where you don't want black) m_pGame->DrawBlackOut(420.0f, 0.0f, 420.0f, 480.0f); @@ -821,14 +961,14 @@ void CNGPCarMenu::DrawFrontEndPage(void) m_pGame->SetMenuColor(IRBRGame::MENU_TEXT); for (unsigned int i = 0; i < COUNT_OF_ITEMS(g_RBRPluginMenu); ++i) { - if (i == 1) - { - char szTextBuf[128]; + if (i == C_MENUCMD_CREATEOPTION) sprintf_s(szTextBuf, sizeof(szTextBuf), "%s: %s", g_RBRPluginMenu[i], g_RBRPluginMenu_CreateOptions[m_iMenuCreateOption]); - m_pGame->WriteText(65.0f, 70.0f + (static_cast< float >(i) * 21.0f), szTextBuf); - } + else if (i == C_MENUCMD_IMAGEOPTION) + sprintf_s(szTextBuf, sizeof(szTextBuf), "%s: %s", g_RBRPluginMenu[i], g_RBRPluginMenu_ImageOptions[m_iMenuImageOption]); else - m_pGame->WriteText(65.0f, 70.0f + (static_cast< float >(i) * 21.0f), g_RBRPluginMenu[i]); + sprintf_s(szTextBuf, sizeof(szTextBuf), "%s", g_RBRPluginMenu[i]); + + m_pGame->WriteText(65.0f, 70.0f + (static_cast< float >(i) * 21.0f), szTextBuf); } if (!m_sMenuStatusText1.empty()) @@ -858,24 +998,26 @@ void CNGPCarMenu::HandleFrontEndEvents(char txtKeyboard, bool bUp, bool bDown, b // Menu item "selected". Do the action. // - if (m_iMenuSelection == 0) + if (m_iMenuSelection == C_MENUCMD_RELOAD) { - // Re-read car preview images + // Re-read car preview images from source files ClearCachedCarPreviewImages(); RefreshSettingsFromPluginINIFile(); // DEBUG. Show/Hide cropping area - bool bNewStatus = !m_bCustomReplayShowCroppingRect; + //bool bNewStatus = !m_bCustomReplayShowCroppingRect; m_bCustomReplayShowCroppingRect = false; D3D9CreateRectangleVertexBuffer(g_pRBRIDirect3DDevice9, (float)this->m_screenshotCroppingRect.left, (float)this->m_screenshotCroppingRect.top, (float)(this->m_screenshotCroppingRect.right - this->m_screenshotCroppingRect.left), (float)(this->m_screenshotCroppingRect.bottom - this->m_screenshotCroppingRect.top), &m_screenshotCroppingRectVertexBuffer); - m_bCustomReplayShowCroppingRect = bNewStatus; - } - else if (m_iMenuSelection == 1) - { - // Do nothing when "Create option" line is selected + //m_bCustomReplayShowCroppingRect = bNewStatus; } - else if (m_iMenuSelection == 2) + //else if (m_iMenuSelection == C_MENUCMD_CREATEOPTION || m_iMenuSelection == C_MENUCMD_IMAGEOPTION) + //{ + // Do nothing when option line is selected + //} + else if (m_iMenuSelection == C_MENUCMD_CREATE) { + // Create new car preview images using BMP or PNG format + m_bCustomReplayShowCroppingRect = false; m_iCustomReplayScreenshotCount = 0; @@ -919,12 +1061,22 @@ void CNGPCarMenu::HandleFrontEndEvents(char txtKeyboard, bool bUp, bool bDown, b // Menu options changed in the current menu line. Options don't wrap around. // Note! Not all menu lines have any additional options. // - if (m_iMenuSelection == 1 && bLeft && (--m_iMenuCreateOption) < 0) + if (m_iMenuSelection == C_MENUCMD_CREATEOPTION && bLeft && (--m_iMenuCreateOption) < 0) m_iMenuCreateOption = 0; - if (m_iMenuSelection == 1 && bRight && (++m_iMenuCreateOption) >= COUNT_OF_ITEMS(g_RBRPluginMenu_CreateOptions)) + if (m_iMenuSelection == C_MENUCMD_CREATEOPTION && bRight && (++m_iMenuCreateOption) >= COUNT_OF_ITEMS(g_RBRPluginMenu_CreateOptions)) m_iMenuCreateOption = COUNT_OF_ITEMS(g_RBRPluginMenu_CreateOptions) - 1; + + int iPrevMenuImageOptionValue = m_iMenuImageOption; + if (m_iMenuSelection == C_MENUCMD_IMAGEOPTION && bLeft && (--m_iMenuImageOption) < 0) + m_iMenuImageOption = 0; + + if (m_iMenuSelection == C_MENUCMD_IMAGEOPTION && bRight && (++m_iMenuImageOption) >= COUNT_OF_ITEMS(g_RBRPluginMenu_ImageOptions)) + m_iMenuImageOption = COUNT_OF_ITEMS(g_RBRPluginMenu_ImageOptions) - 1; + + if(iPrevMenuImageOptionValue != m_iMenuImageOption) + SaveSettingsToPluginINIFile(); } //------------------------------------------------------------------------------------------------ @@ -1048,106 +1200,88 @@ HRESULT __fastcall CustomRBRDirectXBeginScene(void* objPointer) else if (g_pRBRPlugin->m_iCustomReplayState > 0) { // NGPCarMenu plugin is generating car preview screenshot images. Draw a car model and take a screenshot from a specified cropping screen area - + g_pRBRPlugin->m_bCustomReplayShowCroppingRect = true; g_pRBRCarInfo->stageStartCountdown = 1.0f; if (g_pRBRGameMode->gameMode == 0x08 /*&& g_pRBRGameModeExt->gameModeExt == 0x04*/) { - // Move around the car and camera during replay and pause the replay. Don't start the normal replay logic of RBR + // Don't start the normal replay logic of RBR g_pRBRGameMode->gameMode = 0x0A; - g_pRBRGameModeExt->gameModeExt = 0x04; + //g_pRBRGameModeExt->gameModeExt = 0x04; } - if(g_pRBRGameMode->gameMode == 0x0A && g_pRBRGameModeExt->gameModeExt == 0x01) - { - // Prepare a new screenshot. Wait few secs to let RBR to finalize the 3D car drawing and finishing the replay starting animation + if (g_pRBRGameMode->gameMode == 0x0A && g_pRBRGameModeExt->gameModeExt == 0x01 && g_pRBRPlugin->m_iCustomReplayState == 1) + { + g_pRBRPlugin->m_iCustomReplayState = 2; + g_pRBRPlugin->m_tCustomReplayStateStartTime = std::chrono::steady_clock::now(); + } + else if (g_pRBRGameMode->gameMode == 0x0A && g_pRBRGameModeExt->gameModeExt == 0x01 && g_pRBRPlugin->m_iCustomReplayState == 3) + { + g_pRBRPlugin->m_iCustomReplayState = 4; + g_pRBRPlugin->m_tCustomReplayStateStartTime = std::chrono::steady_clock::now(); - g_pRBRPlugin->m_iCustomReplayState++; - if (g_pRBRPlugin->m_iCustomReplayState > 100) - { - g_pRBRPlugin->m_iCustomReplayState = 1; - g_pRBRGameModeExt->gameModeExt = 0x04; - - g_pRBRCameraInfo = g_pRBRCarInfo->pCamera->pCameraInfo; - g_pRBRCarMovement = (PRBRCarMovement) * (DWORD*)(0x008EF660); - - g_pRBRCameraInfo->cameraType = 3; - - // TODO. Read from INI file - // Set car and camera position to create a cool car preview image - g_pRBRCameraInfo->camOrientation.x = 0.664824f; - g_pRBRCameraInfo->camOrientation.y = -0.747000f; - g_pRBRCameraInfo->camOrientation.z = 0.000000f; - g_pRBRCameraInfo->camPOV1.x = 0.699783f; - g_pRBRCameraInfo->camPOV1.y = 0.622800f; - g_pRBRCameraInfo->camPOV1.z = -0.349891f; - g_pRBRCameraInfo->camPOV2.x = 0.261369f; - g_pRBRCameraInfo->camPOV2.y = 0.232616f; - g_pRBRCameraInfo->camPOV2.z = 0.936790f; - g_pRBRCameraInfo->camPOS.x = -4.000000f; - g_pRBRCameraInfo->camPOS.y = -4.559966f; - g_pRBRCameraInfo->camPOS.z = 2.000000f; - g_pRBRCameraInfo->camFOV = 75.000175f; - g_pRBRCameraInfo->camNear = 0.150000f; - - g_pRBRCarMovement->carQuat.x = -0.017969f; - g_pRBRCarMovement->carQuat.y = 0.008247f; - g_pRBRCarMovement->carQuat.z = 0.982173f; - g_pRBRCarMovement->carQuat.w = -0.186811f; - g_pRBRCarMovement->carMapLocation.m[0][0] = -0.929464f; - g_pRBRCarMovement->carMapLocation.m[0][1] = -0.367257f; - g_pRBRCarMovement->carMapLocation.m[0][2] = -0.032216f; - g_pRBRCarMovement->carMapLocation.m[0][3] = 0.366664f; - g_pRBRCarMovement->carMapLocation.m[1][0] = -0.929974f; - g_pRBRCarMovement->carMapLocation.m[1][1] = 0.022913f; - g_pRBRCarMovement->carMapLocation.m[1][2] = -0.038378f; - g_pRBRCarMovement->carMapLocation.m[1][3] = 0.009485f; - g_pRBRCarMovement->carMapLocation.m[2][0] = 0.999218f; - g_pRBRCarMovement->carMapLocation.m[2][1] = 0.000008f; - g_pRBRCarMovement->carMapLocation.m[2][2] = 2.000000f; - g_pRBRCarMovement->carMapLocation.m[2][3] = 524287.968750f; - g_pRBRCarMovement->carMapLocation.m[3][0] = 7.236033f; - g_pRBRCarMovement->carMapLocation.m[3][1] = 149.234146f; - g_pRBRCarMovement->carMapLocation.m[3][2] = 0.287426f; - g_pRBRCarMovement->carMapLocation.m[3][3] = -1.453711f; - } + g_pRBRGameMode->gameMode = 0x0A; + g_pRBRGameModeExt->gameModeExt = 0x04; + + g_pRBRCameraInfo = g_pRBRCarInfo->pCamera->pCameraInfo; + g_pRBRCarMovement = (PRBRCarMovement) * (DWORD*)(0x008EF660); + + g_pRBRCameraInfo->cameraType = 3; + + // TODO. Read from INI file + // Set car and camera position to create a cool car preview image + g_pRBRCameraInfo->camOrientation.x = 0.664824f; + g_pRBRCameraInfo->camOrientation.y = -0.747000f; + g_pRBRCameraInfo->camOrientation.z = 0.000000f; + g_pRBRCameraInfo->camPOV1.x = 0.699783f; + g_pRBRCameraInfo->camPOV1.y = 0.622800f; + g_pRBRCameraInfo->camPOV1.z = -0.349891f; + g_pRBRCameraInfo->camPOV2.x = 0.261369f; + g_pRBRCameraInfo->camPOV2.y = 0.232616f; + g_pRBRCameraInfo->camPOV2.z = 0.936790f; + g_pRBRCameraInfo->camPOS.x = -4.000000f; + g_pRBRCameraInfo->camPOS.y = -4.559966f; + g_pRBRCameraInfo->camPOS.z = 2.000000f; + g_pRBRCameraInfo->camFOV = 75.000175f; + g_pRBRCameraInfo->camNear = 0.150000f; + + g_pRBRCarMovement->carQuat.x = -0.017969f; + g_pRBRCarMovement->carQuat.y = 0.008247f; + g_pRBRCarMovement->carQuat.z = 0.982173f; + g_pRBRCarMovement->carQuat.w = -0.186811f; + g_pRBRCarMovement->carMapLocation.m[0][0] = -0.929464f; + g_pRBRCarMovement->carMapLocation.m[0][1] = -0.367257f; + g_pRBRCarMovement->carMapLocation.m[0][2] = -0.032216f; + g_pRBRCarMovement->carMapLocation.m[0][3] = 0.366664f; + g_pRBRCarMovement->carMapLocation.m[1][0] = -0.929974f; + g_pRBRCarMovement->carMapLocation.m[1][1] = 0.022913f; + g_pRBRCarMovement->carMapLocation.m[1][2] = -0.038378f; + g_pRBRCarMovement->carMapLocation.m[1][3] = 0.009485f; + g_pRBRCarMovement->carMapLocation.m[2][0] = 0.999218f; + g_pRBRCarMovement->carMapLocation.m[2][1] = 0.000008f; + g_pRBRCarMovement->carMapLocation.m[2][2] = 2.000000f; + g_pRBRCarMovement->carMapLocation.m[2][3] = 524287.968750f; + g_pRBRCarMovement->carMapLocation.m[3][0] = 7.236033f; + g_pRBRCarMovement->carMapLocation.m[3][1] = 149.234146f; + g_pRBRCarMovement->carMapLocation.m[3][2] = 0.287426f; + g_pRBRCarMovement->carMapLocation.m[3][3] = -1.453711f; } - else if (g_pRBRGameMode->gameMode == 0x0A && g_pRBRGameModeExt->gameModeExt == 0x04) + else if (g_pRBRPlugin->m_iCustomReplayState == 6) { - // Car and camera is set in a custom location. Show the screenshot cropping rectangle for a few secs before taking the actual screenshot + // Screenshot taken. Prepare the next car for a screenshot + g_pRBRPlugin->m_bCustomReplayShowCroppingRect = false; + g_pRBRCarInfo->stageStartCountdown = 7.0f; - g_pRBRPlugin->m_iCustomReplayState++; + g_pRBRPlugin->m_iCustomReplayState = 0; + g_pRBRPlugin->m_iCustomReplayCarID = g_pRBRPlugin->GetNextScreenshotCarID(g_pRBRPlugin->m_iCustomReplayCarID); - if (g_pRBRPlugin->m_iCustomReplayState <= 8 || g_pRBRPlugin->m_iCustomReplayState > 10) - g_pRBRPlugin->m_bCustomReplayShowCroppingRect = true; - else - g_pRBRPlugin->m_bCustomReplayShowCroppingRect = false; - - if (g_pRBRPlugin->m_iCustomReplayState == 10 && !g_pRBRPlugin->m_screenshotPath.empty() && g_RBRCarSelectionMenuEntry[RBRAPI_MapCarIDToMenuIdx(g_pRBRPlugin->m_iCustomReplayCarID)].wszCarModel[0] != '\0') + if (g_pRBRPlugin->m_iCustomReplayCarID >= 0) { - // Take a RBR car preview screenshot and save it as PNG preview file. - // At this point the cropping highlight rectangle is hidden, so it is not shown in the screenshot. - std::wstring outputFileName = g_pRBRPlugin->m_screenshotPath + L"\\" + g_RBRCarSelectionMenuEntry[RBRAPI_MapCarIDToMenuIdx(g_pRBRPlugin->m_iCustomReplayCarID)].wszCarModel + L".png"; - D3D9SaveScreenToFile(g_pRBRIDirect3DDevice9, g_hRBRWnd, g_pRBRPlugin->m_screenshotCroppingRect, outputFileName); - - g_pRBRPlugin->m_iCustomReplayScreenshotCount++; + if (CNGPCarMenu::PrepareScreenshotReplayFile(g_pRBRPlugin->m_iCustomReplayCarID)) + g_pRBRPlugin->m_iCustomReplayState = 1; } - else if (g_pRBRPlugin->m_iCustomReplayState >= (g_pRBRPlugin->m_iCustomReplayScreenshotCount <= 1 ? 100 : 15)) // Show the cropping rect few secs longer during the first screenshot - { - g_pRBRPlugin->m_bCustomReplayShowCroppingRect = false; - g_pRBRCarInfo->stageStartCountdown = 7.0f; - - g_pRBRPlugin->m_iCustomReplayState = 0; - g_pRBRPlugin->m_iCustomReplayCarID = g_pRBRPlugin->GetNextScreenshotCarID(g_pRBRPlugin->m_iCustomReplayCarID); - if (g_pRBRPlugin->m_iCustomReplayCarID >= 0) - { - if (CNGPCarMenu::PrepareScreenshotReplayFile(g_pRBRPlugin->m_iCustomReplayCarID)) - g_pRBRPlugin->m_iCustomReplayState = 1; - } - - RBRAPI_Replay(g_pRBRPlugin->m_sRBRRootDir, C_REPLAYFILENAME_SCREENSHOT); - } + RBRAPI_Replay(g_pRBRPlugin->m_sRBRRootDir, C_REPLAYFILENAME_SCREENSHOT); } } @@ -1160,7 +1294,7 @@ HRESULT __fastcall CustomRBRDirectXBeginScene(void* objPointer) // HRESULT __fastcall CustomRBRDirectXEndScene(void* objPointer) { - //HRESULT hResult; + HRESULT hResult; if (!g_bRBRHooksInitialized) return S_OK; @@ -1199,7 +1333,7 @@ HRESULT __fastcall CustomRBRDirectXEndScene(void* objPointer) default: rbrPosX = 245; break; } - iFontHeight = pFontCarSpecCustom->GetTextHeight(); + iFontHeight = g_pFontCarSpecCustom->GetTextHeight(); // TODO: Better re-scaler function to map game resolution to screen resolution RBRAPI_MapRBRPointToScreenPoint(rbrPosX, g_pRBRMenuSystem->menuImagePosY, &posX, &posY); @@ -1207,10 +1341,10 @@ HRESULT __fastcall CustomRBRDirectXEndScene(void* objPointer) PRBRCarSelectionMenuEntry pCarSelectionMenuEntry = &g_RBRCarSelectionMenuEntry[selectedCarIdx]; - pFontCarSpecCustom->DrawText(posX, 0 * iFontHeight + posY, C_CARSPECTEXT_COLOR, pCarSelectionMenuEntry->wszCarPhysicsRevision, 0); - pFontCarSpecCustom->DrawText(posX, 1 * iFontHeight + posY, C_CARSPECTEXT_COLOR, pCarSelectionMenuEntry->wszCarPhysicsSpecYear, 0); - pFontCarSpecCustom->DrawText(posX, 2 * iFontHeight + posY, C_CARSPECTEXT_COLOR, pCarSelectionMenuEntry->wszCarPhysics3DModel, 0); - pFontCarSpecCustom->DrawText(posX, 3 * iFontHeight + posY, C_CARSPECTEXT_COLOR, pCarSelectionMenuEntry->wszCarPhysicsCustomTxt, 0); + g_pFontCarSpecCustom->DrawText(posX, 0 * iFontHeight + posY, C_CARSPECTEXT_COLOR, pCarSelectionMenuEntry->wszCarPhysicsRevision, 0); + g_pFontCarSpecCustom->DrawText(posX, 1 * iFontHeight + posY, C_CARSPECTEXT_COLOR, pCarSelectionMenuEntry->wszCarPhysicsSpecYear, 0); + g_pFontCarSpecCustom->DrawText(posX, 2 * iFontHeight + posY, C_CARSPECTEXT_COLOR, pCarSelectionMenuEntry->wszCarPhysics3DModel, 0); + g_pFontCarSpecCustom->DrawText(posX, 3 * iFontHeight + posY, C_CARSPECTEXT_COLOR, pCarSelectionMenuEntry->wszCarPhysicsCustomTxt, 0); if (g_pRBRPlugin->m_carPreviewTexture[selectedCarIdx].pTexture == nullptr && g_pRBRPlugin->m_carPreviewTexture[selectedCarIdx].imgSize.cx >= 0 && g_RBRCarSelectionMenuEntry[selectedCarIdx].wszCarModel[0] != '\0') { @@ -1220,9 +1354,13 @@ HRESULT __fastcall CustomRBRDirectXEndScene(void* objPointer) // TODO. Read image size and re-scale and center the image if it is not in native resolution folder + std::string imgExtension = "."; + imgExtension = imgExtension + g_RBRPluginMenu_ImageOptions[g_pRBRPlugin->m_iMenuImageOption]; // .PNG or .BMP img file format + _ToLowerCase(imgExtension); + HRESULT hResult = D3D9CreateRectangleVertexTexBufferFromFile(g_pRBRIDirect3DDevice9, - g_pRBRPlugin->m_screenshotPath + L"\\" + g_RBRCarSelectionMenuEntry[selectedCarIdx].wszCarModel + L".png", - 0, posYf, 0, 0, + g_pRBRPlugin->m_screenshotPath + L"\\" + g_RBRCarSelectionMenuEntry[selectedCarIdx].wszCarModel + _ToWString(imgExtension), + (float)g_pRBRPlugin->m_carSelectLeftBlackBarRect.left, posYf, 0, 0, &g_pRBRPlugin->m_carPreviewTexture[selectedCarIdx]); // Image not available. Do not try to re-load it again (set cx=-1 to indicate that the image loading failed, so no need to try to re-load it in every frame even when texture is null) @@ -1252,7 +1390,7 @@ HRESULT __fastcall CustomRBRDirectXEndScene(void* objPointer) } } //else if (g_pRBRPlugin->m_iCustomReplayState > 0 && g_pRBRPlugin->m_bCustomReplayShowCroppingRect && g_pRBRPlugin->m_screenshotCroppingRectVertexBuffer != nullptr) - else if (g_pRBRPlugin->m_bCustomReplayShowCroppingRect) + else if (g_pRBRPlugin->m_bCustomReplayShowCroppingRect && g_pRBRPlugin->m_iCustomReplayState >= 2 && g_pRBRPlugin->m_iCustomReplayState != 4) { // Draw rectangle to highlight the screenshot capture area D3D9DrawVertex2D(g_pRBRIDirect3DDevice9, g_pRBRPlugin->m_screenshotCroppingRectVertexBuffer); @@ -1262,7 +1400,7 @@ HRESULT __fastcall CustomRBRDirectXEndScene(void* objPointer) WCHAR szTxtBuffer[200]; swprintf_s(szTxtBuffer, COUNT_OF_ITEMS(szTxtBuffer), L"Mode %d %d. Img (%f,%f)(%f,%f) Timer=%f", g_pRBRGameMode->gameMode, g_pRBRGameModeExt->gameModeExt, g_pRBRMenuSystem->menuImagePosX, g_pRBRMenuSystem->menuImagePosY, g_pRBRMenuSystem->menuImageWidth, g_pRBRMenuSystem->menuImageHeight, g_pRBRCarInfo->stageStartCountdown); - pFontDebug->DrawText(1, 1 * 20, C_DEBUGTEXT_COLOR, szTxtBuffer, 0); + g_pFontDebug->DrawText(1, 1 * 20, C_DEBUGTEXT_COLOR, szTxtBuffer, 0); RECT wndRect; RECT wndClientRect; @@ -1277,12 +1415,67 @@ HRESULT __fastcall CustomRBRDirectXEndScene(void* objPointer) swprintf_s(szTxtBuffer, COUNT_OF_ITEMS(szTxtBuffer), L"hWnd=%x Win=(%d,%d)(%d,%d) Client=(%d,%d)(%d,%d)", (int)creationParameters.hFocusWindow, wndRect.left, wndRect.top, wndRect.right, wndRect.bottom, wndClientRect.left, wndClientRect.top, wndClientRect.right, wndClientRect.bottom); - pFontDebug->DrawText(1, 2 * 20, C_DEBUGTEXT_COLOR, szTxtBuffer, 0); + g_pFontDebug->DrawText(1, 2 * 20, C_DEBUGTEXT_COLOR, szTxtBuffer, 0); swprintf_s(szTxtBuffer, COUNT_OF_ITEMS(szTxtBuffer), L"Mapped=(%d,%d)(%d,%d) GameRes=(%d,%d)", wndMappedRect.left, wndMappedRect.top, wndMappedRect.right, wndMappedRect.bottom, g_pRBRGameConfig->resolutionX, g_pRBRGameConfig->resolutionY); - pFontDebug->DrawText(1, 3 * 20, C_DEBUGTEXT_COLOR, szTxtBuffer, 0); + g_pFontDebug->DrawText(1, 3 * 20, C_DEBUGTEXT_COLOR, szTxtBuffer, 0); + + swprintf_s(szTxtBuffer, COUNT_OF_ITEMS(szTxtBuffer), L"ReplayState=%d", g_pRBRPlugin->m_iCustomReplayState); + g_pFontDebug->DrawText(1, 4 * 20, C_DEBUGTEXT_COLOR, szTxtBuffer, 0); + #endif // Call original RBR DXEndScene function and let it to do whatever needed to complete the drawing of DX framebuffer - return Func_OrigRBRDirectXEndScene(objPointer); + hResult = Func_OrigRBRDirectXEndScene(objPointer); + if (g_pRBRPlugin->m_iCustomReplayState <= 0) + return hResult; + + + // + // Custom "screenshot generation replay" process running. + // State 1=Preparing (replay not yet loaded) + // 2=Replay loaded, camera spinning around the car and blackout fading out + // 3=The startup blackout has faded out. Prepare to move the car and cam to a custom location + // 4=Car and cam moved to a custom location. Prepare to take a screenshot (wait 0.5s before taking the shot) + // 5=Screenshot taken. If this was the first car then show the cropping rect for few secs (easier to check that the cropping rect is in the correct location) + // 6=Ending the screenshot state cycle. End or start all over again with a new car + // + if (g_pRBRPlugin->m_iCustomReplayState == 2) + { + std::chrono::steady_clock::time_point tCustomReplayStateNowTime = std::chrono::steady_clock::now(); + auto iTimeElapsedSec = std::chrono::duration_cast(tCustomReplayStateNowTime - g_pRBRPlugin->m_tCustomReplayStateStartTime).count(); + if (iTimeElapsedSec >= 1200) + g_pRBRPlugin->m_iCustomReplayState = 3; + } + else if (g_pRBRPlugin->m_iCustomReplayState == 4) + { + std::chrono::steady_clock::time_point tCustomReplayStateNowTime = std::chrono::steady_clock::now(); + auto iTimeElapsedSec = std::chrono::duration_cast(tCustomReplayStateNowTime - g_pRBRPlugin->m_tCustomReplayStateStartTime).count(); + if (iTimeElapsedSec >= 500) + { + // Take a RBR car preview screenshot and save it as PNG preview file. + // At this point the cropping highlight rectangle is hidden, so it is not shown in the screenshot. + std::string imgExtension = "."; + imgExtension = imgExtension + g_RBRPluginMenu_ImageOptions[g_pRBRPlugin->m_iMenuImageOption]; // .PNG or .BMP img file format + _ToLowerCase(imgExtension); + + std::wstring outputFileName = g_pRBRPlugin->m_screenshotPath + L"\\" + g_RBRCarSelectionMenuEntry[RBRAPI_MapCarIDToMenuIdx(g_pRBRPlugin->m_iCustomReplayCarID)].wszCarModel + _ToWString(imgExtension); + + D3D9SaveScreenToFile((g_pRBRPlugin->m_screenshotAPIType == C_SCREENSHOTAPITYPE_DIRECTX ? g_pRBRIDirect3DDevice9 : nullptr), + g_hRBRWnd, g_pRBRPlugin->m_screenshotCroppingRect, outputFileName); + + g_pRBRPlugin->m_iCustomReplayScreenshotCount++; + g_pRBRPlugin->m_iCustomReplayState = 5; + g_pRBRPlugin->m_tCustomReplayStateStartTime = std::chrono::steady_clock::now(); + } + } + else if (g_pRBRPlugin->m_iCustomReplayState == 5) + { + std::chrono::steady_clock::time_point tCustomReplayStateNowTime = std::chrono::steady_clock::now(); + auto iTimeElapsedSec = std::chrono::duration_cast(tCustomReplayStateNowTime - g_pRBRPlugin->m_tCustomReplayStateStartTime).count(); + if (g_pRBRPlugin->m_iCustomReplayScreenshotCount > 1 || iTimeElapsedSec >= 3000) + g_pRBRPlugin->m_iCustomReplayState = 6; // Completing the screenshot state for this carID + } + + return hResult; } diff --git a/src/NGPCarMenu.h b/src/NGPCarMenu.h index 782bdd7..a2da7be 100644 --- a/src/NGPCarMenu.h +++ b/src/NGPCarMenu.h @@ -43,6 +43,10 @@ #include "D3D9Helpers.h" // Various text and D3D9 support functions created for this RBR plugin #include "RBRAPI.h" // RBR memory addresses and data structures + +#define C_PLUGIN_TITLE "Custom NGPCarMenu Plugin (v1.02) by MIKA-N" // Remember to tweak the NGPCarMenu.rc version properties also + + #define C_DEBUGTEXT_COLOR D3DCOLOR_ARGB(255, 255,255,255) // White #define C_CARSPECTEXT_COLOR D3DCOLOR_ARGB(255, 0xE0,0xE0,0xE0) // Grey-White @@ -51,7 +55,7 @@ #define C_REPLAYFILENAME_SCREENSHOT "_NGPCarMenu.rpl" // Name of the temporary RBR replay file (char and wchar version) #define C_REPLAYFILENAME_SCREENSHOTW L"_NGPCarMenu.rpl" -#define C_PLUGIN_TITLE "Custom NGPCarMenu Plugin (v1.0) by MIKA-N" + // // The state of the plugin enum @@ -93,6 +97,9 @@ typedef RBRCarSelectionMenuEntry* PRBRCarSelectionMenuEntry; //------------------------------------------------------------------------------------------------ +#define C_SCREENSHOTAPITYPE_DIRECTX 0 +#define C_SCREENSHOTAPITYPE_GDI 1 + class CNGPCarMenu; extern BOOL g_bRBRHooksInitialized; @@ -127,11 +134,13 @@ class CNGPCarMenu : public IPlugin int m_iMenuSelection; // Currently selected plugin menu item idx int m_iMenuCreateOption; // 0 = Generate all car images, 1 = Generate only missing car images + int m_iMenuImageOption; // 0 = Use PNG preview file format to read and create image files, 1 = BMP file format std::string m_sRBRRootDir; // RBR app path, ASCII string std::wstring m_sRBRRootDirW; // RBR app path, Unicode string std::wstring m_screenshotPath; // Path to car preview screenshot images (by default AppPath + \plugins\NGPCarMenu\preview\XResxYRes\) + int m_screenshotAPIType; // Uses DIRECTX or GDI API technique to generate a new screenshot file. 0=DirectX (default), 1=GDI. No GUI option, so tweak this in NGPCarMenu.ini file. std::wstring m_screenshotReplayFileName; // Name of the RBR replay file used when car preview images are generated std::wstring m_rbrCITCarListFilePath; // Path to RBRCIT carList.ini file (the file has NGP car details and specs information) @@ -142,8 +151,10 @@ class CNGPCarMenu : public IPlugin //CUSTOM_VERTEX_2D m_screenshotCroppingRectVertex2D[4]; LPDIRECT3DVERTEXBUFFER9 m_screenshotCroppingRectVertexBuffer; // Screeshot rect vertex to highlight on screen the current capture area - int m_iCustomReplayCarID; // 0..7 = The current carID used in a custom screenshot replay file int m_iCustomReplayState; // 0 = No custom replay (default RBR behaviour), 1 = Custom replay process is running. Take screenshots of a car models. + std::chrono::steady_clock::time_point m_tCustomReplayStateStartTime; + + int m_iCustomReplayCarID; // 0..7 = The current carID used in a custom screenshot replay file int m_iCustomReplayScreenshotCount; // Num of screenshots taken so far during the last "CREATE car preview images" command bool m_bCustomReplayShowCroppingRect; // Show the current car preview screenshot cropping rect area on screen (ie. few secs before the screenshot is taken) @@ -156,6 +167,7 @@ class CNGPCarMenu : public IPlugin static bool PrepareScreenshotReplayFile(int carID); void RefreshSettingsFromPluginINIFile(); + void SaveSettingsToPluginINIFile(); //------------------------------------------------------------------------------------------------ virtual const char* GetName(void); diff --git a/src/NGPCarMenu.ini b/src/NGPCarMenu.ini index d1b7337..aecea74 100644 --- a/src/NGPCarMenu.ini +++ b/src/NGPCarMenu.ini @@ -18,6 +18,8 @@ ; CONFIGURATION OF NGPCarMenu: ; ; ScreenshotPath = Location of car preview images (relative to RBR.EXE location or absolute path). Resolution specific subfolder below this folder. +; ScreenshotFileType = BMP or PNG (the plugin reads and writes screenshot files in this format) +; ScreenshotAPIType = 0 or 1 (the plugin uses either 0=DirectX or 1=GDI technique while creating the screenshot file. Some PCs may have issues with DirectX interface, while GDI works more reliably (but slower). ; ScreenshotReplay = Replay file this plugin uses to generate preview car images. ; ScreenshotCarPosition = Car position within the replay video while taking the car preview screenshot. (well, not yet implemented. Car position is set in the plugin) ; ScreenshotCamPosition = Camera position while taking the screenshot (well, not yet implemented. Cam position is set in the plugin) @@ -27,10 +29,12 @@ ; ScreenshotCropping = Cropping rectangle for the screenshot (make it "big enough but not too big" to fill the bottom part of "Select Car" RBR menu). ; Value are in pixels "left top right bottom". ; (Pro tip. To find a good ScreenshotCropping rectangle coordinates, take a fullscreen screenshot of the car in -; replay video and then use MS-Paint app to find corner coordinates in pixels to fit the car in a rectangle area). +; replay video and then use MS-Paint app to find corner coordinates in pixels to fit the car in a rectangle area). ; ; CarSelectLeftBlackBar = Optional rectangle black bar to hide the stock RBR "left frame" image in "Select Car" menu (empty string or 0 0 0 0 removes black side bars). -; CarSelectRightBlackBar= (see above) +; The left pixel position (the first value) in CarSelectionLeftBlackBar defines also the X-position of the car preview image. +; Users with multiple monitors or virtual desktops may have to tweak these settigs to center the image correctly. +; CarSelectRightBlackBar= (see above) (Pro tip. The same pro tip with ScreenshotCropping option works here also) ; ; The plugin supports resolution specific preview images. Feel free to add new resolution specific configuration entries if the one you use is missing. ; @@ -38,6 +42,8 @@ [Default] FileFormat=1 ScreenshotPath=Plugins\NGPCarMenu\preview +ScreenshotFileType=PNG +ScreenshotAPIType=0 ScreenshotReplay=NGPCarMenu.rpl ScreenshotCarPosition= ScreenshotCamPosition= @@ -48,6 +54,11 @@ ScreenshotCropping=0 236 1920 780 CarSelectLeftBlackBar=0 0 238 1080 CarSelectRightBlackBar=1682 0 1920 1080 +[1680x1050] +ScreenshotCropping=0 250 1680 750 +CarSelectLeftBlackBar=0 0 138 1050 +CarSelectRightBlackBar=1542 0 1680 1050 + [1600x900] ScreenshotCropping=0 220 1600 750 CarSelectLeftBlackBar= @@ -79,9 +90,9 @@ CarSelectLeftBlackBar= CarSelectRightBlackBar= [1024x768] -ScreenshotCropping=0 300 1024 600 -CarSelectLeftBlackBar= -CarSelectRightBlackBar= +ScreenshotCropping=0 180 1024 550 +CarSelectLeftBlackBar= +CarSelectRightBlackBar= [640x480] ScreenshotCropping=0 300 640 400 diff --git a/src/NGPCarMenu.rc b/src/NGPCarMenu.rc index 36c3e72..134c216 100644 --- a/src/NGPCarMenu.rc +++ b/src/NGPCarMenu.rc @@ -51,8 +51,8 @@ END // VS_VERSION_INFO VERSIONINFO - FILEVERSION 1,0,0,1 - PRODUCTVERSION 1,0,0,1 + FILEVERSION 1,0,0,2 + PRODUCTVERSION 1,0,0,2 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -69,12 +69,12 @@ BEGIN BEGIN VALUE "CompanyName", "MIKA-N" VALUE "FileDescription", "NGPCarMenu - Customized ""Select Car"" game menu for Richard Burns Rally (RBR) 1.02 SSE" - VALUE "FileVersion", "1.0.0.1" + VALUE "FileVersion", "1.0.0.2" VALUE "InternalName", "NGPCarMe.dll" VALUE "LegalCopyright", "Copyright (C) 2020 by mika-n. All rights reserved. See LicenseText.txt for more information. This tool is distributed free of charge. Use at your own risk." VALUE "OriginalFilename", "NGPCarMenu.dll" VALUE "ProductName", "NGPCarMenu" - VALUE "ProductVersion", "1.0.0.1" + VALUE "ProductVersion", "1.0.0.2" END END BLOCK "VarFileInfo" diff --git a/src/NGPCarMenu.vcxproj b/src/NGPCarMenu.vcxproj index be6ca40..fba159c 100644 --- a/src/NGPCarMenu.vcxproj +++ b/src/NGPCarMenu.vcxproj @@ -113,7 +113,10 @@ + + + diff --git a/src/NGPCarMenu.vcxproj.filters b/src/NGPCarMenu.vcxproj.filters index c471b2a..7c63014 100644 --- a/src/NGPCarMenu.vcxproj.filters +++ b/src/NGPCarMenu.vcxproj.filters @@ -45,6 +45,9 @@ Source Files + + + diff --git a/src/RBRAPI.cpp b/src/RBRAPI.cpp index 5f23316..46b5fef 100644 --- a/src/RBRAPI.cpp +++ b/src/RBRAPI.cpp @@ -213,7 +213,25 @@ int RBRAPI_MapCarIDToMenuIdx(int carID) } } -// Map RBR game point to screen point +int RBRAPI_MenuIdxToCarID(int menuIdx) +{ + // Map menu order idx to carID (slot#) 0..7 + switch (menuIdx) + { + case 6: return 0; + case 3: return 1; + case 7: return 2; + case 1: return 3; + case 4: return 4; + case 0: return 5; + case 2: return 6; + case 5: return 7; + default: return -1; + } +} + + +// Map RBR game point to screen point (TODO. Not perfect. Doesnt always scale correctly. Try to find better logic) void RBRAPI_MapRBRPointToScreenPoint(const float srcX, const float srcY, int* trgX, int* trgY) { if(trgX != nullptr) @@ -232,6 +250,18 @@ void RBRAPI_MapRBRPointToScreenPoint(const float srcX, const float srcY, float* *trgY = static_cast(srcY * (g_rectRBRWndClient.bottom / 480.0f /*g_pRBRGameConfig->resolutionY*/)); } + +// Update RBR wnd rectangle values up-to-date (in windowed mode the window may have been moved around). +void RBRAPI_RefreshWndRect() +{ + GetWindowRect(g_hRBRWnd, &g_rectRBRWnd); // Window size and position (including potential WinOS window decorations) + GetClientRect(g_hRBRWnd, &g_rectRBRWndClient); // The size or the D3D9 client area (without window decorations and left-top always 0) + CopyRect(&g_rectRBRWndMapped, &g_rectRBRWndClient); + MapWindowPoints(g_hRBRWnd, NULL, (LPPOINT)&g_rectRBRWndMapped, 2); // The client area mapped as physical screen position (left-top relative to screen) +} + + +// Replay RBR replay file BOOL RBRAPI_Replay(const std::string rbrAppFolder, LPCSTR szReplayFileName) { // https://suxin.space/notes/rbr-play-replay/ (Sasha / Suxin) diff --git a/src/RBRAPI.h b/src/RBRAPI.h index e4c109d..3cccc66 100644 --- a/src/RBRAPI.h +++ b/src/RBRAPI.h @@ -89,11 +89,15 @@ extern BOOL WriteOpCodeHexString(const LPVOID writeAddr, LPCSTR sHexText); extern BOOL WriteOpCodeBuffer(const LPVOID writeAddr, const BYTE* buffer, const int iBufLen); extern BOOL WriteOpCodePtr(const LPVOID writeAddr, const LPVOID ptrValue); -extern int RBRAPI_MapCarIDToMenuIdx(int carID); // 00..07 carID is not the same as order of car selection items +extern int RBRAPI_MapCarIDToMenuIdx(int carID); // 00..07 carID is not the same as order of car selection items +extern int RBRAPI_MenuIdxToCarID(int menuIdx); + extern BOOL RBRAPI_Replay(const std::string rbrAppFolder, LPCSTR szReplayFileName); + extern void RBRAPI_MapRBRPointToScreenPoint(const float srcX, const float srcY, int* trgX, int* trgY); extern void RBRAPI_MapRBRPointToScreenPoint(const float srcX, const float srcY, float* trgX, float* trgY); +extern void RBRAPI_RefreshWndRect(); // Overloaded RBR specific DX9 functions. These re-routed functions are used to draw custom graphics on top of RBR graphics. The custom DX9 function should call these "parent functions" to let RBR do it's own things also. typedef HRESULT(__fastcall* tRBRDirectXBeginScene)(void* objPointer);