Skip to content

MJOLNIR CLI cli-v0.2.0

Choose a tag to compare

@github-actions github-actions released this 07 Sep 19:58
· 2 commits to main since this release
Immutable release. Only release title and notes can be modified.
a8005f5

mjolnir live: what the running game has loaded

A new live command reads the running game's own table of loaded tags and its string-id registry, and poke uses that table to find a tag instantly instead of sweeping memory.

The simulation module the game loads keeps two tables that answer questions
this tool used to answer by scanning the whole process: which tags are loaded
and where each one's data is, and which string id names the running game
knows. Both are reached from fixed addresses in the module — addresses that
belong to one build, so the module is hashed first and an unknown build is
refused with its hash rather than read at a guess.

Added

  • mjolnir live status — the build, where the simulation module sits, how
    many tags are loaded, how many string ids are registered.

  • mjolnir live tags [--group weap] [--filter text] [--tsv out] — every
    loaded tag with its handle, group, path and root address.

  • mjolnir live string-ids [--find name] [--out file.json] — the
    registry of names the running game accepts in a string id field. A name
    the game has not registered makes it reject the whole tag, so this is the
    list to check an edit against; defs/hce/string-ids.json is the dump for
    the current build in mission A30.

  • mjolnir new-tag --group <g> --from <donor> --to <path> — put a brand-new
    tag package in front of the game, in a container that registers it itself.
    The wrapper is built from scratch for every group: the preload list from
    the references the body carries, the Unreal binding from the donor (or
    --asset-reference), a model's variants from the donor. The new path can
    be anything. Measured in game twice: a collision model loaded by name the
    moment a model tag was repointed at it, and a new Blueprint-bound
    projectile was fired by the assault rifle. --set edits the body on the
    way; --install-test writes the triplet straight into the Paks folder.

  • mjolnir extract [--group g]... [--filter text] [--out dir] [--dry-run] [--verify] [--shipped-only] — write tags out of the containers as files
    in the layout Blam tooling expects (objects/weapons/rifle/smg.weapon),
    no Python and no Oodle DLL needed. Mod-aware: an installed override wins
    its tag unless --shipped-only.

  • mjolnir values --json — the value tree as JSON: every field with its
    name, type, offset, size and value, blocks with their elements and counts.

  • mjolnir script --extract <dir> — a mission's .hsc source files
    written out, one folder per scenario.

  • mjolnir texture export --out x.dds — the cooked pixel format with
    every mip, no re-encoding, virtual textures untiled; .tif writes an
    uncompressed RGBA TIFF of one mip. PNG as before.

  • mjolnir mesh export --asset SM_… --out x.glb — a static or skeletal
    mesh as glTF binary: every LOD, a primitive per material slot, metres and
    +Y up. A mesh that ships as Nanite cluster pages is decoded from them and
    exported at full detail first, with the classic fallback LODs after it; the
    command reports the page, cluster and triangle counts. An exact asset name
    wins over a longer one that contains it.

  • mjolnir level export --mission a30 --out <dir> — a mission's Unreal
    geometry as glTF: one .glb per World Partition cell (and the persistent
    level), every placed static mesh at its world transform, instanced
    components expanded from their per-instance matrices, Blueprint actors'
    meshes resolved through their templates, and a manifest.json of counts
    and skips. --cell narrows to cells whose id contains a string,
    --nanite places the full-detail geometry, --hlod adds the
    hierarchical-LOD proxies, --dry-run reports without writing.

  • mjolnir ue get|set|roundtrip — read and edit the properties of any
    cooked Unreal package, not just a tag. get lists an export's properties
    as path = value rows (ScalarParameterValues[4].ParameterValue = 100);
    set --field <path> --value <text> changes one or more and writes the
    package into an override container, reading it back first; roundtrip
    counts how many exports of the matching packages decode and re-encode
    byte for byte. Numbers, bools, names, strings, enums, soft object paths
    and the vector-like native structs can be set; object references cannot
    (that is import-map surgery). Proven in game on the assault rifle's
    first-person material: two emissive tints set to red read back as
    80,0,0,1 from the running game's material instance.

  • mjolnir zen-roundtrip — parse and re-serialize every shipped tag
    wrapper (byte-exact for all 12,330 shipped) and count the derivation rules a
    from-scratch wrapper relies on: every bare wrapper rebuilds byte for byte
    from group, path and payload length; every wrapper with properties decodes
    and re-encodes byte-exact and rebuilds from its spec.

  • Live mode on a build without a measured profile. When the tag
    module's hash matches no profile, live and poke derive one from four
    anchors in the module's code — instruction shapes that refer to the tag
    table, the segment table, the string-id registry and the builtin ids,
    each with its displacement wildcarded and decoded at run time — instead
    of falling back to the sweep. On CU4 the derived profile equals the
    measured one, RVA for RVA.

Changed

  • set refuses an unregistered string id. The game rejects a whole tag
    over one string id its registry does not know (the weapon simply
    vanishes), so a string-id value is checked against the registry as the
    game held it in mission A30 (defs/hce/string-ids.json, shipped in the
    tool) and refused unless --allow-unknown-string-id says you know.

  • The stub .pak is written, not copied. new-tag, level and
    texture used to copy the smallest shipped .pak beside a container so
    the game would discover it. They now write the 339-byte empty archive
    themselves (ue_iostore::pak::write_stub), byte-identical to the shipped
    stubs apart from the seed and hash that already differ between those.

  • poke finds a tag through the table on a known build — exact, and no
    scan — and reads the engine's segment table to reach fields inside block
    elements. On a build without a profile it sweeps as before and says so.

Known limits

  • Profiles exist for the current Steam build (2026.08.11.1121610.2, CU4).
    A game update needs its eight addresses measured and added; until then the
    sweep is the path.
  • The tables exist only once a mission is loading; at the menu live reports
    that no mission is loaded.
  • poke still refuses any field the format stores in a trailing section — a
    string id, a data, a tag reference — because changing one resizes the
    tag on disk. Two of those do have a resident form that can be written
    (docs/tag_table_and_string_ids.md), and the tag editor writes them; the
    command line does not yet.

Full changelog · All releases


Install

Windows

scoop bucket add mjolnir https://github.com/devnull9090/mjolnir-core
scoop install mjolnir

macOS and Linux (Homebrew)

brew tap devnull9090/core https://github.com/devnull9090/mjolnir-core
brew install mjolnir

Debian, Ubuntusudo dpkg -i mjolnir_0.2.0_amd64.deb
Fedora, RHEL, CentOS, openSUSEsudo rpm -i mjolnir-0.2.0-1.x86_64.rpm

Or take an archive below, unpack it anywhere, and put that folder on
your PATH. The Linux binary is statically linked, so it runs on any
distribution without installing anything alongside it.

Downloads

File SHA-256
mjolnir-0.2.0-windows-x64.zip 719e2320c3c917befc2be4b2cb7d102f696f22a9f21595c5bee4b28c4a98e27e
mjolnir-0.2.0-linux-x64.tar.gz 1d81eb8955eab1e58234b1a55bb79d14fc110ae5a223a7c4bf5f9035276c46e0
mjolnir-0.2.0-macos-universal.tar.gz dddad7c228033d6d86c92bfa9c5dfd3dc8bab0bc37ebfde3169149e8b2cf5c60

The macOS build is a universal binary — the same file runs on Intel
and on Apple Silicon. .deb and .rpm checksums are in the
checksums file attached below.

Verify your download

(Get-FileHash .\mjolnir-*.zip -Algorithm SHA256).Hash
shasum -a 256 mjolnir-*.tar.gz

Compare the output with the checksum above. They must match exactly.