Skip to content

Releases: UberMorgott/PhoenixPoint-Mod-ContentTool

ContentTool 1.4.1

Choose a tag to compare

@UberMorgott UberMorgott released this 19 Oct 22:03

Install: unzip into Phoenix Point\Mods\ (gives Mods\ContentTool\ContentTool.dll + meta.json), tick ContentTool in the mod manager. Full guide: https://ubermorgott.github.io/PhoenixPoint-Mod-ContentTool/

Fixed

  • Mod weapons stay visible and fire in tactical missions after leaving the geoscape; before, they could come back invisible or without their fitted shoot point (NoValidTarget).
  • Creatures built from a model file shoot from their weapon's muzzle; before, the shot started up to ~0.8 m off the barrel.
  • A creature's melee Strike finds its targets, including when the Strike comes from an inherited animation set (it used to always answer NoValidTarget).
  • The bench 3D view no longer renders black with post-processing upscalers such as Renderforge DLAA enabled.
  • Add a weapon: Revert restores the weapon's rotation and size when ppcontent.json leaves them out; auto scale re-solves after a turn and stays automatic on Save fit; the paste-ready fit report no longer writes the auto scale as a fixed value.
  • Replacing a model works on ContentTool creatures whose mesh lives on the rig.

ContentTool 1.4.0

Choose a tag to compare

@UberMorgott UberMorgott released this 28 Sep 16:15

Install: unzip into Phoenix Point\Mods\ (gives Mods\ContentTool\ContentTool.dll + meta.json), tick ContentTool in the mod manager. Full guide: https://ubermorgott.github.io/PhoenixPoint-Mod-ContentTool/

New

  • The bench looks like the game: its font (Purista), buttons, frames, cards, step bar, main buttons and PASS / WARN / FAIL badges come from the game's own widgets and sprites, and every field, toggle, slider and scroll bar wears the same look.
  • The bench scales to any resolution and window mode (windowed, borderless, fullscreen): the whole layout, the 3D gizmo, the Model Doctor's bone inspector, skeleton markers and legend grow and shrink with the window height, also while the window is resized.
  • Add a weapon starts with an Assault soldier holding a Phoenix assault rifle and playing the game's idle, instead of whatever the previous screen left on the platform.

Fixed

  • A content mod's very long build line no longer reaches the game's own log sink in one piece, which caused the startup error Mesh can not have more than 65000 vertices.
  • The bundled demo mods are re-baked with the current ContentTool (their Dist bundles were baked by 1.0.0).

ContentTool 1.3.0

Choose a tag to compare

@UberMorgott UberMorgott released this 28 Sep 13:24

Install: unzip into Phoenix Point\Mods\ (gives Mods\ContentTool\ContentTool.dll + meta.json), tick ContentTool in the mod manager. Full guide: https://ubermorgott.github.io/PhoenixPoint-Mod-ContentTool/

New

  • Redesigned bench: home screen with six task cards, Home / < Back / breadcrumb navigation (Esc goes back), clickable completed steps, a right-hand pane per task, selectable logs with Copy log / Open log folder.
  • Sounds screen: search and preview game sounds and your own file, then Add to mod & build.
  • Videos screen: preview game clips and your own (pause / stop / seek), replace a game video or add a new one.
  • Add a weapon screen: pick weapon kind + donor, your .glb and a name, build, fit it in the hand with move / turn / size sliders, Save fit, check the kind's moves.
  • Fit a weapon screen: adjust a mod weapon in a soldier's hand with drag handles and save to ppcontent.json.
  • DLL-less content mods build their weapons rows when enabled; ct_voices and ct_mission list are public; meta.json accepts comments and trailing commas.

Changed

  • Replace a model builds on a WARN verdict too; only FAIL blocks.
  • Build & share shows the log beside the steps and can run a single stage.
  • Faster loading with many mods: patched copies reuse the shipped compressed blocks (LZ4 fast repack as fallback), ticking a mod on runs a patch-only bake, failed bakes are not repeated every launch.
  • Conflicting claims on the same bundle / video / sound media ID are resolved by mod ID order.

Fixed

  • Bad ppcontent.json, missing/ambiguous videos and misspelled ct_extract names give clear refusals instead of exceptions.
  • Replaced meshes carry tangents (donor normal maps shade them); _EmissionColor is sRGB-encoded.
  • Mixed-case mod IDs no longer get model keys refused; two weapons cloning the same donor both load.
  • Content published by a mod whose DLL fails while enabling is taken back; creature builds no longer modify borrowed donor animation defs.

