Skip to content

ULTRAKILL

Platform ID com.newblood.ultrakill

DigitalHeaven.Mods.Ultrakill Unity Mono is a BepInEx 5 mod that replaces the first-person arms in ULTRAKILL with the arms of a DigitalHeaven avatar, and shows the whole avatar in place of V1 in mirrors and portals.

Mod loaderBepInEx 5 (Mono, x64; latest 5.4.23.x)
Game versionSteam release (Unity 2022.3.29f1, Mono)
Rendering pipelineBuilt-in (ULTRAKILL/Master shader)

This mod depends on the DigitalHeaven.Unity library for avatar loading. ULTRAKILL is single-player, so no multiplayer features apply.

  1. Download BepInEx_win_x64_5.4.23.x.zip from the BepInEx releases (not the IL2CPP build, and not BepInEx 6)
  2. Extract it into the game folder, so winhttp.dll and doorstop_config.ini sit next to ULTRAKILL.exe
  3. Launch the game once to generate BepInEx/plugins and BepInEx/config
  4. Copy the mod’s output DLLs into BepInEx/plugins/DigitalHeaven/ (the build does this for you when BepInEx is installed, see Building)
FeatureStatus
OverlayWIP
First-person right arm (revolvers, Spawner Arm)WIP
First-person left arm (Feedbacker, Knuckleblaster)WIP
Whole avatar in mirrors and portalsWIP
DH materials (opaque, cutout, transparent, emissive)WIP
Whiplash and the other weaponsNo
Spring bonesNo
Spring collidersNo
Eye look and blink (mirror avatar)Idle
  1. A ViewModelArm component goes on every viewmodel object that carries one of the stock arm rigs below. Harmony postfixes on Revolver.OnEnable and Punch.OnEnable add it to revolvers and punch arms. Each frame, a scan of GunControl.allWeapons and of the punch arms under FistControl covers the rest and anything the hooks missed.
  2. The configured avatar is built once through the full DigitalHeaven pipeline, materials included, and kept whole. The first arm of each side cuts a copy of it down to the triangles weighted entirely to that side’s upper arm, forearm, hand and fingers, and keeps the cut as an inactive template. Both punch arms share one left cut.
  3. Each owner instantiates its side’s template beside the stock armature, on the stock arm’s layer (13, AlwaysOnTop, drawn by the HUD camera), and hides the stock arm mesh (see Rigs). That mesh is the only stock object the mod touches: the gun or gauntlet, its bones and effects keep working.
  4. Every LateUpdate, after the game’s Animator, each avatar bone receives its stock bone’s rotation change from the stock bind pose. Only the DH root moves or scales: it is fitted to the stock arm’s length and translated so the DH upper arm starts where the stock upper arm starts (Anchor).

Every stock rig is Generic, not Humanoid, so bones are found by name. Each rig is described once (StockArmRig): the avatar side it drives, its arm mesh, its upper arm, forearm and hand, and its fingers by anatomy. One retarget code path serves all of them.

