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
.gzdata files into individual JSON assets - Sprite extraction — Extract individual sprites from texture atlas sheets as PNG
- Dialogue export — Reconstruct
.gumscripts 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
.appbundle paths directly - Binary analysis — Detect .NET deployment format (NativeAOT, single-file, self-contained)
- Repacking — Repack modified assets back into
.gzformat - Plugin system — Extend with drop-in
.pyfiles 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)
| File | Size | Uploaded | |
|---|---|---|---|
| murder_unpack-0.2.5.tar.gz | 88.9 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| 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 logRelease 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