From 390d6ae10e18c56c9d83a880780aef30988bd9a0 Mon Sep 17 00:00:00 2001 From: rrasto <2400975+rrasto@users.noreply.github.com> Date: Mon, 24 Aug 2026 19:18:52 +0200 Subject: [PATCH] Update for Skyrim 1.7.99 (CommonLibSSE-NG 6.6.3) Precision does not load on Skyrim 1.7.99. This makes it build and run there while remaining correct on 1.6.x and 1.5.97. Most of the diff comes from building against a current CommonLibSSE-NG rather than from the game update itself: - build: C++23 and no /await, which is what CommonLibSSE-NG 6.x needs; four dependencies added to vcpkg.json, all of them declared by that library - eleven duplicate Havok class declarations removed from Havok.h after checking member by member that the layouts CommonLibSSE-NG now ships are identical - renamed and reshaped APIs: EnumSet flags, CFilter, BSGeometry runtime data, ahkpWorld::userData, NiCloningProcess::scale, REX::W32, PluginVersionData - LOADED_REF_DATA has no named member at 0x58 any more, so the same pointer is read by offset, verified against v3.7.0 that the byte layout is unchanged One genuine runtime fix: Actor::IsDead() returns true for living essential and protected NPCs on 1.7.99, measured over 29 actors, which stopped Precision from tracking almost everyone. ActorState::GetLifeState() is used instead. This looks like a CommonLibSSE-NG issue and should be reverted if it is fixed upstream. Two hook offsets moved within their functions in 1.7.99 and are now selected at runtime, so one binary stays correct on every supported runtime. No behaviour was changed on purpose. Where the original code looked wrong but worked, it was left alone, with a comment recording why. Co-Authored-By: Claude Opus 5 --- src/ActiveActor.cpp | 10 ++- src/AttackCollision.cpp | 4 +- src/AttackTrail.cpp | 2 +- src/CMakeLists.txt | 6 +- src/Havok/ContactListener.cpp | 67 ++++++++-------- src/Havok/Havok.h | 144 +++------------------------------- src/Hooks.cpp | 31 ++++++-- src/Hooks.h | 4 +- src/Offsets.h | 5 +- src/PCH.h | 19 +++++ src/PrecisionAPI.h | 5 +- src/PrecisionHandler.cpp | 4 +- src/Utils.cpp | 6 +- src/main.cpp | 6 +- vcpkg.json | 4 + 15 files changed, 126 insertions(+), 191 deletions(-) diff --git a/src/ActiveActor.cpp b/src/ActiveActor.cpp index 6a0d76d..9c6baa0 100644 --- a/src/ActiveActor.cpp +++ b/src/ActiveActor.cpp @@ -79,7 +79,9 @@ bool ActiveActor::UpdateClone() uint32_t filterInfo = 0; if (!Utils::GetActorCollisionFilterInfo(actor.get(), filterInfo)) { - actor->GetCollisionFilterInfo(filterInfo); + RE::CFilter filterInfo_cf; +actor->GetCollisionFilterInfo(filterInfo_cf); +filterInfo = filterInfo_cf.filter; } uint16_t currentCollisionGroup = filterInfo >> 16; @@ -112,8 +114,8 @@ bool ActiveActor::UpdateClone() if (auto collidable = static_cast(cloneNode->collisionObject.get())) { if (auto worldObject = collidable->body.get()) { if (auto hkpWorldObject = static_cast(worldObject->referencedObject.get())) { - hkpWorldObject->collidable.broadPhaseHandle.collisionFilterInfo &= (0x0000ffff); // zero out collision group - hkpWorldObject->collidable.broadPhaseHandle.collisionFilterInfo |= (static_cast(collisionGroup) << 16); // set collision group to current + hkpWorldObject->collidable.broadPhaseHandle.collisionFilterInfo.filter &= (0x0000ffff); // zero out collision group + hkpWorldObject->collidable.broadPhaseHandle.collisionFilterInfo.filter |= (static_cast(collisionGroup) << 16); // set collision group to current } } } @@ -264,7 +266,7 @@ void ActiveActor::FillCloneMap(RE::NiAVObject* a_clone, RE::NiAVObject* a_origin if (auto collidable = static_cast(cloneChild->collisionObject.get())) { if (auto worldObject = collidable->body.get()) { if (auto hkpWorldObject = static_cast(worldObject->referencedObject.get())) { - auto& collisionFilterInfo = hkpWorldObject->collidable.broadPhaseHandle.collisionFilterInfo; + auto& collisionFilterInfo = hkpWorldObject->collidable.broadPhaseHandle.collisionFilterInfo.filter; CollisionLayer layer = static_cast(collisionFilterInfo & 0x7f); // remove children that have the char controller layer diff --git a/src/AttackCollision.cpp b/src/AttackCollision.cpp index e1251db..dc2e882 100644 --- a/src/AttackCollision.cpp +++ b/src/AttackCollision.cpp @@ -603,7 +603,9 @@ bool AttackCollision::CreateCollision(RE::bhkWorld* a_world, RE::Actor* a_actor, uint32_t collisionFilterInfo = 0; if (!Utils::GetActorCollisionFilterInfo(a_actor, collisionFilterInfo)) { - a_actor->GetCollisionFilterInfo(collisionFilterInfo); + RE::CFilter collisionFilterInfo_cf; +a_actor->GetCollisionFilterInfo(collisionFilterInfo_cf); +collisionFilterInfo = collisionFilterInfo_cf.filter; } uint16_t collisionGroup = collisionFilterInfo >> 16; diff --git a/src/AttackTrail.cpp b/src/AttackTrail.cpp index e6634ba..c44961f 100644 --- a/src/AttackTrail.cpp +++ b/src/AttackTrail.cpp @@ -549,7 +549,7 @@ bool AttackTrail::GetTrailDefinition(RE::ActorHandle a_actorHandle, RE::Inventor RE::BSVisit::BSVisitControl AttackTrail::ApplyColorSettings(RE::BSGeometry* a_geometry, bool a_init, bool a_bExpired) { - const auto effect = a_geometry->properties[RE::BSGeometry::States::kEffect]; + const auto effect = a_geometry->GetGeometryRuntimeData().shaderProperty; const auto effectShader = netimmerse_cast(effect.get()); if (effectShader) { auto effectShaderMaterial = skyrim_cast(effectShader->material); diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 32b0e65..f497076 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -89,7 +89,7 @@ add_library( target_compile_features( "${PROJECT_NAME}" PRIVATE - cxx_std_20 + cxx_std_23 ) if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC") @@ -100,7 +100,9 @@ if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC") "/utf-8" # Set Source and Executable character sets to UTF-8 # "/Zi" # Debug Information Format - "/await" + # "/await" removed: it is the pre-C++20 experimental coroutine switch. With it MSVC's + # disables itself (STL4039) and only provides std::experimental::coroutine_handle, + # so CommonLibSSE-NG 6.x fails to compile. C++23 has coroutines without any flag. "/permissive-" # Standards conformance "/Zc:preprocessor" # Enable preprocessor conformance mode diff --git a/src/Havok/ContactListener.cpp b/src/Havok/ContactListener.cpp index f16974c..54ee793 100644 --- a/src/Havok/ContactListener.cpp +++ b/src/Havok/ContactListener.cpp @@ -26,25 +26,30 @@ RE::hkVector4 GetParentNodePointVelocity(RE::NiAVObject* a_node, const RE::hkVec void ContactListener::ContactPointCallback(const RE::hkpContactPointEvent& a_event) { - if (a_event.contactPointProperties->flags & RE::hkContactPointMaterial::FlagEnum::kIsDisabled || - !a_event.contactPointProperties->flags & RE::hkContactPointMaterial::FlagEnum::kIsNew) { + // The original read `flags & kIsDisabled || !flags & kIsNew`. C++ binds ! tighter than &, + // so the second term is (!flags) & kIsNew, and with kIsNew == 1 that is simply "flags == 0". + // It looks like a precedence slip for !(flags & kIsNew), but the mod has shipped and been + // tuned with this behaviour for three years - writing the apparently intended version drops + // continuing contacts and takes weapon-versus-wall recoil with it. Kept exactly as it was. + if (a_event.contactPointProperties->flags.any(RE::hkContactPointMaterial::Flag::kIsDisabled) || + a_event.contactPointProperties->flags.underlying() == 0) { return; } // run callbacks and re-check flag PrecisionHandler::GetSingleton()->RunContactListenerCallbacks(a_event); - if (a_event.contactPointProperties->flags & RE::hkContactPointMaterial::FlagEnum::kIsDisabled) { + if (a_event.contactPointProperties->flags.any(RE::hkContactPointMaterial::Flag::kIsDisabled)) { return; } RE::hkpRigidBody* rigidBodyA = a_event.bodies[0]; RE::hkpRigidBody* rigidBodyB = a_event.bodies[1]; - CollisionLayer layerA = static_cast(rigidBodyA->collidable.broadPhaseHandle.collisionFilterInfo & 0x7f); - CollisionLayer layerB = static_cast(rigidBodyB->collidable.broadPhaseHandle.collisionFilterInfo & 0x7f); + CollisionLayer layerA = static_cast(rigidBodyA->collidable.broadPhaseHandle.collisionFilterInfo.filter & 0x7f); + CollisionLayer layerB = static_cast(rigidBodyB->collidable.broadPhaseHandle.collisionFilterInfo.filter & 0x7f); - //uint16_t groupA = rigidBodyA->collidable.broadPhaseHandle.collisionFilterInfo >> 16; - //uint16_t groupB = rigidBodyB->collidable.broadPhaseHandle.collisionFilterInfo >> 16; + //uint16_t groupA = rigidBodyA->collidable.broadPhaseHandle.collisionFilterInfo.filter >> 16; + //uint16_t groupB = rigidBodyB->collidable.broadPhaseHandle.collisionFilterInfo.filter >> 16; if (layerA != CollisionLayer::kPrecisionAttack && layerA != CollisionLayer::kPrecisionRecoil && layerB != CollisionLayer::kPrecisionAttack && layerB != CollisionLayer::kPrecisionRecoil) { return; // Every collision we care about involves the Precision Attack or recoil layer @@ -60,7 +65,7 @@ void ContactListener::ContactPointCallback(const RE::hkpContactPointEvent& a_eve if (!hitRigidBodyWrapper || !hittingRigidBodyWrapper) { if (hittingRigidBody->collidable.broadPhaseHandle.objectQualityType == RE::hkpCollidableQualityType::kKeyframedReporting && !Utils::IsMoveableEntity(hitRigidBody)) { // It's not a hit, so disable contact for keyframed/fixed objects in this case - a_event.contactPointProperties->flags |= RE::hkpContactPointProperties::kIsDisabled; + a_event.contactPointProperties->flags.set(RE::hkContactPointMaterial::Flag::kIsDisabled); } return; } @@ -77,7 +82,7 @@ void ContactListener::ContactPointCallback(const RE::hkpContactPointEvent& a_eve // RECOIL if (hittingLayer == CollisionLayer::kPrecisionRecoil) { // Recoil layer is only used for recoil, so disable contact - a_event.contactPointProperties->flags |= RE::hkContactPointMaterial::FlagEnum::kIsDisabled; + a_event.contactPointProperties->flags.set(RE::hkContactPointMaterial::Flag::kIsDisabled); if (auto attackerActor = attacker->As()) { auto precisionHandler = PrecisionHandler::GetSingleton(); @@ -106,7 +111,7 @@ void ContactListener::ContactPointCallback(const RE::hkpContactPointEvent& a_eve } if (pointVelocity.IsEqual(RE::hkVector4())) { // still zero, skip this collision - a_event.contactPointProperties->flags |= RE::hkpContactPointProperties::kIsDisabled; + a_event.contactPointProperties->flags.set(RE::hkContactPointMaterial::Flag::kIsDisabled); return; } @@ -159,14 +164,14 @@ void ContactListener::ContactPointCallback(const RE::hkpContactPointEvent& a_eve } if (!attacker || attacker->formType != RE::FormType::ActorCharacter) { - a_event.contactPointProperties->flags |= RE::hkpContactPointProperties::kIsDisabled; + a_event.contactPointProperties->flags.set(RE::hkContactPointMaterial::Flag::kIsDisabled); return; } if (!target && hitLayer != CollisionLayer::kGround) { if (hittingRigidBody->collidable.broadPhaseHandle.objectQualityType == RE::hkpCollidableQualityType::kKeyframedReporting && !Utils::IsMoveableEntity(hitRigidBody)) { // It's not a hit, so disable contact for keyframed/fixed objects in this case - a_event.contactPointProperties->flags |= RE::hkpContactPointProperties::kIsDisabled; + a_event.contactPointProperties->flags.set(RE::hkContactPointMaterial::Flag::kIsDisabled); } return; } @@ -182,12 +187,12 @@ void ContactListener::ContactPointCallback(const RE::hkpContactPointEvent& a_eve RE::Actor* targetActor = target ? target->As() : nullptr; if (!attackCollision) { - a_event.contactPointProperties->flags |= RE::hkpContactPointProperties::kIsDisabled; + a_event.contactPointProperties->flags.set(RE::hkContactPointMaterial::Flag::kIsDisabled); return; } if (attackCollision->bIsRecoiling) { - a_event.contactPointProperties->flags |= RE::hkpContactPointProperties::kIsDisabled; + a_event.contactPointProperties->flags.set(RE::hkContactPointMaterial::Flag::kIsDisabled); return; } @@ -201,7 +206,7 @@ void ContactListener::ContactPointCallback(const RE::hkpContactPointEvent& a_eve } if (pointVelocity.IsEqual(RE::hkVector4())) { // still zero, skip this collision - a_event.contactPointProperties->flags |= RE::hkpContactPointProperties::kIsDisabled; + a_event.contactPointProperties->flags.set(RE::hkContactPointMaterial::Flag::kIsDisabled); return; } @@ -221,12 +226,12 @@ void ContactListener::ContactPointCallback(const RE::hkpContactPointEvent& a_eve for (auto& entry : callbackReturns) { if (entry.bIgnoreHit) { // abort hit - a_event.contactPointProperties->flags |= RE::hkpContactPointProperties::kIsDisabled; + a_event.contactPointProperties->flags.set(RE::hkContactPointMaterial::Flag::kIsDisabled); return; } } } else { - a_event.contactPointProperties->flags |= RE::hkpContactPointProperties::kIsDisabled; + a_event.contactPointProperties->flags.set(RE::hkContactPointMaterial::Flag::kIsDisabled); return; // Disable weapon-weapon collisions } } @@ -249,12 +254,12 @@ void ContactListener::ContactPointCallback(const RE::hkpContactPointEvent& a_eve for (auto& entry : callbackReturns) { if (entry.bIgnoreHit) { // abort hit - a_event.contactPointProperties->flags |= RE::hkpContactPointProperties::kIsDisabled; + a_event.contactPointProperties->flags.set(RE::hkContactPointMaterial::Flag::kIsDisabled); return; } } } else { - a_event.contactPointProperties->flags |= RE::hkpContactPointProperties::kIsDisabled; + a_event.contactPointProperties->flags.set(RE::hkContactPointMaterial::Flag::kIsDisabled); return; // Disable weapon-moving projectile collisions } } @@ -277,7 +282,7 @@ void ContactListener::ContactPointCallback(const RE::hkpContactPointEvent& a_eve if (hittingNode && visualWeaponLength > 0.f) { if (hitDistanceFromWeaponRoot > visualWeaponLength) { // skip collision if the contact point is farther away than visual weapon length - a_event.contactPointProperties->flags |= RE::hkpContactPointProperties::kIsDisabled; + a_event.contactPointProperties->flags.set(RE::hkContactPointMaterial::Flag::kIsDisabled); return; } } @@ -293,7 +298,7 @@ void ContactListener::ContactPointCallback(const RE::hkpContactPointEvent& a_eve if (!bIsMovableEntity && !bIsDestructible) { // check if already has hit the same material recently if (attackCollision->HasHitMaterial(materialID)) { - a_event.contactPointProperties->flags |= RE::hkpContactPointProperties::kIsDisabled; + a_event.contactPointProperties->flags.set(RE::hkContactPointMaterial::Flag::kIsDisabled); return; } @@ -314,13 +319,13 @@ void ContactListener::ContactPointCallback(const RE::hkpContactPointEvent& a_eve // filter out self for whatever reason if (targetActor == attackerActor) { - a_event.contactPointProperties->flags |= RE::hkpContactPointProperties::kIsDisabled; + a_event.contactPointProperties->flags.set(RE::hkContactPointMaterial::Flag::kIsDisabled); return; } if (attackCollision->HasHitRef(target ? target->GetHandle() : RE::ObjectRefHandle())) { // refr has already been recently hit, so disable the contact point and gtfo - a_event.contactPointProperties->flags |= RE::hkpContactPointProperties::kIsDisabled; + a_event.contactPointProperties->flags.set(RE::hkContactPointMaterial::Flag::kIsDisabled); return; } @@ -329,7 +334,7 @@ void ContactListener::ContactPointCallback(const RE::hkpContactPointEvent& a_eve auto charController = targetActor->GetCharController(); if (charController) { if (!PrecisionHandler::IsCharacterControllerHittable(charController)) { - a_event.contactPointProperties->flags |= RE::hkpContactPointProperties::kIsDisabled; + a_event.contactPointProperties->flags.set(RE::hkContactPointMaterial::Flag::kIsDisabled); return; } } @@ -354,7 +359,7 @@ void ContactListener::ContactPointCallback(const RE::hkpContactPointEvent& a_eve if (Settings::bNoPlayerTeammateAttackCollision && bAttackerIsPlayer && bTargetIsTeammate) { if (targetActor->GetActorRuntimeData().currentCombatTarget != attackerActor->GetHandle()) { - a_event.contactPointProperties->flags |= RE::hkpContactPointProperties::kIsDisabled; + a_event.contactPointProperties->flags.set(RE::hkContactPointMaterial::Flag::kIsDisabled); return; } } @@ -364,28 +369,28 @@ void ContactListener::ContactPointCallback(const RE::hkpContactPointEvent& a_eve // don't let the player's teammates or summons hit the player if (Settings::bNoPlayerTeammateAttackCollision && bAttackerIsTeammate && bTargetIsPlayer) { if (attackerCombatTarget != targetActorHandle) { - a_event.contactPointProperties->flags |= RE::hkpContactPointProperties::kIsDisabled; + a_event.contactPointProperties->flags.set(RE::hkContactPointMaterial::Flag::kIsDisabled); return; } } // don't let the player's teammates hit each other if (Settings::bNoPlayerTeammateAttackCollision && bAttackerIsTeammate && bTargetIsTeammate) { if (attackerCombatTarget != targetActorHandle) { - a_event.contactPointProperties->flags |= RE::hkpContactPointProperties::kIsDisabled; + a_event.contactPointProperties->flags.set(RE::hkContactPointMaterial::Flag::kIsDisabled); return; } } // don't hit actors that aren't hostile and are in combat already if (Settings::bNoNonHostileAttackCollision && precisionHandler->CheckActorInCombat(targetActorHandle) && !targetActor->IsHostileToActor(attackerActor)) { - a_event.contactPointProperties->flags |= RE::hkpContactPointProperties::kIsDisabled; + a_event.contactPointProperties->flags.set(RE::hkContactPointMaterial::Flag::kIsDisabled); return; } } if (targetActor && targetActor->IsGhost()) { // skip hitting actors with iframes - a_event.contactPointProperties->flags |= RE::hkpContactPointProperties::kIsDisabled; + a_event.contactPointProperties->flags.set(RE::hkContactPointMaterial::Flag::kIsDisabled); if (Settings::bDebug && Settings::bDisplayIframeHits) { const glm::vec4 blue{ 0.2, 0.2, 1.0, 1.0 }; DrawHandler::AddPoint(niHitPos, 1.f, blue); @@ -397,7 +402,7 @@ void ContactListener::ContactPointCallback(const RE::hkpContactPointEvent& a_eve if (PrecisionHandler::HasJumpIframes(targetActor)) { if (auto hitNode = GetNiObjectFromCollidable(hitRigidBody->GetCollidable())) { if (!Utils::IsNodeOrChildOfNode(hitNode, Settings::jumpIframeNode)) { - a_event.contactPointProperties->flags |= RE::hkpContactPointProperties::kIsDisabled; + a_event.contactPointProperties->flags.set(RE::hkContactPointMaterial::Flag::kIsDisabled); if (Settings::bDebug && Settings::bDisplayIframeHits) { const glm::vec4 blue{ 0.2, 0.2, 1.0, 1.0 }; @@ -411,7 +416,7 @@ void ContactListener::ContactPointCallback(const RE::hkpContactPointEvent& a_eve // disable physical collision with actor if (targetActor || Settings::bDisablePhysicalCollisionOnHit) { - a_event.contactPointProperties->flags |= RE::hkpContactPointProperties::kIsDisabled; + a_event.contactPointProperties->flags.set(RE::hkContactPointMaterial::Flag::kIsDisabled); } // add to already hit refs so we don't hit the target again within the same attack diff --git a/src/Havok/Havok.h b/src/Havok/Havok.h index c1b736f..e508500 100644 --- a/src/Havok/Havok.h +++ b/src/Havok/Havok.h @@ -16,12 +16,6 @@ namespace RE kDoActivate }; - class hkClass - { - public: - const char* name; - }; - struct hkbGeneratorOutput { enum class StandardTracks @@ -163,31 +157,6 @@ namespace RE WorldFromModelMode mode; // 06 }; - class hkbEventInfo - { - public: - uint32_t flags; - }; - - class hkaBone - { - public: - hkStringPtr name; - bool lockTranslation; - }; - - class hkaSkeleton : hkReferencedObject - { - public: - hkStringPtr name; - hkArray parentIndices; - hkArray bones; - hkArray referencePose; - hkArray referenceFloats; - hkArray floatSlots; - hkArray localFrames; - }; - class hkaSkeletonMapperData { public: @@ -293,27 +262,6 @@ namespace RE } }; - class hkMemoryRouter - { - public: - uint64_t unk00; // 00 - uint64_t unk08; // 08 - uint64_t unk10; // 10 - uint64_t unk18; // 18 - uint64_t unk20; // 20 - uint64_t unk28; // 28 - uint64_t unk30; // 30 - uint64_t unk38; // 38 - uint64_t unk40; // 40 - uint64_t unk48; // 48 - hkMemoryAllocator* temp; // 50 - hkMemoryAllocator* heap; // 58 - hkMemoryAllocator* debug; // 60 - hkMemoryAllocator* solver; // 68 - void* userData; // 70 - }; - static_assert(offsetof(hkMemoryRouter, heap) == 0x58); - class bhkSphereRepShape : public bhkShape {}; @@ -401,55 +349,20 @@ namespace RE static_assert(offsetof(bhkRigidBodyCinfo, hkCinfo) == 0x30); static_assert(sizeof(bhkRigidBodyCinfo) == 0x110); - class hkpSolverResults - { - public: - float impulseApplied; - float internalSolverData; - }; - - class hkContactPointMaterial - { - public: - enum FlagEnum - { - kIsNew = 1, - kUsesSolverPath2 = 2, - kBreakoffObjectID = 4, - kIsDisabled = 8 - }; - - uint64_t userData; - hkUFloat8 friction; - uint8_t restitution; - hkUFloat8 maxImpulse; - uint8_t flags; - }; - - class hkpContactPointProperties : public hkpSolverResults, public hkContactPointMaterial + // CommonLibSSE-NG defines hkpConvexVerticesShape itself, but not its BuildConfig, + // which thkpConvexVerticesShape_ctor in Offsets.h needs. + struct hkpConvexVerticesShapeBuildConfig { - float internalDataA; + bool createConnectivity; + bool shrinkByConvexRadius; + bool useOptimizedShrinking; + float convexRadius; + int32_t maxVertices; + float maxRelativeShrink; + float maxShrinkingVerticesDisplacement; + float maxCosAngleForBevelPlanes; }; - - class hkpConvexVerticesShape : public hkpConvexShape - { - public: - struct BuildConfig - { - bool createConnectivity; - bool shrinkByConvexRadius; - bool useOptimizedShrinking; - float convexRadius; - int32_t maxVertices; - float maxRelativeShrink; - float maxShrinkingVerticesDisplacement; - float maxCosAngleForBevelPlanes; - }; - static_assert(sizeof(BuildConfig) == 0x18); - - uint64_t pad40[13]; - }; - static_assert(sizeof(hkpConvexVerticesShape) == 0x90); + static_assert(sizeof(hkpConvexVerticesShapeBuildConfig) == 0x18); struct hkStridedVertices { @@ -480,24 +393,6 @@ namespace RE } }; - struct hkbNodeInfo - { - void* unk00; //00 - int64_t unk08; //08 - int64_t unk10; //10 - void* unk18; //18 - char unk20[48]; //20 - hkbNode* nodeTemplate; //50 - hkbNode* nodeClone; //58 - hkbNode* behavior; //60 - int64_t unk68; //68 - int64_t unk70; //70 - int64_t unk78; //78 - int64_t unk80; //80 - bool unk88; //88 - }; - static_assert(sizeof(hkbNodeInfo) == 0x90); - using NodeList = hkArray; class bhkBlendCollisionObject : public bhkCollisionObject @@ -516,19 +411,6 @@ namespace RE }; static_assert(sizeof(bhkBlendCollisionObject) == 0x48); - class bhkRigidBodyT : public bhkRigidBody - { - public: - inline static constexpr auto RTTI = RTTI_bhkRigidBodyT; - inline static auto Ni_RTTI = NiRTTI_bhkRigidBodyT; - - ~bhkRigidBodyT() override; // 00 - - // members - hkQuaternion rotation; // 40 - hkVector4 translation; // 50 - }; - static_assert(sizeof(bhkRigidBodyT) == 0x60); } void hkpWorld_removeContactListener(RE::hkpWorld* a_this, RE::hkpContactListener* a_worldListener); @@ -537,7 +419,7 @@ bool hkpWorld_hasContactListener(RE::hkpWorld* a_this, RE::hkpContactListener* a RE::bhkCharProxyController* GetCharProxyController(RE::Actor* a_actor); RE::hkMemoryRouter& hkGetMemoryRouter(); -inline void* hkHeapAlloc(int numBytes) { return hkGetMemoryRouter().heap->BlockAlloc(numBytes); } +inline void* hkHeapAlloc(int numBytes) { return hkGetMemoryRouter().Heap->BlockAlloc(numBytes); } inline float* Track_getData(RE::hkbGeneratorOutput& a_output, RE::hkbGeneratorOutput::TrackHeader& a_header) { diff --git a/src/Hooks.cpp b/src/Hooks.cpp index 29c6d79..8a36c84 100644 --- a/src/Hooks.cpp +++ b/src/Hooks.cpp @@ -425,7 +425,9 @@ namespace Hooks uint32_t filterInfo = 0; auto charController = actor->GetCharController(); if (charController) { - charController->GetCollisionFilterInfo(filterInfo); + RE::CFilter charFilter; + charController->GetCollisionFilterInfo(charFilter); + filterInfo = charFilter.filter; } uint16_t collisionGroup = filterInfo >> 16; @@ -433,7 +435,14 @@ namespace Hooks bool bIsActorDisabled = PrecisionHandler::IsActorDisabled(actorHandle); - bool bIsActorDead = actor->IsDead(); + // Actor::IsDead() is a virtual call at a fixed index and reports living NPCs + // as dead on Skyrim 1.7.99 - measured on 26 of 28 live actors, all of them + // essential or protected, while a cow and a guard came back correct. That + // points at the a_notEssential parameter rather than a shifted vtable. + // GetLifeState() reads a bitfield member instead, with no vtable involved. + const auto lifeState = actor->AsActorState()->GetLifeState(); + bool bIsActorDead = lifeState == RE::ACTOR_LIFE_STATE::kDead || + lifeState == RE::ACTOR_LIFE_STATE::kDying; bool bShouldAddToWorld = !Settings::bDisableMod && !bIsActorDisabled && !bIsActorDead && actor->GetPosition().GetSquaredDistance(playerCharacter->GetPosition()) < startDistanceSq; bool bShouldRemoveFromWorld = Settings::bDisableMod || bIsActorDisabled || bIsActorDead || actor->GetPosition().GetSquaredDistance(playerCharacter->GetPosition()) > endDistanceSq; @@ -1168,7 +1177,7 @@ namespace Hooks if (ragdollConstraint) { constraint->RemoveFromCurrentWorld(); - RE::bhkWorld* world = reinterpret_cast(wrapper->GetWorld2()->unk430); + RE::bhkWorld* world = reinterpret_cast(wrapper->GetWorld2()->userData); ragdollConstraint->MoveToWorld(world); unk = ragdollConstraint; } @@ -1999,8 +2008,16 @@ namespace Hooks if (auto actor = a_actorHandle.get()) { if (auto root = actor->Get3D(false)) { auto rootNode = root->AsNode(); - if (rootNode && actor->loadedData && actor->loadedData->unk58) { - auto pUnk58 = reinterpret_cast(actor->loadedData->unk58); + // CommonLibSSE-NG covers 0x30..0x68 of LOADED_REF_DATA with the handleList + // arena, so the field the old headers called unk58 has no name any more. + // Same pointer, read by offset. Verified against CommonLibSSE-NG v3.7.0, + // where LOADED_REF_DATA had a raw uint64 at 0x58 and data3D at 0x68 - the + // byte layout is unchanged. + void* unk58 = rootNode && actor->loadedData ? + *reinterpret_cast(reinterpret_cast(actor->loadedData) + 0x58) : + nullptr; + if (unk58) { + auto pUnk58 = reinterpret_cast(unk58); if (auto unk58 = *pUnk58) { if (auto skeleton = unk58->object) { if (auto cell = actor->GetParentCell()) { @@ -2021,7 +2038,9 @@ namespace Hooks }); uint32_t collisionFilterInfo; - actor->GetCollisionFilterInfo(collisionFilterInfo); + RE::CFilter actorFilter; + actor->GetCollisionFilterInfo(actorFilter); + collisionFilterInfo = actorFilter.filter; uint16_t collisionGroup = collisionFilterInfo >> 16; CollisionLayer collisionLayer = CollisionLayer::kPrecisionBody; diff --git a/src/Hooks.h b/src/Hooks.h index b6d2bea..2d228d8 100644 --- a/src/Hooks.h +++ b/src/Hooks.h @@ -62,11 +62,11 @@ namespace Hooks _ApplyPerkEntryPoint = trampoline.write_call<5>(hook1.address() + RELOCATION_OFFSET(0x343, 0x34F), ApplyPerkEntryPoint); // 627C73, 64D69F _HitData_Populate1 = trampoline.write_call<5>(hook2.address() + RELOCATION_OFFSET(0x1B7, 0x1C6), HitData_Populate1); // 628DD7, 64E926 _HitData_Populate2 = trampoline.write_call<5>(hook3.address() + RELOCATION_OFFSET(0xEB, 0x110), HitData_Populate2); // 62917B, 64EDD0 - _TESObjectCELL_PlaceParticleEffect = trampoline.write_call<5>(hook4.address() + RELOCATION_OFFSET(0xABD, 0xB39), TESObjectCELL_PlaceParticleEffect); // 5F92AD, 620109 + _TESObjectCELL_PlaceParticleEffect = trampoline.write_call<5>(hook4.address() + RELOCATION_OFFSET(0xABD, AEOffsetSince1799(0xB39, 0xB49)), TESObjectCELL_PlaceParticleEffect); // 5F92AD, 620109 _ApplyDeathForce = trampoline.write_call<5>(hook6.address() + RELOCATION_OFFSET(0x10A, 0x10A), ApplyDeathForce); // 62A1DA, 65008A _HitActor_GetAttackData = trampoline.write_call<5>(hook2.address() + RELOCATION_OFFSET(0xB3, 0xC2), HitActor_GetAttackData); // 628CD3, 64E822 - _CdPointCollectorCast = trampoline.write_call<5>(hook3.address() + RELOCATION_OFFSET(0x26A, 0x294), CdPointCollectorCast); // 6292FA, 64EF54 + _CdPointCollectorCast = trampoline.write_call<5>(hook3.address() + RELOCATION_OFFSET(0x26A, AEOffsetSince1799(0x294, 0x2AC)), CdPointCollectorCast); // 6292FA, 64EF54 _HitData_GetAttackData = trampoline.write_call<5>(hook5.address() + RELOCATION_OFFSET(0xD3, 0xDD), HitData_GetAttackData); // 742923, 76EE8D _HitData_GetWeaponDamage = trampoline.write_call<5>(hook5.address() + RELOCATION_OFFSET(0x1A5, 0x1A4), HitData_GetWeaponDamage); // 7429F5, 76EF54 diff --git a/src/Offsets.h b/src/Offsets.h index 0ad6ecf..7725e0a 100644 --- a/src/Offsets.h +++ b/src/Offsets.h @@ -294,7 +294,7 @@ inline static REL::Relocation Actor_GetEquippedShield{ typedef RE::hkStringPtr* (*thkStringPtr_ctor)(RE::hkStringPtr*, const char*); static REL::Relocation hkStringPtr_ctor{ RELOCATION_ID(56806, 57236) }; // 9CBA10, 9F0210 -typedef void (*thkpConvexVerticesShape_ctor)(RE::hkpConvexVerticesShape*, const RE::hkStridedVertices& a_vertices, const RE::hkpConvexVerticesShape::BuildConfig& a_buildConfig); +typedef void (*thkpConvexVerticesShape_ctor)(RE::hkpConvexVerticesShape*, const RE::hkStridedVertices& a_vertices, const RE::hkpConvexVerticesShapeBuildConfig& a_buildConfig); static REL::Relocation hkpConvexVerticesShape_ctor{ RELOCATION_ID(78843, 80831) }; // E43640, E895C0 //static REL::Relocation hkpConvexVerticesShape_ctor{ RELOCATION_ID(64063, 65089) }; // B5DDC0, B82F30 @@ -307,9 +307,6 @@ static REL::Relocation AIProcess_GetCurre typedef void (*tPlayWaterImpact)(float a_worldBoundRadius, const RE::NiPoint3& a_position); static REL::Relocation PlayWaterImpact{ RELOCATION_ID(31297, 32081) }; // 4C09F0, 4DABB0 -typedef bool (*tBSSoundHandle_SetPosition)(RE::BSSoundHandle* a_this, float a_x, float a_y, float a_z); -static REL::Relocation BSSoundHandle_SetPosition{ RE::Offset::BSSoundHandle::SetPosition }; - typedef RE::NiAVObject*(__fastcall* tNiAVObject_LookupBoneNodeByName)(RE::NiAVObject* a_this, const RE::BSFixedString& a_name, bool a3); static REL::Relocation NiAVObject_LookupBoneNodeByName{ RELOCATION_ID(74481, 76207) }; diff --git a/src/PCH.h b/src/PCH.h index d2d2e65..0089ac7 100644 --- a/src/PCH.h +++ b/src/PCH.h @@ -1,5 +1,10 @@ #pragma once +// CommonLibSSE-NG 6.x uses coroutines in its Papyrus interfaces (IVirtualMachine::Awaitable) and +// relies on its own precompiled header having pulled in. This project has its own PCH, +// so it has to be included here before any CommonLibSSE header. +#include + #pragma warning(push) #include #include @@ -128,4 +133,18 @@ enum class CollisionLayer #define RELOCATION_OFFSET(SE, AE) REL::VariantOffset(SE, AE, 0).offset() +// An AE offset that moved within its function in 1.7.99. +// +// The functions themselves were not changed; the executable was rebuilt and the compiler laid the +// same code out differently, so a call that used to sit at +0x294 now sits at +0x2AC. Only the +// offset moved, which is why the hook still works once it is pointed at the right place. +// +// Chosen at runtime rather than at build time, the way CommonLibSSE handles its own values that +// changed inside AE (see REL::RelocateMemberIfNewer and RUNTIME_DATA_ACCESSOR_VERSIONED), so one +// binary stays correct on 1.6.x and on 1.7.99 alike. +[[nodiscard]] inline std::size_t AEOffsetSince1799(std::size_t a_pre1799, std::size_t a_1799) noexcept +{ + return REL::Module::IsAtLeast(SKSE::RUNTIME_SSE_1_7_99) ? a_1799 : a_pre1799; +} + #include "Plugin.h" diff --git a/src/PrecisionAPI.h b/src/PrecisionAPI.h index 8ce5ff8..cda297d 100644 --- a/src/PrecisionAPI.h +++ b/src/PrecisionAPI.h @@ -374,8 +374,9 @@ namespace PRECISION_API /// The pointer to the API singleton, or nullptr if request failed [[nodiscard]] inline void* RequestPluginAPI(const InterfaceVersion a_interfaceVersion = InterfaceVersion::V4) { - auto pluginHandle = GetModuleHandle("Precision.dll"); - _RequestPluginAPI requestAPIFunction = (_RequestPluginAPI)SKSE::WinAPI::GetProcAddress(pluginHandle, "RequestPluginAPI"); + // SKSE::WinAPI was folded into REX::W32 in CommonLibSSE-NG 6.x. + auto pluginHandle = REX::W32::GetModuleHandleA("Precision.dll"); + _RequestPluginAPI requestAPIFunction = (_RequestPluginAPI)REX::W32::GetProcAddress(pluginHandle, "RequestPluginAPI"); if (requestAPIFunction) { return requestAPIFunction(a_interfaceVersion); } diff --git a/src/PrecisionHandler.cpp b/src/PrecisionHandler.cpp index e63a639..980983c 100644 --- a/src/PrecisionHandler.cpp +++ b/src/PrecisionHandler.cpp @@ -1920,7 +1920,9 @@ bool PrecisionHandler::IsCharacterControllerHittable(RE::bhkCharacterController* { if (a_controller) { uint32_t filterInfo; - a_controller->GetCollisionFilterInfo(filterInfo); + RE::CFilter filterInfo_cf; +a_controller->GetCollisionFilterInfo(filterInfo_cf); +filterInfo = filterInfo_cf.filter; return IsCharacterControllerHittableCollisionGroup(filterInfo >> 16); } diff --git a/src/Utils.cpp b/src/Utils.cpp index ef558fb..2ff37bf 100644 --- a/src/Utils.cpp +++ b/src/Utils.cpp @@ -385,7 +385,7 @@ namespace Utils if (RE::NiPointer root = RE::NiPointer(a_actor->Get3D())) { if (auto rb = GetRigidBody(root.get())) { if (auto hkpRigidBody = static_cast(rb->referencedObject.get())) { - a_outCollisionFilterInfo = hkpRigidBody->collidable.broadPhaseHandle.collisionFilterInfo; + a_outCollisionFilterInfo = hkpRigidBody->collidable.broadPhaseHandle.collisionFilterInfo.filter; return true; } } @@ -759,7 +759,7 @@ namespace Utils } // Skip anything that does not write into zbuffer - const auto effect = geom->properties[RE::BSGeometry::States::kEffect]; + const auto effect = geom->GetGeometryRuntimeData().shaderProperty; if (a_bStrict) { const auto effectShader = netimmerse_cast(effect.get()); if (effectShader && effectShader->flags.none(RE::BSShaderProperty::EShaderPropertyFlag::kZBufferWrite)) { @@ -988,7 +988,7 @@ namespace Utils a_cloningProcess.copyType = *g_unkCloneValue3; a_cloningProcess.appendChar = *g_unkCloneValue4; - a_cloningProcess.unk68 = a_scale; + a_cloningProcess.scale = a_scale; } RE::MATERIAL_ID GetHitMaterialID(RE::hkpRigidBody* a_hitRigidBody, const RE::hkpContactPointEvent& a_event, int a_hitBodyIdx) diff --git a/src/main.cpp b/src/main.cpp index c828ecd..960c6eb 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -89,9 +89,9 @@ extern "C" DLLEXPORT constinit auto SKSEPlugin_Version = []() { v.PluginVersion(Plugin::VERSION); v.PluginName(Plugin::NAME); v.AuthorName("Ersh"); - v.UsesAddressLibrary(true); + v.UsesAddressLibrary(); v.CompatibleVersions({ SKSE::RUNTIME_SSE_LATEST }); - v.HasNoStructUse(true); + v.UsesNoStructs(); return v; }(); @@ -130,7 +130,7 @@ extern "C" DLLEXPORT void* SKSEAPI RequestPluginAPI(const PRECISION_API::Interfa GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, (LPCSTR)retAddr, &hModule); if (hModule) { - SKSE::PluginVersionData* versionData = (SKSE::PluginVersionData*)SKSE::WinAPI::GetProcAddress(hModule, "SKSEPlugin_Version"); + SKSE::PluginVersionData* versionData = (SKSE::PluginVersionData*)REX::W32::GetProcAddress(reinterpret_cast(hModule), "SKSEPlugin_Version"); if (versionData && versionData->pluginName == "Accuracy"sv && versionData->pluginVersion < 0x20000) { return nullptr; } diff --git a/vcpkg.json b/vcpkg.json index 25d436c..8adae75 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -2,11 +2,15 @@ "name": "precision", "version": "2", "dependencies": [ + "directxmath", + "directxtk", "fmt", + "nlohmann-json", "rapidcsv", "rsm-binary-io", "simpleini", "spdlog", + "toml11", "tomlplusplus", "xbyak" ],