From f2d2e99063b22a855b1074c91d9af50dc318d5d1 Mon Sep 17 00:00:00 2001 From: Disquse Date: Tue, 7 Nov 2023 19:18:15 +0300 Subject: [PATCH] tweak(extra-natives/rdr3): improve GraphicsNatives - Reworked draw origin system so now it uses proper structs, making the code to be way more readable. - Reworked ScriptIM drawing so now it uses in-game system directly. - This (hopefully) fixes flickering when using `DRAW_(BOX|POLY|LINE)` natives. - Vertex ordering in `DRAW_POLY` is different now (like in GTA5). - Added support for `DRAW_BOX` and `SET_BACKFACECULLING` (works as in GTA5). --- .../src/GraphicsNatives.cpp | 291 +++++++++++------- 1 file changed, 172 insertions(+), 119 deletions(-) diff --git a/code/components/extra-natives-rdr3/src/GraphicsNatives.cpp b/code/components/extra-natives-rdr3/src/GraphicsNatives.cpp index 2c85c3412e..d9c58825f5 100644 --- a/code/components/extra-natives-rdr3/src/GraphicsNatives.cpp +++ b/code/components/extra-natives-rdr3/src/GraphicsNatives.cpp @@ -1,115 +1,115 @@ #include #include #include -#include -struct DrawOrigin -{ - float posX; // +0 - float posY; // +4 - float posZ; // +8 - float posPad; // +12 - int unkIndex; // +16 - char pad[12]; // +20 -}; // 32 +#include "CfxRGBA.h" +#include "Hooking.Stubs.h" +#include "GamePrimitives.h" -enum DrawType +enum ScriptImDrawType : uint32_t { - Line = 0, - Poly, + SCRIPT_IM_LINE = 0, + SCRIPT_IM_POLY, + SCRIPT_IM_BOX, + SCRIPT_IM_BACKFACE_CULLING_OFF, + SCRIPT_IM_BACKFACE_CULLING_ON, }; -struct Vector3 +struct ScriptImRequest { - float x, y, z; + rage::Vec3V m_first; + rage::Vec3V m_second; + rage::Vec3V m_third; + uint32_t m_color; + ScriptImDrawType m_type; + char m_pad[8]; }; -struct DrawRequest +static constexpr int kMaxScriptImRequests = 1024; +static std::vector g_scriptImRequests{}; + +static ScriptImRequest** g_scriptImBuffer; +static uint16_t* g_scriptImEntriesCount; + +static hook::cdecl_stub initScriptImBuffer([]() { - DrawType type; - uint32_t color; - Vector3 first; - Vector3 second; - Vector3 third; -}; + return hook::get_pattern("48 89 5C 24 ? 57 48 83 EC 20 0F B7 05 ? ? ? ? 33 FF"); +}); -struct WorldhorizonManager +static void(*g_origRenderScriptIm)(); +static void RenderScriptIm() { - char m_unknown; - char m_disableRendering; + // The game never uses this buffer itself, but it *may* be changed in the future updates. + // This code isn't ready for such a scenario, so might potentially require tweaks. + // Like if something allocate script im buffer before us, we would need to resize it + // and not just rewrite everything with out custom stuff. - // etc... -}; + if (g_scriptImRequests.empty()) + { + return; + } -constexpr int NUM_MAX_DRAW_REQUESTS = 1024; + *g_scriptImEntriesCount = g_scriptImRequests.size(); -static DrawRequest drawRequests[NUM_MAX_DRAW_REQUESTS]; + // If the buffer isn't allocated, let's do it. + if (!*g_scriptImBuffer) + { + initScriptImBuffer(); + } -static int drawRequestsCount = 0; + // Failed to allocate? Bail out. + if (!*g_scriptImBuffer) + { + *g_scriptImEntriesCount = 0; + return; + } -static uint32_t ConvertRGBAToHex(uint8_t red, uint8_t green, uint8_t blue, uint8_t alpha) -{ - return ((alpha & 0xFF) << 24) + ((blue & 0xFF) << 16) + ((green & 0xFF) << 8) + (red & 0xFF); + // Move stuff from our temp vector to the in-game buffer. + std::move(g_scriptImRequests.begin(), g_scriptImRequests.end(), *g_scriptImBuffer); + + g_origRenderScriptIm(); + + g_scriptImRequests.clear(); + *g_scriptImEntriesCount = 0; } -static hook::cdecl_stub g_drawPoints([]() +struct DrawOriginData { - return hook::get_call(hook::get_pattern("41 8D 50 03 0F 29 45 F0 8B 41 30 48 8D 4D D0", 0x13)); -}); + rage::Vec3V m_pos; + bool m_is2d; // +16 + char m_pad[15]; +}; // 32 -static void(*g_origScriptImRenderPhase)(); -static void ScriptImRenderPhase() +struct DrawOriginStore { - if (drawRequestsCount > 0) - { - for (int i = 0; i < drawRequestsCount; i++) - { - DrawRequest& data = drawRequests[i]; - - switch (data.type) - { - case Line: - { - float points[8] = { - data.first.x, data.first.y, data.first.z, 0.0f, - data.second.x, data.second.y, data.second.z, 0.0f, - }; - - g_drawPoints((uint64_t)&points, 2, 0, false, data.color); - break; - } - case Poly: - { - float points[12] = { - data.first.x, data.first.y, data.first.z, 0.0f, - data.second.x, data.second.y, data.second.z, 0.0f, - data.third.x, data.third.y, data.third.z, 0.0f, - }; - - g_drawPoints((uint64_t)&points, 3, 0, true, data.color); - break; - } - } - } - - drawRequestsCount = 0; - } - - g_origScriptImRenderPhase(); -} + DrawOriginData m_items[32]; + uint32_t m_count; + char pad_404[12]; +}; static bool(*isGamePaused)(); static int* g_frameDrawIndex1; static int* g_frameDrawIndex2; -static void* g_drawOriginStore; +static DrawOriginStore* g_drawOriginStore; +static uint32_t* g_scriptDrawOriginIndex; -static uint32_t* g_scriptDrawOrigin; +struct WorldhorizonManager +{ + char m_unknown; + char m_disableRendering; + + // etc... +}; static WorldhorizonManager* g_worldhorizonMgr; static HookFunction hookFunction([]() { + static_assert(sizeof(ScriptImRequest) == 64); + static_assert(sizeof(DrawOriginData) == 32); + static_assert(sizeof(DrawOriginStore) == 1040); + { g_worldhorizonMgr = hook::get_address(hook::get_pattern("89 44 24 40 48 8D 0D ? ? ? ? 8B 84 24 90 00", 7)); } @@ -119,47 +119,60 @@ static HookFunction hookFunction([]() g_frameDrawIndex1 = hook::get_address(location - 20); g_frameDrawIndex2 = hook::get_address(location - 7); - g_drawOriginStore = hook::get_address(location + 10); + g_drawOriginStore = hook::get_address(location + 10); hook::set_call(&isGamePaused, location - 27); } { auto location = hook::get_pattern("89 41 34 8B 05 ? ? ? ? 89 41 30 8A 05"); - g_scriptDrawOrigin = hook::get_address(location + 5); + g_scriptDrawOriginIndex = hook::get_address(location + 5); + } + + { + auto location = hook::get_pattern("48 89 05 ? ? ? ? E8 ? ? ? ? 66 89 3D"); + + g_scriptImBuffer = hook::get_address(location + 3); + g_scriptImEntriesCount = hook::get_address(location + 15); + } + + { + auto location = hook::get_pattern("33 C9 E8 ? ? ? ? E8 ? ? ? ? 33 C9 E8 ? ? ? ? 0F", -0x28); + g_origRenderScriptIm = hook::trampoline(location, RenderScriptIm); } fx::ScriptEngine::RegisterNativeHandler("SET_DRAW_ORIGIN", [](fx::ScriptContext& context) { - auto drawIndex = *((isGamePaused()) ? g_frameDrawIndex2 : g_frameDrawIndex1); - auto store = ((uint64_t)g_drawOriginStore + 1040 * drawIndex); - auto usedIndexes = *(uint32_t*)(store + 1024); + auto drawIndex = *(isGamePaused() ? g_frameDrawIndex2 : g_frameDrawIndex1); - if (usedIndexes >= 32) + auto& store = g_drawOriginStore[drawIndex]; + const auto index = store.m_count; + + if (index >= 32) { return; } - *(uint32_t*)(store + 1024) = usedIndexes + 1; + DrawOriginData data; + data.m_pos.x = context.GetArgument(0); + data.m_pos.y = context.GetArgument(1); + data.m_pos.z = context.GetArgument(2); + data.m_is2d = context.GetArgument(3); - DrawOrigin data; - data.posX = context.GetArgument(0); - data.posY = context.GetArgument(1); - data.posZ = context.GetArgument(2); - data.unkIndex = context.GetArgument(3); + store.m_items[index] = data; + store.m_count += 1; - *(DrawOrigin*)(store + 32 * usedIndexes) = data; - *g_scriptDrawOrigin = usedIndexes; + *g_scriptDrawOriginIndex = store.m_count; }); fx::ScriptEngine::RegisterNativeHandler("CLEAR_DRAW_ORIGIN", [](fx::ScriptContext& context) { - *g_scriptDrawOrigin = -1; + *g_scriptDrawOriginIndex = -1; }); fx::ScriptEngine::RegisterNativeHandler("DRAW_LINE", [](fx::ScriptContext& context) { - if (drawRequestsCount >= NUM_MAX_DRAW_REQUESTS) + if (g_scriptImRequests.size() >= kMaxScriptImRequests) { return; } @@ -172,23 +185,23 @@ static HookFunction hookFunction([]() auto toY = context.GetArgument(4); auto toZ = context.GetArgument(5); - auto red = context.GetArgument(6); - auto green = context.GetArgument(7); - auto blue = context.GetArgument(8); - auto alpha = context.GetArgument(9); + auto red = context.GetArgument(6); + auto green = context.GetArgument(7); + auto blue = context.GetArgument(8); + auto alpha = context.GetArgument(9); - DrawRequest req; - req.type = Line; - req.first = { fromX, fromY, fromZ }; - req.second = { toX, toY, toZ }; - req.color = ConvertRGBAToHex(red, green, blue, alpha); + ScriptImRequest req; + req.m_type = SCRIPT_IM_LINE; + req.m_first = { fromX, fromY, fromZ }; + req.m_second = { toX, toY, toZ }; + req.m_color = CRGBA(red, green, blue, alpha).AsABGR(); - drawRequests[drawRequestsCount++] = req; + g_scriptImRequests.push_back(req); }); fx::ScriptEngine::RegisterNativeHandler("DRAW_POLY", [](fx::ScriptContext& context) { - if (drawRequestsCount >= NUM_MAX_DRAW_REQUESTS) + if (g_scriptImRequests.size() >= kMaxScriptImRequests) { return; } @@ -205,19 +218,63 @@ static HookFunction hookFunction([]() auto thirdY = context.GetArgument(7); auto thirdZ = context.GetArgument(8); - auto red = context.GetArgument(9); - auto green = context.GetArgument(10); - auto blue = context.GetArgument(11); - auto alpha = context.GetArgument(12); + auto red = context.GetArgument(9); + auto green = context.GetArgument(10); + auto blue = context.GetArgument(11); + auto alpha = context.GetArgument(12); - DrawRequest req; - req.type = Poly; - req.first = { firstX, firstY, firstZ }; - req.second = { secondX, secondY, secondZ }; - req.third = { thirdX, thirdY, thirdZ }; - req.color = ConvertRGBAToHex(red, green, blue, alpha); + ScriptImRequest req; + req.m_type = SCRIPT_IM_POLY; + req.m_first = { firstX, firstY, firstZ }; + req.m_second = { secondX, secondY, secondZ }; + req.m_third = { thirdX, thirdY, thirdZ }; + req.m_color = CRGBA(red, green, blue, alpha).AsABGR(); - drawRequests[drawRequestsCount++] = req; + g_scriptImRequests.push_back(req); + }); + + fx::ScriptEngine::RegisterNativeHandler("DRAW_BOX", [](fx::ScriptContext& context) + { + if (g_scriptImRequests.size() >= kMaxScriptImRequests) + { + return; + } + + auto fromX = context.GetArgument(0); + auto fromY = context.GetArgument(1); + auto fromZ = context.GetArgument(2); + + auto toX = context.GetArgument(3); + auto toY = context.GetArgument(4); + auto toZ = context.GetArgument(5); + + auto red = context.GetArgument(6); + auto green = context.GetArgument(7); + auto blue = context.GetArgument(8); + auto alpha = context.GetArgument(9); + + ScriptImRequest req; + req.m_type = SCRIPT_IM_BOX; + req.m_first = { fromX, fromY, fromZ }; + req.m_second = { toX, toY, toZ }; + req.m_color = CRGBA(red, green, blue, alpha).AsABGR(); + + g_scriptImRequests.push_back(req); + }); + + fx::ScriptEngine::RegisterNativeHandler("SET_BACKFACECULLING", [](fx::ScriptContext& context) + { + if (g_scriptImRequests.size() >= kMaxScriptImRequests) + { + return; + } + + auto flag = context.GetArgument(0); + + ScriptImRequest req; + req.m_type = flag ? SCRIPT_IM_BACKFACE_CULLING_ON : SCRIPT_IM_BACKFACE_CULLING_OFF; + + g_scriptImRequests.push_back(req); }); fx::ScriptEngine::RegisterNativeHandler("DISABLE_WORLDHORIZON_RENDERING", [](fx::ScriptContext& context) @@ -229,8 +286,4 @@ static HookFunction hookFunction([]() g_worldhorizonMgr->m_disableRendering = flag; } }); - - MH_Initialize(); - MH_CreateHook(hook::get_pattern("33 C9 E8 ? ? ? ? E8 ? ? ? ? 33 C9 E8 ? ? ? ? 0F", -0x28), ScriptImRenderPhase, (void**)&g_origScriptImRenderPhase); - MH_EnableHook(MH_ALL_HOOKS); });