Full list: https://ubermorgott.github.io/PhoenixPoint-Mod-ContentTool/whats-new/

ContentTool 1.2.1

Choose a tag to compare

@UberMorgott UberMorgott released this 10 Sep 22:11

ContentTool 1.2.1 lists shipped sounds by name and extracts loose audio as WAV files so you can find the sound you want to replace.

Find a sound by name or id

  • ct_list audio [filter] now shows every shipped sound by name, read at runtime from the game's own SoundbanksInfo.xml in <install>\PhoenixPointWin64_Data\StreamingAssets\Audio\GeneratedSoundBanks\Windows\.
  • Each row is <id> <name> <bank> loose|in-bank, for example: 6372602 5_IND_Taunt_03 Barks loose.
  • The filter is a case-insensitive substring of the name, the id or the bank: ct_list audio taunt, ct_list audio 6372602, ct_list audio Barks.
  • Media in several banks show their bank names joined with + and match any of them.
  • The list covers 7691 named media: 3105 loose .wem files are extractable; 4591 inside .bnk soundbanks are listed as in-bank so you can find their ids, but are not extractable.
  • The pane shows the first 10 rows and then names a file; the whole list is always written to <persistentDataPath>\ContentTool\Logs\ct_list-audio-<stamp>.txt. A capture such as PPCLI still receives every row.
  • If the XML is missing, the header ends with - NO NAMES: <reason> and rows fall back to numeric names.

Extract one sound or all loose audio

  • ct_extract audio <mediaId> now also writes <ShortName>__<id>.wav beside the byte-for-byte .wem.
  • Use ct_extract audio --all [filter] to decode every loose media the filter matches into ...\ContentTool\Extracted\audio\<ShortName>__<id>.wav.
  • Bulk extraction writes index.csv with columns id,shortName,bank,loose,wav, covering all media including the in-bank ones.
  • Progress is reported every 200 files. The whole library is 3105 decodes.
  • The final line is extracted N of M loose media (K in-bank skipped) into <dir>.
  • A failing decode is counted and named, never fatal.

Use the media id for replacements

  • The Wwise MEDIA id is the number in a loose .wem filename. It is what ContentTool replaces and what these commands take.
  • The Wwise EVENT id is AK.Wwise.Event in the game's defs. It is never a loose filename.
  • If the only id you have is an event id, search the sound's name in the list instead.
  • Replacing a sound is unchanged: the manifest row and the file still use the media id.

Follow the updated guides

  • The sounds guide now covers finding and extracting sounds. The old ct_list audio confirm example is gone: it could never match anything because the list held only numbers.
  • The find-content guide has also been rewritten.

Keep long output visible

  • The game's mod logger creates a console text object with shadow per call. One call over roughly 8 000 characters throws Mesh can not have more than 65000 vertices, losing the frame's console geometry. Splitting at 8 000 characters still failed: five parts, five throws.
  • ContentTool now sends one bounded message per command, at most 5 000 characters: verdict head, ... N line(s) not shown, last lines including ct_project: …, and the full-text file path <persistentDataPath>\ContentTool\Logs\log-<stamp>.txt. The console pane shows the same bounded text. ct_list audio keeps 10 rows plus ct_list-audio-<stamp>.txt; a capture such as PPCLI receives every line.
  • Verified in game: long-verdict ct_project, ct_list audio, and enabled project startup bake; zero exceptions, trailer visible.

Install

  • Unzip so that Mods\ContentTool\ContentTool.dll and Mods\ContentTool\meta.json exist under your Phoenix Point Mods folder.
  • Enable ContentTool in the in-game mod manager, then restart.
  • Mods that depend on ContentTool list com.morgott.ContentTool in their meta.json Dependencies.

ContentTool 1.2.0

Choose a tag to compare

@UberMorgott UberMorgott released this 06 Sep 19:00

ContentTool 1.2.0 adds an in-game bench to check replacement meshes and take a project from validation to packaging.

Work from the loaded game

  • Open the bench with ct_bench open from a loaded geoscape, or press Ctrl+Alt+B.
  • FIT keeps the weapon-fit tools; MODEL DOCTOR and LIFECYCLE are new.
  • The bench guide covers the three tabs.