RigOwnerArm meshAvatar armFingers
Revolverevery revolver, standard and Alternative, and the WasherRightArmrightthumb (2), index, middle, ring, little (3 each)
Spawner Armthe sandbox arm’s four copiesMainArmleft, mirroredas the revolver
FeedbackerArm Blue(Clone), and Arm White in 8-SFeedbacker/Feedbackerleftfive, three joints each
KnuckleblasterArm Red(Clone)arm_lp, partlyleftthumb, index, little (2 each)
  • Revolver: every Finger_*_Base starts at the knuckle, a full palm length from the wrist, so Base, Bridge and Tip are the proximal, intermediate and distal phalanges. Finger_Thumb_Base starts on the thenar side, a third of the way up the palm, and Finger_Thumb_Tip continues it. In the bind pose the thumb sticks straight out across the palm; in the idle grip Finger_Thumb_Base has swung about 100° toward the palm and the knuckles, which no metacarpal can do, so the two act as the thumb’s phalanges.
  • Spawner Arm: its model shares the legacy blue arm’s skeleton, which is a left hand, and the prefab root’s X scale of -1 mirrors it into a right arm. So it drives the avatar’s left arm, which the same mirror turns into a right arm on screen. Asymmetric avatar details appear mirrored, as in left-handed mode. It also imports at a quarter of the revolver’s scale, while its Equip state plays a clip from Arm Blue.fbx whose armature is at full scale, so the arm briefly grows 4x while drawn. The live fit follows that.
  • Feedbacker: a clean UpperArm/Forearm/Hand chain with Index, Middle, Ring, Pinky and Thumb _01–_03. Thumb_01 starts 30% up the palm. The mesh object Feedbacker/Feedbacker holds only its renderer, so it is deactivated. The effects, the parry light (PunchZone) and the held-item Holder sit elsewhere and keep working.
  • Knuckleblaster: an IK-baked rig. wrist hangs off clavicle, not lowerarm, and hand is its child. The retarget matches upperarm, lowerarm and hand by name and works in the mesh parent’s space, so the hierarchy does not matter. It has three two-joint fingers. Middle borrows the index finger and ring borrows the little finger: each takes the swing its neighbor got and follows its neighbor’s stock bones.
    • arm_lp is one mesh with one material, gauntlet included, so it cannot be split by submesh. Every vertex is weighted to a single bone, though, so the mesh stays on and every bone not in KnuckleblasterKeptBones is collapsed: its skinning is pointed at a zero-scale child of that bone. By default the mechanism stays (lever arms, shells, barrels and hand rings) and the limb goes (upper arm, forearm casing, palm and fingers). The stock bones are untouched, so ShootPoint, Holder and the shell ejector keep working, and the mesh does not need to be readable.
    • This choice is open. Adding lowerarm keeps the forearm casing, and an empty list hides the whole gauntlet.

The stock arm mesh object is deactivated, unless it holds anything else, in which case only its renderer is disabled.

Each avatar finger is a chain. It starts at the first of its standard bones the rig maps, or at a child of the hand whose name carries the finger’s name (pinky counts as little). It continues through children, preferring one the rig maps for that finger, then the deepest subtree. Children that sit exactly on their parent are looked through, not taken; these are accessory armatures (rings, gloves) linked in by name. The avatar’s thumb starts at the hand’s own child. Rigs written with Unity humanoid names file the metacarpal as *ThumbProximal (Unity’s “Thumb Proximal” is anatomically the metacarpal), so the mod does not trust the key. A finger with no stock bones borrows its nearest neighbor’s.

One alignment ties the rigs together: the hand frame, which points at the knuckles and is rolled so the knuckle lines agree (index to little finger, or the widest pair both rigs have, else wrist to thumb). Every other bone takes that alignment, then swings the least amount that points it along its stock counterpart in the stock bind pose. At runtime each bone receives its stock bone’s rotation change from that bind pose.

A bone’s direction points at the next bone of its chain. For a chain’s last bone, it points along the bone’s own child (the stock *_end leaf). Earlier builds reused the parent’s direction there, which dropped the last joint’s curl and made the revolver thumb’s two segments read identically.

The stock bind pose is the mesh’s skin bind pose, a flat hand: for the revolver, straight fingers and a thumb straight out across the palm. The FBX’s rest rotations (a curled grip) are not it. The per-bone swing lines each avatar bone up with that pose, so the stock rotation changes land on matching bones. A bone with no direction keeps the hand alignment. The stock frames come from the arm mesh’s bind pose, taken into the mesh parent’s space, when every mapped bone skins it; otherwise they come from the pose at build time.

Thumb. The stock thumbs start a third of the way up the palm and swing far more than a metacarpal can. Pairing them with the avatar’s metacarpal (which starts at the wrist on most rigs) buried the thumb in the palm. Each stock thumb chain pairs with the avatar thumb bone that starts nearest it along the palm, measured as a fraction of each rig’s palm; avatar thumb bones before that one keep their rest pose. By default (ThumbMode SwingOnly), each thumb bone then takes only the swing of the stock thumb’s rotation change from bind, as the hand sees it: a swing-twist decomposition about the bone’s bind axis drops the twist. The thumb points where the stock thumb points and keeps the avatar’s own roll, so the nail stays on the nail side. Copying the whole change also copied the stock thumb’s roll, which put the paw pad where the nail belongs. [ArmThumb] logs the dropped twist per bone once the build has settled. Matched pairs the same way and copies the whole change. ThumbMode also offers Anatomical (pair from the metacarpal; it keeps its rest direction and the rest swing), Swing (pair from the metacarpal; every bone swings), Delta (no thumb bone swings) and Rest (the thumb is not driven). A change rebuilds the arms, for live comparison.

