SKSE Plugin Development/Why Game Updates Break Everything
Skyrim updates, and a myriad of SKSE-based mods stop working. This happens after almost every game patch, it has happened for years, and it will happen again. This page explains why that is baked into how these mods work, what a regular user can do about it, and what a mod author actually has to do to fix it. The short version: mods like these change the game's code at exact locations, and a game update moves everything around.
How SKSE mods work[edit | edit source]
A code mod such as an SKSE plugin does not edit the game's files. It runs inside the live game and edits the game's own logic and data directly, at specific locations in memory. Those locations are decided when Bethesda builds SkyrimSE.exe.
When Bethesda ships an update — even a small one — they rebuild the game. It's kind of like editing a large word document. Add a few lines here and there and the layout completely changes. The same thing happens with code. Adding or changing a few lines of code shifts everything after it: a bit of code that used to sit at one location is now a little further along, the data next to it moves, and so on.
The game plays the same, but is just laid out a little differently. This is a normal, unavoidable side effect of rebuilding any large program; it is not something Bethesda does to mods, it just happens.
SKSE mods work by modifying things at specific locations. Kind of like saying
"Hey, I want to change the sixth word on page seven to Jarl."
If the location is correct, the mod might change the sentence
"In the village lived a farmer."
to
"In the village lived a Jarl."
but once everything moves, applying the same edit might give you something like
"In Jarl village lived a farmer."
The game will try to read this, say "I don't know anything about a Jarl village!" and crash. To prevent this from happening, your mods tell SKSE which versions they support, and refuse to work with anything else.
So every SKSE mod is, in some sense, tied to the exact game build it was made for. If their internal map is incorrect, it won't be able to run. Some mods have come up with clever ways around this, which will be discussed a bit later.
What this means for you, the user[edit | edit source]
If you are here because your game just broke, the important things to understand:
- Nothing you did is wrong, and nothing on disk is broken. The mod files are fine, but describe a version of the game you are no longer running.
- Editing files will not fix it. These mods do not work by files-on-disk (like Papyrus scripts, meshes, textures). An SKSE mod's problem is that it is looking at the wrong locations, which no file edit changes.
- The usual fix is to wait, then update everything together. SKSE itself, the Address Library data file (explained below), and each plugin all have to be brought in line with the new game build. That takes the various authors some time after an update. How long exactly varies, depending on the size of the changes and how active the mod author is. Yelling at them to update is not going to speed things up.
- SKSE must match the game build exactly. The SKSE runtime is compiled for one game version. If the game updated but SKSE did not, SKSE will refuse to run rather than risk a crash. You need the SKSE build made for your current game version. Check SKSE's official page for updates.
- Rolling the game back is the other option. Because the trouble is the game changing, staying on (or returning to) the previous build keeps your existing mods valid. How to hold or revert a game version is launcher-specific and outside this page's scope, but it is why many users disable automatic updates. The downside to this is that some mod authors decide not to back-port new features or bug fixes to old game versions, and you're left with a functional, but outdated mod, until you update.
To summarize: the game version and your SKSE mods are a matched set. Break the match — by updating one side and not the other — and things fall over until the set is matched again.
[edit | edit source]
If every plugin memorized the game's memory layout directly, every plugin would break on every patch and each author would have to re-find every location by hand. The community solved this with the Address Library: a separate data file that lists, for one specific game build, where each of thousands of numbered items lives.
The idea is a layer of indirection. Instead of a plugin hard-coding "this function is at location X", it says "give me item #1234", and looks that number up in the Address Library file for whatever build is running. When the game updates, only the Address Library file needs to be regenerated for the new build; a plugin that asks by number keeps working, because #1234 still means the same function — it just resolves to a new location.
A tiny illustration (numbers invented for the example):
| Item | Address Library for build A | Address Library for build B |
|---|---|---|
#1234 |
0x1000 |
0x2000
|
#1235 |
0x1800 |
0x2600
|
The plugin only ever mentions #1234. The move from 0x1000 to
0x2000 is absorbed by swapping the Address Library file.
SKSE itself does not use this file to find its own code. SKSE64 hard-codes its locations for the build it was compiled against, so it needs no lookup.
Note: SKSE does check whether the file is present. It builds the expected filename and tries to open it
(CheckAddressLibrary),
but never reads a single location out of it. That check exists purely so SKSE can warn a plugin that declared it relies on the library when the library is missing or out of date
(the check is run only for such plugins;
the "update your Address Library" prompt).
What this means for you, a mod author[edit | edit source]
A plugin author's resilience to game updates is decided by how the plugin resolves the game's locations. There are two broad strategies.
- Hard-coded offsets. The plugin (or the SDK/library under it) bakes in the actual locations for one build. SKSE64 itself works this way: it defines a table of known game builds (the
RUNTIME_VERSION_*table, with one marked "current") and is compiled against a single one. When the game moves, a hard-coded plugin must be recompiled and re-released for the new build.
- Address Library IDs. The plugin asks by number and lets a support library (CommonLib, below) resolve the number against the Address Library file at start-up. The difference from hard-coding is what the binary stores: it holds an ID that doesn't change, not an address. The real location is computed at load time — the module's actual base plus the offset that ID maps to in the Address Library file for the build that is running (
address() = base() + offset(), the offset looked up at run time).
So when the game moves and someone regenerates that file, the same plugin binary keeps working with no recompile. This is fairly resilient, so much of the modern ecosystem is built on it.
A plugin also declares its stance to SKSE. A plugin that relies on the post-AE Address Library sets a flag in its exported version block (kVersionIndependent_AddressLibraryPostAE, in SKSEPluginVersionData); that is the flag whose presence makes SKSE run the library-presence check above. (For how that version block is read at load, see plugin-abi.)
The important caveat: "no recompile" is only true while the Address Library file format stays the same. If the format of the file itself changes, like in August of 2026, a recompile might be necessary because the support library could no longer read the file.
Two ways CommonLib handles a game update[edit | edit source]
"CommonLib" is not one single definitive library, but a family of community-maintained forks that plugins build on to resolve game locations by Address Library ID. Among the forks in active use, two resolution models are relevant for what a game update does, because they take different stances on exactly the version-coupling problem this page is about. Each is cited from its own repository.
| Fork / lineage | How it resolves the running game |
|---|---|
| CommonLibSSE (PowerOfThree) | Address Library IDs resolved at start-up, but a given build is compiled for one edition. A build-time switch (SKYRIM_SUPPORT_AE, default off) picks the SE file format vs the AE one, and the module carries a version but no runtime kind (Module has no Runtime enum). One DLL is either SE or AE.
|
| CommonLibSSE-NG (alandtse) | Same ID resolution, but one binary serves multiple runtimes and decides at load. It detects the runtime kind (enum class Runtime { Unknown, AE, SE, VR }) and branches per detected runtime (Relocate(se, ae, vr)). With only one runtime enabled the branches collapse to compile-time constants (SKYRIM_ADDR).
|
Special cases: the 1.7.99 update (August 2026)[edit | edit source]
The Address Library file begins with a small header whose first field is a format number. The support library reads that number first and refuses the file if it is not a format it understands. For example, CommonLibSSE fails with "Unsupported address library format" on anything unexpected (ID.h). Historically this number was 1 for the Skyrim SE (1.5.x) files, distributed as version-*.bin, and 2 for the Anniversary Edition (1.6.x) files, distributed as versionlib-*.bin (the filename and format are chosen accordingly).
With the 1.7.99 build the format number changed to 5, due to a re-licensing of Commonlib from MIT to GPL-3.0.
Community context: Maintainers on the xSE Reverse Engineering Discord have described the format change as intended to force plugins to be recompiled against an up-to-date CommonLib, and have connected it to those forks' move to the GPL-3.0 licence as a way to discourage low-effort or AI-generated uploads.
- A support library built before format 5 existed reads the new file, sees an unrecognised number, and rejects it. Every plugin on top of it can't resolve anything, and breaks. Even the resilient, ask-by-number plugins are affected, because the file they depend on can no longer be read.
- The fix is on the support library side: it has to learn the new format. Once it does, plugins rebuilt against the updated library work again.
This is visible directly in the two most active CommonLib forks. CommonLibSSE-NG added format 5 as a first-class case — its parser now switches on the header number and handles 1, 2 and 5 (IDDB.cpp), with the new format named SSEv5 (IDDB.h;
support shipped in release 6.4.0, changelog). At the time of writing, at the commit pinned here, CommonLibSSE (Po3) still accepts only formats 1 and 2 (ID.h), so plugins built on it needed to wait for its update. By the time you're reading this, the update will likely have come through, so please be sure to check compatibility yourself.