From dbdc89c2ead3156f2d2df4c99c91ab5052fa9c6a Mon Sep 17 00:00:00 2001 From: Max Date: Wed, 1 Nov 2023 05:39:07 +0100 Subject: [PATCH] feat(onesync/server): basic state bag write policies * Basic rejection logic for write-protected keys in player/entity state bags. * Resync replicated state bags with the source client (on rejection). * Native handler 'SET_STATE_BAG_KEY_POLICY' to allow setting write policies per key. --- .../include/StateBagComponent.h | 10 ++++ .../src/StateBagComponent.cpp | 50 +++++++++++++++++-- .../src/ResourceScriptFunctions.cpp | 11 ++++ ext/native-decls/SetStateBagKeyPolicy.md | 16 ++++++ 4 files changed, 84 insertions(+), 3 deletions(-) create mode 100644 ext/native-decls/SetStateBagKeyPolicy.md diff --git a/code/components/citizen-resources-core/include/StateBagComponent.h b/code/components/citizen-resources-core/include/StateBagComponent.h index f6b1c15623..be269f9eaf 100644 --- a/code/components/citizen-resources-core/include/StateBagComponent.h +++ b/code/components/citizen-resources-core/include/StateBagComponent.h @@ -119,6 +119,16 @@ class CRC_EXPORT StateBagComponent : public fwRefCountable, public IAttached GetStateBag(std::string_view id) = 0; + // + // Sets the write policy for a state bag key. + // + virtual void SetKeyPolicy(std::string_view key, bool allow = true) = 0; + + // + // Gets the write policy for a state bag key. + // + virtual bool GetKeyPolicy(std::string_view key) = 0; + // // Registers a state bag for the specified identifier. The pointer returned should be // the *only* reference, every reference kept internally should be weak. diff --git a/code/components/citizen-resources-core/src/StateBagComponent.cpp b/code/components/citizen-resources-core/src/StateBagComponent.cpp index b9c7017d8e..4561681fe3 100644 --- a/code/components/citizen-resources-core/src/StateBagComponent.cpp +++ b/code/components/citizen-resources-core/src/StateBagComponent.cpp @@ -31,6 +31,10 @@ class StateBagComponentImpl : public StateBagComponent virtual std::shared_ptr GetStateBag(std::string_view id) override; + virtual void SetKeyPolicy(std::string_view key, bool allow = true) override; + + virtual bool GetKeyPolicy(std::string_view key) override; + virtual std::shared_ptr RegisterStateBag(std::string_view id, bool useParentTargets = false) override; virtual void SetGameInterface(StateBagGameInterface* gi) override; @@ -82,6 +86,9 @@ class StateBagComponentImpl : public StateBagComponent std::unordered_map> m_stateBags; std::shared_mutex m_mapMutex; + std::unordered_set m_keyPolicies; + std::shared_mutex m_keyPolicyMutex; + // pre-created state bag stuff // list of state bag prefixes @@ -476,6 +483,31 @@ std::shared_ptr StateBagComponentImpl::GetStateBag(std::string_view id return (bag != m_stateBags.end()) ? bag->second.lock() : nullptr; } +bool StateBagComponentImpl::GetKeyPolicy(std::string_view key) +{ +#ifdef IS_FXSERVER + std::shared_lock lock(m_keyPolicyMutex); + return m_keyPolicies.find(std::string{ key }) == m_keyPolicies.end(); +#else + return true; +#endif +} + +void StateBagComponentImpl::SetKeyPolicy(std::string_view key, bool allow) +{ +#ifdef IS_FXSERVER + std::shared_lock lock(m_keyPolicyMutex); + if (allow) + { + m_keyPolicies.erase(std::string(key)); + } + else + { + m_keyPolicies.insert(std::string(key)); + } +#endif +} + void StateBagComponentImpl::RegisterTarget(int id) { bool isNew = false; @@ -669,17 +701,29 @@ void StateBagComponentImpl::HandlePacket(int source, std::string_view dataRaw, s if (bag) { auto bagRef = std::static_pointer_cast(bag); + auto key = std::string_view{ keyBuffer.data(), keyBuffer.size() }; + auto writePolicy = GetKeyPolicy(key); // TODO: rate checks, policy checks auto peer = bagRef->GetOwningPeer(); - if (!peer.has_value() || source == *peer) + if ((!peer.has_value() || source == *peer) && writePolicy) { bagRef->SetKey( source, - std::string_view{ keyBuffer.data(), keyBuffer.size() }, + key, std::string_view{ reinterpret_cast(data.data()), data.size() }, m_role == StateBagRole::Server); - } + } +#ifdef IS_FXSERVER + // if we're the server, trigger resend to keep the client in sync + else if (!writePolicy) + { + if (auto serverBag = GetStateBag(bagName); auto serverKey = serverBag->GetKey(key)) + { + bagRef->SendKeyValue(source, key, serverKey.value()); + } + } +#endif } else if(outBagNameName != nullptr) { diff --git a/code/components/citizen-scripting-core/src/ResourceScriptFunctions.cpp b/code/components/citizen-scripting-core/src/ResourceScriptFunctions.cpp index bcc6fce60a..ddea9b367c 100644 --- a/code/components/citizen-scripting-core/src/ResourceScriptFunctions.cpp +++ b/code/components/citizen-scripting-core/src/ResourceScriptFunctions.cpp @@ -396,4 +396,15 @@ static InitFunction initFunction([] () sbac->OnStateBagChange.Disconnect(size_t(cookie)); }); + + fx::ScriptEngine::RegisterNativeHandler("SET_STATE_BAG_KEY_POLICY", [](fx::ScriptContext& context) + { + auto keyName = context.CheckArgument(0); + auto writePolicy = context.GetArgument(1); + + auto rm = fx::ResourceManager::GetCurrent(); + auto sbac = rm->GetComponent(); + + sbac->SetKeyPolicy(keyName, writePolicy); + }); }); diff --git a/ext/native-decls/SetStateBagKeyPolicy.md b/ext/native-decls/SetStateBagKeyPolicy.md new file mode 100644 index 0000000000..f3724d6a53 --- /dev/null +++ b/ext/native-decls/SetStateBagKeyPolicy.md @@ -0,0 +1,16 @@ +--- +ns: CFX +apiset: server +--- +## SET_STATE_BAG_KEY_POLICY + +```c +void SET_STATE_BAG_KEY_POLICY(char* keyName, BOOL writePolicy); +``` + +Sets the write policy for a statebag key. +(This affects all player and entity statebags.) + +## Parameters +* **keyName**: The key name to set the write policy for. +* **writePolicy**: Whether to allow clients to write to this key.