Forearm. The stock arms are rigid robot parts, so a stock forearm’s roll against its hand is arbitrary; the Knuckleblaster’s hand is not even its child. Copying that roll onto a skinned forearm wrings it into a thin ribbon. The DH forearm is instead carried by the upper arm, bent the least that points it along the stock forearm, and given ForearmTwist of the hand’s twist about its own axis; the wrist takes the rest.

Fit. The arm’s scale is the bind-pose fit, stock upper arm plus forearm over the avatar’s, times the stock upper arm’s live length against bind. It is measured per instance, every frame, and follows the Equip clips that the Feedbacker, Knuckleblaster and Spawner Arm borrow from Arm Blue.fbx, whose armature imports at a different scale.

Anchor. By default (Anchor Shoulder) the DH upper arm starts where the stock upper arm starts, and the hand lands where the rotation-only chain puts it. The avatar’s upper arm and forearm proportions differ from the stock arm’s (Taidum’s upper arm is 1.43 times its forearm; the stock arms’ are about 1), so pinning the wrists instead drifted the shoulder whenever the elbow bent, and more so on the Knuckleblaster, whose wrist moves independently of its forearm. Wrist pins the wrists (plus the hand offsets) as before. [ArmPose] logs both joints against the stock ones in the viewmodel camera’s viewport.

Culling. Every DH arm renderer has updateWhenOffscreen on, so its bounds follow the skinned pose every frame. [ArmPose] warns once per build if a DH arm renderer goes undrawn by every camera.

Surface children. The Spawner Arm’s tool icon (Hand/Canvas) hangs off the stock hand bone. Each frame it is pushed out along the line from the hand bone through it until it clears the DH hand’s surface, measured from the DH hand’s own vertices.

The arm lives inside its owner’s hierarchy, so it switches on and off with the owner: weapon switches and FistControl.ArmChange both use SetActive. It follows the middle hold position and is mirrored by left-handed mode exactly like the stock arm. All rotation math is done relative to the stock mesh’s parent, so the -1 X scale that ViewModelFlip applies to Guns and Punch, and the Spawner Arm’s own, never enter it. Dual wield clones the weapon with the arm inside it; each clone rebuilds its own arm.

The avatar’s materials are the ones the DigitalHeaven build creates. In this game that means Standard, since nothing else in the pipeline ships. Standard is present in the build (shader_unitybuiltinshaders.bundle), but the project has only 22 Standard materials, all with no keywords. So only its plain opaque variant can exist, and cutout, blending, normal maps and emission would silently fall back to it. It is also pixel-lit, while every viewmodel is vertex-lit.

By default (GameShader), each DH-built material is read back through MaterialReadback and written onto ULTRAKILL/Master. The mapped material starts from the stock arm material, whose keywords are known to ship, and carries over:

  • Albedo: the base map with its tiling and offset, into _MainTex. With no map, the property’s white default keeps a color-only surface’s tint.
  • Tint color: into _Color, alpha included.
  • Culling: into _CullMode.
  • Cutout: ALPHA_TEST, with _Opacity set to 1 minus the cutoff, since Master clips below 1 minus _Opacity.
  • Transparency: TRANSPARENCY, alpha or premultiplied blending, no depth writes and the transparent queue.
  • Emission: EMISSIVE, with _EmissiveTex and _EmissiveColor. HDR emission is split into a color and _EmissiveIntensity.

Only keyword sets the game’s own Master materials use are produced: _FOG_ON alone, with ALPHA_TEST, with TRANSPARENCY, with EMISSIVE, or with EMISSIVE TRANSPARENCY. The game has no cutout emissive variant with fog, so a cutout material drops its emission and says so in the log. Normal maps are dropped: Master is vertex-lit.

In this mode, the arm mesh’s vertex colors are set to white, since Master multiplies albedo by vertex color. An avatar’s colors are authoring data (masks, baked occlusion) that glTF stores linear, so a mid gray arrives near black.

AsBuilt leaves the DH-built materials and vertex colors exactly as DigitalHeaven made them, for comparison.

Turning Replace Arms off (or one punch arm’s toggle), clearing the avatar, or a failed load restores the stock arm and removes the DH arm. Changing the avatar or the material mode reloads the avatar and rebuilds every arm.

