Team Cereustooling wiki

Port a Dark Souls III or Elden Ring character

Partly checkedagainst the code on 2026-11-12 11:37

The DS3 route is one Python script, ds3-arena/build.py, driven by a config file per character. It calls the shared C# tools below. This page lists what each tool does and its command line.

Checked against code: every command line below, read from each tool's source. Not checked: the body of build.py past its header, the config file format, the Node FX scripts, and anything in game. I found no separate one-command route for an Elden Ring character in eclipse-build. StatPort and AiPort accept ER sources; the rest of an ER port is not written up here.

EB means py -3.12 -I -B S:/eclipse-build/eb.py. Get tools with EB tool build <name> and run heavy steps with EB run --mem <GB> -- <command>. See ../build/use-shared-tools.md.

Most of these tools change the current directory to their own folder so that Oodle loads. Always pass absolute paths.

The DS3 route

py -3.12 -I build.py <port.json> [--from STAGE] [--to STAGE] [--only STAGE] [--keep-work]

build.py is S:/souls-script-kt/scripts/tools/npc-ports/ds3-arena/build.py. Stages, from its header:

Stage What runs
extract NpcPort ds3-extract, NpcPort ds3-regulation, NrUnpackCli for the NR templates
model NpcPort flver-port, NpcPort tex-port
anim NpcPort hk-convert
sfx NpcPort ffx-roots, then sfx_port.mjs --auto, then NpcPort sfxbnd-build; FxModelsGen fix when the config has fxModels = true
tae NpcPort tae-port, then anibnd-build
havok Havok2014 ragdoll, cloth, behavior-character; chrbnd
behaviour the NR universal humanoid graph (c3361 behbnd renamed), own hks
ai DSLuaDecompiler, ai_port.py, aicheck, airun, luabnd
sound wwise_port.py --spec, then the Sister Friede bank builder
params NpcPort param-port-npc
taefix Ds3Fix tae
lint port-lint fix, then port-lint
rowlist Ds3Fix rowlist (a Polish rowlist, see edit-params-and-rowlists.md)
package output/npc-port-ds3-<slug>/mod, rowlist.tsv, arena.json, reports

As of this check build.py still runs the souls-script-kt copy of NpcPort (pontiff-knight/NpcPort/bin/Release/net9.0/NpcPort.exe), not the eclipse-build one. Ds3Fix is not in tools.toml.

NpcPort

EB tool build npcport. Generic DS3 to NR steps. Every command takes explicit paths and ids.

Command Arguments
ds3-extract <DS3 Game dir> <out dir> <DS3-File-Dictionary.json|txt> <path-prefix>... A prefix that starts with re: is a regex. --list only lists.
ds3-regulation <DS3 Game dir> <out parambnd path>
bnd-ls <binder>
bnd-x <binder> <out dir>
flver-info <flver or binder>
tae-info <anibnd or tae> and optionally an anim id filter
matbin-info <matbinbnd> <name regex>...
tpf-info <tpf | texbnd>
flver-port <src flver|chrbnd> <out.flver> [--rename c2030=c9760]...
tex-port <src texbnd|tpf> <out dir> <chr> [--rename c2030=c9760] [--low-drop 2] Writes <chr>_h.texbnd.dcx and <chr>_l.texbnd.dcx.
bnd-repack <template binder> <out> [--rename from=to]... [--set <id>=<file>]... [--drop <id>]... [--add <id>=<file>=<entry name>]... [--dcx KRAK|DFLT|none]
sfxbnd-build <out ffxbnd.dcx> <fx dir (f*.fxr + f*.ffxreslist)> --resources <source resource bnd>... [--model <id>]...
luabnd-build <out luabnd.dcx> <script.lua> <names.txt>
anibnd-build <out anibnd.dcx> <chr> <hkx dir> <tae file> [--skeleton skeleton.HKX]
tae-port <src tae|anibnd> <container tae|anibnd (dst game, header donor)> <out.tae> --src-template <xml> --dst-template <xml> --id <tae id> --chr <cXXXX> [--map <csv>] [--sound-remap 2030:976:1000000]... [--ffx-remap old=new]...
ragdoll-port <src chrbnd | .HKX> <out .hkx> --donor <NR chrbnd | .hkx> [--report <txt>] [--xml <out.xml>], or --verify <.hkx>
hk-convert <hk2014 packfile> <out hk2018 .hkx> [--report txt]
hk-xml <hkx | binder> <out.xml> [--entry <name suffix>]
ffx-roots <src tae|anibnd> <src regulation> <src game> --src-npc <id> --src-template <TAE xml> [--out <txt>]
param-port-npc builds the param patch CSVs. Arguments are in ParamPort.cs (--dst-npc, --ref-base, --atk-scale and more); not listed here.
param-dump <regulation> <NR|ER|DS3> <ParamName> [id | id-id]... [--fields a,b,c]
param-free <regulation> <NR|ER> <ParamName> <from>-<to>... Prints rows that already exist in the ranges.
verify-package <mod dir> <chr> [--goal <battle goal id>] Exit code 5 on any failure.

tae-port map file lines: srcType,dstType[,srcParam:dstParam;...[,dstParam=constant;...]] or srcType,drop. # starts a comment.

param-port-npc writes one CSV per param with the header id,copyFrom,name,<field>.... Ids it gives: NpcParam and NpcThinkParam are --dst-npc; BehaviorParam is 200000000 + variation*1000 + judge; AtkParam_Npc, Bullet and SpEffect are --ref-base + (source id % 1000). DS3 dark damage goes to NR's atkDark, which is the holy slot.

Hard-coded paths still in NpcPort (README-IMPORT.md): the paramdex root S:\souls-script-kt\configs\paramdex, the DS3 defs under S:\Smithbox-havok-pin, and the Havok class XML folders there. Never delete S:\Smithbox-havok-pin.

AiPort

EB tool build aiport. Ports DS1, DS3 and ER battle AI Lua to Nightreign.

AiPort <luabnd-extract|luabnd-pack|luabnd-verify|luabnd-bytecode|decompile|inventory|aicheck|port|localize|airun|tae-info|catalog> ...

The code also accepts shim-import and spefx-remap. The two commands whose arguments I read:

port --map <ai-map.json> --src-dir <decompiled source dir> --api <nr-api.tsv> --shim <aiport_shim.lua> --out <battle.lua> [--report <txt>]
localize --in <file.lua> --out <file.lua> [--api nr-api.tsv] [--shim aiport_shim.lua] [--peers <dir>]... [--aicommon-lua <dir>]... [--off]

port joins the decompiled goals, renames identifiers, applies the map's edits and remaps animation and SpEffect ids. localize moves file-private functions onto local AIPORT_L = {} so two ports cannot clash on a global name. The other commands are documented in each source file header. AiPort finds shim/aiport_shim.lua and data/nr-api.tsv by walking up from its own folder, so a bare eclipse-build copy needs those passed as arguments.

StatPort

EB tool build statport. Carries a source enemy's stats onto the NR row.

StatPort dump  --game NR|ER|DS3|DS1R|DS2S [--regulation <path>] <Param> [id[-id]]...
StatPort list  --game <g> [--regulation <path>]
StatPort port  --config <port.json> [--out <csv>] [--verify]
StatPort port  --game DS3 --source <id[,id]> --target <NR id[,id]> --slug <s> --out <csv> [--throw <ids>] [--verify]
StatPort all   --configs <dir> --out <dir> [--verify]
StatPort audit --configs <dir> --out <md>
StatPort apply --regulation <NR regulation.bin> --csv <slug.csv> [--rows <slug.rows.csv>] [--out <regulation.bin>] [--verify]

dump reads the environment variable FIELDS=a,b to limit columns. The code also has defdiff. StatPort reads ../../data/snapshots relative to its own folder.

PortLint

EB tool build portlint (the exe is port-lint.exe). Offline checks for a port, one rule per known failure. From usage.txt:

