Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

40 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

StarfieldAnimationFramework-

Starfield Animation Framework (SAF) is a framework for playing custom animations in Starfield, designed as a modern counterpart to NAF (Native Animation Framework) that works with current versions of the game.

SAF was created for two main reasons:

  • the original CCF (Custom Command Framework) used by NAF is no longer maintained and no longer works correctly with the latest Starfield,
  • I wanted a tool that behaves like NAF but is built on the current ecosystem (SFSE + CommonLibSF) and on a new Modern Command Framework (MCF).

The framework is an SFSE plugin. It registers its own console command saf, which allows you to:

  • play animations on the player or on a chosen actor,
  • load animations from external files (e.g. GLTF, GLB),
  • control transition time, speed, playback mode, etc. (depending on the exact command and parameters).

SAF’s internal logic is modeled after NAF:

  • the animation engine works directly on actor skeletons,
  • data from animation files are mapped onto character bones and layered on top of the current state,
  • the framework tries to minimize interference with the rest of the game – once an animation finishes, the actor returns to normal behavior,
  • character skeleton same as NAF (HumanRace.json).

At the moment, due to changes between game versions and the lack of a stable way to access the “selected reference” from the console, the behavior is:

  • saf play <animation> – by default plays the animation on the player,
  • to play an animation on an NPC you must provide its FormID: saf play <animation> <NPC_FormID> (the FormID can be read from the console after clicking on the NPC).
  • 'saf stop' - Stops the animation from playing - for Player
  • 'saf stop <NPC_FormID>`- Stops the animation from playing - for NPC

Building (Linux → Windows cross-compile, real MSVC under Wine)

This fork adds an xmake build that cross-compiles the SFSE plugin from Linux to a Windows x64 DLL using the real Microsoft toolchaincl.exe, link.exe and rc.exe — run under Wine via msvc-wine. Earlier revisions used clang-cl + lld-link against an xwin SDK; that is gone.

1. Toolchain prerequisites

  • wine (64-bit) and xmake on PATH. Verified against wine-11.7 (Staging) and xmake v3.0.9.
  • An msvc-wine installation. On this machine msvc-wine itself lives in /mnt/sata/msvc-wine/ and the MSVC + Windows SDK tree it produced lives in /mnt/sata/msvc/, whose wrappers are /mnt/sata/msvc/bin/x64/{cl,link,lib,rc}. To reproduce it elsewhere:
git clone https://github.com/mstorsjo/msvc-wine.git /mnt/sata/msvc-wine
cd /mnt/sata/msvc-wine
./vsdownload.py --accept-license --dest /mnt/sata/msvc   # downloads MSVC + Windows SDK
./install.sh /mnt/sata/msvc                              # writes the bin/x64 wrappers

Verify with /mnt/sata/msvc/bin/x64/cl — it should print the cl.exe banner. At the time of writing that is Microsoft (R) C/C++ Optimizing Compiler Version 19.51.36248 for x64 (MSVC toolset 14.51.36231, Windows SDK 10.0.26100.0).

2. What the wrappers do

The build does not call msvc-wine's wrappers directly. build-tools/msvc-wine/{cl,link,lib,rc,ml64,dumpbin} are symlinks to a single msvc-wrapper.sh that sits in front of them, picks the tool from the name it was invoked by, and finds the installation through --msvc-wine-bin (default /mnt/sata/msvc/bin/x64) or $MSVC_WINE_BIN. It exists to handle two things that otherwise break the build:

  • Response files. xmake passes the full argument list on the command line. Wine hands that to the Windows process as a single UNICODE_STRING, capped at 32767 characters, and the link line here (~700 objects with build/.objs/... paths) is several times that — link.exe would silently receive a truncated command line. Anything past the threshold is written to an @response file instead, which the MSVC tools expand themselves.

  • Closing inherited file descriptors. Wine's daemons (wineserver, mspdbsrv.exe, services.exe, …) outlive the compiler invocation that starts them and get reparented to init, keeping every file descriptor they inherited. xmake does not set CLOEXEC on its flock for .xmake/<plat>/<arch>/project.lock, so a build killed mid-compile leaves that lock held by daemons whose owning process is gone — and since an flock releases only when every descriptor on the open file description closes, no later xmake can ever acquire it. The wrapper closes everything above stderr before exec'ing the real tool.

One more Linux-specific piece, in modules/detect/tools/find_rc.lua: xmake resolves the mrc toolset by importing detect.tools.find_rc, and the builtin returns nil on the first line when the host is not Windows. version.rc then fails with cannot get program for mrc. The project-local override keeps the builtin's probe logic minus that host check, and xmake.lua registers it with add_moduledirs("modules").

