diff --git a/code/components/extra-natives-five/src/NativeFixes.cpp b/code/components/extra-natives-five/src/NativeFixes.cpp index 509b1d3a41..fe3dc9d1dc 100644 --- a/code/components/extra-natives-five/src/NativeFixes.cpp +++ b/code/components/extra-natives-five/src/NativeFixes.cpp @@ -12,6 +12,7 @@ #include #include +#include #include #include #include @@ -778,6 +779,139 @@ static void FixNatives() } } +struct CompactStringBuffer +{ + std::vector slots; + std::deque strings; +}; + +static bool StartsWith(const char* data, size_t len, const char* prefix) +{ + const size_t prefixLen = std::strlen(prefix); + return len >= prefixLen && std::memcmp(data, prefix, prefixLen) == 0; +} + +static size_t StrnLenSafe(const char* data, size_t maxLen) +{ + size_t len = 0; + + while (len < maxLen && data[len] != '\0') + { + ++len; + } + + return len; +} + +static CompactStringBuffer CompactCfxStringMarkers(const void* input, size_t inputSize) +{ + constexpr size_t kSlotSize = 8; + constexpr size_t kInlineStringSize = 128; + constexpr size_t kOutputSize = 0xB0; + constexpr size_t kOutputSlotCount = kOutputSize / kSlotSize; + constexpr const char* kPrefix = "__cfx_str:"; + + CompactStringBuffer out; + + if (!input || inputSize == 0) + { + out.slots.assign(kOutputSlotCount, 0); + return out; + } + + const auto* bytes = static_cast(input); + size_t offset = 0; + + out.slots.reserve(kOutputSlotCount); + + while (offset + kSlotSize <= inputSize && out.slots.size() < kOutputSlotCount) + { + const size_t remaining = inputSize - offset; + const char* current = reinterpret_cast(bytes + offset); + + if (remaining >= kInlineStringSize && StartsWith(current, kInlineStringSize, kPrefix)) + { + const size_t prefixLen = std::strlen(kPrefix); + const size_t strLen = StrnLenSafe(current + prefixLen, kInlineStringSize - prefixLen); + + out.strings.emplace_back(current + prefixLen, strLen); + + out.slots.push_back( + out.strings.back().empty() + ? 0 + : static_cast(reinterpret_cast(out.strings.back().c_str()))); + + offset += kInlineStringSize; + continue; + } + + uint64_t value = 0; + std::memcpy(&value, bytes + offset, kSlotSize); + + out.slots.push_back(value); + offset += kSlotSize; + } + + while (out.slots.size() < kOutputSlotCount) + { + out.slots.push_back(0); + } + + if (out.slots.size() > kOutputSlotCount) + { + out.slots.resize(kOutputSlotCount); + } + + return out; +} + +static void FixScriptedAnimNative(uint64_t nativeHash) +{ + auto handler = fx::ScriptEngine::GetNativeHandler(nativeHash); + + if (!handler) + { + return; + } + + fx::ScriptEngine::RegisterNativeHandler(nativeHash, [handler](fx::ScriptContext& ctx) + { + constexpr size_t kExpandedAnimSlotSize = 0x380; + + auto low = CompactCfxStringMarkers(ctx.GetArgument(1), kExpandedAnimSlotSize); + auto mid = CompactCfxStringMarkers(ctx.GetArgument(2), kExpandedAnimSlotSize); + auto high = CompactCfxStringMarkers(ctx.GetArgument(3), kExpandedAnimSlotSize); + + //trace("low compact slots=%zu strings=%zu\n", low.slots.size(), low.strings.size()); + + if (!low.slots.empty()) + { + ctx.SetArgument(1, low.slots.data()); + } + + if (!mid.slots.empty()) + { + ctx.SetArgument(2, mid.slots.data()); + } + + if (!high.slots.empty()) + { + ctx.SetArgument(3, high.slots.data()); + } + + handler(ctx); + }); + +} + +static void FixScriptedAnimNatives() +{ + constexpr const uint64_t PlayEntityScriptedAnimHash = 0x77A1EEC547E7FCF1; + constexpr const uint64_t TaskScriptedAnimHash = 0x126EF75F1E17ABE5; + FixScriptedAnimNative(PlayEntityScriptedAnimHash); + FixScriptedAnimNative(TaskScriptedAnimHash); +} + namespace { @@ -908,6 +1042,8 @@ static HookFunction hookFunction([]() FixNatives(); + FixScriptedAnimNatives(); + if (xbr::IsGameBuildOrGreater<2612>()) { // IS_BIT_SET is missing in b2612+, re-adding for compatibility