From e0927735b26c154b323f654737ff5eb725c7d7f7 Mon Sep 17 00:00:00 2001 From: Viny <77700518+vinymayan@users.noreply.github.com> Date: Wed, 17 Jun 2026 15:55:19 -0300 Subject: [PATCH 1/8] cacetada de coisa --- .../Source/Scripts/DynamicPersistentForms.psc | 7 +- .../Source/Scripts/DynamicPersistentForms.psc | 7 +- SKSE_Plugin/include/Services.h | 28 +- SKSE_Plugin/include/form.h | 4 +- SKSE_Plugin/include/form_record.h | 3 +- SKSE_Plugin/include/model.h | 30 +- SKSE_Plugin/include/papyrus.h | 4 +- SKSE_Plugin/include/persistence.h | 8 +- SKSE_Plugin/include/serializer.h | 50 +- SKSE_Plugin/public/DPFAPI.h | 48 ++ SKSE_Plugin/src/Services.cpp | 19 +- SKSE_Plugin/src/form.cpp | 112 ++++- SKSE_Plugin/src/model.cpp | 117 ++++- SKSE_Plugin/src/papyrus.cpp | 7 +- SKSE_Plugin/src/persistence.cpp | 465 ++++++++++++++---- SKSE_Plugin/src/plugin.cpp | 44 +- 16 files changed, 738 insertions(+), 215 deletions(-) create mode 100644 SKSE_Plugin/public/DPFAPI.h diff --git a/DIST/Dynamic Persistent Forms/Source/Scripts/DynamicPersistentForms.psc b/DIST/Dynamic Persistent Forms/Source/Scripts/DynamicPersistentForms.psc index 77dfc76..bf1a592 100644 --- a/DIST/Dynamic Persistent Forms/Source/Scripts/DynamicPersistentForms.psc +++ b/DIST/Dynamic Persistent Forms/Source/Scripts/DynamicPersistentForms.psc @@ -1,7 +1,9 @@ -scriptName DynamicPersistentForms hidden +scriptName DynamicPersistentForms hidden ; Creates a new form that is a copy of given base form, changes to that form will be persisted in the save game. Form function Create(Form item) global native +; Creates a new empty form of the requested FormType and lets DynamicPersistentForms own its persistent FormID slot. +Form function CreateByType(int formType) global native ; Dispose a form that was created using the previous function. function Dispose(Form item) global native @@ -44,4 +46,5 @@ function SetAmmoProjectile(Ammo ammo, Projectile projectile) global native ; Grand = 5 function SetSoulGemCapacity(SoulGem gem, int capacity) global native function SetSoulGemCurrentSoul(SoulGem gem, int capacity) global native -function LinkSoulGems(SoulGem empty, SoulGem filled) global native \ No newline at end of file +function LinkSoulGems(SoulGem empty, SoulGem filled) global native + diff --git a/PapyrusInterface/Source/Scripts/DynamicPersistentForms.psc b/PapyrusInterface/Source/Scripts/DynamicPersistentForms.psc index 77dfc76..bf1a592 100644 --- a/PapyrusInterface/Source/Scripts/DynamicPersistentForms.psc +++ b/PapyrusInterface/Source/Scripts/DynamicPersistentForms.psc @@ -1,7 +1,9 @@ -scriptName DynamicPersistentForms hidden +scriptName DynamicPersistentForms hidden ; Creates a new form that is a copy of given base form, changes to that form will be persisted in the save game. Form function Create(Form item) global native +; Creates a new empty form of the requested FormType and lets DynamicPersistentForms own its persistent FormID slot. +Form function CreateByType(int formType) global native ; Dispose a form that was created using the previous function. function Dispose(Form item) global native @@ -44,4 +46,5 @@ function SetAmmoProjectile(Ammo ammo, Projectile projectile) global native ; Grand = 5 function SetSoulGemCapacity(SoulGem gem, int capacity) global native function SetSoulGemCurrentSoul(SoulGem gem, int capacity) global native -function LinkSoulGems(SoulGem empty, SoulGem filled) global native \ No newline at end of file +function LinkSoulGems(SoulGem empty, SoulGem filled) global native + diff --git a/SKSE_Plugin/include/Services.h b/SKSE_Plugin/include/Services.h index f74ee41..b3e053a 100644 --- a/SKSE_Plugin/include/Services.h +++ b/SKSE_Plugin/include/Services.h @@ -1,33 +1,19 @@ -#pragma once -#include - -namespace DPF { - // Defina um ID único para sua interface - constexpr auto InterfaceName = "DynamicPersistentForms"; - constexpr uint32_t InterfaceVersion = 1; - - class IDynamicPersistentForms { - public: - virtual ~IDynamicPersistentForms() = default; - virtual uint32_t GetVersion() const = 0; +#pragma once - - virtual RE::TESForm* Create(RE::TESForm* baseItem) = 0; - virtual void Dispose(RE::TESForm* form) = 0; - virtual void Track(RE::TESForm* item) = 0; - virtual void UnTrack(RE::TESForm* item) = 0; - }; -} +#include "../public/DPFAPI.h" +#include namespace Services { - // Mutex global para thread safety inline std::mutex serviceMutex; RE::TESForm* Create(RE::TESForm* baseItem); + RE::TESForm* CreateByType(uint32_t formType); + void Track(RE::TESForm* baseItem); void UnTrack(RE::TESForm* form); void Dispose(RE::TESForm* form); -} \ No newline at end of file +} + diff --git a/SKSE_Plugin/include/form.h b/SKSE_Plugin/include/form.h index c3f18e6..ae3e62a 100644 --- a/SKSE_Plugin/include/form.h +++ b/SKSE_Plugin/include/form.h @@ -19,4 +19,6 @@ void copyComponent(RE::TESForm* from, RE::TESForm* to) { void copyAppearence(RE::TESForm* source, RE::TESForm* target); -RE::TESForm* AddForm(RE::TESForm* baseItem); \ No newline at end of file +RE::TESForm* AddForm(RE::TESForm* baseItem); + +RE::TESForm* AddFormByType(RE::FormType formType); diff --git a/SKSE_Plugin/include/form_record.h b/SKSE_Plugin/include/form_record.h index 42567fe..908f6e2 100644 --- a/SKSE_Plugin/include/form_record.h +++ b/SKSE_Plugin/include/form_record.h @@ -27,4 +27,5 @@ class FormRecord { bool deleted = false; RE::FormType formType{}; RE::FormID formId{}; -}; \ No newline at end of file + uint32_t order = 0; +}; diff --git a/SKSE_Plugin/include/model.h b/SKSE_Plugin/include/model.h index 01a5735..689cb8f 100644 --- a/SKSE_Plugin/include/model.h +++ b/SKSE_Plugin/include/model.h @@ -1,16 +1,21 @@ #pragma once -#include -#include #include +#include +#include +#include +#include #include "form_record.h" inline std::vector formData; inline std::vector formRef; inline bool espFound = false; -inline uint32_t lastFormId = 0; -inline uint32_t firstFormId = 0; +inline uint32_t firstDynamicLocalId = 0x801; +inline uint32_t nextDynamicLocalId = 0x801; +inline uint32_t nextRecordOrder = 1; inline uint32_t dynamicModId = 0; +inline std::string dynamicPluginName = "Dynamic Persistent Forms.esp"; +inline std::unordered_set reservedDynamicLocalIds; void AddFormData(FormRecord* item); @@ -22,11 +27,20 @@ void EachFormData(const std::function& iteration); void EachFormRef(const std::function& iteration); -void incrementLastFormID(); +bool IsDynamicFormID(RE::FormID formId); + +uint32_t ToDynamicLocalID(RE::FormID formId); + +RE::FormID MakeDynamicFormID(uint32_t localId); + +void ReserveDynamicLocalID(uint32_t localId); + +void ReserveDynamicFormID(RE::FormID formId); +RE::FormID AllocateDynamicFormID(); -void UpdateId(); +void ReadFirstFormIdFromESP(); -void ResetId(); +void ResetDynamicState(); -void ReadFirstFormIdFromESP(); \ No newline at end of file +void ClearRecords(bool deleteActualForms); diff --git a/SKSE_Plugin/include/papyrus.h b/SKSE_Plugin/include/papyrus.h index 81b09a5..4a7dfd2 100644 --- a/SKSE_Plugin/include/papyrus.h +++ b/SKSE_Plugin/include/papyrus.h @@ -3,6 +3,8 @@ RE::TESForm* Create(RE::StaticFunctionTag*, RE::TESForm* baseItem); +RE::TESForm* CreateByType(RE::StaticFunctionTag*, uint32_t formType); + void Track(RE::StaticFunctionTag*, RE::TESForm* baseItem); void UnTrack(RE::StaticFunctionTag*, RE::TESForm* form); @@ -57,4 +59,4 @@ void SetSoulGemCurrentSoul(RE::StaticFunctionTag*, RE::TESSoulGem* soulGem, uint void LinkSoulGems(RE::StaticFunctionTag*, RE::TESSoulGem* empty, RE::TESSoulGem* filled); -bool PapyrusFunctions(RE::BSScript::IVirtualMachine* vm); \ No newline at end of file +bool PapyrusFunctions(RE::BSScript::IVirtualMachine* vm); diff --git a/SKSE_Plugin/include/persistence.h b/SKSE_Plugin/include/persistence.h index ebdc7dc..d8957ce 100644 --- a/SKSE_Plugin/include/persistence.h +++ b/SKSE_Plugin/include/persistence.h @@ -1,14 +1,12 @@ #pragma once -#include "rapidjson/document.h" -#include "rapidjson/filereadstream.h" #include #include +#include namespace fs = std::filesystem; -std::string GetCacheFilePath(); -void LoadCache(); -void SaveCache(); +std::string BuildStateJson(); +bool RestoreStateJson(const std::string& json); void SaveCallback(SKSE::SerializationInterface* a_intfc); diff --git a/SKSE_Plugin/include/serializer.h b/SKSE_Plugin/include/serializer.h index b20a97f..b6a22b6 100644 --- a/SKSE_Plugin/include/serializer.h +++ b/SKSE_Plugin/include/serializer.h @@ -6,6 +6,8 @@ #include #include #include +#include +#include class StreamWrapper { std::stringstream stream; @@ -194,7 +196,7 @@ class Serializer { Write(localId); } else if (modId == 0xfe) { logger::trace("light"); - const auto lightId = (formId >> 12) & 0xFFF; + const auto lightId = static_cast((formId >> 12) & 0xFFF); const auto file = dataHandler->LookupLoadedLightModByIndex(lightId); if (file) { const auto localId = formId & 0xFFF; @@ -208,7 +210,7 @@ class Serializer { } } else { logger::trace("regular"); - const auto file = dataHandler->LookupLoadedModByIndex(modId); + const auto file = dataHandler->LookupLoadedModByIndex(static_cast(modId)); if (file) { const auto localId = formId & 0xFFFFFF; const std::string fileName = file->fileName; @@ -322,4 +324,46 @@ class FileReader : public Serializer { logger::error("Error: File not open for reading."); return T(); } -}; \ No newline at end of file +}; + +class MemoryWriter : public Serializer { + std::vector bytes; + +public: + const std::vector& Data() const { return bytes; } + + template + T ReadImplementation() { + return T(); + } + + template + void WriteImplementation(const T value) { + const auto* raw = reinterpret_cast(&value); + bytes.insert(bytes.end(), raw, raw + sizeof(T)); + } +}; + +class MemoryReader : public Serializer { + std::vector bytes; + size_t offset = 0; + +public: + explicit MemoryReader(std::vector input) : bytes(std::move(input)) {} + + template + void WriteImplementation(T) { + } + + template + T ReadImplementation() { + T value{}; + if (offset + sizeof(T) > bytes.size()) { + logger::error("MemoryReader reached end of buffer"); + return value; + } + std::memcpy(&value, bytes.data() + offset, sizeof(T)); + offset += sizeof(T); + return value; + } +}; diff --git a/SKSE_Plugin/public/DPFAPI.h b/SKSE_Plugin/public/DPFAPI.h new file mode 100644 index 0000000..caa04e2 --- /dev/null +++ b/SKSE_Plugin/public/DPFAPI.h @@ -0,0 +1,48 @@ +#pragma once + +#include "RE/Skyrim.h" +#include +#include + +namespace DPF { + constexpr auto InterfaceName = "DynamicPersistentForms"; + constexpr uint32_t InterfaceVersion = 2; + + class IDynamicPersistentForms { + public: + virtual ~IDynamicPersistentForms() = default; + + virtual uint32_t GetVersion() const = 0; + + // v1 + virtual RE::TESForm* Create(RE::TESForm* baseItem) = 0; + virtual void Dispose(RE::TESForm* form) = 0; + virtual void Track(RE::TESForm* item) = 0; + virtual void UnTrack(RE::TESForm* item) = 0; + + // v2 + virtual RE::TESForm* CreateByType(uint32_t formType) = 0; + }; + + using GetDPFAPI = void* (*)(); + + inline IDynamicPersistentForms* GetAPI() { + const auto module = GetModuleHandleA("DynamicPersistentForms.dll"); + if (!module) { + return nullptr; + } + + const auto getter = reinterpret_cast(GetProcAddress(module, "GetDPFAPI")); + if (!getter) { + return nullptr; + } + + auto* api = static_cast(getter()); + if (!api || api->GetVersion() < InterfaceVersion) { + return nullptr; + } + + return api; + } +} + diff --git a/SKSE_Plugin/src/Services.cpp b/SKSE_Plugin/src/Services.cpp index c5fd81f..66d1581 100644 --- a/SKSE_Plugin/src/Services.cpp +++ b/SKSE_Plugin/src/Services.cpp @@ -1,4 +1,4 @@ -#include "Services.h" +#include "Services.h" #include "form.h" #include "model.h" @@ -17,6 +17,19 @@ RE::TESForm* Services::Create(RE::TESForm* baseItem) { } } +RE::TESForm* Services::CreateByType(const uint32_t formType) { + std::lock_guard lock(serviceMutex); + try { + auto* newForm = AddFormByType(static_cast(formType)); + if (newForm) { + logger::info("new form id", newForm->GetFormID()); + } + return newForm; + } catch (const std::exception&) { + return nullptr; + } +} + void Services::Track(RE::TESForm* baseItem) { std::lock_guard lock(serviceMutex); try { @@ -31,7 +44,7 @@ void Services::Track(RE::TESForm* baseItem) { found = true; return false; } - // Correção da lógica original que parecia ter um if duplicado + // Correção da lógica original que parecia ter um if duplicado return true; }); if (!found) { @@ -75,4 +88,4 @@ void Services::Dispose(RE::TESForm* form) { }); } catch (const std::exception&) { } -} \ No newline at end of file +} diff --git a/SKSE_Plugin/src/form.cpp b/SKSE_Plugin/src/form.cpp index d0d4c6a..86e5ff2 100644 --- a/SKSE_Plugin/src/form.cpp +++ b/SKSE_Plugin/src/form.cpp @@ -140,38 +140,104 @@ void copyAppearence(RE::TESForm* source, RE::TESForm* target) { } -RE::TESForm* AddForm(RE::TESForm* baseItem) { - if (!espFound) { - return nullptr; +namespace { + RE::TESForm* CreateFormInstance(const RE::FormType formType, const RE::FormID formId) { + const auto factory = RE::IFormFactory::GetFormFactoryByType(formType); + if (!factory) { + logger::error("No form factory for form type {}", static_cast(formType)); + return nullptr; + } + + auto* result = factory->Create(); + if (!result) { + logger::error("Factory returned null for form type {}", static_cast(formType)); + return nullptr; + } + + result->SetFormID(formId, false); + return result; } - RE::TESForm* result = nullptr; - EachFormData([&](FormRecord* item) { - if (item->deleted) { - logger::info("item undeleted", item->formId); - const auto factory = RE::IFormFactory::GetFormFactoryByType(baseItem->GetFormType()); - result = factory->Create(); - result->SetFormID(item->formId, false); - item->Undelete(result, baseItem->GetFormType()); + RE::TESForm* ReuseDeletedSlot(const RE::FormType formType, RE::TESForm* baseItem) { + RE::TESForm* result = nullptr; + EachFormData([&](FormRecord* item) { + if (!item->deleted || item->formType != formType || !IsDynamicFormID(item->formId)) { + return true; + } + + logger::info("Reusing deleted DPF slot {:08X}", item->formId); + result = CreateFormInstance(formType, item->formId); + if (!result) { + return false; + } + + item->Undelete(result, formType); item->baseForm = baseItem; - applyPattern(item); + item->modelForm = nullptr; + if (baseItem) { + applyPattern(item); + } return false; - } - return true; - }); - - if (result) { + }); return result; } +} + +RE::TESForm* AddForm(RE::TESForm* baseItem) { + if (!espFound) { + return nullptr; + } + if (!baseItem) { + logger::error("Create(baseItem) was called with a null baseItem. Use CreateByType for empty forms."); + return nullptr; + } + + if (auto* reused = ReuseDeletedSlot(baseItem->GetFormType(), baseItem)) { + return reused; + } logger::info("item created"); - const auto factory = RE::IFormFactory::GetFormFactoryByType(baseItem->GetFormType()); - const auto newForm = factory->Create(); - newForm->SetFormID(lastFormId, false); - const auto slot = FormRecord::CreateNew(newForm, baseItem->GetFormType(), lastFormId); - incrementLastFormID(); + const auto formId = AllocateDynamicFormID(); + if (formId == 0) { + return nullptr; + } + + const auto newForm = CreateFormInstance(baseItem->GetFormType(), formId); + if (!newForm) { + return nullptr; + } + + const auto slot = FormRecord::CreateNew(newForm, baseItem->GetFormType(), formId); slot->baseForm = baseItem; applyPattern(slot); AddFormData(slot); return newForm; -} \ No newline at end of file +} + +RE::TESForm* AddFormByType(const RE::FormType formType) { + if (!espFound) { + return nullptr; + } + if (formType == RE::FormType::None) { + logger::error("CreateByType called with FormType::None"); + return nullptr; + } + + if (auto* reused = ReuseDeletedSlot(formType, nullptr)) { + return reused; + } + + const auto formId = AllocateDynamicFormID(); + if (formId == 0) { + return nullptr; + } + + auto* newForm = CreateFormInstance(formType, formId); + if (!newForm) { + return nullptr; + } + + auto* slot = FormRecord::CreateNew(newForm, formType, formId); + AddFormData(slot); + return newForm; +} diff --git a/SKSE_Plugin/src/model.cpp b/SKSE_Plugin/src/model.cpp index b5a4c8e..95b1eac 100644 --- a/SKSE_Plugin/src/model.cpp +++ b/SKSE_Plugin/src/model.cpp @@ -1,16 +1,28 @@ -#include "model.h" +#include "model.h" +#include void AddFormData(FormRecord* item) { if (!item) { return; } + if (item->order == 0) { + item->order = nextRecordOrder++; + } else if (item->order >= nextRecordOrder) { + nextRecordOrder = item->order + 1; + } formData.push_back(item); + ReserveDynamicFormID(item->formId); } void AddFormRef(FormRecord* item) { if (!item) { return; } + if (item->order == 0) { + item->order = nextRecordOrder++; + } else if (item->order >= nextRecordOrder) { + nextRecordOrder = item->order + 1; + } formRef.push_back(item); } @@ -30,34 +42,99 @@ void EachFormRef(const std::function& iteration) { } } -void incrementLastFormID() { - ++lastFormId; +bool IsDynamicFormID(const RE::FormID formId) { + if (!espFound || formId == 0) { + return false; + } + return ((formId >> 24) & 0xff) == dynamicModId; } -void UpdateId() { - EachFormData([&](FormRecord* item) { - if (item->formId > lastFormId) { - lastFormId = item->formId; - } - return true; - }); - incrementLastFormID(); +uint32_t ToDynamicLocalID(const RE::FormID formId) { + return formId & 0x00ffffff; } -void ResetId() { - lastFormId = firstFormId; +RE::FormID MakeDynamicFormID(const uint32_t localId) { + return (dynamicModId << 24) | (localId & 0x00ffffff); +} + +void ReserveDynamicLocalID(const uint32_t localId) { + if (localId == 0) { + return; + } + reservedDynamicLocalIds.insert(localId & 0x00ffffff); + if (localId >= nextDynamicLocalId) { + nextDynamicLocalId = localId + 1; + } +} + +void ReserveDynamicFormID(const RE::FormID formId) { + if (IsDynamicFormID(formId)) { + ReserveDynamicLocalID(ToDynamicLocalID(formId)); + } +} + +RE::FormID AllocateDynamicFormID() { + if (!espFound) { + logger::error("Cannot allocate DynamicPersistentForms FormID because Dynamic Persistent Forms.esp was not found"); + return 0; + } + + uint32_t localId = std::max(nextDynamicLocalId, firstDynamicLocalId); + while (localId < 0x00ffffff) { + const auto fullId = MakeDynamicFormID(localId); + const bool reserved = reservedDynamicLocalIds.contains(localId); + const bool existsInGame = RE::TESForm::LookupByID(fullId) != nullptr; + if (!reserved && !existsInGame) { + ReserveDynamicLocalID(localId); + logger::info("Allocated DynamicPersistentForms FormID {:08X}", fullId); + return fullId; + } + ++localId; + } + + logger::error("Dynamic Persistent Forms.esp has no available FormID slots"); + return 0; } void ReadFirstFormIdFromESP() { const auto dataHandler = RE::TESDataHandler::GetSingleton(); + espFound = false; + dynamicModId = 0; + firstDynamicLocalId = 0x801; + nextDynamicLocalId = firstDynamicLocalId; + dynamicPluginName = "Dynamic Persistent Forms.esp"; + + const auto modIndex = dataHandler->GetLoadedModIndex(dynamicPluginName); + if (!modIndex.has_value()) { + logger::error("Dynamic Persistent Forms.esp was not found"); + return; + } - const auto id = dataHandler->LookupFormID(0x800, "Dynamic Persistent Forms.esp"); + espFound = true; + dynamicModId = modIndex.value(); + logger::info("Dynamic Persistent Forms.esp found at mod index {:02X}", dynamicModId); +} + +void ResetDynamicState() { + reservedDynamicLocalIds.clear(); + nextDynamicLocalId = firstDynamicLocalId; + nextRecordOrder = 1; +} - if (id != 0) { - espFound = true; +void ClearRecords(const bool deleteActualForms) { + while (!formRef.empty()) { + delete formRef.back(); + formRef.pop_back(); } - firstFormId = id + 1; - lastFormId = firstFormId; - dynamicModId = (firstFormId >> 24) & 0xff; -} \ No newline at end of file + while (!formData.empty()) { + const auto item = formData.back(); + if (deleteActualForms && item && item->actualForm) { + item->actualForm->SetDelete(true); + } + delete item; + formData.pop_back(); + } + + ResetDynamicState(); +} diff --git a/SKSE_Plugin/src/papyrus.cpp b/SKSE_Plugin/src/papyrus.cpp index 0a00771..e1945af 100644 --- a/SKSE_Plugin/src/papyrus.cpp +++ b/SKSE_Plugin/src/papyrus.cpp @@ -9,6 +9,10 @@ RE::TESForm* Create(RE::StaticFunctionTag*, RE::TESForm* baseItem) { return Services::Create(baseItem); } +RE::TESForm* CreateByType(RE::StaticFunctionTag*, const uint32_t formType) { + return Services::CreateByType(formType); +} + void Track(RE::StaticFunctionTag*, RE::TESForm* baseItem) { Services::Track(baseItem); } @@ -298,6 +302,7 @@ void LinkSoulGems(RE::StaticFunctionTag*, RE::TESSoulGem* empty, RE::TESSoulGem* bool PapyrusFunctions(RE::BSScript::IVirtualMachine* vm) { vm->RegisterFunction("Create", "DynamicPersistentForms", Create); + vm->RegisterFunction("CreateByType", "DynamicPersistentForms", CreateByType); vm->RegisterFunction("Dispose", "DynamicPersistentForms", Dispose); vm->RegisterFunction("Track", "DynamicPersistentForms", Track); vm->RegisterFunction("UnTrack", "DynamicPersistentForms", UnTrack); @@ -328,4 +333,4 @@ bool PapyrusFunctions(RE::BSScript::IVirtualMachine* vm) { vm->RegisterFunction("LinkSoulGems", "DynamicPersistentForms", LinkSoulGems); return true; -} \ No newline at end of file +} diff --git a/SKSE_Plugin/src/persistence.cpp b/SKSE_Plugin/src/persistence.cpp index f363d7c..a755556 100644 --- a/SKSE_Plugin/src/persistence.cpp +++ b/SKSE_Plugin/src/persistence.cpp @@ -1,140 +1,415 @@ -#include "persistence.h" +#include "persistence.h" +#include "form.h" #include "form_record_serializer.h" +#include "model.h" #include "serializer.h" +#include "rapidjson/document.h" +#include "rapidjson/prettywriter.h" +#include "rapidjson/stringbuffer.h" +#include +#include +#include +#include +#include +#include -std::mutex callbackMutext; +namespace { + constexpr uint32_t kJsonRecord = 'JSN1'; + constexpr uint32_t kJsonVersion = 1; -void SaveCallback(SKSE::SerializationInterface* a_intfc) { - std::lock_guard lock(callbackMutext); - try { - logger::info("SAVE CAllBACK"); - if (!a_intfc->OpenRecord('ARR_', 1)) { - logger::error("Failed to open record for arr!"); - } - else { - const auto serializer = new SaveDataSerializer(a_intfc); - StoreAllFormRecords(serializer); + std::mutex callbackMutex; + + std::string ToHex(const uint32_t value, const int width = 0) { + std::ostringstream stream; + stream << "0x" << std::uppercase << std::hex << std::setfill('0'); + if (width > 0) { + stream << std::setw(width); } - SaveCache(); + stream << value; + return stream.str(); } - catch (const std::exception&) { - logger::error("error saving"); + + uint32_t ParseUInt(const rapidjson::Value& value, const uint32_t fallback = 0) { + if (value.IsUint()) { + return value.GetUint(); + } + if (!value.IsString()) { + return fallback; + } + + const std::string text = value.GetString(); + try { + return static_cast(std::stoul(text, nullptr, 0)); + } catch (...) { + return fallback; + } } -} -void LoadCallback(SKSE::SerializationInterface* a_intfc) { - std::lock_guard lock(callbackMutext); - try { - logger::info("LOAD CAllBACK"); + std::string BytesToHex(const std::vector& bytes) { + static constexpr char digits[] = "0123456789ABCDEF"; + std::string output; + output.reserve(bytes.size() * 2); + for (const auto byte : bytes) { + output.push_back(digits[(byte >> 4) & 0x0f]); + output.push_back(digits[byte & 0x0f]); + } + return output; + } - uint32_t type; - uint32_t version; - uint32_t length; - bool refreshGame = false; + std::vector HexToBytes(const std::string& text) { + std::vector bytes; + if (text.size() % 2 != 0) { + return bytes; + } - while (a_intfc->GetNextRecordInfo(type, version, length)) { - switch (type) { - case 'ARR_': { - const auto serializer = new SaveDataSerializer(a_intfc); - refreshGame = RestoreAllFormRecords(serializer); - delete serializer; - } - break; - default: - logger::error("Unrecognized signature type!"); - break; + bytes.reserve(text.size() / 2); + for (size_t i = 0; i < text.size(); i += 2) { + uint32_t value = 0; + const auto result = std::from_chars(text.data() + i, text.data() + i + 2, value, 16); + if (result.ec != std::errc()) { + bytes.clear(); + return bytes; } + bytes.push_back(static_cast(value)); + } + return bytes; + } + + template + rapidjson::Value StringValue(Allocator& allocator, const std::string& text) { + rapidjson::Value value; + value.SetString(text.c_str(), static_cast(text.size()), allocator); + return value; + } + + template + rapidjson::Value FormRefToJson(RE::TESForm* form, Allocator& allocator) { + if (!form) { + return rapidjson::Value(rapidjson::kNullType); + } + + rapidjson::Value result(rapidjson::kObjectType); + if (IsDynamicFormID(form->GetFormID())) { + result.AddMember("dynamic", true, allocator); + result.AddMember("localId", StringValue(allocator, ToHex(ToDynamicLocalID(form->GetFormID()), 6)), allocator); + return result; } - if (refreshGame) { - SaveCache(); - UpdateId(); - RE::PlayerCharacter::GetSingleton()->KillImmediate(); + + if (const auto file = form->GetFile(0)) { + result.AddMember("file", StringValue(allocator, file->fileName), allocator); + result.AddMember("localId", StringValue(allocator, ToHex(form->GetLocalFormID(), 6)), allocator); + return result; } - logger::info("CAllBACK LOADED"); + result.AddMember("formId", StringValue(allocator, ToHex(form->GetFormID(), 8)), allocator); + return result; } - catch (const std::exception&) { - logger::error("error loading"); + + RE::TESForm* JsonToFormRef(const rapidjson::Value& value) { + if (!value.IsObject()) { + return nullptr; + } + + if (value.HasMember("dynamic") && value["dynamic"].IsBool() && value["dynamic"].GetBool()) { + const auto localId = value.HasMember("localId") ? ParseUInt(value["localId"]) : 0; + return localId ? RE::TESForm::LookupByID(MakeDynamicFormID(localId)) : nullptr; + } + + if (value.HasMember("file") && value["file"].IsString() && value.HasMember("localId")) { + const auto localId = ParseUInt(value["localId"]); + const auto fullId = RE::TESDataHandler::GetSingleton()->LookupFormID(localId, value["file"].GetString()); + return fullId ? RE::TESForm::LookupByID(fullId) : nullptr; + } + + if (value.HasMember("formId")) { + const auto formId = ParseUInt(value["formId"]); + return formId ? RE::TESForm::LookupByID(formId) : nullptr; + } + + return nullptr; } -} -std::string GetCacheFilePath() -{ - const std::string settingsFile = "Data/SKSE/Plugins/DPF_Settings.json"; - const std::string defaultPath = "Data/SKSE/Plugins/[NoDelete] DPF/DynamicPersistentFormsCache.bin"; + std::string SerializeRecordData(FormRecord* record) { + if (!record) { + return {}; + } - if (!fs::exists(settingsFile)) { - return defaultPath; + MemoryWriter writer; + StoreFormRecordData(&writer, record); + return BytesToHex(writer.Data()); } - FILE* fp = fopen(settingsFile.c_str(), "rb"); - if (!fp) return defaultPath; + bool DeserializeRecordData(FormRecord* record, const std::string& hex) { + if (!record || hex.empty()) { + return false; + } - char readBuffer[65536]; - rapidjson::FileReadStream is(fp, readBuffer, sizeof(readBuffer)); - rapidjson::Document d; - d.ParseStream(is); - fclose(fp); + auto bytes = HexToBytes(hex); + if (bytes.empty()) { + return false; + } - if (!d.HasParseError() && d.HasMember("SavePath") && d["SavePath"].IsString()) { - std::string customDir = d["SavePath"].GetString(); - return customDir + "/DynamicPersistentFormsCache.bin"; + MemoryReader reader(std::move(bytes)); + RestoreFormRecordData(&reader, record); + return true; } - return defaultPath; -} + std::vector SortedRecords(const std::vector& records) { + auto sorted = records; + std::ranges::sort(sorted, [](const FormRecord* lhs, const FormRecord* rhs) { + return lhs && rhs ? lhs->order < rhs->order : lhs != nullptr; + }); + return sorted; + } -void LoadCache() { - logger::info("LOAD CACHE"); + template + rapidjson::Value CreatedRecordToJson(FormRecord* record, Allocator& allocator) { + rapidjson::Value item(rapidjson::kObjectType); + const auto localId = ToDynamicLocalID(record->formId); - std::string path = GetCacheFilePath(); + item.AddMember("order", record->order, allocator); + item.AddMember("localId", StringValue(allocator, ToHex(localId, 6)), allocator); + item.AddMember("formId", StringValue(allocator, ToHex(record->formId, 8)), allocator); + item.AddMember("formType", static_cast(record->formType), allocator); + item.AddMember("deleted", record->deleted, allocator); + item.AddMember("baseForm", FormRefToJson(record->baseForm, allocator), allocator); + item.AddMember("modelForm", FormRefToJson(record->modelForm, allocator), allocator); + item.AddMember("data", StringValue(allocator, SerializeRecordData(record)), allocator); + return item; + } - if (!fs::exists(path)) { - logger::info("Cache file not found. Creating initial file at: {}", path); - SaveCache(); - return; + template + rapidjson::Value TrackedRecordToJson(FormRecord* record, Allocator& allocator) { + rapidjson::Value item(rapidjson::kObjectType); + item.AddMember("order", record->order, allocator); + item.AddMember("form", FormRefToJson(record->actualForm, allocator), allocator); + item.AddMember("formId", StringValue(allocator, ToHex(record->formId, 8)), allocator); + item.AddMember("deleted", record->deleted, allocator); + item.AddMember("modelForm", FormRefToJson(record->modelForm, allocator), allocator); + item.AddMember("data", StringValue(allocator, SerializeRecordData(record)), allocator); + return item; } - const auto fileReader = new FileReader(path, std::ios::in | std::ios::binary); - if (!fileReader->IsOpen()) { - logger::error("File not found"); - return; + FormRecord* CreateRecordFromJson(const rapidjson::Value& item) { + const auto order = item.HasMember("order") ? ParseUInt(item["order"]) : 0; + const auto localId = item.HasMember("localId") ? ParseUInt(item["localId"]) : 0; + const auto formType = static_cast(item.HasMember("formType") ? ParseUInt(item["formType"]) : 0); + const bool deleted = item.HasMember("deleted") && item["deleted"].IsBool() && item["deleted"].GetBool(); + const auto formId = MakeDynamicFormID(localId); + + FormRecord* record = nullptr; + if (deleted) { + record = FormRecord::CreateDeleted(formId); + record->formType = formType; + } else { + const auto factory = RE::IFormFactory::GetFormFactoryByType(formType); + if (!factory) { + logger::error("No factory for saved form type {}", static_cast(formType)); + return nullptr; + } + + auto* form = factory->Create(); + if (!form) { + logger::error("Factory returned null for saved form type {}", static_cast(formType)); + return nullptr; + } + + form->SetFormID(formId, false); + record = FormRecord::CreateNew(form, formType, formId); + record->baseForm = item.HasMember("baseForm") ? JsonToFormRef(item["baseForm"]) : nullptr; + record->modelForm = item.HasMember("modelForm") ? JsonToFormRef(item["modelForm"]) : nullptr; + if (record->baseForm || record->modelForm) { + applyPattern(record); + } + } + + record->order = order; + AddFormData(record); + return record; } - RestoreAllFormRecords(fileReader); - UpdateId(); + FormRecord* CreateTrackedRecordFromJson(const rapidjson::Value& item) { + const auto order = item.HasMember("order") ? ParseUInt(item["order"]) : 0; + const bool deleted = item.HasMember("deleted") && item["deleted"].IsBool() && item["deleted"].GetBool(); + auto* actualForm = item.HasMember("form") ? JsonToFormRef(item["form"]) : nullptr; - delete fileReader; + FormRecord* record = nullptr; + if (!actualForm || deleted) { + const auto formId = item.HasMember("formId") ? ParseUInt(item["formId"]) : 0; + record = FormRecord::CreateDeleted(formId); + } else { + record = FormRecord::CreateReference(actualForm); + record->modelForm = item.HasMember("modelForm") ? JsonToFormRef(item["modelForm"]) : nullptr; + if (record->modelForm) { + applyPattern(record); + } + } + + record->order = order; + AddFormRef(record); + return record; + } - logger::info("Property data has been loaded from file successfully."); } -void SaveCache() { - logger::info("save cache"); +std::string BuildStateJson() { + rapidjson::Document document(rapidjson::kObjectType); + auto& allocator = document.GetAllocator(); - std::string fullPath = GetCacheFilePath(); - fs::path p(fullPath); + document.AddMember("schemaVersion", 1, allocator); + document.AddMember("dynamicPluginFile", StringValue(allocator, dynamicPluginName), allocator); + document.AddMember("firstDynamicLocalId", StringValue(allocator, ToHex(firstDynamicLocalId, 6)), allocator); - try { - if (p.has_parent_path() && !fs::exists(p.parent_path())) { - fs::create_directories(p.parent_path()); + rapidjson::Value createdForms(rapidjson::kArrayType); + for (auto* record : SortedRecords(formData)) { + if (record && IsDynamicFormID(record->formId)) { + createdForms.PushBack(CreatedRecordToJson(record, allocator), allocator); + } + } + document.AddMember("createdForms", createdForms, allocator); + + rapidjson::Value trackedForms(rapidjson::kArrayType); + for (auto* record : SortedRecords(formRef)) { + if (record) { + trackedForms.PushBack(TrackedRecordToJson(record, allocator), allocator); } + } + document.AddMember("trackedForms", trackedForms, allocator); - const auto fileWriter = new FileWriter(fullPath, - std::ios::out | std::ios::binary | std::ios::trunc); + rapidjson::StringBuffer buffer; + rapidjson::PrettyWriter writer(buffer); + document.Accept(writer); + return { buffer.GetString(), buffer.GetSize() }; +} - if (!fileWriter->IsOpen()) { - logger::error("Failed to open file for writing: {}", fullPath); - delete fileWriter; - return; +bool RestoreStateJson(const std::string& json) { + if (json.empty()) { + return false; + } + + rapidjson::Document document; + document.Parse(json.c_str(), json.size()); + if (document.HasParseError() || !document.IsObject()) { + logger::error("DynamicPersistentForms save record is not valid JSON"); + return false; + } + + if (!espFound) { + ReadFirstFormIdFromESP(); + } + if (!espFound) { + return false; + } + + ClearRecords(true); + + if (document.HasMember("createdForms") && document["createdForms"].IsArray()) { + for (const auto& item : document["createdForms"].GetArray()) { + if (item.IsObject() && item.HasMember("localId")) { + ReserveDynamicLocalID(ParseUInt(item["localId"])); + } } - StoreAllFormRecords(fileWriter); - delete fileWriter; + std::vector items; + for (const auto& item : document["createdForms"].GetArray()) { + if (item.IsObject()) { + items.push_back(&item); + } + } + std::ranges::sort(items, [](const rapidjson::Value* lhs, const rapidjson::Value* rhs) { + return ParseUInt((*lhs)["order"]) < ParseUInt((*rhs)["order"]); + }); - logger::info("Property data has been written successfully to: {}", fullPath); + std::vector> restored; + for (const auto* item : items) { + auto* record = CreateRecordFromJson(*item); + if (record) { + restored.emplace_back(record, item); + } + } + + for (const auto& [record, item] : restored) { + if (item->HasMember("data") && (*item)["data"].IsString()) { + DeserializeRecordData(record, (*item)["data"].GetString()); + } + } } - catch (const std::exception& e) { - logger::error("Error during save: {}", e.what()); + + if (document.HasMember("trackedForms") && document["trackedForms"].IsArray()) { + std::vector items; + for (const auto& item : document["trackedForms"].GetArray()) { + if (item.IsObject()) { + items.push_back(&item); + } + } + std::ranges::sort(items, [](const rapidjson::Value* lhs, const rapidjson::Value* rhs) { + return ParseUInt((*lhs)["order"]) < ParseUInt((*rhs)["order"]); + }); + + std::vector> restored; + for (const auto* item : items) { + auto* record = CreateTrackedRecordFromJson(*item); + if (record) { + restored.emplace_back(record, item); + } + } + + for (const auto& [record, item] : restored) { + if (item->HasMember("data") && (*item)["data"].IsString()) { + DeserializeRecordData(record, (*item)["data"].GetString()); + } + } + } + + logger::info("DynamicPersistentForms restored {} created forms and {} tracked forms", formData.size(), formRef.size()); + return true; +} + +void SaveCallback(SKSE::SerializationInterface* a_intfc) { + std::lock_guard lock(callbackMutex); + try { + const auto json = BuildStateJson(); + if (!a_intfc->OpenRecord(kJsonRecord, kJsonVersion)) { + logger::error("Failed to open DynamicPersistentForms JSON save record"); + return; + } + if (!json.empty() && !a_intfc->WriteRecordData(json.data(), static_cast(json.size()))) { + logger::error("Failed to write DynamicPersistentForms JSON save record"); + return; + } + } catch (const std::exception& e) { + logger::error("Error saving DynamicPersistentForms JSON state: {}", e.what()); } -} \ No newline at end of file +} + +void LoadCallback(SKSE::SerializationInterface* a_intfc) { + std::lock_guard lock(callbackMutex); + try { + uint32_t type = 0; + uint32_t version = 0; + uint32_t length = 0; + + while (a_intfc->GetNextRecordInfo(type, version, length)) { + if (type != kJsonRecord) { + logger::warn("Ignoring unrecognized DPF record {:08X}", type); + continue; + } + if (version != kJsonVersion) { + logger::warn("Ignoring unsupported DynamicPersistentForms JSON version {}", version); + continue; + } + + std::string json(length, '\0'); + const auto read = a_intfc->ReadRecordData(json.data(), length); + if (read != length) { + logger::error("Could not read full DynamicPersistentForms JSON record"); + return; + } + + RestoreStateJson(json); + } + } catch (const std::exception& e) { + logger::error("Error loading DynamicPersistentForms JSON state: {}", e.what()); + } +} + diff --git a/SKSE_Plugin/src/plugin.cpp b/SKSE_Plugin/src/plugin.cpp index 660a839..70e386f 100644 --- a/SKSE_Plugin/src/plugin.cpp +++ b/SKSE_Plugin/src/plugin.cpp @@ -1,9 +1,8 @@ -#include "form_record.h" +#include "Services.h" +#include "logger.h" #include "model.h" -#include "persistence.h" #include "papyrus.h" -#include "Services.h" - +#include "persistence.h" class DPFInterfaceImpl : public DPF::IDynamicPersistentForms { public: @@ -31,6 +30,10 @@ class DPFInterfaceImpl : public DPF::IDynamicPersistentForms { void UnTrack(RE::TESForm* item) override { Services::UnTrack(item); } + + RE::TESForm* CreateByType(const uint32_t formType) override { + return Services::CreateByType(formType); + } }; extern "C" __declspec(dllexport) void* GetDPFAPI() { @@ -38,46 +41,29 @@ extern "C" __declspec(dllexport) void* GetDPFAPI() { } namespace { - // ReSharper disable once CppParameterMayBeConstPtrOrRef void OnMessage(SKSE::MessagingInterface::Message* message) { if (message->type == SKSE::MessagingInterface::kDataLoaded) { ReadFirstFormIdFromESP(); - LoadCache(); - logger::info("loaded"); + logger::info("DPF data loaded"); } else if (message->type == SKSE::MessagingInterface::kNewGame) { - std::filesystem::remove("DynamicPersistentFormsCache.bin"); - while (formRef.size() > 0) { - delete formRef.back(); - formRef.pop_back(); - } - while (formData.size() > 0) { - if (formData.back()) { - if (formData.back()->actualForm) { - formData.back()->actualForm->SetDelete(true); - } - } - delete formData.back(); - formData.pop_back(); - } - ResetId(); - logger::info("new game"); + ClearRecords(true); + logger::info("DPF new game state cleared"); } } } - SKSEPluginLoad(const SKSE::LoadInterface *skse) { - SKSE::Init(skse); - - //EnableLog("DynamicPersistentFormsLog.txt", "DPF 2"); + SetupLog(); + logger::info("Plugin loaded"); + SKSE::Init(skse); SKSE::GetPapyrusInterface()->Register(PapyrusFunctions); SKSE::GetMessagingInterface()->RegisterListener(OnMessage); - auto serialization = SKSE::GetSerializationInterface(); + const auto serialization = SKSE::GetSerializationInterface(); serialization->SetUniqueID('DPF1'); serialization->SetSaveCallback(SaveCallback); serialization->SetLoadCallback(LoadCallback); return true; -} \ No newline at end of file +} From cdaf1bf582f09721f72e91e5ccb679384e0182fd Mon Sep 17 00:00:00 2001 From: Viny <77700518+vinymayan@users.noreply.github.com> Date: Wed, 17 Jun 2026 16:39:19 -0300 Subject: [PATCH 2/8] e tome mais uma cacetada --- .../Source/Scripts/DynamicPersistentForms.psc | 16 + .../Source/Scripts/DynamicPersistentForms.psc | 16 + SKSE_Plugin/include/Services.h | 15 +- SKSE_Plugin/include/form.h | 17 +- SKSE_Plugin/include/model.h | 35 +- SKSE_Plugin/include/papyrus.h | 18 +- SKSE_Plugin/include/persistence.h | 10 +- SKSE_Plugin/public/DPFAPI.h | 14 +- SKSE_Plugin/src/Services.cpp | 64 ++- SKSE_Plugin/src/form.cpp | 164 +++++-- SKSE_Plugin/src/model.cpp | 177 +++++++- SKSE_Plugin/src/papyrus.cpp | 40 ++ SKSE_Plugin/src/persistence.cpp | 406 ++++-------------- SKSE_Plugin/src/plugin.cpp | 35 +- 14 files changed, 643 insertions(+), 384 deletions(-) diff --git a/DIST/Dynamic Persistent Forms/Source/Scripts/DynamicPersistentForms.psc b/DIST/Dynamic Persistent Forms/Source/Scripts/DynamicPersistentForms.psc index bf1a592..6dd2b33 100644 --- a/DIST/Dynamic Persistent Forms/Source/Scripts/DynamicPersistentForms.psc +++ b/DIST/Dynamic Persistent Forms/Source/Scripts/DynamicPersistentForms.psc @@ -4,6 +4,20 @@ Form function Create(Form item) global native ; Creates a new empty form of the requested FormType and lets DynamicPersistentForms own its persistent FormID slot. Form function CreateByType(int formType) global native +; Returns or creates a persistent form using a known localId from Dynamic Persistent Forms.esp. +Form function GetOrCreateByLocalId(int localId, int formType) global native +; Returns or creates a persistent form using a form whose ID belongs to Dynamic Persistent Forms.esp. +Form function GetOrCreateByFormId(Form formIdSource, int formType) global native +; Creates or returns a form owned by owner/key. +Form function CreateByTypeForOwner(String owner, String key, int formType) global native +; Creates or returns a form owned by owner/key. +Form function GetOrCreateByOwnerKey(String owner, String key, int formType) global native +; Releases one owned slot by owner/key. +bool function ReleaseByOwnerKey(String owner, String key) global native +; Releases one slot by localId if owner matches. +bool function ReleaseByLocalId(int localId, String owner) global native +; Releases all slots owned by owner and returns count. +int function ReleaseOwner(String owner) global native ; Dispose a form that was created using the previous function. function Dispose(Form item) global native @@ -48,3 +62,5 @@ function SetSoulGemCapacity(SoulGem gem, int capacity) global native function SetSoulGemCurrentSoul(SoulGem gem, int capacity) global native function LinkSoulGems(SoulGem empty, SoulGem filled) global native + + diff --git a/PapyrusInterface/Source/Scripts/DynamicPersistentForms.psc b/PapyrusInterface/Source/Scripts/DynamicPersistentForms.psc index bf1a592..6dd2b33 100644 --- a/PapyrusInterface/Source/Scripts/DynamicPersistentForms.psc +++ b/PapyrusInterface/Source/Scripts/DynamicPersistentForms.psc @@ -4,6 +4,20 @@ Form function Create(Form item) global native ; Creates a new empty form of the requested FormType and lets DynamicPersistentForms own its persistent FormID slot. Form function CreateByType(int formType) global native +; Returns or creates a persistent form using a known localId from Dynamic Persistent Forms.esp. +Form function GetOrCreateByLocalId(int localId, int formType) global native +; Returns or creates a persistent form using a form whose ID belongs to Dynamic Persistent Forms.esp. +Form function GetOrCreateByFormId(Form formIdSource, int formType) global native +; Creates or returns a form owned by owner/key. +Form function CreateByTypeForOwner(String owner, String key, int formType) global native +; Creates or returns a form owned by owner/key. +Form function GetOrCreateByOwnerKey(String owner, String key, int formType) global native +; Releases one owned slot by owner/key. +bool function ReleaseByOwnerKey(String owner, String key) global native +; Releases one slot by localId if owner matches. +bool function ReleaseByLocalId(int localId, String owner) global native +; Releases all slots owned by owner and returns count. +int function ReleaseOwner(String owner) global native ; Dispose a form that was created using the previous function. function Dispose(Form item) global native @@ -48,3 +62,5 @@ function SetSoulGemCapacity(SoulGem gem, int capacity) global native function SetSoulGemCurrentSoul(SoulGem gem, int capacity) global native function LinkSoulGems(SoulGem empty, SoulGem filled) global native + + diff --git a/SKSE_Plugin/include/Services.h b/SKSE_Plugin/include/Services.h index b3e053a..f4b8c31 100644 --- a/SKSE_Plugin/include/Services.h +++ b/SKSE_Plugin/include/Services.h @@ -10,10 +10,23 @@ namespace Services { RE::TESForm* CreateByType(uint32_t formType); + RE::TESForm* GetOrCreateByLocalId(uint32_t localId, uint32_t formType); + + RE::TESForm* GetOrCreateByFormId(RE::FormID formId, uint32_t formType); + + RE::TESForm* CreateByTypeForOwner(const char* owner, const char* key, uint32_t formType); + + RE::TESForm* GetOrCreateByOwnerKey(const char* owner, const char* key, uint32_t formType); + + bool ReleaseByOwnerKey(const char* owner, const char* key); + + bool ReleaseByLocalId(uint32_t localId, const char* owner); + + uint32_t ReleaseOwner(const char* owner); + void Track(RE::TESForm* baseItem); void UnTrack(RE::TESForm* form); void Dispose(RE::TESForm* form); } - diff --git a/SKSE_Plugin/include/form.h b/SKSE_Plugin/include/form.h index ae3e62a..04f7093 100644 --- a/SKSE_Plugin/include/form.h +++ b/SKSE_Plugin/include/form.h @@ -1,4 +1,4 @@ -#pragma once +#pragma once #include "form_record.h" static void copyFormArmorModel(RE::TESForm* source, RE::TESForm* target); @@ -18,7 +18,20 @@ void copyComponent(RE::TESForm* from, RE::TESForm* to) { void copyAppearence(RE::TESForm* source, RE::TESForm* target); - RE::TESForm* AddForm(RE::TESForm* baseItem); RE::TESForm* AddFormByType(RE::FormType formType); + +RE::TESForm* AddFormByTypeForOwner(const char* owner, const char* key, RE::FormType formType); + +RE::TESForm* GetOrCreateFormByLocalId(uint32_t localId, RE::FormType formType); + +RE::TESForm* GetOrCreateFormByFormId(RE::FormID formId, RE::FormType formType); + +RE::TESForm* GetOrCreateFormByOwnerKey(const char* owner, const char* key, RE::FormType formType); + +bool ReleaseFormByOwnerKey(const char* owner, const char* key); + +bool ReleaseFormByLocalId(uint32_t localId, const char* owner); + +uint32_t ReleaseFormsByOwner(const char* owner); diff --git a/SKSE_Plugin/include/model.h b/SKSE_Plugin/include/model.h index 689cb8f..47396cd 100644 --- a/SKSE_Plugin/include/model.h +++ b/SKSE_Plugin/include/model.h @@ -1,11 +1,20 @@ -#pragma once +#pragma once #include #include +#include #include +#include #include #include #include "form_record.h" +struct DynamicSlot { + uint32_t localId = 0; + RE::FormType formType = RE::FormType::None; + std::string owner; + std::string key; +}; + inline std::vector formData; inline std::vector formRef; @@ -16,7 +25,8 @@ inline uint32_t nextRecordOrder = 1; inline uint32_t dynamicModId = 0; inline std::string dynamicPluginName = "Dynamic Persistent Forms.esp"; inline std::unordered_set reservedDynamicLocalIds; - +inline std::unordered_map dynamicSlots; +inline std::unordered_map dynamicOwnerKeyIndex; void AddFormData(FormRecord* item); @@ -26,7 +36,6 @@ void EachFormData(const std::function& iteration); void EachFormRef(const std::function& iteration); - bool IsDynamicFormID(RE::FormID formId); uint32_t ToDynamicLocalID(RE::FormID formId); @@ -37,6 +46,26 @@ void ReserveDynamicLocalID(uint32_t localId); void ReserveDynamicFormID(RE::FormID formId); +std::string NormalizeOwnerKeyPart(const char* value); + +std::string MakeOwnerKey(std::string_view owner, std::string_view key); + +bool RegisterDynamicSlot(uint32_t localId, RE::FormType formType, std::string owner = {}, std::string key = {}); + +std::optional GetRegisteredDynamicSlot(uint32_t localId); + +std::optional GetRegisteredDynamicSlotType(uint32_t localId); + +std::optional FindDynamicSlotByOwnerKey(std::string_view owner, std::string_view key); + +bool IsDynamicLocalIDRegistered(uint32_t localId); + +bool ReleaseDynamicSlot(uint32_t localId, std::string_view owner); + +bool ReleaseDynamicSlotByOwnerKey(std::string_view owner, std::string_view key); + +uint32_t ReleaseDynamicSlotsByOwner(std::string_view owner); + RE::FormID AllocateDynamicFormID(); void ReadFirstFormIdFromESP(); diff --git a/SKSE_Plugin/include/papyrus.h b/SKSE_Plugin/include/papyrus.h index 4a7dfd2..b7460c4 100644 --- a/SKSE_Plugin/include/papyrus.h +++ b/SKSE_Plugin/include/papyrus.h @@ -1,10 +1,24 @@ -#pragma once +#pragma once RE::TESForm* Create(RE::StaticFunctionTag*, RE::TESForm* baseItem); RE::TESForm* CreateByType(RE::StaticFunctionTag*, uint32_t formType); +RE::TESForm* GetOrCreateByLocalId(RE::StaticFunctionTag*, uint32_t localId, uint32_t formType); + +RE::TESForm* GetOrCreateByFormId(RE::StaticFunctionTag*, RE::TESForm* formIdSource, uint32_t formType); + +RE::TESForm* CreateByTypeForOwner(RE::StaticFunctionTag*, RE::BSFixedString owner, RE::BSFixedString key, uint32_t formType); + +RE::TESForm* GetOrCreateByOwnerKey(RE::StaticFunctionTag*, RE::BSFixedString owner, RE::BSFixedString key, uint32_t formType); + +bool ReleaseByOwnerKey(RE::StaticFunctionTag*, RE::BSFixedString owner, RE::BSFixedString key); + +bool ReleaseByLocalId(RE::StaticFunctionTag*, uint32_t localId, RE::BSFixedString owner); + +uint32_t ReleaseOwner(RE::StaticFunctionTag*, RE::BSFixedString owner); + void Track(RE::StaticFunctionTag*, RE::TESForm* baseItem); void UnTrack(RE::StaticFunctionTag*, RE::TESForm* form); @@ -60,3 +74,5 @@ void SetSoulGemCurrentSoul(RE::StaticFunctionTag*, RE::TESSoulGem* soulGem, uint void LinkSoulGems(RE::StaticFunctionTag*, RE::TESSoulGem* empty, RE::TESSoulGem* filled); bool PapyrusFunctions(RE::BSScript::IVirtualMachine* vm); + + diff --git a/SKSE_Plugin/include/persistence.h b/SKSE_Plugin/include/persistence.h index d8957ce..6857f86 100644 --- a/SKSE_Plugin/include/persistence.h +++ b/SKSE_Plugin/include/persistence.h @@ -1,12 +1,14 @@ -#pragma once +#pragma once #include -#include #include namespace fs = std::filesystem; -std::string BuildStateJson(); -bool RestoreStateJson(const std::string& json); +std::string BuildRegistryJson(); +bool RestoreRegistryJson(const std::string& json); +bool LoadGlobalRegistry(); +bool SaveGlobalRegistry(); +std::string GetGlobalRegistryPath(); void SaveCallback(SKSE::SerializationInterface* a_intfc); diff --git a/SKSE_Plugin/public/DPFAPI.h b/SKSE_Plugin/public/DPFAPI.h index caa04e2..e040b71 100644 --- a/SKSE_Plugin/public/DPFAPI.h +++ b/SKSE_Plugin/public/DPFAPI.h @@ -6,7 +6,7 @@ namespace DPF { constexpr auto InterfaceName = "DynamicPersistentForms"; - constexpr uint32_t InterfaceVersion = 2; + constexpr uint32_t InterfaceVersion = 4; class IDynamicPersistentForms { public: @@ -22,6 +22,17 @@ namespace DPF { // v2 virtual RE::TESForm* CreateByType(uint32_t formType) = 0; + + // v3 + virtual RE::TESForm* GetOrCreateByLocalId(uint32_t localId, uint32_t formType) = 0; + virtual RE::TESForm* GetOrCreateByFormId(RE::FormID formId, uint32_t formType) = 0; + + // v4 + virtual RE::TESForm* CreateByTypeForOwner(const char* owner, const char* key, uint32_t formType) = 0; + virtual RE::TESForm* GetOrCreateByOwnerKey(const char* owner, const char* key, uint32_t formType) = 0; + virtual bool ReleaseByOwnerKey(const char* owner, const char* key) = 0; + virtual bool ReleaseByLocalId(uint32_t localId, const char* owner) = 0; + virtual uint32_t ReleaseOwner(const char* owner) = 0; }; using GetDPFAPI = void* (*)(); @@ -45,4 +56,3 @@ namespace DPF { return api; } } - diff --git a/SKSE_Plugin/src/Services.cpp b/SKSE_Plugin/src/Services.cpp index 66d1581..1c2b416 100644 --- a/SKSE_Plugin/src/Services.cpp +++ b/SKSE_Plugin/src/Services.cpp @@ -30,6 +30,69 @@ RE::TESForm* Services::CreateByType(const uint32_t formType) { } } +RE::TESForm* Services::GetOrCreateByLocalId(const uint32_t localId, const uint32_t formType) { + std::lock_guard lock(serviceMutex); + try { + return GetOrCreateFormByLocalId(localId, static_cast(formType)); + } catch (const std::exception&) { + return nullptr; + } +} + +RE::TESForm* Services::GetOrCreateByFormId(const RE::FormID formId, const uint32_t formType) { + std::lock_guard lock(serviceMutex); + try { + return GetOrCreateFormByFormId(formId, static_cast(formType)); + } catch (const std::exception&) { + return nullptr; + } +} + +RE::TESForm* Services::CreateByTypeForOwner(const char* owner, const char* key, const uint32_t formType) { + std::lock_guard lock(serviceMutex); + try { + return AddFormByTypeForOwner(owner, key, static_cast(formType)); + } catch (const std::exception&) { + return nullptr; + } +} + +RE::TESForm* Services::GetOrCreateByOwnerKey(const char* owner, const char* key, const uint32_t formType) { + std::lock_guard lock(serviceMutex); + try { + return GetOrCreateFormByOwnerKey(owner, key, static_cast(formType)); + } catch (const std::exception&) { + return nullptr; + } +} + +bool Services::ReleaseByOwnerKey(const char* owner, const char* key) { + std::lock_guard lock(serviceMutex); + try { + return ReleaseFormByOwnerKey(owner, key); + } catch (const std::exception&) { + return false; + } +} + +bool Services::ReleaseByLocalId(const uint32_t localId, const char* owner) { + std::lock_guard lock(serviceMutex); + try { + return ReleaseFormByLocalId(localId, owner); + } catch (const std::exception&) { + return false; + } +} + +uint32_t Services::ReleaseOwner(const char* owner) { + std::lock_guard lock(serviceMutex); + try { + return ReleaseFormsByOwner(owner); + } catch (const std::exception&) { + return 0; + } +} + void Services::Track(RE::TESForm* baseItem) { std::lock_guard lock(serviceMutex); try { @@ -44,7 +107,6 @@ void Services::Track(RE::TESForm* baseItem) { found = true; return false; } - // Correção da lógica original que parecia ter um if duplicado return true; }); if (!found) { diff --git a/SKSE_Plugin/src/form.cpp b/SKSE_Plugin/src/form.cpp index 86e5ff2..e28c902 100644 --- a/SKSE_Plugin/src/form.cpp +++ b/SKSE_Plugin/src/form.cpp @@ -1,5 +1,6 @@ -#include "form.h" +#include "form.h" #include "model.h" +#include "persistence.h" void copyFormArmorModel(RE::TESForm* source, RE::TESForm* target) { const auto* sourceModelBipedForm = source->As(); @@ -158,73 +159,131 @@ namespace { return result; } - RE::TESForm* ReuseDeletedSlot(const RE::FormType formType, RE::TESForm* baseItem) { - RE::TESForm* result = nullptr; + FormRecord* FindCreatedRecord(const RE::FormID formId) { + FormRecord* found = nullptr; EachFormData([&](FormRecord* item) { - if (!item->deleted || item->formType != formType || !IsDynamicFormID(item->formId)) { - return true; + if (item && item->formId == formId) { + found = item; + return false; } + return true; + }); + return found; + } - logger::info("Reusing deleted DPF slot {:08X}", item->formId); - result = CreateFormInstance(formType, item->formId); - if (!result) { - return false; + RE::TESForm* CreateRegisteredForm(const uint32_t localId, const RE::FormType formType, RE::TESForm* baseItem, + std::string owner = {}, std::string key = {}) { + if (!espFound || localId == 0 || formType == RE::FormType::None) { + return nullptr; + } + + const auto existingSlot = GetRegisteredDynamicSlot(localId); + if (existingSlot.has_value()) { + if (existingSlot->formType != formType) { + logger::error("Slot {:06X} is registered as type {}, requested {}", localId, + static_cast(existingSlot->formType), static_cast(formType)); + return nullptr; } + if ((!owner.empty() || !key.empty()) && (existingSlot->owner != owner || existingSlot->key != key)) { + logger::error("Slot {:06X} is owned by '{}'/'{}', requested '{}'/'{}'", localId, + existingSlot->owner, existingSlot->key, owner, key); + return nullptr; + } + } - item->Undelete(result, formType); - item->baseForm = baseItem; - item->modelForm = nullptr; - if (baseItem) { - applyPattern(item); + const auto formId = MakeDynamicFormID(localId); + if (auto* existingForm = RE::TESForm::LookupByID(formId)) { + if (existingForm->GetFormType() != formType) { + logger::error("FormID {:08X} exists as type {}, requested {}", formId, + static_cast(existingForm->GetFormType()), static_cast(formType)); + return nullptr; } - return false; - }); - return result; + if (!RegisterDynamicSlot(localId, formType, std::move(owner), std::move(key))) { + return nullptr; + } + if (!FindCreatedRecord(formId)) { + auto* record = FormRecord::CreateNew(existingForm, formType, formId); + record->baseForm = baseItem; + AddFormData(record); + } + SaveGlobalRegistry(); + return existingForm; + } + + if (!RegisterDynamicSlot(localId, formType, std::move(owner), std::move(key))) { + return nullptr; + } + + auto* newForm = CreateFormInstance(formType, formId); + if (!newForm) { + return nullptr; + } + + auto* record = FormRecord::CreateNew(newForm, formType, formId); + record->baseForm = baseItem; + if (baseItem) { + applyPattern(record); + } + AddFormData(record); + SaveGlobalRegistry(); + return newForm; } } RE::TESForm* AddForm(RE::TESForm* baseItem) { - if (!espFound) { - return nullptr; - } if (!baseItem) { logger::error("Create(baseItem) was called with a null baseItem. Use CreateByType for empty forms."); return nullptr; } - if (auto* reused = ReuseDeletedSlot(baseItem->GetFormType(), baseItem)) { - return reused; - } - - logger::info("item created"); const auto formId = AllocateDynamicFormID(); if (formId == 0) { return nullptr; } - const auto newForm = CreateFormInstance(baseItem->GetFormType(), formId); - if (!newForm) { + return CreateRegisteredForm(ToDynamicLocalID(formId), baseItem->GetFormType(), baseItem); +} + +RE::TESForm* AddFormByType(const RE::FormType formType) { + if (formType == RE::FormType::None) { + logger::error("CreateByType called with FormType::None"); return nullptr; } - const auto slot = FormRecord::CreateNew(newForm, baseItem->GetFormType(), formId); - slot->baseForm = baseItem; - applyPattern(slot); - AddFormData(slot); - return newForm; + const auto formId = AllocateDynamicFormID(); + if (formId == 0) { + return nullptr; + } + + return CreateRegisteredForm(ToDynamicLocalID(formId), formType, nullptr); } -RE::TESForm* AddFormByType(const RE::FormType formType) { - if (!espFound) { +RE::TESForm* AddFormByTypeForOwner(const char* owner, const char* key, const RE::FormType formType) { + return GetOrCreateFormByOwnerKey(owner, key, formType); +} + +RE::TESForm* GetOrCreateFormByLocalId(const uint32_t localId, const RE::FormType formType) { + return CreateRegisteredForm(localId & 0x00ffffff, formType, nullptr); +} + +RE::TESForm* GetOrCreateFormByFormId(const RE::FormID formId, const RE::FormType formType) { + if (!IsDynamicFormID(formId)) { + logger::error("FormID {:08X} does not belong to Dynamic Persistent Forms.esp", formId); return nullptr; } - if (formType == RE::FormType::None) { - logger::error("CreateByType called with FormType::None"); + return GetOrCreateFormByLocalId(ToDynamicLocalID(formId), formType); +} + +RE::TESForm* GetOrCreateFormByOwnerKey(const char* ownerRaw, const char* keyRaw, const RE::FormType formType) { + const auto owner = NormalizeOwnerKeyPart(ownerRaw); + const auto key = NormalizeOwnerKeyPart(keyRaw); + if (owner.empty() || key.empty()) { + logger::error("Owner and key are required for owned DPF slots"); return nullptr; } - if (auto* reused = ReuseDeletedSlot(formType, nullptr)) { - return reused; + if (const auto existingLocalId = FindDynamicSlotByOwnerKey(owner, key)) { + return CreateRegisteredForm(existingLocalId.value(), formType, nullptr, owner, key); } const auto formId = AllocateDynamicFormID(); @@ -232,12 +291,29 @@ RE::TESForm* AddFormByType(const RE::FormType formType) { return nullptr; } - auto* newForm = CreateFormInstance(formType, formId); - if (!newForm) { - return nullptr; + return CreateRegisteredForm(ToDynamicLocalID(formId), formType, nullptr, owner, key); +} + +bool ReleaseFormByOwnerKey(const char* ownerRaw, const char* keyRaw) { + const auto released = ReleaseDynamicSlotByOwnerKey(NormalizeOwnerKeyPart(ownerRaw), NormalizeOwnerKeyPart(keyRaw)); + if (released) { + SaveGlobalRegistry(); + } + return released; +} + +bool ReleaseFormByLocalId(const uint32_t localId, const char* ownerRaw) { + const auto released = ReleaseDynamicSlot(localId, NormalizeOwnerKeyPart(ownerRaw)); + if (released) { + SaveGlobalRegistry(); } + return released; +} - auto* slot = FormRecord::CreateNew(newForm, formType, formId); - AddFormData(slot); - return newForm; +uint32_t ReleaseFormsByOwner(const char* ownerRaw) { + const auto released = ReleaseDynamicSlotsByOwner(NormalizeOwnerKeyPart(ownerRaw)); + if (released > 0) { + SaveGlobalRegistry(); + } + return released; } diff --git a/SKSE_Plugin/src/model.cpp b/SKSE_Plugin/src/model.cpp index 95b1eac..d49d275 100644 --- a/SKSE_Plugin/src/model.cpp +++ b/SKSE_Plugin/src/model.cpp @@ -1,6 +1,30 @@ #include "model.h" #include +namespace { + void RemoveRuntimeRecordForLocalId(const uint32_t localId, const bool deleteActualForm) { + const auto formId = MakeDynamicFormID(localId); + for (auto it = formData.begin(); it != formData.end();) { + auto* item = *it; + if (item && item->formId == formId) { + if (deleteActualForm && item->actualForm) { + item->actualForm->SetDelete(true); + } + delete item; + it = formData.erase(it); + } else { + ++it; + } + } + } + + void RemoveOwnerKeyIndex(const DynamicSlot& slot) { + if (!slot.owner.empty() || !slot.key.empty()) { + dynamicOwnerKeyIndex.erase(MakeOwnerKey(slot.owner, slot.key)); + } + } +} + void AddFormData(FormRecord* item) { if (!item) { return; @@ -12,6 +36,9 @@ void AddFormData(FormRecord* item) { } formData.push_back(item); ReserveDynamicFormID(item->formId); + if (IsDynamicFormID(item->formId) && item->formType != RE::FormType::None) { + RegisterDynamicSlot(ToDynamicLocalID(item->formId), item->formType); + } } void AddFormRef(FormRecord* item) { @@ -61,9 +88,10 @@ void ReserveDynamicLocalID(const uint32_t localId) { if (localId == 0) { return; } - reservedDynamicLocalIds.insert(localId & 0x00ffffff); - if (localId >= nextDynamicLocalId) { - nextDynamicLocalId = localId + 1; + const auto normalized = localId & 0x00ffffff; + reservedDynamicLocalIds.insert(normalized); + if (normalized >= nextDynamicLocalId) { + nextDynamicLocalId = normalized + 1; } } @@ -73,6 +101,136 @@ void ReserveDynamicFormID(const RE::FormID formId) { } } +std::string NormalizeOwnerKeyPart(const char* value) { + return value ? std::string(value) : std::string(); +} + +std::string MakeOwnerKey(std::string_view owner, std::string_view key) { + std::string result; + result.reserve(owner.size() + key.size() + 1); + result.append(owner.data(), owner.size()); + result.push_back('\x1f'); + result.append(key.data(), key.size()); + return result; +} + +bool RegisterDynamicSlot(const uint32_t localId, const RE::FormType formType, std::string owner, std::string key) { + const auto normalized = localId & 0x00ffffff; + if (normalized == 0 || formType == RE::FormType::None) { + return false; + } + + const auto ownerKey = MakeOwnerKey(owner, key); + const bool hasOwnerKey = !owner.empty() || !key.empty(); + if (hasOwnerKey) { + const auto existingByOwner = dynamicOwnerKeyIndex.find(ownerKey); + if (existingByOwner != dynamicOwnerKeyIndex.end() && existingByOwner->second != normalized) { + logger::error("Owner/key '{}'/'{}' is already registered to slot {:06X}", owner, key, existingByOwner->second); + return false; + } + } + + const auto existing = dynamicSlots.find(normalized); + if (existing != dynamicSlots.end()) { + const auto& slot = existing->second; + if (slot.formType != formType) { + logger::error("Dynamic slot {:06X} is already registered as type {}, requested {}", normalized, + static_cast(slot.formType), static_cast(formType)); + return false; + } + if ((!owner.empty() || !key.empty()) && (slot.owner != owner || slot.key != key)) { + logger::error("Dynamic slot {:06X} is owned by '{}'/'{}', requested '{}'/'{}'", normalized, + slot.owner, slot.key, owner, key); + return false; + } + ReserveDynamicLocalID(normalized); + return true; + } + + DynamicSlot slot; + slot.localId = normalized; + slot.formType = formType; + slot.owner = std::move(owner); + slot.key = std::move(key); + dynamicSlots[normalized] = slot; + if (!slot.owner.empty() || !slot.key.empty()) { + dynamicOwnerKeyIndex[MakeOwnerKey(slot.owner, slot.key)] = normalized; + } + ReserveDynamicLocalID(normalized); + return true; +} + +std::optional GetRegisteredDynamicSlot(const uint32_t localId) { + const auto existing = dynamicSlots.find(localId & 0x00ffffff); + if (existing == dynamicSlots.end()) { + return std::nullopt; + } + return existing->second; +} + +std::optional GetRegisteredDynamicSlotType(const uint32_t localId) { + const auto slot = GetRegisteredDynamicSlot(localId); + if (!slot.has_value()) { + return std::nullopt; + } + return slot->formType; +} + +std::optional FindDynamicSlotByOwnerKey(std::string_view owner, std::string_view key) { + const auto existing = dynamicOwnerKeyIndex.find(MakeOwnerKey(owner, key)); + if (existing == dynamicOwnerKeyIndex.end()) { + return std::nullopt; + } + return existing->second; +} + +bool IsDynamicLocalIDRegistered(const uint32_t localId) { + return dynamicSlots.contains(localId & 0x00ffffff); +} + +bool ReleaseDynamicSlot(const uint32_t localId, std::string_view owner) { + const auto normalized = localId & 0x00ffffff; + const auto existing = dynamicSlots.find(normalized); + if (existing == dynamicSlots.end()) { + return false; + } + if (existing->second.owner != owner) { + logger::error("Owner '{}' cannot release slot {:06X} owned by '{}'", owner, normalized, existing->second.owner); + return false; + } + + RemoveRuntimeRecordForLocalId(normalized, true); + RemoveOwnerKeyIndex(existing->second); + dynamicSlots.erase(existing); + reservedDynamicLocalIds.erase(normalized); + return true; +} + +bool ReleaseDynamicSlotByOwnerKey(std::string_view owner, std::string_view key) { + const auto localId = FindDynamicSlotByOwnerKey(owner, key); + if (!localId.has_value()) { + return false; + } + return ReleaseDynamicSlot(localId.value(), owner); +} + +uint32_t ReleaseDynamicSlotsByOwner(std::string_view owner) { + std::vector toRelease; + for (const auto& [localId, slot] : dynamicSlots) { + if (slot.owner == owner) { + toRelease.push_back(localId); + } + } + + uint32_t released = 0; + for (const auto localId : toRelease) { + if (ReleaseDynamicSlot(localId, owner)) { + ++released; + } + } + return released; +} + RE::FormID AllocateDynamicFormID() { if (!espFound) { logger::error("Cannot allocate DynamicPersistentForms FormID because Dynamic Persistent Forms.esp was not found"); @@ -83,8 +241,9 @@ RE::FormID AllocateDynamicFormID() { while (localId < 0x00ffffff) { const auto fullId = MakeDynamicFormID(localId); const bool reserved = reservedDynamicLocalIds.contains(localId); + const bool registered = dynamicSlots.contains(localId); const bool existsInGame = RE::TESForm::LookupByID(fullId) != nullptr; - if (!reserved && !existsInGame) { + if (!reserved && !registered && !existsInGame) { ReserveDynamicLocalID(localId); logger::info("Allocated DynamicPersistentForms FormID {:08X}", fullId); return fullId; @@ -101,7 +260,7 @@ void ReadFirstFormIdFromESP() { espFound = false; dynamicModId = 0; firstDynamicLocalId = 0x801; - nextDynamicLocalId = firstDynamicLocalId; + nextDynamicLocalId = std::max(nextDynamicLocalId, firstDynamicLocalId); dynamicPluginName = "Dynamic Persistent Forms.esp"; const auto modIndex = dataHandler->GetLoadedModIndex(dynamicPluginName); @@ -117,6 +276,8 @@ void ReadFirstFormIdFromESP() { void ResetDynamicState() { reservedDynamicLocalIds.clear(); + dynamicSlots.clear(); + dynamicOwnerKeyIndex.clear(); nextDynamicLocalId = firstDynamicLocalId; nextRecordOrder = 1; } @@ -136,5 +297,9 @@ void ClearRecords(const bool deleteActualForms) { formData.pop_back(); } - ResetDynamicState(); + reservedDynamicLocalIds.clear(); + nextRecordOrder = 1; + for (const auto& [localId, slot] : dynamicSlots) { + ReserveDynamicLocalID(localId); + } } diff --git a/SKSE_Plugin/src/papyrus.cpp b/SKSE_Plugin/src/papyrus.cpp index e1945af..d4d5584 100644 --- a/SKSE_Plugin/src/papyrus.cpp +++ b/SKSE_Plugin/src/papyrus.cpp @@ -13,6 +13,37 @@ RE::TESForm* CreateByType(RE::StaticFunctionTag*, const uint32_t formType) { return Services::CreateByType(formType); } +RE::TESForm* GetOrCreateByLocalId(RE::StaticFunctionTag*, const uint32_t localId, const uint32_t formType) { + return Services::GetOrCreateByLocalId(localId, formType); +} + +RE::TESForm* GetOrCreateByFormId(RE::StaticFunctionTag*, RE::TESForm* formIdSource, const uint32_t formType) { + if (!formIdSource) { + return nullptr; + } + return Services::GetOrCreateByFormId(formIdSource->GetFormID(), formType); +} + +RE::TESForm* CreateByTypeForOwner(RE::StaticFunctionTag*, const RE::BSFixedString owner, const RE::BSFixedString key, const uint32_t formType) { + return Services::CreateByTypeForOwner(owner.c_str(), key.c_str(), formType); +} + +RE::TESForm* GetOrCreateByOwnerKey(RE::StaticFunctionTag*, const RE::BSFixedString owner, const RE::BSFixedString key, const uint32_t formType) { + return Services::GetOrCreateByOwnerKey(owner.c_str(), key.c_str(), formType); +} + +bool ReleaseByOwnerKey(RE::StaticFunctionTag*, const RE::BSFixedString owner, const RE::BSFixedString key) { + return Services::ReleaseByOwnerKey(owner.c_str(), key.c_str()); +} + +bool ReleaseByLocalId(RE::StaticFunctionTag*, const uint32_t localId, const RE::BSFixedString owner) { + return Services::ReleaseByLocalId(localId, owner.c_str()); +} + +uint32_t ReleaseOwner(RE::StaticFunctionTag*, const RE::BSFixedString owner) { + return Services::ReleaseOwner(owner.c_str()); +} + void Track(RE::StaticFunctionTag*, RE::TESForm* baseItem) { Services::Track(baseItem); } @@ -303,6 +334,13 @@ void LinkSoulGems(RE::StaticFunctionTag*, RE::TESSoulGem* empty, RE::TESSoulGem* bool PapyrusFunctions(RE::BSScript::IVirtualMachine* vm) { vm->RegisterFunction("Create", "DynamicPersistentForms", Create); vm->RegisterFunction("CreateByType", "DynamicPersistentForms", CreateByType); + vm->RegisterFunction("GetOrCreateByLocalId", "DynamicPersistentForms", GetOrCreateByLocalId); + vm->RegisterFunction("GetOrCreateByFormId", "DynamicPersistentForms", GetOrCreateByFormId); + vm->RegisterFunction("CreateByTypeForOwner", "DynamicPersistentForms", CreateByTypeForOwner); + vm->RegisterFunction("GetOrCreateByOwnerKey", "DynamicPersistentForms", GetOrCreateByOwnerKey); + vm->RegisterFunction("ReleaseByOwnerKey", "DynamicPersistentForms", ReleaseByOwnerKey); + vm->RegisterFunction("ReleaseByLocalId", "DynamicPersistentForms", ReleaseByLocalId); + vm->RegisterFunction("ReleaseOwner", "DynamicPersistentForms", ReleaseOwner); vm->RegisterFunction("Dispose", "DynamicPersistentForms", Dispose); vm->RegisterFunction("Track", "DynamicPersistentForms", Track); vm->RegisterFunction("UnTrack", "DynamicPersistentForms", UnTrack); @@ -334,3 +372,5 @@ bool PapyrusFunctions(RE::BSScript::IVirtualMachine* vm) { return true; } + + diff --git a/SKSE_Plugin/src/persistence.cpp b/SKSE_Plugin/src/persistence.cpp index a755556..3a9e68e 100644 --- a/SKSE_Plugin/src/persistence.cpp +++ b/SKSE_Plugin/src/persistence.cpp @@ -1,8 +1,5 @@ #include "persistence.h" -#include "form.h" -#include "form_record_serializer.h" #include "model.h" -#include "serializer.h" #include "rapidjson/document.h" #include "rapidjson/prettywriter.h" #include "rapidjson/stringbuffer.h" @@ -12,12 +9,11 @@ #include #include #include +#include namespace { - constexpr uint32_t kJsonRecord = 'JSN1'; - constexpr uint32_t kJsonVersion = 1; - - std::mutex callbackMutex; + constexpr uint32_t kRegistrySchemaVersion = 2; + std::mutex registryMutex; std::string ToHex(const uint32_t value, const int width = 0) { std::ostringstream stream; @@ -37,244 +33,53 @@ namespace { return fallback; } - const std::string text = value.GetString(); try { - return static_cast(std::stoul(text, nullptr, 0)); + return static_cast(std::stoul(value.GetString(), nullptr, 0)); } catch (...) { return fallback; } } - std::string BytesToHex(const std::vector& bytes) { - static constexpr char digits[] = "0123456789ABCDEF"; - std::string output; - output.reserve(bytes.size() * 2); - for (const auto byte : bytes) { - output.push_back(digits[(byte >> 4) & 0x0f]); - output.push_back(digits[byte & 0x0f]); - } - return output; - } - - std::vector HexToBytes(const std::string& text) { - std::vector bytes; - if (text.size() % 2 != 0) { - return bytes; - } - - bytes.reserve(text.size() / 2); - for (size_t i = 0; i < text.size(); i += 2) { - uint32_t value = 0; - const auto result = std::from_chars(text.data() + i, text.data() + i + 2, value, 16); - if (result.ec != std::errc()) { - bytes.clear(); - return bytes; - } - bytes.push_back(static_cast(value)); - } - return bytes; - } - template rapidjson::Value StringValue(Allocator& allocator, const std::string& text) { rapidjson::Value value; value.SetString(text.c_str(), static_cast(text.size()), allocator); return value; } +} - template - rapidjson::Value FormRefToJson(RE::TESForm* form, Allocator& allocator) { - if (!form) { - return rapidjson::Value(rapidjson::kNullType); - } - - rapidjson::Value result(rapidjson::kObjectType); - if (IsDynamicFormID(form->GetFormID())) { - result.AddMember("dynamic", true, allocator); - result.AddMember("localId", StringValue(allocator, ToHex(ToDynamicLocalID(form->GetFormID()), 6)), allocator); - return result; - } - - if (const auto file = form->GetFile(0)) { - result.AddMember("file", StringValue(allocator, file->fileName), allocator); - result.AddMember("localId", StringValue(allocator, ToHex(form->GetLocalFormID(), 6)), allocator); - return result; - } - - result.AddMember("formId", StringValue(allocator, ToHex(form->GetFormID(), 8)), allocator); - return result; - } - - RE::TESForm* JsonToFormRef(const rapidjson::Value& value) { - if (!value.IsObject()) { - return nullptr; - } - - if (value.HasMember("dynamic") && value["dynamic"].IsBool() && value["dynamic"].GetBool()) { - const auto localId = value.HasMember("localId") ? ParseUInt(value["localId"]) : 0; - return localId ? RE::TESForm::LookupByID(MakeDynamicFormID(localId)) : nullptr; - } - - if (value.HasMember("file") && value["file"].IsString() && value.HasMember("localId")) { - const auto localId = ParseUInt(value["localId"]); - const auto fullId = RE::TESDataHandler::GetSingleton()->LookupFormID(localId, value["file"].GetString()); - return fullId ? RE::TESForm::LookupByID(fullId) : nullptr; - } - - if (value.HasMember("formId")) { - const auto formId = ParseUInt(value["formId"]); - return formId ? RE::TESForm::LookupByID(formId) : nullptr; - } - - return nullptr; - } - - std::string SerializeRecordData(FormRecord* record) { - if (!record) { - return {}; - } - - MemoryWriter writer; - StoreFormRecordData(&writer, record); - return BytesToHex(writer.Data()); - } - - bool DeserializeRecordData(FormRecord* record, const std::string& hex) { - if (!record || hex.empty()) { - return false; - } - - auto bytes = HexToBytes(hex); - if (bytes.empty()) { - return false; - } - - MemoryReader reader(std::move(bytes)); - RestoreFormRecordData(&reader, record); - return true; - } - - std::vector SortedRecords(const std::vector& records) { - auto sorted = records; - std::ranges::sort(sorted, [](const FormRecord* lhs, const FormRecord* rhs) { - return lhs && rhs ? lhs->order < rhs->order : lhs != nullptr; - }); - return sorted; - } - - template - rapidjson::Value CreatedRecordToJson(FormRecord* record, Allocator& allocator) { - rapidjson::Value item(rapidjson::kObjectType); - const auto localId = ToDynamicLocalID(record->formId); - - item.AddMember("order", record->order, allocator); - item.AddMember("localId", StringValue(allocator, ToHex(localId, 6)), allocator); - item.AddMember("formId", StringValue(allocator, ToHex(record->formId, 8)), allocator); - item.AddMember("formType", static_cast(record->formType), allocator); - item.AddMember("deleted", record->deleted, allocator); - item.AddMember("baseForm", FormRefToJson(record->baseForm, allocator), allocator); - item.AddMember("modelForm", FormRefToJson(record->modelForm, allocator), allocator); - item.AddMember("data", StringValue(allocator, SerializeRecordData(record)), allocator); - return item; - } - - template - rapidjson::Value TrackedRecordToJson(FormRecord* record, Allocator& allocator) { - rapidjson::Value item(rapidjson::kObjectType); - item.AddMember("order", record->order, allocator); - item.AddMember("form", FormRefToJson(record->actualForm, allocator), allocator); - item.AddMember("formId", StringValue(allocator, ToHex(record->formId, 8)), allocator); - item.AddMember("deleted", record->deleted, allocator); - item.AddMember("modelForm", FormRefToJson(record->modelForm, allocator), allocator); - item.AddMember("data", StringValue(allocator, SerializeRecordData(record)), allocator); - return item; - } - - FormRecord* CreateRecordFromJson(const rapidjson::Value& item) { - const auto order = item.HasMember("order") ? ParseUInt(item["order"]) : 0; - const auto localId = item.HasMember("localId") ? ParseUInt(item["localId"]) : 0; - const auto formType = static_cast(item.HasMember("formType") ? ParseUInt(item["formType"]) : 0); - const bool deleted = item.HasMember("deleted") && item["deleted"].IsBool() && item["deleted"].GetBool(); - const auto formId = MakeDynamicFormID(localId); - - FormRecord* record = nullptr; - if (deleted) { - record = FormRecord::CreateDeleted(formId); - record->formType = formType; - } else { - const auto factory = RE::IFormFactory::GetFormFactoryByType(formType); - if (!factory) { - logger::error("No factory for saved form type {}", static_cast(formType)); - return nullptr; - } - - auto* form = factory->Create(); - if (!form) { - logger::error("Factory returned null for saved form type {}", static_cast(formType)); - return nullptr; - } - - form->SetFormID(formId, false); - record = FormRecord::CreateNew(form, formType, formId); - record->baseForm = item.HasMember("baseForm") ? JsonToFormRef(item["baseForm"]) : nullptr; - record->modelForm = item.HasMember("modelForm") ? JsonToFormRef(item["modelForm"]) : nullptr; - if (record->baseForm || record->modelForm) { - applyPattern(record); - } - } - - record->order = order; - AddFormData(record); - return record; - } - - FormRecord* CreateTrackedRecordFromJson(const rapidjson::Value& item) { - const auto order = item.HasMember("order") ? ParseUInt(item["order"]) : 0; - const bool deleted = item.HasMember("deleted") && item["deleted"].IsBool() && item["deleted"].GetBool(); - auto* actualForm = item.HasMember("form") ? JsonToFormRef(item["form"]) : nullptr; - - FormRecord* record = nullptr; - if (!actualForm || deleted) { - const auto formId = item.HasMember("formId") ? ParseUInt(item["formId"]) : 0; - record = FormRecord::CreateDeleted(formId); - } else { - record = FormRecord::CreateReference(actualForm); - record->modelForm = item.HasMember("modelForm") ? JsonToFormRef(item["modelForm"]) : nullptr; - if (record->modelForm) { - applyPattern(record); - } - } - - record->order = order; - AddFormRef(record); - return record; - } - +std::string GetGlobalRegistryPath() { + return "Data/SKSE/Plugins/DPF_Cache.json"; } -std::string BuildStateJson() { +std::string BuildRegistryJson() { rapidjson::Document document(rapidjson::kObjectType); auto& allocator = document.GetAllocator(); - document.AddMember("schemaVersion", 1, allocator); + document.AddMember("schemaVersion", kRegistrySchemaVersion, allocator); document.AddMember("dynamicPluginFile", StringValue(allocator, dynamicPluginName), allocator); - document.AddMember("firstDynamicLocalId", StringValue(allocator, ToHex(firstDynamicLocalId, 6)), allocator); + document.AddMember("nextLocalId", StringValue(allocator, ToHex(nextDynamicLocalId, 6)), allocator); - rapidjson::Value createdForms(rapidjson::kArrayType); - for (auto* record : SortedRecords(formData)) { - if (record && IsDynamicFormID(record->formId)) { - createdForms.PushBack(CreatedRecordToJson(record, allocator), allocator); - } + rapidjson::Value slots(rapidjson::kArrayType); + std::vector sorted; + sorted.reserve(dynamicSlots.size()); + for (const auto& [localId, slot] : dynamicSlots) { + sorted.push_back(slot); } - document.AddMember("createdForms", createdForms, allocator); + std::ranges::sort(sorted, [](const auto& lhs, const auto& rhs) { + return lhs.localId < rhs.localId; + }); - rapidjson::Value trackedForms(rapidjson::kArrayType); - for (auto* record : SortedRecords(formRef)) { - if (record) { - trackedForms.PushBack(TrackedRecordToJson(record, allocator), allocator); - } + for (const auto& slotData : sorted) { + rapidjson::Value slot(rapidjson::kObjectType); + slot.AddMember("localId", StringValue(allocator, ToHex(slotData.localId, 6)), allocator); + slot.AddMember("formType", static_cast(slotData.formType), allocator); + slot.AddMember("state", StringValue(allocator, "used"), allocator); + slot.AddMember("owner", StringValue(allocator, slotData.owner), allocator); + slot.AddMember("key", StringValue(allocator, slotData.key), allocator); + slots.PushBack(slot, allocator); } - document.AddMember("trackedForms", trackedForms, allocator); + document.AddMember("slots", slots, allocator); rapidjson::StringBuffer buffer; rapidjson::PrettyWriter writer(buffer); @@ -282,7 +87,7 @@ std::string BuildStateJson() { return { buffer.GetString(), buffer.GetSize() }; } -bool RestoreStateJson(const std::string& json) { +bool RestoreRegistryJson(const std::string& json) { if (json.empty()) { return false; } @@ -290,126 +95,91 @@ bool RestoreStateJson(const std::string& json) { rapidjson::Document document; document.Parse(json.c_str(), json.size()); if (document.HasParseError() || !document.IsObject()) { - logger::error("DynamicPersistentForms save record is not valid JSON"); + logger::error("DPF global registry is not valid JSON"); return false; } - if (!espFound) { - ReadFirstFormIdFromESP(); - } - if (!espFound) { - return false; + if (!document.HasMember("schemaVersion") || !document["schemaVersion"].IsUint() || + document["schemaVersion"].GetUint() != kRegistrySchemaVersion) { + logger::warn("DPF global registry schema is missing or unsupported; starting with empty schema {} registry", kRegistrySchemaVersion); + ResetDynamicState(); + return SaveGlobalRegistry(); } - ClearRecords(true); + ResetDynamicState(); - if (document.HasMember("createdForms") && document["createdForms"].IsArray()) { - for (const auto& item : document["createdForms"].GetArray()) { - if (item.IsObject() && item.HasMember("localId")) { - ReserveDynamicLocalID(ParseUInt(item["localId"])); - } - } - - std::vector items; - for (const auto& item : document["createdForms"].GetArray()) { - if (item.IsObject()) { - items.push_back(&item); - } - } - std::ranges::sort(items, [](const rapidjson::Value* lhs, const rapidjson::Value* rhs) { - return ParseUInt((*lhs)["order"]) < ParseUInt((*rhs)["order"]); - }); + if (document.HasMember("nextLocalId")) { + nextDynamicLocalId = std::max(ParseUInt(document["nextLocalId"], firstDynamicLocalId), firstDynamicLocalId); + } - std::vector> restored; - for (const auto* item : items) { - auto* record = CreateRecordFromJson(*item); - if (record) { - restored.emplace_back(record, item); + if (document.HasMember("slots") && document["slots"].IsArray()) { + for (const auto& slot : document["slots"].GetArray()) { + if (!slot.IsObject() || !slot.HasMember("localId") || !slot.HasMember("formType")) { + continue; } - } - for (const auto& [record, item] : restored) { - if (item->HasMember("data") && (*item)["data"].IsString()) { - DeserializeRecordData(record, (*item)["data"].GetString()); + const auto localId = ParseUInt(slot["localId"]); + const auto formType = static_cast(ParseUInt(slot["formType"])); + const std::string owner = slot.HasMember("owner") && slot["owner"].IsString() ? slot["owner"].GetString() : ""; + const std::string key = slot.HasMember("key") && slot["key"].IsString() ? slot["key"].GetString() : ""; + if (!RegisterDynamicSlot(localId, formType, owner, key)) { + logger::warn("Ignoring invalid DPF registry slot {:06X}", localId); } } } - if (document.HasMember("trackedForms") && document["trackedForms"].IsArray()) { - std::vector items; - for (const auto& item : document["trackedForms"].GetArray()) { - if (item.IsObject()) { - items.push_back(&item); - } - } - std::ranges::sort(items, [](const rapidjson::Value* lhs, const rapidjson::Value* rhs) { - return ParseUInt((*lhs)["order"]) < ParseUInt((*rhs)["order"]); - }); + logger::info("Loaded DPF global registry with {} slots", dynamicSlots.size()); + return true; +} - std::vector> restored; - for (const auto* item : items) { - auto* record = CreateTrackedRecordFromJson(*item); - if (record) { - restored.emplace_back(record, item); - } - } +bool LoadGlobalRegistry() { + std::lock_guard lock(registryMutex); + const auto path = GetGlobalRegistryPath(); + if (!fs::exists(path)) { + logger::info("DPF global registry not found at {}; starting empty", path); + ResetDynamicState(); + return SaveGlobalRegistry(); + } - for (const auto& [record, item] : restored) { - if (item->HasMember("data") && (*item)["data"].IsString()) { - DeserializeRecordData(record, (*item)["data"].GetString()); - } - } + std::ifstream file(path, std::ios::in | std::ios::binary); + if (!file.is_open()) { + logger::error("Could not open DPF global registry at {}", path); + return false; } - logger::info("DynamicPersistentForms restored {} created forms and {} tracked forms", formData.size(), formRef.size()); - return true; + std::stringstream buffer; + buffer << file.rdbuf(); + return RestoreRegistryJson(buffer.str()); } -void SaveCallback(SKSE::SerializationInterface* a_intfc) { - std::lock_guard lock(callbackMutex); +bool SaveGlobalRegistry() { + const auto path = GetGlobalRegistryPath(); try { - const auto json = BuildStateJson(); - if (!a_intfc->OpenRecord(kJsonRecord, kJsonVersion)) { - logger::error("Failed to open DynamicPersistentForms JSON save record"); - return; + const fs::path registryPath(path); + if (registryPath.has_parent_path()) { + fs::create_directories(registryPath.parent_path()); } - if (!json.empty() && !a_intfc->WriteRecordData(json.data(), static_cast(json.size()))) { - logger::error("Failed to write DynamicPersistentForms JSON save record"); - return; + + std::ofstream file(registryPath, std::ios::out | std::ios::binary | std::ios::trunc); + if (!file.is_open()) { + logger::error("Could not write DPF global registry at {}", path); + return false; } + + file << BuildRegistryJson(); + logger::info("Saved DPF global registry to {}", path); + return true; } catch (const std::exception& e) { - logger::error("Error saving DynamicPersistentForms JSON state: {}", e.what()); + logger::error("Error saving DPF global registry: {}", e.what()); + return false; } } -void LoadCallback(SKSE::SerializationInterface* a_intfc) { - std::lock_guard lock(callbackMutex); - try { - uint32_t type = 0; - uint32_t version = 0; - uint32_t length = 0; - - while (a_intfc->GetNextRecordInfo(type, version, length)) { - if (type != kJsonRecord) { - logger::warn("Ignoring unrecognized DPF record {:08X}", type); - continue; - } - if (version != kJsonVersion) { - logger::warn("Ignoring unsupported DynamicPersistentForms JSON version {}", version); - continue; - } - - std::string json(length, '\0'); - const auto read = a_intfc->ReadRecordData(json.data(), length); - if (read != length) { - logger::error("Could not read full DynamicPersistentForms JSON record"); - return; - } - - RestoreStateJson(json); - } - } catch (const std::exception& e) { - logger::error("Error loading DynamicPersistentForms JSON state: {}", e.what()); - } +void SaveCallback(SKSE::SerializationInterface*) { + std::lock_guard lock(registryMutex); + SaveGlobalRegistry(); } +void LoadCallback(SKSE::SerializationInterface*) { + LoadGlobalRegistry(); +} diff --git a/SKSE_Plugin/src/plugin.cpp b/SKSE_Plugin/src/plugin.cpp index 70e386f..43b0396 100644 --- a/SKSE_Plugin/src/plugin.cpp +++ b/SKSE_Plugin/src/plugin.cpp @@ -1,4 +1,4 @@ -#include "Services.h" +#include "Services.h" #include "logger.h" #include "model.h" #include "papyrus.h" @@ -34,6 +34,34 @@ class DPFInterfaceImpl : public DPF::IDynamicPersistentForms { RE::TESForm* CreateByType(const uint32_t formType) override { return Services::CreateByType(formType); } + + RE::TESForm* GetOrCreateByLocalId(const uint32_t localId, const uint32_t formType) override { + return Services::GetOrCreateByLocalId(localId, formType); + } + + RE::TESForm* GetOrCreateByFormId(const RE::FormID formId, const uint32_t formType) override { + return Services::GetOrCreateByFormId(formId, formType); + } + + RE::TESForm* CreateByTypeForOwner(const char* owner, const char* key, const uint32_t formType) override { + return Services::CreateByTypeForOwner(owner, key, formType); + } + + RE::TESForm* GetOrCreateByOwnerKey(const char* owner, const char* key, const uint32_t formType) override { + return Services::GetOrCreateByOwnerKey(owner, key, formType); + } + + bool ReleaseByOwnerKey(const char* owner, const char* key) override { + return Services::ReleaseByOwnerKey(owner, key); + } + + bool ReleaseByLocalId(const uint32_t localId, const char* owner) override { + return Services::ReleaseByLocalId(localId, owner); + } + + uint32_t ReleaseOwner(const char* owner) override { + return Services::ReleaseOwner(owner); + } }; extern "C" __declspec(dllexport) void* GetDPFAPI() { @@ -44,7 +72,8 @@ namespace { void OnMessage(SKSE::MessagingInterface::Message* message) { if (message->type == SKSE::MessagingInterface::kDataLoaded) { ReadFirstFormIdFromESP(); - logger::info("DPF data loaded"); + LoadGlobalRegistry(); + logger::info("DynamicPersistentForms data loaded"); } else if (message->type == SKSE::MessagingInterface::kNewGame) { ClearRecords(true); logger::info("DPF new game state cleared"); @@ -67,3 +96,5 @@ SKSEPluginLoad(const SKSE::LoadInterface *skse) { return true; } + + From f9b0c397a37875c9506f7d9b401897b5f99811d8 Mon Sep 17 00:00:00 2001 From: Viny <77700518+vinymayan@users.noreply.github.com> Date: Wed, 17 Jun 2026 17:03:26 -0300 Subject: [PATCH 3/8] easier to debug with GetOrCreateFormByOwnerKeyEx so it knows now if the formid already existed --- SKSE_Plugin/include/Services.h | 4 +++- SKSE_Plugin/include/form.h | 4 +++- SKSE_Plugin/public/DPFAPI.h | 8 ++++++-- SKSE_Plugin/src/Services.cpp | 17 ++++++++++++++++- SKSE_Plugin/src/form.cpp | 31 +++++++++++++++++++++++++++++-- SKSE_Plugin/src/plugin.cpp | 7 ++++++- 6 files changed, 63 insertions(+), 8 deletions(-) diff --git a/SKSE_Plugin/include/Services.h b/SKSE_Plugin/include/Services.h index f4b8c31..34094b0 100644 --- a/SKSE_Plugin/include/Services.h +++ b/SKSE_Plugin/include/Services.h @@ -1,4 +1,4 @@ -#pragma once +#pragma once #include "../public/DPFAPI.h" #include @@ -18,6 +18,8 @@ namespace Services { RE::TESForm* GetOrCreateByOwnerKey(const char* owner, const char* key, uint32_t formType); + RE::TESForm* GetOrCreateByOwnerKeyEx(const char* owner, const char* key, uint32_t formType, uint32_t* localId, bool* existed); + bool ReleaseByOwnerKey(const char* owner, const char* key); bool ReleaseByLocalId(uint32_t localId, const char* owner); diff --git a/SKSE_Plugin/include/form.h b/SKSE_Plugin/include/form.h index 04f7093..61ebd03 100644 --- a/SKSE_Plugin/include/form.h +++ b/SKSE_Plugin/include/form.h @@ -1,4 +1,4 @@ -#pragma once +#pragma once #include "form_record.h" static void copyFormArmorModel(RE::TESForm* source, RE::TESForm* target); @@ -30,6 +30,8 @@ RE::TESForm* GetOrCreateFormByFormId(RE::FormID formId, RE::FormType formType); RE::TESForm* GetOrCreateFormByOwnerKey(const char* owner, const char* key, RE::FormType formType); +RE::TESForm* GetOrCreateFormByOwnerKeyEx(const char* owner, const char* key, RE::FormType formType, uint32_t* localId, bool* existed); + bool ReleaseFormByOwnerKey(const char* owner, const char* key); bool ReleaseFormByLocalId(uint32_t localId, const char* owner); diff --git a/SKSE_Plugin/public/DPFAPI.h b/SKSE_Plugin/public/DPFAPI.h index e040b71..2c7ed79 100644 --- a/SKSE_Plugin/public/DPFAPI.h +++ b/SKSE_Plugin/public/DPFAPI.h @@ -1,4 +1,4 @@ -#pragma once +#pragma once #include "RE/Skyrim.h" #include @@ -6,7 +6,7 @@ namespace DPF { constexpr auto InterfaceName = "DynamicPersistentForms"; - constexpr uint32_t InterfaceVersion = 4; + constexpr uint32_t InterfaceVersion = 5; class IDynamicPersistentForms { public: @@ -33,6 +33,10 @@ namespace DPF { virtual bool ReleaseByOwnerKey(const char* owner, const char* key) = 0; virtual bool ReleaseByLocalId(uint32_t localId, const char* owner) = 0; virtual uint32_t ReleaseOwner(const char* owner) = 0; + + // v5 + virtual RE::TESForm* GetOrCreateByOwnerKeyEx(const char* owner, const char* key, uint32_t formType, + uint32_t* localId, bool* existed) = 0; }; using GetDPFAPI = void* (*)(); diff --git a/SKSE_Plugin/src/Services.cpp b/SKSE_Plugin/src/Services.cpp index 1c2b416..e9a1bd7 100644 --- a/SKSE_Plugin/src/Services.cpp +++ b/SKSE_Plugin/src/Services.cpp @@ -1,4 +1,4 @@ -#include "Services.h" +#include "Services.h" #include "form.h" #include "model.h" @@ -65,6 +65,21 @@ RE::TESForm* Services::GetOrCreateByOwnerKey(const char* owner, const char* key, return nullptr; } } +RE::TESForm* Services::GetOrCreateByOwnerKeyEx(const char* owner, const char* key, const uint32_t formType, + uint32_t* localId, bool* existed) { + std::lock_guard lock(serviceMutex); + try { + return GetOrCreateFormByOwnerKeyEx(owner, key, static_cast(formType), localId, existed); + } catch (const std::exception&) { + if (localId) { + *localId = 0; + } + if (existed) { + *existed = false; + } + return nullptr; + } +} bool Services::ReleaseByOwnerKey(const char* owner, const char* key) { std::lock_guard lock(serviceMutex); diff --git a/SKSE_Plugin/src/form.cpp b/SKSE_Plugin/src/form.cpp index e28c902..dfd4719 100644 --- a/SKSE_Plugin/src/form.cpp +++ b/SKSE_Plugin/src/form.cpp @@ -1,4 +1,4 @@ -#include "form.h" +#include "form.h" #include "model.h" #include "persistence.h" @@ -275,6 +275,18 @@ RE::TESForm* GetOrCreateFormByFormId(const RE::FormID formId, const RE::FormType } RE::TESForm* GetOrCreateFormByOwnerKey(const char* ownerRaw, const char* keyRaw, const RE::FormType formType) { + return GetOrCreateFormByOwnerKeyEx(ownerRaw, keyRaw, formType, nullptr, nullptr); +} + +RE::TESForm* GetOrCreateFormByOwnerKeyEx(const char* ownerRaw, const char* keyRaw, const RE::FormType formType, + uint32_t* localIdOut, bool* existedOut) { + if (localIdOut) { + *localIdOut = 0; + } + if (existedOut) { + *existedOut = false; + } + const auto owner = NormalizeOwnerKeyPart(ownerRaw); const auto key = NormalizeOwnerKeyPart(keyRaw); if (owner.empty() || key.empty()) { @@ -283,6 +295,12 @@ RE::TESForm* GetOrCreateFormByOwnerKey(const char* ownerRaw, const char* keyRaw, } if (const auto existingLocalId = FindDynamicSlotByOwnerKey(owner, key)) { + if (localIdOut) { + *localIdOut = existingLocalId.value(); + } + if (existedOut) { + *existedOut = true; + } return CreateRegisteredForm(existingLocalId.value(), formType, nullptr, owner, key); } @@ -291,7 +309,16 @@ RE::TESForm* GetOrCreateFormByOwnerKey(const char* ownerRaw, const char* keyRaw, return nullptr; } - return CreateRegisteredForm(ToDynamicLocalID(formId), formType, nullptr, owner, key); + const auto localId = ToDynamicLocalID(formId); + auto* form = CreateRegisteredForm(localId, formType, nullptr, owner, key); + if (!form) { + return nullptr; + } + + if (localIdOut) { + *localIdOut = localId; + } + return form; } bool ReleaseFormByOwnerKey(const char* ownerRaw, const char* keyRaw) { diff --git a/SKSE_Plugin/src/plugin.cpp b/SKSE_Plugin/src/plugin.cpp index 43b0396..fb287fe 100644 --- a/SKSE_Plugin/src/plugin.cpp +++ b/SKSE_Plugin/src/plugin.cpp @@ -1,4 +1,4 @@ -#include "Services.h" +#include "Services.h" #include "logger.h" #include "model.h" #include "papyrus.h" @@ -62,6 +62,11 @@ class DPFInterfaceImpl : public DPF::IDynamicPersistentForms { uint32_t ReleaseOwner(const char* owner) override { return Services::ReleaseOwner(owner); } + + RE::TESForm* GetOrCreateByOwnerKeyEx(const char* owner, const char* key, const uint32_t formType, + uint32_t* localId, bool* existed) override { + return Services::GetOrCreateByOwnerKeyEx(owner, key, formType, localId, existed); + } }; extern "C" __declspec(dllexport) void* GetDPFAPI() { From fc211d42993bcfae74cfc47b16395270092436d9 Mon Sep 17 00:00:00 2001 From: Viny <77700518+vinymayan@users.noreply.github.com> Date: Thu, 18 Jun 2026 07:09:23 -0300 Subject: [PATCH 4/8] cleaning old stuff + serialized json --- .../Source/Scripts/DynamicPersistentForms.psc | 2 - .../Source/Scripts/DynamicPersistentForms.psc | 2 - SKSE_Plugin/include/Services.h | 6 +- SKSE_Plugin/include/form.h | 6 +- SKSE_Plugin/include/papyrus.h | 2 - SKSE_Plugin/public/DPFAPI.h | 14 +- SKSE_Plugin/src/Services.cpp | 21 +-- SKSE_Plugin/src/form.cpp | 10 +- SKSE_Plugin/src/model.cpp | 1 + SKSE_Plugin/src/papyrus.cpp | 7 +- SKSE_Plugin/src/persistence.cpp | 123 ++++++++++-------- SKSE_Plugin/src/plugin.cpp | 14 +- 12 files changed, 85 insertions(+), 123 deletions(-) diff --git a/DIST/Dynamic Persistent Forms/Source/Scripts/DynamicPersistentForms.psc b/DIST/Dynamic Persistent Forms/Source/Scripts/DynamicPersistentForms.psc index 6dd2b33..13bf644 100644 --- a/DIST/Dynamic Persistent Forms/Source/Scripts/DynamicPersistentForms.psc +++ b/DIST/Dynamic Persistent Forms/Source/Scripts/DynamicPersistentForms.psc @@ -9,8 +9,6 @@ Form function GetOrCreateByLocalId(int localId, int formType) global native ; Returns or creates a persistent form using a form whose ID belongs to Dynamic Persistent Forms.esp. Form function GetOrCreateByFormId(Form formIdSource, int formType) global native ; Creates or returns a form owned by owner/key. -Form function CreateByTypeForOwner(String owner, String key, int formType) global native -; Creates or returns a form owned by owner/key. Form function GetOrCreateByOwnerKey(String owner, String key, int formType) global native ; Releases one owned slot by owner/key. bool function ReleaseByOwnerKey(String owner, String key) global native diff --git a/PapyrusInterface/Source/Scripts/DynamicPersistentForms.psc b/PapyrusInterface/Source/Scripts/DynamicPersistentForms.psc index 6dd2b33..13bf644 100644 --- a/PapyrusInterface/Source/Scripts/DynamicPersistentForms.psc +++ b/PapyrusInterface/Source/Scripts/DynamicPersistentForms.psc @@ -9,8 +9,6 @@ Form function GetOrCreateByLocalId(int localId, int formType) global native ; Returns or creates a persistent form using a form whose ID belongs to Dynamic Persistent Forms.esp. Form function GetOrCreateByFormId(Form formIdSource, int formType) global native ; Creates or returns a form owned by owner/key. -Form function CreateByTypeForOwner(String owner, String key, int formType) global native -; Creates or returns a form owned by owner/key. Form function GetOrCreateByOwnerKey(String owner, String key, int formType) global native ; Releases one owned slot by owner/key. bool function ReleaseByOwnerKey(String owner, String key) global native diff --git a/SKSE_Plugin/include/Services.h b/SKSE_Plugin/include/Services.h index 34094b0..ff88e3d 100644 --- a/SKSE_Plugin/include/Services.h +++ b/SKSE_Plugin/include/Services.h @@ -14,11 +14,7 @@ namespace Services { RE::TESForm* GetOrCreateByFormId(RE::FormID formId, uint32_t formType); - RE::TESForm* CreateByTypeForOwner(const char* owner, const char* key, uint32_t formType); - - RE::TESForm* GetOrCreateByOwnerKey(const char* owner, const char* key, uint32_t formType); - - RE::TESForm* GetOrCreateByOwnerKeyEx(const char* owner, const char* key, uint32_t formType, uint32_t* localId, bool* existed); + RE::TESForm* GetOrCreateByOwnerKey(const char* owner, const char* key, uint32_t formType, uint32_t* localId, bool* existed); bool ReleaseByOwnerKey(const char* owner, const char* key); diff --git a/SKSE_Plugin/include/form.h b/SKSE_Plugin/include/form.h index 61ebd03..2da7945 100644 --- a/SKSE_Plugin/include/form.h +++ b/SKSE_Plugin/include/form.h @@ -22,15 +22,11 @@ RE::TESForm* AddForm(RE::TESForm* baseItem); RE::TESForm* AddFormByType(RE::FormType formType); -RE::TESForm* AddFormByTypeForOwner(const char* owner, const char* key, RE::FormType formType); - RE::TESForm* GetOrCreateFormByLocalId(uint32_t localId, RE::FormType formType); RE::TESForm* GetOrCreateFormByFormId(RE::FormID formId, RE::FormType formType); -RE::TESForm* GetOrCreateFormByOwnerKey(const char* owner, const char* key, RE::FormType formType); - -RE::TESForm* GetOrCreateFormByOwnerKeyEx(const char* owner, const char* key, RE::FormType formType, uint32_t* localId, bool* existed); +RE::TESForm* GetOrCreateFormByOwnerKey(const char* owner, const char* key, RE::FormType formType, uint32_t* localId, bool* existed); bool ReleaseFormByOwnerKey(const char* owner, const char* key); diff --git a/SKSE_Plugin/include/papyrus.h b/SKSE_Plugin/include/papyrus.h index b7460c4..c43377b 100644 --- a/SKSE_Plugin/include/papyrus.h +++ b/SKSE_Plugin/include/papyrus.h @@ -9,8 +9,6 @@ RE::TESForm* GetOrCreateByLocalId(RE::StaticFunctionTag*, uint32_t localId, uint RE::TESForm* GetOrCreateByFormId(RE::StaticFunctionTag*, RE::TESForm* formIdSource, uint32_t formType); -RE::TESForm* CreateByTypeForOwner(RE::StaticFunctionTag*, RE::BSFixedString owner, RE::BSFixedString key, uint32_t formType); - RE::TESForm* GetOrCreateByOwnerKey(RE::StaticFunctionTag*, RE::BSFixedString owner, RE::BSFixedString key, uint32_t formType); bool ReleaseByOwnerKey(RE::StaticFunctionTag*, RE::BSFixedString owner, RE::BSFixedString key); diff --git a/SKSE_Plugin/public/DPFAPI.h b/SKSE_Plugin/public/DPFAPI.h index 2c7ed79..5da3011 100644 --- a/SKSE_Plugin/public/DPFAPI.h +++ b/SKSE_Plugin/public/DPFAPI.h @@ -6,7 +6,7 @@ namespace DPF { constexpr auto InterfaceName = "DynamicPersistentForms"; - constexpr uint32_t InterfaceVersion = 5; + constexpr uint32_t InterfaceVersion = 1; class IDynamicPersistentForms { public: @@ -14,29 +14,21 @@ namespace DPF { virtual uint32_t GetVersion() const = 0; - // v1 virtual RE::TESForm* Create(RE::TESForm* baseItem) = 0; virtual void Dispose(RE::TESForm* form) = 0; virtual void Track(RE::TESForm* item) = 0; virtual void UnTrack(RE::TESForm* item) = 0; - // v2 virtual RE::TESForm* CreateByType(uint32_t formType) = 0; - // v3 virtual RE::TESForm* GetOrCreateByLocalId(uint32_t localId, uint32_t formType) = 0; virtual RE::TESForm* GetOrCreateByFormId(RE::FormID formId, uint32_t formType) = 0; - // v4 - virtual RE::TESForm* CreateByTypeForOwner(const char* owner, const char* key, uint32_t formType) = 0; - virtual RE::TESForm* GetOrCreateByOwnerKey(const char* owner, const char* key, uint32_t formType) = 0; + virtual RE::TESForm* GetOrCreateByOwnerKey(const char* owner, const char* key, uint32_t formType, + uint32_t* localId, bool* existed) = 0; virtual bool ReleaseByOwnerKey(const char* owner, const char* key) = 0; virtual bool ReleaseByLocalId(uint32_t localId, const char* owner) = 0; virtual uint32_t ReleaseOwner(const char* owner) = 0; - - // v5 - virtual RE::TESForm* GetOrCreateByOwnerKeyEx(const char* owner, const char* key, uint32_t formType, - uint32_t* localId, bool* existed) = 0; }; using GetDPFAPI = void* (*)(); diff --git a/SKSE_Plugin/src/Services.cpp b/SKSE_Plugin/src/Services.cpp index e9a1bd7..a6943a0 100644 --- a/SKSE_Plugin/src/Services.cpp +++ b/SKSE_Plugin/src/Services.cpp @@ -48,28 +48,11 @@ RE::TESForm* Services::GetOrCreateByFormId(const RE::FormID formId, const uint32 } } -RE::TESForm* Services::CreateByTypeForOwner(const char* owner, const char* key, const uint32_t formType) { - std::lock_guard lock(serviceMutex); - try { - return AddFormByTypeForOwner(owner, key, static_cast(formType)); - } catch (const std::exception&) { - return nullptr; - } -} - -RE::TESForm* Services::GetOrCreateByOwnerKey(const char* owner, const char* key, const uint32_t formType) { - std::lock_guard lock(serviceMutex); - try { - return GetOrCreateFormByOwnerKey(owner, key, static_cast(formType)); - } catch (const std::exception&) { - return nullptr; - } -} -RE::TESForm* Services::GetOrCreateByOwnerKeyEx(const char* owner, const char* key, const uint32_t formType, +RE::TESForm* Services::GetOrCreateByOwnerKey(const char* owner, const char* key, const uint32_t formType, uint32_t* localId, bool* existed) { std::lock_guard lock(serviceMutex); try { - return GetOrCreateFormByOwnerKeyEx(owner, key, static_cast(formType), localId, existed); + return GetOrCreateFormByOwnerKey(owner, key, static_cast(formType), localId, existed); } catch (const std::exception&) { if (localId) { *localId = 0; diff --git a/SKSE_Plugin/src/form.cpp b/SKSE_Plugin/src/form.cpp index dfd4719..c66f265 100644 --- a/SKSE_Plugin/src/form.cpp +++ b/SKSE_Plugin/src/form.cpp @@ -258,10 +258,6 @@ RE::TESForm* AddFormByType(const RE::FormType formType) { return CreateRegisteredForm(ToDynamicLocalID(formId), formType, nullptr); } -RE::TESForm* AddFormByTypeForOwner(const char* owner, const char* key, const RE::FormType formType) { - return GetOrCreateFormByOwnerKey(owner, key, formType); -} - RE::TESForm* GetOrCreateFormByLocalId(const uint32_t localId, const RE::FormType formType) { return CreateRegisteredForm(localId & 0x00ffffff, formType, nullptr); } @@ -274,11 +270,7 @@ RE::TESForm* GetOrCreateFormByFormId(const RE::FormID formId, const RE::FormType return GetOrCreateFormByLocalId(ToDynamicLocalID(formId), formType); } -RE::TESForm* GetOrCreateFormByOwnerKey(const char* ownerRaw, const char* keyRaw, const RE::FormType formType) { - return GetOrCreateFormByOwnerKeyEx(ownerRaw, keyRaw, formType, nullptr, nullptr); -} - -RE::TESForm* GetOrCreateFormByOwnerKeyEx(const char* ownerRaw, const char* keyRaw, const RE::FormType formType, +RE::TESForm* GetOrCreateFormByOwnerKey(const char* ownerRaw, const char* keyRaw, const RE::FormType formType, uint32_t* localIdOut, bool* existedOut) { if (localIdOut) { *localIdOut = 0; diff --git a/SKSE_Plugin/src/model.cpp b/SKSE_Plugin/src/model.cpp index d49d275..80b7d87 100644 --- a/SKSE_Plugin/src/model.cpp +++ b/SKSE_Plugin/src/model.cpp @@ -203,6 +203,7 @@ bool ReleaseDynamicSlot(const uint32_t localId, std::string_view owner) { RemoveOwnerKeyIndex(existing->second); dynamicSlots.erase(existing); reservedDynamicLocalIds.erase(normalized); + nextDynamicLocalId = std::min(nextDynamicLocalId, normalized); return true; } diff --git a/SKSE_Plugin/src/papyrus.cpp b/SKSE_Plugin/src/papyrus.cpp index d4d5584..d0b683f 100644 --- a/SKSE_Plugin/src/papyrus.cpp +++ b/SKSE_Plugin/src/papyrus.cpp @@ -24,12 +24,8 @@ RE::TESForm* GetOrCreateByFormId(RE::StaticFunctionTag*, RE::TESForm* formIdSour return Services::GetOrCreateByFormId(formIdSource->GetFormID(), formType); } -RE::TESForm* CreateByTypeForOwner(RE::StaticFunctionTag*, const RE::BSFixedString owner, const RE::BSFixedString key, const uint32_t formType) { - return Services::CreateByTypeForOwner(owner.c_str(), key.c_str(), formType); -} - RE::TESForm* GetOrCreateByOwnerKey(RE::StaticFunctionTag*, const RE::BSFixedString owner, const RE::BSFixedString key, const uint32_t formType) { - return Services::GetOrCreateByOwnerKey(owner.c_str(), key.c_str(), formType); + return Services::GetOrCreateByOwnerKey(owner.c_str(), key.c_str(), formType, nullptr, nullptr); } bool ReleaseByOwnerKey(RE::StaticFunctionTag*, const RE::BSFixedString owner, const RE::BSFixedString key) { @@ -336,7 +332,6 @@ bool PapyrusFunctions(RE::BSScript::IVirtualMachine* vm) { vm->RegisterFunction("CreateByType", "DynamicPersistentForms", CreateByType); vm->RegisterFunction("GetOrCreateByLocalId", "DynamicPersistentForms", GetOrCreateByLocalId); vm->RegisterFunction("GetOrCreateByFormId", "DynamicPersistentForms", GetOrCreateByFormId); - vm->RegisterFunction("CreateByTypeForOwner", "DynamicPersistentForms", CreateByTypeForOwner); vm->RegisterFunction("GetOrCreateByOwnerKey", "DynamicPersistentForms", GetOrCreateByOwnerKey); vm->RegisterFunction("ReleaseByOwnerKey", "DynamicPersistentForms", ReleaseByOwnerKey); vm->RegisterFunction("ReleaseByLocalId", "DynamicPersistentForms", ReleaseByLocalId); diff --git a/SKSE_Plugin/src/persistence.cpp b/SKSE_Plugin/src/persistence.cpp index 3a9e68e..bd553dd 100644 --- a/SKSE_Plugin/src/persistence.cpp +++ b/SKSE_Plugin/src/persistence.cpp @@ -1,43 +1,21 @@ -#include "persistence.h" +#include "persistence.h" #include "model.h" #include "rapidjson/document.h" -#include "rapidjson/prettywriter.h" #include "rapidjson/stringbuffer.h" +#include "rapidjson/writer.h" #include -#include #include -#include #include #include +#include #include namespace { - constexpr uint32_t kRegistrySchemaVersion = 2; + constexpr uint32_t kRegistrySchemaVersion = 1; std::mutex registryMutex; - std::string ToHex(const uint32_t value, const int width = 0) { - std::ostringstream stream; - stream << "0x" << std::uppercase << std::hex << std::setfill('0'); - if (width > 0) { - stream << std::setw(width); - } - stream << value; - return stream.str(); - } - uint32_t ParseUInt(const rapidjson::Value& value, const uint32_t fallback = 0) { - if (value.IsUint()) { - return value.GetUint(); - } - if (!value.IsString()) { - return fallback; - } - - try { - return static_cast(std::stoul(value.GetString(), nullptr, 0)); - } catch (...) { - return fallback; - } + return value.IsUint() ? value.GetUint() : fallback; } template @@ -46,6 +24,19 @@ namespace { value.SetString(text.c_str(), static_cast(text.size()), allocator); return value; } + + void RecalculateNextDynamicLocalId() { + uint32_t localId = firstDynamicLocalId; + while (localId < 0x00ffffff) { + if (!reservedDynamicLocalIds.contains(localId) && !dynamicSlots.contains(localId)) { + nextDynamicLocalId = localId; + return; + } + ++localId; + } + + nextDynamicLocalId = 0x00ffffff; + } } std::string GetGlobalRegistryPath() { @@ -56,11 +47,6 @@ std::string BuildRegistryJson() { rapidjson::Document document(rapidjson::kObjectType); auto& allocator = document.GetAllocator(); - document.AddMember("schemaVersion", kRegistrySchemaVersion, allocator); - document.AddMember("dynamicPluginFile", StringValue(allocator, dynamicPluginName), allocator); - document.AddMember("nextLocalId", StringValue(allocator, ToHex(nextDynamicLocalId, 6)), allocator); - - rapidjson::Value slots(rapidjson::kArrayType); std::vector sorted; sorted.reserve(dynamicSlots.size()); for (const auto& [localId, slot] : dynamicSlots) { @@ -70,19 +56,43 @@ std::string BuildRegistryJson() { return lhs.localId < rhs.localId; }); + std::unordered_map ownerIndexes; + rapidjson::Value owners(rapidjson::kArrayType); for (const auto& slotData : sorted) { - rapidjson::Value slot(rapidjson::kObjectType); - slot.AddMember("localId", StringValue(allocator, ToHex(slotData.localId, 6)), allocator); - slot.AddMember("formType", static_cast(slotData.formType), allocator); - slot.AddMember("state", StringValue(allocator, "used"), allocator); - slot.AddMember("owner", StringValue(allocator, slotData.owner), allocator); - slot.AddMember("key", StringValue(allocator, slotData.key), allocator); + if (slotData.owner.empty() || ownerIndexes.contains(slotData.owner)) { + continue; + } + + const auto index = static_cast(ownerIndexes.size()); + ownerIndexes[slotData.owner] = index; + auto ownerValue = StringValue(allocator, slotData.owner); + owners.PushBack(ownerValue, allocator); + } + + rapidjson::Value slots(rapidjson::kArrayType); + for (const auto& slotData : sorted) { + rapidjson::Value slot(rapidjson::kArrayType); + slot.PushBack(slotData.localId, allocator); + slot.PushBack(static_cast(slotData.formType), allocator); + if (slotData.owner.empty()) { + rapidjson::Value ownerIndex; + ownerIndex.SetNull(); + slot.PushBack(ownerIndex, allocator); + } else { + slot.PushBack(ownerIndexes[slotData.owner], allocator); + } + auto keyValue = StringValue(allocator, slotData.key); + slot.PushBack(keyValue, allocator); slots.PushBack(slot, allocator); } - document.AddMember("slots", slots, allocator); + + document.AddMember("v", kRegistrySchemaVersion, allocator); + document.AddMember("p", StringValue(allocator, dynamicPluginName), allocator); + document.AddMember("o", owners, allocator); + document.AddMember("s", slots, allocator); rapidjson::StringBuffer buffer; - rapidjson::PrettyWriter writer(buffer); + rapidjson::Writer writer(buffer); document.Accept(writer); return { buffer.GetString(), buffer.GetSize() }; } @@ -99,8 +109,7 @@ bool RestoreRegistryJson(const std::string& json) { return false; } - if (!document.HasMember("schemaVersion") || !document["schemaVersion"].IsUint() || - document["schemaVersion"].GetUint() != kRegistrySchemaVersion) { + if (!document.HasMember("v") || !document["v"].IsUint() || document["v"].GetUint() != kRegistrySchemaVersion) { logger::warn("DPF global registry schema is missing or unsupported; starting with empty schema {} registry", kRegistrySchemaVersion); ResetDynamicState(); return SaveGlobalRegistry(); @@ -108,26 +117,38 @@ bool RestoreRegistryJson(const std::string& json) { ResetDynamicState(); - if (document.HasMember("nextLocalId")) { - nextDynamicLocalId = std::max(ParseUInt(document["nextLocalId"], firstDynamicLocalId), firstDynamicLocalId); + if (document.HasMember("p") && document["p"].IsString()) { + dynamicPluginName = document["p"].GetString(); + } + + std::vector owners; + if (document.HasMember("o") && document["o"].IsArray()) { + for (const auto& owner : document["o"].GetArray()) { + owners.emplace_back(owner.IsString() ? owner.GetString() : ""); + } } - if (document.HasMember("slots") && document["slots"].IsArray()) { - for (const auto& slot : document["slots"].GetArray()) { - if (!slot.IsObject() || !slot.HasMember("localId") || !slot.HasMember("formType")) { + if (document.HasMember("s") && document["s"].IsArray()) { + for (const auto& slot : document["s"].GetArray()) { + if (!slot.IsArray() || slot.Size() < 4) { continue; } - const auto localId = ParseUInt(slot["localId"]); - const auto formType = static_cast(ParseUInt(slot["formType"])); - const std::string owner = slot.HasMember("owner") && slot["owner"].IsString() ? slot["owner"].GetString() : ""; - const std::string key = slot.HasMember("key") && slot["key"].IsString() ? slot["key"].GetString() : ""; + const auto localId = ParseUInt(slot[0]); + const auto formType = static_cast(ParseUInt(slot[1])); + std::string owner; + if (slot[2].IsUint() && slot[2].GetUint() < owners.size()) { + owner = owners[slot[2].GetUint()]; + } + const std::string key = slot[3].IsString() ? slot[3].GetString() : ""; if (!RegisterDynamicSlot(localId, formType, owner, key)) { logger::warn("Ignoring invalid DPF registry slot {:06X}", localId); } } } + RecalculateNextDynamicLocalId(); + logger::info("Loaded DPF global registry with {} slots", dynamicSlots.size()); return true; } diff --git a/SKSE_Plugin/src/plugin.cpp b/SKSE_Plugin/src/plugin.cpp index fb287fe..d724fe6 100644 --- a/SKSE_Plugin/src/plugin.cpp +++ b/SKSE_Plugin/src/plugin.cpp @@ -43,12 +43,9 @@ class DPFInterfaceImpl : public DPF::IDynamicPersistentForms { return Services::GetOrCreateByFormId(formId, formType); } - RE::TESForm* CreateByTypeForOwner(const char* owner, const char* key, const uint32_t formType) override { - return Services::CreateByTypeForOwner(owner, key, formType); - } - - RE::TESForm* GetOrCreateByOwnerKey(const char* owner, const char* key, const uint32_t formType) override { - return Services::GetOrCreateByOwnerKey(owner, key, formType); + RE::TESForm* GetOrCreateByOwnerKey(const char* owner, const char* key, const uint32_t formType, + uint32_t* localId, bool* existed) override { + return Services::GetOrCreateByOwnerKey(owner, key, formType, localId, existed); } bool ReleaseByOwnerKey(const char* owner, const char* key) override { @@ -62,11 +59,6 @@ class DPFInterfaceImpl : public DPF::IDynamicPersistentForms { uint32_t ReleaseOwner(const char* owner) override { return Services::ReleaseOwner(owner); } - - RE::TESForm* GetOrCreateByOwnerKeyEx(const char* owner, const char* key, const uint32_t formType, - uint32_t* localId, bool* existed) override { - return Services::GetOrCreateByOwnerKeyEx(owner, key, formType, localId, existed); - } }; extern "C" __declspec(dllexport) void* GetDPFAPI() { From 7034b6bc7e8fe1aeacc481351ea0bb93cf94b48b Mon Sep 17 00:00:00 2001 From: Viny <77700518+vinymayan@users.noreply.github.com> Date: Thu, 18 Jun 2026 07:38:17 -0300 Subject: [PATCH 5/8] clean up logs --- SKSE_Plugin/src/Services.cpp | 6 +++--- SKSE_Plugin/src/persistence.cpp | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/SKSE_Plugin/src/Services.cpp b/SKSE_Plugin/src/Services.cpp index a6943a0..6807230 100644 --- a/SKSE_Plugin/src/Services.cpp +++ b/SKSE_Plugin/src/Services.cpp @@ -1,4 +1,4 @@ -#include "Services.h" +#include "Services.h" #include "form.h" #include "model.h" @@ -9,7 +9,7 @@ RE::TESForm* Services::Create(RE::TESForm* baseItem) { auto* newForm = AddForm(baseItem); if (newForm) { - logger::info("new form id", newForm->GetFormID()); + logger::debug("new form id", newForm->GetFormID()); } return newForm; } catch (const std::exception&) { @@ -22,7 +22,7 @@ RE::TESForm* Services::CreateByType(const uint32_t formType) { try { auto* newForm = AddFormByType(static_cast(formType)); if (newForm) { - logger::info("new form id", newForm->GetFormID()); + logger::debug("new form id", newForm->GetFormID()); } return newForm; } catch (const std::exception&) { diff --git a/SKSE_Plugin/src/persistence.cpp b/SKSE_Plugin/src/persistence.cpp index bd553dd..02a00b1 100644 --- a/SKSE_Plugin/src/persistence.cpp +++ b/SKSE_Plugin/src/persistence.cpp @@ -1,4 +1,4 @@ -#include "persistence.h" +#include "persistence.h" #include "model.h" #include "rapidjson/document.h" #include "rapidjson/stringbuffer.h" @@ -188,7 +188,7 @@ bool SaveGlobalRegistry() { } file << BuildRegistryJson(); - logger::info("Saved DPF global registry to {}", path); + logger::debug("Saved DPF global registry to {}", path); return true; } catch (const std::exception& e) { logger::error("Error saving DPF global registry: {}", e.what()); From a1ade668522073e9ff2a2ead70731a96a9d63b77 Mon Sep 17 00:00:00 2001 From: Viny <77700518+vinymayan@users.noreply.github.com> Date: Thu, 18 Jun 2026 08:54:27 -0300 Subject: [PATCH 6/8] back to use bin --- SKSE_Plugin/include/persistence.h | 2 - SKSE_Plugin/src/persistence.cpp | 200 +++++++++++++----------------- 2 files changed, 88 insertions(+), 114 deletions(-) diff --git a/SKSE_Plugin/include/persistence.h b/SKSE_Plugin/include/persistence.h index 6857f86..9272d56 100644 --- a/SKSE_Plugin/include/persistence.h +++ b/SKSE_Plugin/include/persistence.h @@ -4,8 +4,6 @@ namespace fs = std::filesystem; -std::string BuildRegistryJson(); -bool RestoreRegistryJson(const std::string& json); bool LoadGlobalRegistry(); bool SaveGlobalRegistry(); std::string GetGlobalRegistryPath(); diff --git a/SKSE_Plugin/src/persistence.cpp b/SKSE_Plugin/src/persistence.cpp index 02a00b1..7ae8979 100644 --- a/SKSE_Plugin/src/persistence.cpp +++ b/SKSE_Plugin/src/persistence.cpp @@ -1,28 +1,41 @@ -#include "persistence.h" +#include "persistence.h" #include "model.h" -#include "rapidjson/document.h" -#include "rapidjson/stringbuffer.h" -#include "rapidjson/writer.h" +#include "serializer.h" #include -#include +#include #include -#include #include #include namespace { + constexpr uint32_t kRegistryMagic = 0x44504643; // DPFC constexpr uint32_t kRegistrySchemaVersion = 1; + constexpr uint32_t kNoOwnerIndex = std::numeric_limits::max(); std::mutex registryMutex; - uint32_t ParseUInt(const rapidjson::Value& value, const uint32_t fallback = 0) { - return value.IsUint() ? value.GetUint() : fallback; + template + void WriteString(Serializer* serializer, const std::string& value) { + serializer->WriteString(value.c_str()); } - template - rapidjson::Value StringValue(Allocator& allocator, const std::string& text) { - rapidjson::Value value; - value.SetString(text.c_str(), static_cast(text.size()), allocator); - return value; + template + std::string ReadStoredString(Serializer* serializer) { + const auto value = serializer->ReadString(); + std::string result = value ? value : ""; + delete[] value; + return result; + } + + std::vector GetSortedSlots() { + std::vector sorted; + sorted.reserve(dynamicSlots.size()); + for (const auto& [localId, slot] : dynamicSlots) { + sorted.push_back(slot); + } + std::ranges::sort(sorted, [](const auto& lhs, const auto& rhs) { + return lhs.localId < rhs.localId; + }); + return sorted; } void RecalculateNextDynamicLocalId() { @@ -37,120 +50,85 @@ namespace { nextDynamicLocalId = 0x00ffffff; } -} -std::string GetGlobalRegistryPath() { - return "Data/SKSE/Plugins/DPF_Cache.json"; -} + template + void StoreRegistry(Serializer* serializer) { + const auto sorted = GetSortedSlots(); -std::string BuildRegistryJson() { - rapidjson::Document document(rapidjson::kObjectType); - auto& allocator = document.GetAllocator(); + std::unordered_map ownerIndexes; + std::vector owners; + for (const auto& slot : sorted) { + if (slot.owner.empty() || ownerIndexes.contains(slot.owner)) { + continue; + } - std::vector sorted; - sorted.reserve(dynamicSlots.size()); - for (const auto& [localId, slot] : dynamicSlots) { - sorted.push_back(slot); - } - std::ranges::sort(sorted, [](const auto& lhs, const auto& rhs) { - return lhs.localId < rhs.localId; - }); - - std::unordered_map ownerIndexes; - rapidjson::Value owners(rapidjson::kArrayType); - for (const auto& slotData : sorted) { - if (slotData.owner.empty() || ownerIndexes.contains(slotData.owner)) { - continue; + const auto index = static_cast(owners.size()); + ownerIndexes[slot.owner] = index; + owners.push_back(slot.owner); } - const auto index = static_cast(ownerIndexes.size()); - ownerIndexes[slotData.owner] = index; - auto ownerValue = StringValue(allocator, slotData.owner); - owners.PushBack(ownerValue, allocator); - } + serializer->template Write(kRegistryMagic); + serializer->template Write(kRegistrySchemaVersion); + WriteString(serializer, dynamicPluginName); - rapidjson::Value slots(rapidjson::kArrayType); - for (const auto& slotData : sorted) { - rapidjson::Value slot(rapidjson::kArrayType); - slot.PushBack(slotData.localId, allocator); - slot.PushBack(static_cast(slotData.formType), allocator); - if (slotData.owner.empty()) { - rapidjson::Value ownerIndex; - ownerIndex.SetNull(); - slot.PushBack(ownerIndex, allocator); - } else { - slot.PushBack(ownerIndexes[slotData.owner], allocator); + serializer->template Write(static_cast(owners.size())); + for (const auto& owner : owners) { + WriteString(serializer, owner); } - auto keyValue = StringValue(allocator, slotData.key); - slot.PushBack(keyValue, allocator); - slots.PushBack(slot, allocator); - } - - document.AddMember("v", kRegistrySchemaVersion, allocator); - document.AddMember("p", StringValue(allocator, dynamicPluginName), allocator); - document.AddMember("o", owners, allocator); - document.AddMember("s", slots, allocator); - - rapidjson::StringBuffer buffer; - rapidjson::Writer writer(buffer); - document.Accept(writer); - return { buffer.GetString(), buffer.GetSize() }; -} -bool RestoreRegistryJson(const std::string& json) { - if (json.empty()) { - return false; + serializer->template Write(static_cast(sorted.size())); + for (const auto& slot : sorted) { + serializer->template Write(slot.localId); + serializer->template Write(static_cast(slot.formType)); + serializer->template Write(slot.owner.empty() ? kNoOwnerIndex : ownerIndexes[slot.owner]); + WriteString(serializer, slot.key); + } } - rapidjson::Document document; - document.Parse(json.c_str(), json.size()); - if (document.HasParseError() || !document.IsObject()) { - logger::error("DPF global registry is not valid JSON"); - return false; - } + template + bool RestoreRegistry(Serializer* serializer) { + const auto magic = serializer->template Read(); + const auto version = serializer->template Read(); + if (magic != kRegistryMagic || version != kRegistrySchemaVersion) { + logger::warn("DPF registry binary cache is missing or unsupported; starting with empty schema {} registry", kRegistrySchemaVersion); + ResetDynamicState(); + return SaveGlobalRegistry(); + } - if (!document.HasMember("v") || !document["v"].IsUint() || document["v"].GetUint() != kRegistrySchemaVersion) { - logger::warn("DPF global registry schema is missing or unsupported; starting with empty schema {} registry", kRegistrySchemaVersion); ResetDynamicState(); - return SaveGlobalRegistry(); - } - - ResetDynamicState(); + dynamicPluginName = ReadStoredString(serializer); - if (document.HasMember("p") && document["p"].IsString()) { - dynamicPluginName = document["p"].GetString(); - } - - std::vector owners; - if (document.HasMember("o") && document["o"].IsArray()) { - for (const auto& owner : document["o"].GetArray()) { - owners.emplace_back(owner.IsString() ? owner.GetString() : ""); + std::vector owners; + const auto ownerCount = serializer->template Read(); + owners.reserve(ownerCount); + for (uint32_t i = 0; i < ownerCount; ++i) { + owners.push_back(ReadStoredString(serializer)); } - } - if (document.HasMember("s") && document["s"].IsArray()) { - for (const auto& slot : document["s"].GetArray()) { - if (!slot.IsArray() || slot.Size() < 4) { - continue; - } - - const auto localId = ParseUInt(slot[0]); - const auto formType = static_cast(ParseUInt(slot[1])); + const auto slotCount = serializer->template Read(); + for (uint32_t i = 0; i < slotCount; ++i) { + const auto localId = serializer->template Read(); + const auto formType = static_cast(serializer->template Read()); + const auto ownerIndex = serializer->template Read(); std::string owner; - if (slot[2].IsUint() && slot[2].GetUint() < owners.size()) { - owner = owners[slot[2].GetUint()]; + if (ownerIndex != kNoOwnerIndex && ownerIndex < owners.size()) { + owner = owners[ownerIndex]; } - const std::string key = slot[3].IsString() ? slot[3].GetString() : ""; + const auto key = ReadStoredString(serializer); + if (!RegisterDynamicSlot(localId, formType, owner, key)) { logger::warn("Ignoring invalid DPF registry slot {:06X}", localId); } } - } - RecalculateNextDynamicLocalId(); + RecalculateNextDynamicLocalId(); + logger::info("Loaded DPF global registry with {} slots", dynamicSlots.size()); + return true; + } +} - logger::info("Loaded DPF global registry with {} slots", dynamicSlots.size()); - return true; +std::string GetGlobalRegistryPath() { + return "Data/SKSE/Plugins/DPF_Cache.bin"; } bool LoadGlobalRegistry() { @@ -162,15 +140,13 @@ bool LoadGlobalRegistry() { return SaveGlobalRegistry(); } - std::ifstream file(path, std::ios::in | std::ios::binary); - if (!file.is_open()) { + FileReader fileReader(path, std::ios::in | std::ios::binary); + if (!fileReader.IsOpen()) { logger::error("Could not open DPF global registry at {}", path); return false; } - std::stringstream buffer; - buffer << file.rdbuf(); - return RestoreRegistryJson(buffer.str()); + return RestoreRegistry(&fileReader); } bool SaveGlobalRegistry() { @@ -181,13 +157,13 @@ bool SaveGlobalRegistry() { fs::create_directories(registryPath.parent_path()); } - std::ofstream file(registryPath, std::ios::out | std::ios::binary | std::ios::trunc); - if (!file.is_open()) { + FileWriter fileWriter(path, std::ios::out | std::ios::binary | std::ios::trunc); + if (!fileWriter.IsOpen()) { logger::error("Could not write DPF global registry at {}", path); return false; } - file << BuildRegistryJson(); + StoreRegistry(&fileWriter); logger::debug("Saved DPF global registry to {}", path); return true; } catch (const std::exception& e) { From 9fcadd8001cbcaf9b096490352df03b5ebb22a5a Mon Sep 17 00:00:00 2001 From: Viny <77700518+vinymayan@users.noreply.github.com> Date: Thu, 18 Jun 2026 09:05:16 -0300 Subject: [PATCH 7/8] compact bin info --- SKSE_Plugin/src/persistence.cpp | 87 ++++++++++++++++++++++----------- 1 file changed, 58 insertions(+), 29 deletions(-) diff --git a/SKSE_Plugin/src/persistence.cpp b/SKSE_Plugin/src/persistence.cpp index 7ae8979..f964659 100644 --- a/SKSE_Plugin/src/persistence.cpp +++ b/SKSE_Plugin/src/persistence.cpp @@ -2,27 +2,56 @@ #include "model.h" #include "serializer.h" #include -#include #include #include #include namespace { constexpr uint32_t kRegistryMagic = 0x44504643; // DPFC - constexpr uint32_t kRegistrySchemaVersion = 1; - constexpr uint32_t kNoOwnerIndex = std::numeric_limits::max(); + constexpr uint8_t kRegistrySchemaVersion = 2; std::mutex registryMutex; template - void WriteString(Serializer* serializer, const std::string& value) { - serializer->WriteString(value.c_str()); + void WriteVarUInt(Serializer* serializer, uint32_t value) { + while (value >= 0x80) { + serializer->template Write(static_cast(value | 0x80)); + value >>= 7; + } + serializer->template Write(static_cast(value)); + } + + template + uint32_t ReadVarUInt(Serializer* serializer) { + uint32_t result = 0; + uint32_t shift = 0; + while (shift < 32) { + const auto byte = serializer->template Read(); + result |= static_cast(byte & 0x7f) << shift; + if ((byte & 0x80) == 0) { + return result; + } + shift += 7; + } + logger::error("DPF registry cache has an invalid varint"); + return 0; } template - std::string ReadStoredString(Serializer* serializer) { - const auto value = serializer->ReadString(); - std::string result = value ? value : ""; - delete[] value; + void WriteCompactString(Serializer* serializer, const std::string& value) { + WriteVarUInt(serializer, static_cast(value.size())); + for (const auto ch : value) { + serializer->template Write(ch); + } + } + + template + std::string ReadCompactString(Serializer* serializer) { + const auto size = ReadVarUInt(serializer); + std::string result; + result.resize(size); + for (uint32_t i = 0; i < size; ++i) { + result[i] = serializer->template Read(); + } return result; } @@ -68,27 +97,27 @@ namespace { } serializer->template Write(kRegistryMagic); - serializer->template Write(kRegistrySchemaVersion); - WriteString(serializer, dynamicPluginName); + serializer->template Write(kRegistrySchemaVersion); + WriteCompactString(serializer, dynamicPluginName); - serializer->template Write(static_cast(owners.size())); + WriteVarUInt(serializer, static_cast(owners.size())); for (const auto& owner : owners) { - WriteString(serializer, owner); + WriteCompactString(serializer, owner); } - serializer->template Write(static_cast(sorted.size())); + WriteVarUInt(serializer, static_cast(sorted.size())); for (const auto& slot : sorted) { - serializer->template Write(slot.localId); - serializer->template Write(static_cast(slot.formType)); - serializer->template Write(slot.owner.empty() ? kNoOwnerIndex : ownerIndexes[slot.owner]); - WriteString(serializer, slot.key); + WriteVarUInt(serializer, slot.localId); + WriteVarUInt(serializer, static_cast(slot.formType)); + WriteVarUInt(serializer, slot.owner.empty() ? 0 : ownerIndexes[slot.owner] + 1); + WriteCompactString(serializer, slot.key); } } template bool RestoreRegistry(Serializer* serializer) { const auto magic = serializer->template Read(); - const auto version = serializer->template Read(); + const auto version = serializer->template Read(); if (magic != kRegistryMagic || version != kRegistrySchemaVersion) { logger::warn("DPF registry binary cache is missing or unsupported; starting with empty schema {} registry", kRegistrySchemaVersion); ResetDynamicState(); @@ -96,25 +125,25 @@ namespace { } ResetDynamicState(); - dynamicPluginName = ReadStoredString(serializer); + dynamicPluginName = ReadCompactString(serializer); std::vector owners; - const auto ownerCount = serializer->template Read(); + const auto ownerCount = ReadVarUInt(serializer); owners.reserve(ownerCount); for (uint32_t i = 0; i < ownerCount; ++i) { - owners.push_back(ReadStoredString(serializer)); + owners.push_back(ReadCompactString(serializer)); } - const auto slotCount = serializer->template Read(); + const auto slotCount = ReadVarUInt(serializer); for (uint32_t i = 0; i < slotCount; ++i) { - const auto localId = serializer->template Read(); - const auto formType = static_cast(serializer->template Read()); - const auto ownerIndex = serializer->template Read(); + const auto localId = ReadVarUInt(serializer); + const auto formType = static_cast(ReadVarUInt(serializer)); + const auto ownerIndex = ReadVarUInt(serializer); std::string owner; - if (ownerIndex != kNoOwnerIndex && ownerIndex < owners.size()) { - owner = owners[ownerIndex]; + if (ownerIndex > 0 && ownerIndex - 1 < owners.size()) { + owner = owners[ownerIndex - 1]; } - const auto key = ReadStoredString(serializer); + const auto key = ReadCompactString(serializer); if (!RegisterDynamicSlot(localId, formType, owner, key)) { logger::warn("Ignoring invalid DPF registry slot {:06X}", localId); From 2545b8aee9216922d72baa9e65eddc9a7bcd83d8 Mon Sep 17 00:00:00 2001 From: Viny <77700518+vinymayan@users.noreply.github.com> Date: Thu, 18 Jun 2026 15:58:42 -0300 Subject: [PATCH 8/8] fix forms now showing on datahandle eu esqueciiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii --- SKSE_Plugin/src/form.cpp | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/SKSE_Plugin/src/form.cpp b/SKSE_Plugin/src/form.cpp index c66f265..5736ae7 100644 --- a/SKSE_Plugin/src/form.cpp +++ b/SKSE_Plugin/src/form.cpp @@ -142,6 +142,31 @@ void copyAppearence(RE::TESForm* source, RE::TESForm* target) { namespace { + bool EnsureFormInDataHandler(RE::TESForm* form) { + if (!form) { + return false; + } + + auto* dataHandler = RE::TESDataHandler::GetSingleton(); + if (!dataHandler) { + logger::warn("Could not register form {:08X} in TESDataHandler: data handler is unavailable", form->GetFormID()); + return false; + } + + auto& forms = dataHandler->GetFormArray(form->GetFormType()); + if (std::ranges::find(forms, form) != forms.end()) { + return true; + } + + if (!dataHandler->AddFormToDataHandler(form)) { + logger::warn("Could not register form {:08X} in TESDataHandler", form->GetFormID()); + return false; + } + + logger::debug("Registered form {:08X} in TESDataHandler", form->GetFormID()); + return true; + } + RE::TESForm* CreateFormInstance(const RE::FormType formType, const RE::FormID formId) { const auto factory = RE::IFormFactory::GetFormFactoryByType(formType); if (!factory) { @@ -198,6 +223,7 @@ namespace { static_cast(existingForm->GetFormType()), static_cast(formType)); return nullptr; } + EnsureFormInDataHandler(existingForm); if (!RegisterDynamicSlot(localId, formType, std::move(owner), std::move(key))) { return nullptr; } @@ -218,6 +244,7 @@ namespace { if (!newForm) { return nullptr; } + EnsureFormInDataHandler(newForm); auto* record = FormRecord::CreateNew(newForm, formType, formId); record->baseForm = baseItem;