Every level’s player carries a full third-person V1, Player/GroundCheck/v1_combined, on layer 30 (Portal). The main camera never draws that layer. Mirrors are portals with mirror set, and a portal whose canSeePortalLayer is on (the default) adds layer 30 to its camera. So V1 shows in mirrors and in see-through portals, and nowhere else. PlayerAnimations, on the model’s root, animates it: an Animator, then in its LateUpdate the chest turned with the camera, the head set to the camera’s rotation, and the right arm aimed down the camera’s forward.

  1. A MirrorAvatar component goes on the PlayerAnimations object. A Harmony postfix on PlayerAnimations.Start adds it, and the per-frame scan backs it up through PlayerAnimations.Instance. Nothing is keyed to a level, so any level with the player’s model is covered.
  2. The body is the skinned mesh directly under the model (v1_mdl). V1’s skeleton is read as a humanoid by the shared HumanoidTopology inference MegaBonk uses for its stock rigs, and a humanoid Avatar is built for it from the mesh’s bind pose, with the limbs straightened into a T-pose (StockHumanoid, in DigitalHeaven.Unity).
  3. The avatar is instantiated whole under the model, from a copy of the one load the arms use, through the shared HumanoidClone that MegaBonk’s skins also use. Every object goes on the model’s layer, so only mirrors and portals draw it. Its materials are mapped onto v1_mdl’s own material (V1, ULTRAKILL/Master) exactly as the arms map theirs, see Materials.
  4. The avatar is scaled so its head-to-feet height matches V1’s in its bind pose, times HeightScale. The model’s own scale is not uniform (2.47, 2.63, 2.47 in the project), so the avatar’s root divides it out first and stays uniform.
  5. A postfix on PlayerAnimations.LateUpdate copies the pose through Unity’s muscle space with the shared HumanoidPoseMirror the clone carries, so it runs after the Animator and after the game’s own head, chest and aim rotations. Bones only rotate. The avatar’s root moves so the hips meet, then so the feet meet. V1 maps no jaw, so the avatar’s Jaw is kept out of the transfer and stays closed.
  6. Only V1’s body renderers are disabled. Its third-person weapons, hat and bones keep working. V1’s Animator is set to always animate while its mesh is hidden, so culling cannot freeze the pose being copied.

V1’s skeleton is a Blender Rigify metarig:

HumanoidV1
Hipsspine
Spine, Chest, UpperChestspine.001, spine.002, spine.003
Neck, Headspine.004, spine.006 (spine.005 is an unmapped second neck bone)
Shoulder, UpperArm, LowerArm, Handshoulder, upper_arm, forearm, hand (.L, .R)
UpperLeg, LowerLeg, Footthigh, shin, foot (.L, .R)

Eight three-bone wing chains (wing_1_1 to wing_8_3) also hang off spine.003. They are as long as the neck chain, so the neck is the one that carries the spine’s numbering on. The legs are plantigrade (foot above a forward toe and a heel.02). The toes and the index and thumb bones are not mapped, so the avatar’s toes and fingers keep a neutral pose.

Turning Mirror Avatar off, clearing the avatar or a failed load shows V1 again.

Config file: BepInEx/config/io.mltn.digitalheaven.ultrakill.cfg