Check a mesh before you bake

  • Model Doctor compares your replacement mesh against a shipped prototype slot.
  • Pick a prototype, a variant and a slot, then choose your GLB with Browse....
  • There are now 350 variants across 36 prototypes. Armour sets have separate variants, such as Human / PX_HeavyStarting.
  • Before 1.2.0, only one variant per manager was listed, which left most armour sets unreachable.
  • The header tells you whether the result is BY NAME - your weights will be used, nearest-bone, not rigged, or IMPORT REFUSED (N reason(s)).
  • Rows name the problem: missing or extra bones, a 1-triangle shard where the target has two materials, no armature, or a rigged file on a static target.
  • Foreign rigs, including Khronos sample models, never get a false BY NAME and never throw. They are refused or downgraded with the reason.
  • In the SHIP section, CREATE, BAKE & APPLY writes a ready mod folder beside ContentTool with ppcontent.json, meta.json and your GLB, then bakes it and applies it live.
  • The Model Doctor guide covers the checks and shipping step.

Run the project loop in one panel

  • The Lifecycle tab has Validate, Bake, Apply, Verify and Package rows.
  • Use Run all for the whole loop, per-row Run for one stage, or Cancel to stop.
  • The status line only shows what the stages said.
  • Validate now catches a texture outside Content\Textures\, a mesh outside Content\Meshes\, and two source files with the same stem offline, before bake time.
  • A .glb without an armature is reported as a note during validation.
  • Apply reports restart required when the game already holds an older copy of the bundle. Verify refuses until you restart.
  • Package writes to %LOCALAPPDATA%\ContentTool\Packages\<projectId>\<yyyyMMdd-HHmmss>-<runId>.
  • The panel uses the same producers as ct_project, ct_route7 apply, ct_route7 verify and ct_package. Those console commands are unchanged and still documented.
  • The Lifecycle tab guide covers each stage.

Follow a real armour replacement

  • The worked armour-set example takes a PX Heavy torso, both legs and albedo from a Blender export to the final green run.
  • It includes the mistakes the tool caught: no armature, a nearest-bone export, a texture in the wrong folder, a 1-triangle shard, and a texture row naming an asset that does not exist.

Keep the verdict visible and the bench usable

  • A suspicious submesh, such as a tiny shard beside a real part, is now a WARNING instead of a bake failure. The run still tells you about it.
  • Long ct_project verdicts no longer blank the in-game console. At most 60 lines are printed, and the last lines, including ALL PASS or N FAILURE(S), always survive.
  • The full verdict goes to <persistentDataPath>\ContentTool\Logs\console-<stamp>.txt; the console names the file.
  • Bench gizmos and handles are drawn at output resolution from OnGUI, removing ghost trails under temporal upscalers such as DLSS via Renderforge.
  • The Doctor header no longer clips mid-word. Expected import refusals produce one warning line instead of an error with a stack trace.
  • Cancel stays reachable while a stage waits for the panel to paint. Switching tabs no longer trips an IMGUI layout error.

Install

  • Unzip so that Mods\ContentTool\ContentTool.dll and Mods\ContentTool\meta.json exist under your Phoenix Point Mods folder.
  • Enable ContentTool in the in-game mod manager, then restart.
  • Mods that depend on ContentTool list com.morgott.ContentTool in their meta.json Dependencies.

ContentTool 1.1.3

Choose a tag to compare

@UberMorgott UberMorgott released this 01 Sep 17:11

A reliability release. Nothing you write changes; what the tool tells you does.

Runs that used to die now finish, and name what was wrong

  • A replace row pointing at an asset that is not in the bundle used to kill the whole bake — no per-row line, no summary, and the rows already patched went unreported. Now that row alone is REFUSED, by name, and the line tells you to run ct_list assets <bundle> <type> to see what the bundle actually contains. All four kinds are covered: materials, animation clips, meshes, textures.
  • One malformed replace, sounds or publish row now costs you that row instead of the whole run.
  • A mistyped bundle name gives a plain REFUSED line instead of a FileNotFoundException.
  • A bad sound row no longer stops the bake at the first one — the run finishes and still cleans up after itself.

The tool no longer claims success for work it did not do

  • Every refusal is counted, so a run with omitted work can no longer end in ALL PASS.
  • A partial patch no longer calls itself "the last good bake", and no longer stamps the cache.
  • Same-name collisions and a negative scale are refused by name.
  • A sound another mod already owns, and a video that never actually served, now count as failures instead of quietly passing.
  • The bundle-redirect success line now says the change takes effect on the next load, rather than implying it is already live.

Packaging no longer poisons your output folder

  • An empty or brace-unbalanced ppcontent.json used to package as a success. It is now refused.
  • A sounds row with a file extension the tool does not accept now trips the NEVER BAKED check instead of being silently skipped.
  • An IO error partway through a copy used to leave a half-staged output folder that permanently refused every later run. It cleans up now.

