Skip to main content

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: init → set 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

AI Disclosure

AI was used to assist in the creation of some of this tool's base code.

License

MIT - yuna0x0

Metadata

Release files for murder-unpack 0.2.5

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for murder-unpack 0.2.5
File Size Uploaded
murder_unpack-0.2.5.tar.gz 88.9 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for murder-unpack 0.2.5
File Interpreter ABI Platform
murder_unpack-0.2.5-py3-none-any.whl Python 3 none any Details

Total release size: 160.9 kB

Release files / murder_unpack-0.2.5.tar.gz

Download URL murder_unpack-0.2.5.tar.gz
Size 88.9 kB
Tags Source
SHA-256 checksum
How to use checksums
24cb975677798aa645c0267e7094dd94075686418ee0353899d3b797605070a5
BLAKE2b-256 checksum
How to use checksums
26d38673f32d41d7331bf8fc64005028123634c782016fb39ace279a4496f39f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Apr 24, 2026.

Transparency log

Release files / murder_unpack-0.2.5-py3-none-any.whl

Download URL murder_unpack-0.2.5-py3-none-any.whl
Size 72.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
df05606ad478f350f2df458fc9b7a64da1272710052bd38655e27812feb7b10e
BLAKE2b-256 checksum
How to use checksums
d00196ec918dd2bd05d4e4b4749e3d7c8605c359018f29c5a140cc0b37535897
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Apr 24, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.2.5 This release

2 release files

0.2.4

2 release files

0.2.3

2 release files

0.2.2

2 release files

0.2.1

2 release files

0.2.0

2 release files

0.1.9

2 release files

0.1.8

2 release files

0.1.7

2 release files

0.1.6

2 release files

0.1.5

2 release files

0.1.4

2 release files

0.1.3

2 release files

0.1.2

2 release files

0.1.1

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page