SettingSectionDefaultDescription
Enabled[General]trueReplace the first-person arms (master switch)
PlayerAvatar[General]""Full barcode of the avatar whose arms are used, and whose whole body mirrors show
MaterialMode[Arm]GameShaderGameShader maps the DH-built materials onto ULTRAKILL/Master; AsBuilt keeps them as DigitalHeaven built them
ArmScale[Arm]1Multiplier on the automatic fit, which matches the avatar’s upper arm plus forearm to the stock arm’s length (every arm)
Anchor[Arm]ShoulderShoulder starts the DH upper arm at the stock one; Wrist meets the stock wrist
HandOffset[Arm]{"x":0.0,"y":0.0,"z":0.0}Where the avatar’s wrist sits on the revolvers and the Spawner Arm, as a local position under the stock Hand bone, with Anchor Wrist
ReplaceFeedbacker[LeftArm]trueReplace the Feedbacker (blue) punch arm
ReplaceKnuckleblaster[LeftArm]trueReplace the Knuckleblaster (red) punch arm
KnuckleblasterKeptBones[LeftArm]lever arms, shells, barrels, hand ringsKnuckleblaster bones whose part of arm_lp stays visible, comma-separated; the rest collapses. Add lowerarm to keep the forearm casing; empty hides the whole gauntlet
FeedbackerHandOffset[LeftArm]{"x":0.0,"y":0.0,"z":0.0}Where the avatar’s wrist sits on the Feedbacker, under its stock Hand bone, with Anchor Wrist
KnuckleblasterHandOffset[LeftArm]{"x":0.0,"y":0.0,"z":0.0}Where the avatar’s wrist sits on the Knuckleblaster, under its stock hand bone, with Anchor Wrist
ShowOriginalArm[Debug]falseKeep the stock arm visible beside the DH arm, for alignment checks
ForearmTwist[Arm]0.5Share of the hand’s twist the forearm takes, from 0 (all at the wrist) to 1 (all at the elbow)
ThumbMode[Debug]SwingOnlyHow the thumb follows the stock thumb: SwingOnly, Matched, Anatomical, Swing, Delta or Rest (see Alignment)
Enabled[Mirror]trueShow the whole avatar in mirrors and portals in place of V1’s third-person model (see Mirrors)
HeightScale[Mirror]1Multiplier on the automatic fit, which matches the avatar’s head-to-feet height to V1’s
ShowOriginal[Mirror]falseKeep V1’s own model visible in mirrors beside the avatar, for alignment checks
EyeLook[Mirror]trueLet avatars look around and blink on their own. The game’s own look targets are followed either way

The hand offsets are per rig because each rig’s Hand bone has its own units. Enabled, ReplaceFeedbacker, ReplaceKnuckleblaster, PlayerAvatar, MaterialMode, Anchor, ThumbMode and the mirror’s Enabled (Mirror Avatar) are also in the overlay’s Config window, and ShowOriginalArm and the mirror’s ShowOriginal are in its Debug window. The mirror uses the arms’ MaterialMode. Opening the overlay in a level pauses the game.

The game’s release build filters Unity’s Debug.Log, so the mod routes DigitalHeaven’s own diagnostics (material, shader and texture lines) into BepInEx/LogOutput.log through DiagnosticSettings.Sink.

  • [ArmHook] names each weapon or punch arm once: whether it gets a DH arm, which side, in place of which mesh, under which rig, and which path found it (Revolver.OnEnable, Punch.OnEnable, loadout scan or fist scan). For the Knuckleblaster it also lists which bones’ geometry is kept and which is collapsed, and any kept name with no bone.
  • Arm avatar <side> arm ready reports how many meshes and bones survived each side’s cut.
  • <rig> arm retarget reports how many bones are driven, whether the stock bind pose or the current pose was used, what set the roll, the fit at bind and now, and which hand (left or right) each rig’s finger layout reads as. A warning follows when the two disagree.
  • [ArmRig] lists the avatar’s hand subtree once per avatar and owner, each bone with its offset from its parent. Linked accessory bones show up here at offset 0.
  • [ArmMap] lists every stock-to-avatar bone pair once per avatar and owner, with where the avatar bone came from. The source is rig <standard bone>, hand child above rig <standard bone>, hierarchy or name hint, plus borrows <finger> for a finger with no stock bones. It also lists the stock bones left without a partner.
  • [ArmThumb] gives each thumb bone’s own bind-pose direction for both rigs in their own hand frame (z toward the knuckles, y across them toward the thumb side), its length and where along the palm it starts, both in palms. Matching thumbs read alike.
  • [ArmPose] gives the DH and stock shoulder and hand in the viewmodel camera’s viewport, and the gap between each pair, once the build has settled and again whenever the gap at the unpinned joint grows by a quarter, which catches the extremes of a punch. A warning under the same tag reports a DH arm renderer no camera drew.
  • [ArmMap] also gives each finger’s twist about its own axis against the hand, once the build has settled.
  • [ArmFit] gives the upper arm, forearm and wrist-to-knuckle bind lengths of both rigs, the arm fit, the fit a hand match would give, and the stock upper arm’s live scale against bind.
  • [ArmMaterial] describes the stock arm’s material once. Then, for each arm material, it gives two lines:
    • source: the DH-built material, with its shader, whether it is supported, keywords, queue, and its read-back surface (maps, colors, culling, alpha mode).
    • final: the material the arm renders with, with its Master properties, what was dropped, and the vertex color range and what happened to it.
  • is not readable and cannot be cut means the avatar’s mesh cannot be read on the CPU.
  • [Mirror] covers the mirror avatar:
    • once per model: its path, the body meshes it replaces, their layer, the renderers it keeps, and which path found it (PlayerAnimations.Start or player scan);
    • V1’s topology mapping (Hips='spine' ...), or why it could not be read as a humanoid;
    • per build: the fit (both heights and how they were measured, the factor, HeightScale and both world scales), every V1-to-avatar bone pair, the layer and the material path;
    • once: the hidden renderers and the Animator’s culling change.
    • The mirror’s materials are described by the same [ArmMaterial] lines as the arms’, with v1_mdl as the stock renderer. [HumanoidRestPose] lines come from the T-pose both humanoid Avatars are built from.
  • Mirrored in left-handed mode. The DH arms are mirrored, as the stock arms are. Asymmetric avatar details swap sides. The Spawner Arm shows a mirrored left arm in every mode.
  • The top of the upper arm is open. Vertices weighted partly to the shoulder or chest are cut, which leaves the arm’s top edge open. It is normally off screen.
  • Short stock chains. The revolver thumb and the Knuckleblaster’s fingers have two stock segments. Avatar bones past the stock chain, and thumb bones before it, keep their rest pose against their parent.
  • Knuckleblaster effects follow the stock bones. ShootPoint, Holder and the lever arms stay on the stock skeleton, which the DH arm matches only at the wrist.
  • The Whiplash is untouched.
  • Game-shader limits. In GameShader mode, normal maps and avatar vertex colors are not shown, and cutout materials lose emission.
  • No spring bones are tuned for the viewmodel.
  • Mirror: the third-person weapons stay on V1’s hand. They hang off V1’s hand.R, which the avatar’s hand matches only as closely as the two bodies’ proportions allow.
  • Mirror: muscle limits. The pose travels through Unity’s humanoid muscles with default limits, so an extreme aim or slide pose is clamped. V1’s wings, toes and fingers are not carried over.
  • Mirror: only portals with canSeePortalLayer show it, as they do V1. Some portal doors turn it off on purpose.