Verified in a live game (build 76bf7b48): all 12 shipped demos re-baked with no pass-to-failure regression, and each fix confirmed against its own output line.

Install

Drop ContentTool.dll and meta.json into a ContentTool folder under the game's Mods directory, replacing the previous version.

ContentTool 1.1.2

Choose a tag to compare

@UberMorgott UberMorgott released this 01 Sep 17:26

Published after 1.1.3 to fill a gap in the numbering. 1.1.3 is the newest release — use that one.

A missing replace target no longer kills the bake

  • A replace row pointing at an asset that is not in the shipped bundle used to abort the entire bake: no per-row line, no ct_project: N FAILURE(S) summary, and the rows that had already been patched were reported nowhere. It looked like the tool simply gave up.
  • Now every replace kind — material, animation clip, mesh, texture — checks the target first. If it is missing or ambiguous, you get a REFUSED line naming that row, pointing you at ct_list assets <bundle> <type> to see what the bundle really contains. The row is counted as a failure and the run continues with the rest of your rows.

Install

Drop ContentTool.dll and meta.json into a ContentTool folder under the game's Mods directory, replacing the previous version.

ContentTool 1.1.1

Choose a tag to compare

@UberMorgott UberMorgott released this 01 Sep 07:41

A verification patch on top of 1.1.0. No behaviour of the importer or the baker changed; what changed is what is now measured, and one demo's documentation that was wrong.

Corrected numbers

  • ReplaceUiSounds stated the wrong replacement lengths. Its README and meta.json quoted 888 / 456 / 914 ms — the banks of 2026-08-27, before the .mp3s were regenerated. The shipped banks actually measure 340 / 444 / 601 ms. Both length columns are now read off the real files by tests\ObjCodecTests (DemoBankTests.cs): the replacement out of the shipped Dist\Sounds\<mediaId>.bnk, the shipped one out of the game's own .wem. A re-bake that changes a length now fails the test instead of leaving the table wrong.

Verified in the game

docs\PROVEN-FOUNDATIONS.md has nothing filed PENDING-INGAME any more. Closed this cycle, each with the reading behind it:

  • Animation clips — the uniform grid reaching the last key (U8-grid), a STEP curve's hold (U8-step), and a .glb whose clips are not all bakeable or not all uniquely named still baking (U9-plan). These paths no shipped file reaches, so they shipped a real fixture: lib\u9_probe.glb, read and baked by the bake self-check, so each row has two readings that can fail apart — what the importer made of the file, and what the engine made of the clip baked from it.
  • The loop flag (U12) and the chosen clip (U13) — read off a live actor's AnimatorOverrideController: the declared clips come back isLooping=True, the undeclared ones False, and the Animator drives the "play" clip even though it is deliberately not the file's first. The idle loop was also confirmed by eye in a live mission.
  • The drive verdict (U9-verdict) on its pass path, and the donor-shader path (U3b/U3c) at runtime.
  • Q1, the quit cutscene — the discriminating log line was finally captured, so it is the cutscene's own callback that quits the game, not the watchdog deadline.
  • A content mod that does not declare ContentTool as a dependency comes back on a cold launch through the startup reconcile alone (demos\NoDepTexture).
  • GUI_StatsPlusClick is pinned: the soldier progression screen's three stat + buttons (Willpower, Strength, Speed), found live rather than guessed — the event is wired by def, not by name in code, which is why it had zero hits in the decompile.

ContentTool 1.1.0

Choose a tag to compare

@UberMorgott UberMorgott released this 31 Aug 21:54

Import and bake fixes driven by a real modding session — a modder could not get his character models into the game, and every problem he hit turned out to be ours.

Skinning quality

  • Up to four bone influences per vertex. ContentTool used to keep only the two strongest weights and discard the rest. That was never a Phoenix Point or Unity requirement — the game itself ships meshes with both two and four, declared per mesh. Replacing one of its four-influence meshes actively downgraded it. A replacement now keeps whatever the target mesh declares, and a new model added from a .glb carries what the file really has. More than four is still unsupported and is refused rather than silently trimmed.
  • Per-bone bounds cover every influence. Bones weighted only in the third or fourth slot used to get empty or undersized boxes, which could make body parts cull or pop during animation.

