Skip to content

Building from Source

Zaldaryon edited this page Jul 28, 2026 · 9 revisions

Building from Source

This page is for developers who want to modify Optimum. End users should use the Installation page instead — no build step is needed to use Optimum.

Requirements

  • .NET 10 SDK
  • ilspycmd from 10.1.0.8386 through 10.1.1.8388, inclusive; the build prefers 10.1.1.8388 and rejects versions outside the range
  • bash (Linux/WSL/Git Bash)
  • python3, git, curl, perl

Quick Start (Linux / WSL / Git Bash)

git clone https://github.com/Zaldaryon/Optimum.git
cd Optimum
make build    # downloads ~570MB client, decompiles, clones forks, patches, compiles
make test     # runs the Optimum.Tests and Optimum.Launcher.Tests suites (400+ cases)
make run      # deploys DLLs + shaders to .vanilla/ and launches the client

The first make build takes 2-5 minutes depending on download speed. Subsequent builds take 5-10 seconds.

Quick Start (Windows PowerShell)

git clone https://github.com/Zaldaryon/Optimum.git
cd Optimum
.\scripts\bootstrap.ps1
dotnet build VintageStory.slnx -c Release

The bootstrap script downloads the official Windows installer, extracts it with Inno Setup 6, decompiles with ILSpy, clones the open-source forks at pinned refs, and applies patches.

What Bootstrap Does

  1. Downloads the official Vintage Story 1.22.5 client archive for your platform.
  2. Extracts it into .vanilla/.
  3. Decompiles VintagestoryLib.dll and Vintagestory.dll with ilspycmd into build/.
  4. Runs post-decompile fixup regex (ref-casts, GeneratedRegex, ErrorCallback alias, ambiguous calls).
  5. Creates .baseline/ snapshot (unmodified decompiled source for diff generation).
  6. Clones the open-source forks (vsapi, Cairo, vsessentialsmod, vssurvivalmod, vscreativemod) at the refs pinned in forks.json.
  7. Applies the patches in patches/ over both the decompiled and fork sources (111 patch files, of which 20 under patches/runtime/ target the ILSpy-decompiled layout the launcher transplants at runtime).
  8. Copies Optimum-original sources from sources/ into the working tree.

Project Structure

Optimum/
├── Optimum.Launcher/          ← Runtime launcher (outputs Optimum.exe): cache, splash, assembly loading
├── Optimum.Launcher.Tests/    ← Launcher tests (cache, packaging, solution integrity)
├── Optimum.Patcher/           ← Cecil IL patcher: engine (Program.cs), API (api-patcher.cs), mods (mod-patcher.cs)
├── Optimum.Tests/             ← xunit test suite
├── optimum-api-contracts/     ← Optimum bridge methods called from patched vanilla API code
├── optimum-game-content/      ← Optimum runtime mod system (status command, diagnostics)
├── patches/                   ← 111 patch files (generated by extract-patches.sh)
│   ├── runtime/                ← Patches against the ILSpy-decompiled layout, applied at launch
│   └── cecil-owned.list        ← Patches that ship via Cecil transplant
├── sources/                   ← Optimum-original source files, shaders and lang strings
├── scripts/                   ← Build, package, install scripts
├── build/                     ← Decompiled VintagestoryLib + Vintagestory (gitignored)
├── .baseline/                 ← Clean decompiled snapshot for diff (gitignored)
├── .vanilla/                  ← Vanilla client archives (gitignored)
├── VintagestoryApi/           ← Forked API project (gitignored, populated by bootstrap)
├── VSEssentials/               ← Forked essentials mod (gitignored)
├── VSSurvivalMod/               ← Forked survival mod (gitignored)
└── VSCreativeMod/               ← Forked creative mod (gitignored)

The Patcher

The patcher has three entry points, one per shipping path:

Optimum.Patcher/Program.cs — the engine transplant into VintagestoryLib.dll:

  • 4 types to inject (OptimumInfo, OptimumUpdateChecker, EntityLightBatchBuffer, OptimumGreedyMeshEmitter)
  • 112 member entries to inject into existing types (fields, methods, properties)
  • 52 methods to transplant (replace a vanilla method body with the optimized version)
  • 1 IL hook (inserts _AddOptimumTab into GuiCompositeSettings.ComposerHeader)

Optimum.Patcher/api-patcher.cs — ABI-safe rules against VintagestoryAPI.dll: two inventory dirty hooks, the chisel LOD hook, the chisel LOD shadow-pass hook, the logger static-initializer rewrite, and the GameVersion.LongGameVersion label rewrite.

Optimum.Patcher/mod-patcher.csEssentialsManifest() and SurvivalManifest(), naming the individual types, members and methods injected into VSEssentials.dll and VSSurvivalMod.dll.

A method containing a lambda or LINQ predicate cannot be transplanted. The C# compiler caches those as static delegates on a generated <>c type; injection clones only the named method, so the transplanted IL references something that does not exist and SelfConsistencyVerifier refuses to write output. Rewrite as an explicit loop first. Likewise, a Cecil-injected field never runs its C# initializer, because injection adds the field slot without constructor IL — use a local, or lazily allocate with ??= at the use site.

Deploy and Run

make deploy runs the Cecil IL patcher and copies results into .vanilla/:

make deploy   # patch + copy
make run      # deploy + launch

Tests

make test

Runs the xunit suites (400+ cases across Optimum.Tests and Optimum.Launcher.Tests). Tests verify patch logic, data structures, and IL compatibility without requiring a bootstrapped working tree.

Patch Workflow

To add a new optimization:

  1. Make the change in the appropriate project (VintagestoryApi/, build/VintagestoryLib/, etc.).
  2. make build — verify compilation.
  3. make test — verify tests pass.
  4. If it's a VintagestoryLib change: add the method to Optimum.Patcher/Program.cs targets.
  5. scripts/extract-patches.sh — regenerate patches from working tree vs baseline.
  6. scripts/check-patches.sh — verify 0 conflicts, 0 pending, 0 orphans.
  7. make run — verify in-game.

build/VintagestoryLib and build/Vintagestory are regenerated from the cached decompile plus patches/ on every bootstrap. Editing them directly without re-running scripts/extract-patches.sh is invisible until the next fresh bootstrap, which silently reverts the edit.

Makefile Targets

Target Description
make check Report installed tools (installs nothing)
make bootstrap Download, decompile, clone, patch
make build Bootstrap if needed, then compile
make clean Remove obj/ and bin/ from project dirs
make refresh Force full re-bootstrap
make patches Regenerate patches/ from the working tree
make deploy Run Cecil patcher, copy DLLs + shaders into .vanilla/
make run Build, deploy, launch client
make test Run unit tests
make package Build all release archives
make package-linux Linux tar.gz
make package-appimage Linux AppImage
make package-macos macOS .dmg
make package-win Windows zip

Clone this wiki locally