Skip to content

Update for Skyrim 1.7.99 (CommonLibSSE-NG 6.6.3) - #4

Open
rrasto wants to merge 1 commit into
ersh1:mainfrom
rrasto:skyrim-1.7.99
Open

Update for Skyrim 1.7.99 (CommonLibSSE-NG 6.6.3)#4
rrasto wants to merge 1 commit into
ersh1:mainfrom
rrasto:skyrim-1.7.99

Conversation

@rrasto

@rrasto rrasto commented Aug 24, 2026

Copy link
Copy Markdown

I have also written to you on Nexus, in case GitHub notifications do not reach you. That message
asks whether you would be willing to let me publish this as an unofficial update if you no longer
have time for the mod.

Precision does not load on Skyrim 1.7.99. This updates it to build and run there, while remaining
correct on 1.6.x and 1.5.97. Tested in game on 1.7.99.

The bulk of the diff is not really about 1.7.99. Building against a current CommonLibSSE-NG accounts
for most of it, and only one change is a genuine runtime fix.

Build

  • cxx_std_20 to cxx_std_23, required by CommonLibSSE-NG 6.x.
  • Removed /await from the MSVC options. It is the pre-C++20 experimental coroutine switch, and with
    it MSVC's <coroutine> disables itself (STL4039) and provides only
    std::experimental::coroutine_handle, so CommonLibSSE-NG 6.x does not compile. C++23 has
    coroutines without any flag.
  • #include <coroutine> at the top of PCH.h. CommonLibSSE-NG 6.x uses coroutines in
    IVirtualMachine, and relies on its own precompiled header having pulled the header in; this
    project has its own PCH.
  • Added directxmath, directxtk, nlohmann-json and toml11 to vcpkg.json. These are not new
    requirements of Precision; they are CommonLibSSE-NG 6.6.3's own declared dependencies, and the
    library is consumed here through add_subdirectory.

Duplicate Havok declarations removed

src/Havok/Havok.h declared eleven Havok types that CommonLibSSE-NG has since added, which now
collide. I compared all eleven member by member before deleting any of them, and the byte layouts
are identical in every case
, so the removal is safe.

hkpConvexVerticesShape::BuildConfig is the one exception: CommonLibSSE-NG defines the class but not
that nested struct, so it stays here as hkpConvexVerticesShapeBuildConfig for
thkpConvexVerticesShape_ctor.

CommonLibSSE-NG API changes

what change
hkContactPointMaterial::flags now REX::EnumSet, so |= becomes .set() and & becomes .any()
collisionFilterInfo now RE::CFilter rather than a raw uint32_t; GetCollisionFilterInfo takes CFilter&
BSGeometry::properties[States::kEffect] moved into GetGeometryRuntimeData().shaderProperty
ahkpWorld::unk430 named userData
NiCloningProcess::unk68 named scale
SKSE::WinAPI now REX::W32
PluginVersionData UsesAddressLibrary(true) to UsesAddressLibrary(), HasNoStructUse(true) to UsesNoStructs()
RE::Offset::BSSoundHandle::SetPosition the RE::Offset namespace is gone in 6.x; its only call site was already commented out, so the relocation was removed

LOADED_REF_DATA needs a word of its own. CommonLibSSE-NG now covers 0x30 to 0x68 with the
handleList arena, so the field the old headers called unk58 has no name any more. I verified
against CommonLibSSE-NG v3.7.0, where that range was six raw uint64 values with data3D at 0x68,
that the byte layout is unchanged, and read the same pointer by offset with a comment saying so.

The one real fix: Actor::IsDead() on 1.7.99

CanAddToWorld passed for every actor, yet almost none were tracked. The gate was bIsActorDead.

Actor::IsDead() is a virtual call at a fixed index, and on 1.7.99 it returns true for living
NPCs. Measured over 29 actors in Whiterun:

IsDead() GetLifeState() count
true kAlive 26
false kAlive 2
true kDead 1

The two it got right were a cow and a guard; every actor it got wrong is essential or protected,
including the player. That points at the a_notEssential parameter rather than a shifted vtable, and
a shifted vtable is independently unlikely since other mods hook Actor virtuals at higher indices
on 1.7.99 without trouble.

ActorState::GetLifeState() reads a bitfield member with no vtable involved, so bIsActorDead now
derives from that. This looks like a CommonLibSSE-NG issue rather than a Precision one, and I
intend to report it upstream; if it is fixed there, this change can be reverted.

Two hook offsets recomputed for 1.7.99

_TESObjectCELL_PlaceParticleEffect and _CdPointCollectorCast sit at offsets inside their parent
functions, and 1.7.99 moved both. The functions were not changed; the executable was rebuilt and the
compiler laid the same code out differently.

  • hook4 + 0xB39 becomes +0xB49
  • hook3 + 0x294 becomes +0x2AC

They are selected at runtime by AEOffsetSince1799() in PCH.h, in the spirit of
REL::RelocateMemberIfNewer, so one binary stays correct on 1.6.x and 1.7.99 alike.

On how they were found, since this is the part of the diff that deserves the most scepticism: both
were recovered from the executables by two independent methods, one resolving the call target through
the Address Library and one matching a byte fingerprint with the rel32 operand wildcarded. The
tooling was validated by reproducing 32 of the 33 offsets already in this file on 1.6.1170 before it
was pointed at 1.7.99. They are also consistent with in game behaviour, since hits register once
rather than twice and impact effects appear in the right place, but that is corroboration and not
proof.

Deliberately not changed

ContactPointCallback opens with what looks like a precedence slip:

if (flags & kIsDisabled || !flags & kIsNew)

! binds tighter than &, so the second term is (!flags) & kIsNew, and with kIsNew == 1 that is
simply "no flags set" rather than the apparently intended !(flags & kIsNew). I wrote the intended
version, and it silently broke weapon versus wall recoil by dropping continuing contacts. It is
preserved exactly as it was, with a comment recording why.

Licensing

One consequence worth flagging, because it is easy to miss and it is not caused by this pull request.

The README pins CommonLibSSE-NG v3.4.0, which was MIT. The 6.x series is GPL-3.0-or-later, with
two additional permissions in EXCEPTIONS.md. Neither applies here: the Modding Exception covers
linking against Skyrim itself and explicitly requires that all other code used in conjunction with the
library obey the GPL, and the second covers combining with other modding libraries under incompatible
licences.

In practice:

  • The sources can stay MIT. MIT is GPL compatible, so nothing in this repository needs
    relicensing and I have not touched LICENSE.
  • A binary linking 6.x has to be conveyed under GPL-3.0-or-later, with corresponding source
    available. That applies to any release built from this branch, by either of us.

There is no way around it while supporting 1.7.99: the game changed the Address Library format, and
every CommonLibSSE-NG version that can read the new format is GPL licensed.

Testing

Built and played on Skyrim 1.7.99 with SKSE 2.3.0. Verified in game: skeleton colliders on all living
actors within the active distance, attack collisions and hit detection, weapon versus wall recoil, hit
material resolution, and the recoil material list loading from Precision.esp.

Not tested on 1.6.x or 1.5.97, on VR, or in first person.

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 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant