diff --git a/DIST/Dynamic Persistent Forms/Source/Scripts/DynamicPersistentForms.psc b/DIST/Dynamic Persistent Forms/Source/Scripts/DynamicPersistentForms.psc index 77dfc76..13bf644 100644 --- a/DIST/Dynamic Persistent Forms/Source/Scripts/DynamicPersistentForms.psc +++ b/DIST/Dynamic Persistent Forms/Source/Scripts/DynamicPersistentForms.psc @@ -1,7 +1,21 @@ -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 +; 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 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 @@ -44,4 +58,7 @@ 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..13bf644 100644 --- a/PapyrusInterface/Source/Scripts/DynamicPersistentForms.psc +++ b/PapyrusInterface/Source/Scripts/DynamicPersistentForms.psc @@ -1,7 +1,21 @@ -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 +; 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 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 @@ -44,4 +58,7 @@ 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..ff88e3d 100644 --- a/SKSE_Plugin/include/Services.h +++ b/SKSE_Plugin/include/Services.h @@ -1,33 +1,30 @@ #pragma once + +#include "../public/DPFAPI.h" #include -namespace DPF { - // Defina um ID único para sua interface - constexpr auto InterfaceName = "DynamicPersistentForms"; - constexpr uint32_t InterfaceVersion = 1; +namespace Services { + inline std::mutex serviceMutex; - class IDynamicPersistentForms { - public: - virtual ~IDynamicPersistentForms() = default; - virtual uint32_t GetVersion() const = 0; + RE::TESForm* Create(RE::TESForm* baseItem); + RE::TESForm* CreateByType(uint32_t formType); - 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; - }; -} + RE::TESForm* GetOrCreateByLocalId(uint32_t localId, uint32_t formType); -namespace Services { - // Mutex global para thread safety - inline std::mutex serviceMutex; + RE::TESForm* GetOrCreateByFormId(RE::FormID formId, uint32_t formType); - RE::TESForm* Create(RE::TESForm* baseItem); + 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); + + 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); -} \ No newline at end of file +} diff --git a/SKSE_Plugin/include/form.h b/SKSE_Plugin/include/form.h index c3f18e6..2da7945 100644 --- a/SKSE_Plugin/include/form.h +++ b/SKSE_Plugin/include/form.h @@ -18,5 +18,18 @@ void copyComponent(RE::TESForm* from, RE::TESForm* to) { void copyAppearence(RE::TESForm* source, RE::TESForm* target); +RE::TESForm* AddForm(RE::TESForm* baseItem); -RE::TESForm* AddForm(RE::TESForm* baseItem); \ No newline at end of file +RE::TESForm* AddFormByType(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, uint32_t* localId, bool* existed); + +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/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..47396cd 100644 --- a/SKSE_Plugin/include/model.h +++ b/SKSE_Plugin/include/model.h @@ -1,17 +1,32 @@ -#pragma once -#include -#include +#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; 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; +inline std::unordered_map dynamicSlots; +inline std::unordered_map dynamicOwnerKeyIndex; void AddFormData(FormRecord* item); @@ -21,12 +36,40 @@ void EachFormData(const std::function& iteration); void EachFormRef(const std::function& iteration); +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); + +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); -void incrementLastFormID(); +uint32_t ReleaseDynamicSlotsByOwner(std::string_view owner); +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..c43377b 100644 --- a/SKSE_Plugin/include/papyrus.h +++ b/SKSE_Plugin/include/papyrus.h @@ -1,8 +1,22 @@ -#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* 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); @@ -57,4 +71,6 @@ 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..9272d56 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" +#pragma once #include -#include +#include namespace fs = std::filesystem; -std::string GetCacheFilePath(); -void LoadCache(); -void SaveCache(); +bool LoadGlobalRegistry(); +bool SaveGlobalRegistry(); +std::string GetGlobalRegistryPath(); 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..5da3011 --- /dev/null +++ b/SKSE_Plugin/public/DPFAPI.h @@ -0,0 +1,54 @@ +#pragma once + +#include "RE/Skyrim.h" +#include +#include + +namespace DPF { + constexpr auto InterfaceName = "DynamicPersistentForms"; + constexpr uint32_t InterfaceVersion = 1; + + class IDynamicPersistentForms { + public: + virtual ~IDynamicPersistentForms() = default; + + virtual uint32_t GetVersion() const = 0; + + 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; + + virtual RE::TESForm* CreateByType(uint32_t formType) = 0; + + virtual RE::TESForm* GetOrCreateByLocalId(uint32_t localId, uint32_t formType) = 0; + virtual RE::TESForm* GetOrCreateByFormId(RE::FormID formId, 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; + }; + + 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..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&) { @@ -17,6 +17,80 @@ 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::debug("new form id", newForm->GetFormID()); + } + return newForm; + } catch (const std::exception&) { + return nullptr; + } +} + +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::GetOrCreateByOwnerKey(const char* owner, const char* key, const uint32_t formType, + uint32_t* localId, bool* existed) { + std::lock_guard lock(serviceMutex); + try { + return GetOrCreateFormByOwnerKey(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); + 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 { @@ -31,7 +105,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) { @@ -75,4 +148,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..5736ae7 100644 --- a/SKSE_Plugin/src/form.cpp +++ b/SKSE_Plugin/src/form.cpp @@ -1,5 +1,6 @@ #include "form.h" #include "model.h" +#include "persistence.h" void copyFormArmorModel(RE::TESForm* source, RE::TESForm* target) { const auto* sourceModelBipedForm = source->As(); @@ -140,38 +141,225 @@ void copyAppearence(RE::TESForm* source, RE::TESForm* target) { } -RE::TESForm* AddForm(RE::TESForm* baseItem) { - if (!espFound) { - return nullptr; - } +namespace { + bool EnsureFormInDataHandler(RE::TESForm* form) { + if (!form) { + return false; + } - 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()); - item->baseForm = baseItem; - applyPattern(item); + 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) { + 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; + } - if (result) { + result->SetFormID(formId, false); return result; } - 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(); - slot->baseForm = baseItem; - applyPattern(slot); - AddFormData(slot); - return newForm; -} \ No newline at end of file + FormRecord* FindCreatedRecord(const RE::FormID formId) { + FormRecord* found = nullptr; + EachFormData([&](FormRecord* item) { + if (item && item->formId == formId) { + found = item; + return false; + } + return true; + }); + return found; + } + + 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; + } + } + + 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; + } + EnsureFormInDataHandler(existingForm); + 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; + } + EnsureFormInDataHandler(newForm); + + 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 (!baseItem) { + logger::error("Create(baseItem) was called with a null baseItem. Use CreateByType for empty forms."); + return nullptr; + } + + const auto formId = AllocateDynamicFormID(); + if (formId == 0) { + return nullptr; + } + + 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 formId = AllocateDynamicFormID(); + if (formId == 0) { + return nullptr; + } + + return CreateRegisteredForm(ToDynamicLocalID(formId), formType, nullptr); +} + +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; + } + return GetOrCreateFormByLocalId(ToDynamicLocalID(formId), formType); +} + +RE::TESForm* GetOrCreateFormByOwnerKey(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()) { + logger::error("Owner and key are required for owned DPF slots"); + return nullptr; + } + + if (const auto existingLocalId = FindDynamicSlotByOwnerKey(owner, key)) { + if (localIdOut) { + *localIdOut = existingLocalId.value(); + } + if (existedOut) { + *existedOut = true; + } + return CreateRegisteredForm(existingLocalId.value(), formType, nullptr, owner, key); + } + + const auto formId = AllocateDynamicFormID(); + if (formId == 0) { + return nullptr; + } + + 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) { + 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; +} + +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 b5a4c8e..80b7d87 100644 --- a/SKSE_Plugin/src/model.cpp +++ b/SKSE_Plugin/src/model.cpp @@ -1,16 +1,55 @@ -#include "model.h" +#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; } + if (item->order == 0) { + item->order = nextRecordOrder++; + } else if (item->order >= nextRecordOrder) { + nextRecordOrder = item->order + 1; + } 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) { 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 +69,238 @@ 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; +} + +uint32_t ToDynamicLocalID(const RE::FormID formId) { + return formId & 0x00ffffff; +} + +RE::FormID MakeDynamicFormID(const uint32_t localId) { + return (dynamicModId << 24) | (localId & 0x00ffffff); +} + +void ReserveDynamicLocalID(const uint32_t localId) { + if (localId == 0) { + return; + } + const auto normalized = localId & 0x00ffffff; + reservedDynamicLocalIds.insert(normalized); + if (normalized >= nextDynamicLocalId) { + nextDynamicLocalId = normalized + 1; + } } -void UpdateId() { - EachFormData([&](FormRecord* item) { - if (item->formId > lastFormId) { - lastFormId = item->formId; +void ReserveDynamicFormID(const RE::FormID formId) { + if (IsDynamicFormID(formId)) { + ReserveDynamicLocalID(ToDynamicLocalID(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; - }); - incrementLastFormID(); + } + + 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; } -void ResetId() { - lastFormId = firstFormId; +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); + nextDynamicLocalId = std::min(nextDynamicLocalId, 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"); + 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 registered = dynamicSlots.contains(localId); + const bool existsInGame = RE::TESForm::LookupByID(fullId) != nullptr; + if (!reserved && !registered && !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 = std::max(nextDynamicLocalId, firstDynamicLocalId); + dynamicPluginName = "Dynamic Persistent Forms.esp"; - const auto id = dataHandler->LookupFormID(0x800, "Dynamic Persistent Forms.esp"); + const auto modIndex = dataHandler->GetLoadedModIndex(dynamicPluginName); + if (!modIndex.has_value()) { + logger::error("Dynamic Persistent Forms.esp was not found"); + return; + } - if (id != 0) { - espFound = true; + espFound = true; + dynamicModId = modIndex.value(); + logger::info("Dynamic Persistent Forms.esp found at mod index {:02X}", dynamicModId); +} + +void ResetDynamicState() { + reservedDynamicLocalIds.clear(); + dynamicSlots.clear(); + dynamicOwnerKeyIndex.clear(); + nextDynamicLocalId = firstDynamicLocalId; + nextRecordOrder = 1; +} + +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(); + } + + 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 0a00771..d0b683f 100644 --- a/SKSE_Plugin/src/papyrus.cpp +++ b/SKSE_Plugin/src/papyrus.cpp @@ -9,6 +9,37 @@ 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); +} + +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* 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, nullptr, nullptr); +} + +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); } @@ -298,6 +329,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("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); @@ -328,4 +366,6 @@ 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..f964659 100644 --- a/SKSE_Plugin/src/persistence.cpp +++ b/SKSE_Plugin/src/persistence.cpp @@ -1,140 +1,211 @@ #include "persistence.h" -#include "form_record_serializer.h" +#include "model.h" #include "serializer.h" +#include +#include +#include +#include + +namespace { + constexpr uint32_t kRegistryMagic = 0x44504643; // DPFC + constexpr uint8_t kRegistrySchemaVersion = 2; + std::mutex registryMutex; + + template + 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)); + } -std::mutex callbackMutext; + 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; + } -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!"); + template + void WriteCompactString(Serializer* serializer, const std::string& value) { + WriteVarUInt(serializer, static_cast(value.size())); + for (const auto ch : value) { + serializer->template Write(ch); } - else { - const auto serializer = new SaveDataSerializer(a_intfc); - StoreAllFormRecords(serializer); + } + + 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(); } - SaveCache(); + return result; } - catch (const std::exception&) { - logger::error("error saving"); + + 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 LoadCallback(SKSE::SerializationInterface* a_intfc) { - std::lock_guard lock(callbackMutext); - try { - logger::info("LOAD CAllBACK"); - - uint32_t type; - uint32_t version; - uint32_t length; - bool refreshGame = false; - - while (a_intfc->GetNextRecordInfo(type, version, length)) { - switch (type) { - case 'ARR_': { - const auto serializer = new SaveDataSerializer(a_intfc); - refreshGame = RestoreAllFormRecords(serializer); - delete serializer; + void RecalculateNextDynamicLocalId() { + uint32_t localId = firstDynamicLocalId; + while (localId < 0x00ffffff) { + if (!reservedDynamicLocalIds.contains(localId) && !dynamicSlots.contains(localId)) { + nextDynamicLocalId = localId; + return; } - break; - default: - logger::error("Unrecognized signature type!"); - break; + ++localId; + } + + nextDynamicLocalId = 0x00ffffff; + } + + template + void StoreRegistry(Serializer* serializer) { + const auto sorted = GetSortedSlots(); + + std::unordered_map ownerIndexes; + std::vector owners; + for (const auto& slot : sorted) { + if (slot.owner.empty() || ownerIndexes.contains(slot.owner)) { + continue; } + + const auto index = static_cast(owners.size()); + ownerIndexes[slot.owner] = index; + owners.push_back(slot.owner); } - if (refreshGame) { - SaveCache(); - UpdateId(); - RE::PlayerCharacter::GetSingleton()->KillImmediate(); + + serializer->template Write(kRegistryMagic); + serializer->template Write(kRegistrySchemaVersion); + WriteCompactString(serializer, dynamicPluginName); + + WriteVarUInt(serializer, static_cast(owners.size())); + for (const auto& owner : owners) { + WriteCompactString(serializer, owner); } - logger::info("CAllBACK LOADED"); - } - catch (const std::exception&) { - logger::error("error loading"); + WriteVarUInt(serializer, static_cast(sorted.size())); + for (const auto& slot : sorted) { + WriteVarUInt(serializer, slot.localId); + WriteVarUInt(serializer, static_cast(slot.formType)); + WriteVarUInt(serializer, slot.owner.empty() ? 0 : ownerIndexes[slot.owner] + 1); + WriteCompactString(serializer, slot.key); + } } -} -std::string GetCacheFilePath() -{ - const std::string settingsFile = "Data/SKSE/Plugins/DPF_Settings.json"; - const std::string defaultPath = "Data/SKSE/Plugins/[NoDelete] DPF/DynamicPersistentFormsCache.bin"; + 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 (!fs::exists(settingsFile)) { - return defaultPath; - } + ResetDynamicState(); + dynamicPluginName = ReadCompactString(serializer); - FILE* fp = fopen(settingsFile.c_str(), "rb"); - if (!fp) return defaultPath; + std::vector owners; + const auto ownerCount = ReadVarUInt(serializer); + owners.reserve(ownerCount); + for (uint32_t i = 0; i < ownerCount; ++i) { + owners.push_back(ReadCompactString(serializer)); + } - char readBuffer[65536]; - rapidjson::FileReadStream is(fp, readBuffer, sizeof(readBuffer)); - rapidjson::Document d; - d.ParseStream(is); - fclose(fp); + const auto slotCount = ReadVarUInt(serializer); + for (uint32_t i = 0; i < slotCount; ++i) { + const auto localId = ReadVarUInt(serializer); + const auto formType = static_cast(ReadVarUInt(serializer)); + const auto ownerIndex = ReadVarUInt(serializer); + std::string owner; + if (ownerIndex > 0 && ownerIndex - 1 < owners.size()) { + owner = owners[ownerIndex - 1]; + } + const auto key = ReadCompactString(serializer); - if (!d.HasParseError() && d.HasMember("SavePath") && d["SavePath"].IsString()) { - std::string customDir = d["SavePath"].GetString(); - return customDir + "/DynamicPersistentFormsCache.bin"; - } + if (!RegisterDynamicSlot(localId, formType, owner, key)) { + logger::warn("Ignoring invalid DPF registry slot {:06X}", localId); + } + } - return defaultPath; + RecalculateNextDynamicLocalId(); + logger::info("Loaded DPF global registry with {} slots", dynamicSlots.size()); + return true; + } } -void LoadCache() { - logger::info("LOAD CACHE"); - - std::string path = GetCacheFilePath(); +std::string GetGlobalRegistryPath() { + return "Data/SKSE/Plugins/DPF_Cache.bin"; +} +bool LoadGlobalRegistry() { + std::lock_guard lock(registryMutex); + const auto path = GetGlobalRegistryPath(); if (!fs::exists(path)) { - logger::info("Cache file not found. Creating initial file at: {}", path); - SaveCache(); - return; + logger::info("DPF global registry not found at {}; starting empty", path); + ResetDynamicState(); + return SaveGlobalRegistry(); } - const auto fileReader = new FileReader(path, std::ios::in | std::ios::binary); - if (!fileReader->IsOpen()) { - logger::error("File not found"); - return; + FileReader fileReader(path, std::ios::in | std::ios::binary); + if (!fileReader.IsOpen()) { + logger::error("Could not open DPF global registry at {}", path); + return false; } - RestoreAllFormRecords(fileReader); - - UpdateId(); - delete fileReader; - - logger::info("Property data has been loaded from file successfully."); + return RestoreRegistry(&fileReader); } -void SaveCache() { - logger::info("save cache"); - - std::string fullPath = GetCacheFilePath(); - fs::path p(fullPath); - +bool SaveGlobalRegistry() { + const auto path = GetGlobalRegistryPath(); try { - if (p.has_parent_path() && !fs::exists(p.parent_path())) { - fs::create_directories(p.parent_path()); + const fs::path registryPath(path); + if (registryPath.has_parent_path()) { + fs::create_directories(registryPath.parent_path()); } - const auto fileWriter = new FileWriter(fullPath, - std::ios::out | std::ios::binary | std::ios::trunc); - - if (!fileWriter->IsOpen()) { - logger::error("Failed to open file for writing: {}", fullPath); - delete fileWriter; - return; + 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; } - StoreAllFormRecords(fileWriter); - delete fileWriter; - - logger::info("Property data has been written successfully to: {}", fullPath); - } - catch (const std::exception& e) { - logger::error("Error during save: {}", e.what()); + StoreRegistry(&fileWriter); + logger::debug("Saved DPF global registry to {}", path); + return true; + } catch (const std::exception& e) { + logger::error("Error saving DPF global registry: {}", e.what()); + return false; } -} \ No newline at end of file +} + +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 660a839..d724fe6 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,35 @@ 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); + } + + 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* 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 { + 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() { @@ -38,46 +66,32 @@ 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"); + LoadGlobalRegistry(); + logger::info("DynamicPersistentForms 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 +} + +