port-lint <mod folder> [--chr c9760 ...] [--regulation <regulation.bin>|none] [--vanilla <NR regulation.bin>|none]
          [--arena] [--rules id,id] [--table out.md] [--tsv out.tsv] [--havok <Havok2014.exe>] [--defs <Smithbox NR Defs>] [--quiet]
port-lint fix <mod folder> [--chr c9760 ...] [--fixes id,id] [--skip id,id] [--regulation <regulation.bin>|none] [--params <csv dir>]
          [--template-reg <NR regulation.bin>] [--flver-ref <vanilla c3181.chrbnd>] [--report out.txt]   (in place)
          [--fill-report <tae-port anim-fill.csv>...]
port-lint flverinfo <chrbnd> | taeinfo <anibnd> [anim=ID|type] | paramhist <regulation.bin> <Param> <field> [lo hi]
port-lint rules

Exit code 1 when any rule FAILs. fix edits the mod folder in place. The comment at the top of Program.cs still shows an older fix <in> <out> form; usage.txt and the callers use the in-place form. port-lint rules prints every rule id. PortLint looks for an npc-ports folder above its own folder and falls back to S:\souls-script-kt\scripts\tools\npc-ports.

MaterialFix

EB tool build materialfix. Repairs materials on finished texbnds. It never writes in place.

gloss <h.texbnd.dcx> <l.texbnd.dcx> --out DIR [--work DIR] [--only stem,stem] [--no-gloss] [--metal0 stem,stem] [--gain stem=g,stem=g]
matparam <in.matbinbnd.dcx> <out.matbinbnd.dcx> <spec.tsv>
matdump <bnd;bnd> <stem>...

gloss turns a roughness value in the normal map's blue channel into Nightreign's gloss. It refuses to run twice on a texture and refuses an --out under a release folder. matparam spec lines: <matbin stem> TAB <param name suffix> TAB <comma separated values>. It calls texconv at a hard-coded path (S:\the-heolstor-files\tools\texconv\texconv.exe).

DummyAim

EB tool build dummyaim. Adds FLVER dummies that stay rigid with a bone and point a beam or hit capsule at another pitch.

dummyaim <in chrbnd> <out chrbnd> <spec.tsv>
dummyaim info <chrbnd> <id>[:<fromId>]...
dummyaim rebnd <in dcx binder> <out>

Spec lines, tab separated, # comments:

  • END <newId> <startId> <refEndId> <extraPitchDeg>: a copy of dummy refEndId, placed from startId along the same direction turned further down.
  • BEAM <newId> <startId> <endId> <attachBone> <expectLength>: a beam frame at startId. Fails unless the length matches within 5 cm.

A newId that already exists fails the run and nothing is written. First used for the Moonlight Butterfly's beam.

KneelRetarget

EB tool build kneelretarget. Retargets clips from one skeleton to another, for example riposte receive poses.

info <anibnd> [--div <div anibnd>]... [ids csv]
bones <src anibnd> <dst anibnd> [dst chrbnd] [--div <src div>]...
build <config.json> [--only id,..]
verify <config.json>
taedump <anibnd> [animIdRegex]
roundtrip <anibnd> [--div ..] <id>
track <anibnd> <chrbnd> <animId> [--step N] [names...]
concat <in anibnd> <out anibnd> <dst anim id> <src id A> <src id B> [--xfade frames]
rootinfo <anibnd> <id...>

The code also has pack, dmyinfo, meshes, maskflver, flvbind and ls, with no usage text. The config format is in the tool's README in souls-script-kt (npc-ports/kneel-retarget), not read here.

CritFix

EB tool build critfix. Fixes the body pop when a lethal crit switches from the receive anim to the death anim.

CritFix scan <anibnd> <R:last[:D]>... | death <in> <out> <D> <seg+seg..> [--xfade N] [--events K] | info <anibnd> <id>...

scan is read only. death rebuilds death anim D from segments written <anim id>@<from>-<to>. The code also has tags and verify. CritFix sets its current directory to a hard-coded souls-script-kt path.