3. Sources

git clone --recurse-submodules https://github.com/0xra0/StarfieldAnimationFramework.git
# or, in an existing clone:
git submodule update --init --recursive

4. Configure and build

xmake f -p windows -a x64 -m release --toolchain=msvc-wine -y
xmake build -j4

-j4 caps parallelism at 4 jobs. xmake otherwise uses every core, and each job is a separate Wine process — pick a number your cooling is comfortable with, and pass the same -j on every rebuild.

If MSVC lives somewhere else, point the config at it: xmake f ... --toolchain=msvc-wine --msvc-wine-bin=/path/to/msvc/bin/x64 -y.

What gets compiled

The build uses the repository's top-level source tree (Animation/, Commands/, Papyrus/, …), which is the tree upstream maintains. Plugin/src/ is an older snapshot of the same code, kept for reference and not built.

Dependencies (git submodules under extern/)

Built as separate static-library targets and linked in:

Target Source Version
fmt_lib extern/fmt/ 11.0.2
spdlog_lib extern/spdlog/ master (f355b3d)
simdjson_lib extern/simdjson/ master (e341c8b4)
ozz_lib extern/ozz-animation/ 0.16.0 — base + animation runtime + animation offline
zlib_lib extern/zlib/ 1.3.1

Compiled directly into the plugin target rather than into a library of their own:

  • extern/commonlibsf/ — CommonLibSF (libxse fork, branch starfield-crosshairref), targeting game runtime 1.16.244.0. Only the parts SAF needs are built: all of REL/, REX/ and SFSE/, plus a hand-picked set of RE/ translation units for Papyrus marshalling — pulling in the whole RE/ layer is not necessary.
  • extern/fastgltf/ (0.8.0) — its src/*.cpp minus the bundled simdjson, which would collide with simdjson_lib.
  • extern/zstr/ — header-only gzip streams layered on zlib's headers, which zlib_lib exports publicly.

ozz-animation, zlib and zstr used to come from xrepo; they are vendored now because xrepo would rebuild them with cmake, which has no path to MSVC-under-Wine.

Output

build/windows/x64/release/StarfieldAnimationFramework.dll, alongside a StarfieldAnimationFramework.pdb. The DLL is roughly 3.3 MB and exports exactly seven symbols: SFSEPlugin_Version, SFSEPlugin_Query, SFSEPlugin_Load and the four SAFAPI_* entry points. Releases up to v1.0.2 were ~9.7 MB because the build applied xmake's utils.symbols.export_all rule, which added ~23,500 mangled symbols to the export table; everything SAF actually needs already carries __declspec(dllexport), so the rule is not used.

Troubleshooting

xmake build prints nothing, uses no CPU, and never finishes. A previous build was interrupted and its orphaned Wine daemons are still holding the flock on .xmake/<plat>/<arch>/project.lock (see "What the wrappers do" above). Confirm it:

ps -o pid,stat,wchan:24 -p "$(pgrep -f 'xmake build')"   # WCHAN = locks_lock_inode_wait
fuser -v .xmake/linux/x86_64/project.lock                # lists the wine processes holding it

Kill the listed wineserver / *.exe processes and the lock drops immediately. The fd-closing in msvc-wrapper.sh prevents new builds from creating this state, but a lock left behind by an older build still has to be cleared once by hand.

fatal error LNK1136: invalid or corrupt file. A build killed mid-compile can leave zero-byte object files and cache them, so every subsequent build faithfully restores the empty objects from build/.build_cache and the link keeps failing. Purge just the bad entries rather than the whole cache:

find build/.build_cache -type f -size 0 -print | while read -r f; do rm -f "$f" "$f.txt"; done
find build/.objs -name '*.obj' -size 0 -delete
xmake build -j4

error: cannot get program for mrc. modules/detect/tools/find_rc.lua is missing, or add_moduledirs("modules") is absent from xmake.lua. See the end of "What the wrappers do".

Credits

Original mod by mielu91mhttps://github.com/mielu91m/StarfieldAnimationFramework-. This repository is a modified fork that adds the Linux cross-compile build system above; all mod functionality and design are the original author's work.

License

Distributed under the MIT License — see LICENSE. Note: the upstream repository declares no license; MIT is applied here for consistency with the sibling Modern Command Framework by the same author.

About

Starfield Animation Framework (SAF) — a modern replacement for NAF (Native Animation Framework). SFSE plugin, cross-compiled from Linux with clang-cl + CommonLibSF. Fork of mielu91m's mod adding an xmake build.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages