Skip to main content

Decompile, unpack, recover, and repack tools for Murder Engine games

Project description

murder-unpack

Reverse-engineer exported Murder Engine games back into editor-openable projects. Extracts sprites, dialogues, world data, and more.

Features

  • Project recovery — Reconstruct a Murder Engine editor project from an exported game
  • C# decompilation — Full source recovery from managed single-file bundles via bundled per-type decompiler (ilspycmd fallback)
  • C# stub generation — Fallback: auto-generate typed C# classes from packed JSON data (NativeAOT games)
  • Per-game fixes — Auto-detected decompiler artifact fixes with extensible registry
  • Asset extraction — Unpack .gz data files into individual JSON assets
  • Sprite extraction — Extract individual sprites from texture atlas sheets as PNG
  • Dialogue export — Reconstruct .gum scripts and export to markdown
  • Localization export — Export localization CSV files matching Murder's editor format
  • Engine version detection — Per-game lookup for known games, fingerprint detection for unknown games
  • macOS .app support — All commands accept .app bundle paths directly
  • Binary analysis — Detect .NET deployment format (NativeAOT, single-file, self-contained)
  • Repacking — Repack modified assets back into .gz format
  • Plugin system — Extend with drop-in .py files or pip-installable packages

Quick Start

Requirements

  • Python 3.11+
  • uv (recommended) or pip
  • Git (for engine cloning)
  • .NET SDK matching the game's engine version (net7.0 or net8.0; for building recovered projects and the bundled decompiler)

Install

# With uv (recommended)
uv tool install murder-unpack

# Or from source
git clone https://github.com/yuna0x0/murder-unpack.git
cd murder-unpack
uv sync

Usage

# Show game info and detected engine version
murder-unpack info "path/to/game"

# Extract all data, sprites, dialogues, and localization
murder-unpack extract-all "path/to/game" output/

# Recover into a full editor project
murder-unpack recover "path/to/game" recovered/

# List assets with optional filters
murder-unpack list-assets "path/to/game" --type WorldAsset

Commands

Command Description
info Show game info, asset counts, detected engine version
extract-all Full extraction: data, sprites, dialogues, localization
extract-data Dump all .gz data files as plain JSON
extract-sprites Extract sprites from atlas sheets as PNG
extract-dialogue Export dialogues as .gum scripts, markdown, or both
list-assets List assets with --type and --name filters
decode-qoi Convert a single QOI image to PNG
recover Full editor project recovery
engine-versions List available Murder Engine branches and tags
repack Repack modified assets back into .gz format
analyze-binary Detect .NET format, extract types, decompile
plugins List loaded plugins and plugin directories

Recovery Options

murder-unpack recover "path/to/game" recovered/ \
    --engine-version rel/11.0 \    # Override auto-detected version (branch/tag/commit)
    --engine-repo https://...  \   # Use a fork or custom engine repo
    --game-name MyGame \           # Project name (auto-detected)
    --engine-path /path/to/murder  # Use existing engine clone
    --skip-engine \                # Don't clone engine
    --no-stubs \                   # Skip C# stub/decompilation
    --decompile-timeout 1200       # Decompilation timeout (default: 600)
    --game-fix neverway            # Per-game fix (auto-detected, 'none' to skip)

Per-Game Fixes

Decompiled code sometimes has game-specific issues that can't be fixed generically (lost tuple element names, readonly field assignments, duplicate local functions). The fix registry auto-detects the game and applies known fixes.

Detection uses: assembly name, game namespace, Steam App ID, or game_config $type. Each game fix can also provide the exact engine commit for accurate recovery.

Adding a fix for a new game

Create murder_unpack/fixes/my_game.py:

from murder_unpack.fixes import GameFix, Replacement

FIX = GameFix(
    id="my-game",
    name="My Game",
    assembly_names=["MyGame"],
    steam_app_ids=["123456"],
    engine_version="abc123...",  # exact engine commit (optional)
    replacements=[
        Replacement(
            file_glob="**/SomeFile.cs",
            old="broken code",
            new="fixed code",
            description="CS1234: description of the issue",
        ),
    ],
)

Register in murder_unpack/fixes/__init__.py:

def _load_builtin_fixes() -> None:
    from murder_unpack.fixes import my_game
    _registry.register(my_game.FIX)

Or register via plugin:

def register(registry):
    from murder_unpack.fixes import get_registry
    get_registry().register(my_fix)

Plugin System

Plugins extend murder-unpack with custom asset handlers, extractors, commands, and hooks.

Drop-in plugins — Place .py files in ~/.murder-unpack/plugins/ or ./plugins/:

# plugins/my_plugin.py
def register(registry):
    registry.asset_handlers["my_handler"] = MyHandler()

class MyHandler:
    name = "my_handler"
    asset_types = ["Custom.Assets.MyAsset"]

    def export(self, asset, output_path):
        output_path.write_text(str(asset))

Pip-installable plugins — Use entry points in pyproject.toml:

[project.entry-points."murder_unpack.asset_handlers"]
my_handler = "my_plugin:MyHandler"

[project.entry-points."murder_unpack.commands"]
my_cmd = "my_plugin.cli:my_command"

Available extension points: asset_handlers, extractors, commands, hooks (pre_extract, post_extract, pre_recover, post_recover)

Limitations

C# Source Recovery

  • Managed single-file bundles — Full source recovery via bundled decompile-helper (per-type decompilation with timeouts). Falls back to ilspycmd, then to stub generation.
  • NativeAOT binaries — Cannot be decompiled. Recovery generates C# stubs for compilation but without behavior.

With full decompilation, the recovery uses the decompiled game class directly and applies targeted compatibility fixes: initset on engine types that cause CS8852 errors (detected via trial build), readonly removal on class fields for JSON deserialization, and JsonStringEnumConverter for string-based enum keys.

Engine Version Detection

For known games, the fix registry provides the exact engine commit. For unknown games, fingerprint detection covers rel/3.6 through rel/11.0 (some ranges are indistinguishable, e.g. rel/8.0-10.0 default to rel/10.0). Use --engine-version to override with a branch, tag, or commit hash.

Dialogue Reconstruction

.gum script reconstruction from compiled dialogue graphs is best-effort. Semantic content is preserved; original formatting may differ.

Development

git clone https://github.com/yuna0x0/murder-unpack.git
cd murder-unpack
uv sync

License

MIT - yuna0x0

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

murder_unpack-0.2.3.tar.gz (88.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

murder_unpack-0.2.3-py3-none-any.whl (72.1 kB view details)

Uploaded Python 3

File details

Details for the file murder_unpack-0.2.3.tar.gz.

File metadata

  • Download URL: murder_unpack-0.2.3.tar.gz
  • Upload date:
  • Size: 88.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for murder_unpack-0.2.3.tar.gz
Algorithm Hash digest
SHA256 d7bb88af9ba2f8a5ba6af50bcda52271bc68d6366be97b18e8a4be590ebf0ddc
MD5 5fe9b5fcbb295bad023b80bd70a7dffc
BLAKE2b-256 c19de26cfae235e7f3dec4f91d25d10a82b3ab53b2b98003512b2c333271b28e

See more details on using hashes here.

Provenance

The following attestation bundles were made for murder_unpack-0.2.3.tar.gz:

Publisher: release.yml on yuna0x0/murder-unpack

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file murder_unpack-0.2.3-py3-none-any.whl.

File metadata

  • Download URL: murder_unpack-0.2.3-py3-none-any.whl
  • Upload date:
  • Size: 72.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for murder_unpack-0.2.3-py3-none-any.whl
Algorithm Hash digest
SHA256 031d1c91bedf937031d701965302f9f26c65f9d1f6a365cf08825c847a83b9ec
MD5 017a20057278035113e2753843862bc4
BLAKE2b-256 ad3b22c3cb7ca02dd9a7463bdf0f36866a73dd46aca6f46d2d2e4c8204965d1c

See more details on using hashes here.

Provenance

The following attestation bundles were made for murder_unpack-0.2.3-py3-none-any.whl:

Publisher: release.yml on yuna0x0/murder-unpack

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page