Effects: FxBnd and FxModelsGen

EB tool build fxbnd fxmodelsgen.

FxBnd unpack  <bnd> <outDir>
FxBnd ds2-xml <ffxbnd> <id[,id..]> <outDir>
FxBnd pack    <stageDir> <templateBnd> <out>
FxBnd verify  <bnd>

pack takes stageDir/{fxr,tex,model,reslist}/*, clones the header and DCX type of the template binder, then verifies. The code also has dummies, stage-res, ds2-tex and flver-info.

FxModelsGen dump <sfxbnd>
FxModelsGen fix <in sfxbnd> <ffx-map.tsv> <out sfxbnd> <texFree lo-hi> <DS3 resource ffxbnd>...

fix rewrites DS3 effect models the way vanilla NR effect models are built and ships every texture they name under a port id. ffx-map.tsv maps old to new FXR ids; <ffx-map.tsv>.tex.tsv and .model.tsv map texture and model numbers. SYSTEX_* textures are kept as they are. FxModelsGen does not change directory itself: run it with its own folder as the working directory.

Havok2014

EB tool build havok2014. Converts DS3 Havok 2014 files to the 2018 format. A file argument is a path or binder.dcx::entry. The DS3 route uses:

ragdoll --src <DS3 chrbnd|HKX> --template <NR chrbnd|hkx> --out <hkx> [--report t] [--verify] [--anim-skeleton anibnd::Skeleton.hkx] [--drag none|template]
cloth   --src <DS3 chrbnd|_c.hkx> --template <NR chrbnd[,..]> --out <hkx> [--types-db db] [--report t] [--verify] [--anim-skeleton s] [--flver chrbnd] [--buffer-layout ds3|nr]
behavior-character --src <DS3 behbnd> --template <NR behbnd> --out <hkx> [--name cXXXX] [--verify] [--anim-skeleton s]
tagcheck <hk2018 file|binder[::entry]>... [--max-hull-vertices 64] [--max-distance 4096] [--reference vanilla file]
relayout <in hk2018> <out> [--types <vanilla ref>]

Running Havok2014 with no arguments prints the full list. Its navmesh commands are on map-tools.md.

Notes

  • Gear ids stay below 0x10000000 (268,435,456). Dated note, 2026-10 (memory nr-item-id-limit): Nightreign keeps the item category in the top 4 bits of an item id, so an EquipParam id at or above that value decodes to a different row. The four human ports with such ids were the only invisible ones and were moved to 9947 to 9950 in 0.0.53. Whether that cured them was not confirmed in game when the note was written. No shared tool in tools/dotnet checks this limit. New human ports get 7-digit 99xxxxx ids.
  • Numbers in hks are 32-bit floats. Dated note, 2026-11-08 (memory nr-hks-float32), proven in game by Stan: an hks call with SpEffect id 28140015 applied 28140016. Any id above 16,777,216 can round to a neighbour. In hks use SpEffect ids below 16,777,216, or ids a float holds exactly. The same note says the shipped hks tests human-port marker ids in the 529 million range, which round; not fixed as of that note.
  • A riposte receive pose borrowed from another character is a KneelRetarget job. See the tool's README in souls-script-kt.
Source files this page documents (12)
  • eb:tools/dotnet/NpcPort/
  • eb:tools/dotnet/AiPort/
  • eb:tools/dotnet/StatPort/Program.cs
  • eb:tools/dotnet/PortLint/
  • eb:tools/dotnet/MaterialFix/Program.cs
  • eb:tools/dotnet/DummyAim/Program.cs
  • eb:tools/dotnet/KneelRetarget/
  • eb:tools/dotnet/CritFix/Program.cs
  • eb:tools/dotnet/FxBnd/
  • eb:tools/dotnet/FxModelsGen/
  • eb:tools/dotnet/Havok2014/Cli.cs
  • sskt:scripts/tools/npc-ports/ds3-arena/build.py

Changed one of these? Update this page: ports/port-a-ds3-or-er-character.md (how).