The mod project lives at Platforms/DigitalHeaven.Mods.Ultrakill/ and is included in DigitalHeaven.sln under the Platforms folder. Scouting notes on how the game spawns, flips and renders its viewmodel are in Platforms/DigitalHeaven.Mods.Ultrakill/NOTES.md.

  • ULTRAKILL installed via Steam
  • BepInEx is not needed to build: the BepInEx 5 and Harmony references come from NuGet (nuget.bepinex.dev)

The game path defaults to D:\SteamLibrary\steamapps\common\ULTRAKILL. To change it, copy DigitalHeaven.Mods.Ultrakill.csproj.user.example and remove the .example extension. This file configures your local paths and is git-ignored:

DigitalHeaven.Mods.Ultrakill.csproj.user
<Project>
<PropertyGroup>
<UltrakillDir>D:\SteamLibrary\steamapps\common\ULTRAKILL</UltrakillDir>
<!-- Optional, if BepInEx or the managed assemblies live elsewhere -->
<!-- <UltrakillBepInExDir>C:\path\to\BepInEx</UltrakillBepInExDir> -->
<!-- <UltrakillManagedDir>C:\path\to\ULTRAKILL_Data\Managed</UltrakillManagedDir> -->
</PropertyGroup>
</Project>

The same properties can be passed on the command line, for example -p:UltrakillDir=....

The project targets netstandard2.1 and references Assembly-CSharp and the Unity modules from $(UltrakillManagedDir). It references DigitalHeaven.Core, DigitalHeaven.Unity, DigitalHeaven.Overlay and DigitalHeaven.Unity.Overlay as project dependencies.

After building, a post-build target copies the mod and its dependencies to $(UltrakillBepInExDir)\plugins\DigitalHeaven\. It runs only when that plugins folder exists, so nothing is written to the game folder until BepInEx is installed. Pass -p:SkipDeploy=true to build without deploying:

Terminal window
dotnet build Platforms/DigitalHeaven.Mods.Ultrakill/DigitalHeaven.Mods.Ultrakill.csproj -p:SkipDeploy=true