Models that used to be refused or mangled

  • Body-part meshes exported with the rig now import. Each attachment joint hangs under a different bone, which is normal for that kind of export; the importer used to reject it and told you to run Object > Apply > All Transforms. That advice was wrong and could damage a rig — it is gone, along with the refusal. You do not need to apply transforms.
  • Models ripped from a live scene keep their weights. The game renames attachment bones at runtime to a decorated form, so no bone matched by name and every vertex was silently welded to its nearest bone. Those names are now understood.
  • A mesh with no armature is refused on a rigged character instead of being welded into something that collapses the moment it animates. Unrigged replacements for static objects, such as weapons, work exactly as before.

Baking

  • One unusable file no longer kills the whole build. Any refusal — a missing texture, a bad material, an unfound mesh — used to abandon that bundle and every bundle after it, producing no output at all while reporting a single failure. Problems are now reported and skipped, and the rest still bakes.
  • A failed bake can no longer leave an older bundle marked current, so you cannot unknowingly test stale content. Note the consequence: while a project still reports a failure, it re-bakes on every launch.
  • Refusal messages say what is actually wrong and where — a texture in the wrong folder now names the folder it is in and the one it belongs in.
  • New warning: if a source mesh contains a tiny leftover part (a stray triangle from cutting or optimising), it now tells you, because the game assigns materials by part order and a shard can push your real geometry onto the wrong material.

Workbench

  • The unit picker is visible again. The list of 791 templates was being pushed off the bottom of the panel, so the workbench appeared stuck on one model. Added ct_bench unit <name> to select one from the console.

Documentation

New guide: Replace vs Add — which one you want, why a replacement binds to the game's own skeleton, and why a Replace mod bakes once on the player's machine while an Add mod ships ready to run.

ContentTool 1.0.0

Choose a tag to compare

@UberMorgott UberMorgott released this 30 Aug 15:15

ContentTool is the content engine other Phoenix Point mods build on. A mod that depends on it can
replace what the game ships — textures, models, materials, animation curves, sounds, videos,
whole asset bundles — and add content the game never had, such as a new weapon or a new
creature. Everything is applied in memory while the game runs: your Phoenix Point installation is
never written to
, and removing the mod leaves nothing behind to undo.

On its own ContentTool changes nothing. You are most likely here because a mod you installed
requires it.

Install

  1. Download ContentTool-1.0.0.zip below.
  2. Copy the ContentTool folder into <Steam library>\steamapps\common\Phoenix Point\Mods\.
  3. Start the game, open Mods, tick Content Tool.

If a content mod declares ContentTool as a dependency, the mod manager enables it for you.

What is new in this release

A model off the internet can be a playable soldier — with no C# at all.
The HumanoidSoldier demo takes a foreign humanoid .glb, fits it onto Phoenix Point's own
skeleton, and the campaign starts you with it. The whole mod is a meta.json and a
ppcontent.json; there is no assembly, no Harmony patch, nothing to compile.

A shipped character can be given a new body. ReplaceCharacterBody swaps the body of a
character the game already ships, again from the manifest alone.

"startingRoster": true puts a code-less mod's unit in the roster the campaign begins with, so
a new soldier is playable from the first mission rather than something you have to spawn.

"useGameAnimations": true plays the game's own animation clips on your model. A mod that
sets it ships zero animation bytes — the walk, the aim, the reload all come from Phoenix Point.
This is new and not yet battle-tested: it is the fastest route to a moving character, but expect
rough edges on rigs that stray far from the shipped proportions.

The offline model pipeline — tools\ppskel.py (fit a foreign rig to the game's skeleton),
ClipCensus (sample the shipped clips), tools\ppretarget.py (retarget onto them),
tools\ppzip.py (compress). Together they cut the demo model by 65% with every clip kept.

Fixes that matter if you are building a .glb creature

  • The aimed shot that never came back. A null in AimIK on a custom-rigged creature froze the
    camera mid-shot and left the turn hanging. Fixed — an aimed shot on a foreign rig now resolves.
  • Animation events are inherited from the vanilla clips. Without them a custom body played its
    animation silently and without effect: no footstep, no muzzle flash, no hit frame. Your model now
    gets the events the original clip carried.

Notes

  • Sounds are the one thing that cannot be taken back mid-session. Unticking a mod leaves a replaced
    sound loaded until you restart; restarting is the clean undo.
  • One shipped resource has exactly one owning mod. Two mods claiming the same one is refused by
    name
    in your player log, never silently.
  • If a mod's content does not appear, the reason is a named line in Player.log — search it for
    ct_.

Documentation for modders: https://ubermorgott.github.io/PhoenixPoint-Mod-ContentTool/