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
.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 — Auto-detect engine version from game_config fingerprinting
- 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 8 SDK (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
--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.
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"],
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
Covers rel/3.6 through rel/11.0. Some version ranges are indistinguishable (rel/8.0–10.0 default to rel/10.0). Use --engine-version to override.
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
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file murder_unpack-0.2.2.tar.gz.
File metadata
- Download URL: murder_unpack-0.2.2.tar.gz
- Upload date:
- Size: 88.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
04f90db794046227fd0db92e181218336700e8f08135c1be46ba35013ae2b83a
|
|
| MD5 |
e3cafdb43cb2319f28b328c696c505de
|
|
| BLAKE2b-256 |
dadade25acb33056fe326a3d9391136ea03eef07d47cd8320d3fd853e9da85f5
|
Provenance
The following attestation bundles were made for murder_unpack-0.2.2.tar.gz:
Publisher:
release.yml on yuna0x0/murder-unpack
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
murder_unpack-0.2.2.tar.gz -
Subject digest:
04f90db794046227fd0db92e181218336700e8f08135c1be46ba35013ae2b83a - Sigstore transparency entry: 1296623540
- Sigstore integration time:
-
Permalink:
yuna0x0/murder-unpack@0d097304da57cbb5ebae61a111bbd55d977c0172 -
Branch / Tag:
refs/tags/v0.2.2 - Owner: https://github.com/yuna0x0
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@0d097304da57cbb5ebae61a111bbd55d977c0172 -
Trigger Event:
push
-
Statement type:
File details
Details for the file murder_unpack-0.2.2-py3-none-any.whl.
File metadata
- Download URL: murder_unpack-0.2.2-py3-none-any.whl
- Upload date:
- Size: 71.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
00adf9ea11c322ce8f9493eed4b5fad0b320d808125a6dea326428d8c0b0a10f
|
|
| MD5 |
83b6a352ca8fd2d97d7d1c8ee617de19
|
|
| BLAKE2b-256 |
35927df4230cc227f328d0ffd49181ca6dab74f7f13152a8f6f37c6068254666
|
Provenance
The following attestation bundles were made for murder_unpack-0.2.2-py3-none-any.whl:
Publisher:
release.yml on yuna0x0/murder-unpack
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
murder_unpack-0.2.2-py3-none-any.whl -
Subject digest:
00adf9ea11c322ce8f9493eed4b5fad0b320d808125a6dea326428d8c0b0a10f - Sigstore transparency entry: 1296623647
- Sigstore integration time:
-
Permalink:
yuna0x0/murder-unpack@0d097304da57cbb5ebae61a111bbd55d977c0172 -
Branch / Tag:
refs/tags/v0.2.2 - Owner: https://github.com/yuna0x0
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@0d097304da57cbb5ebae61a111bbd55d977c0172 -
Trigger Event:
push
-
Statement type: