Skip to content

Commit dae9a26

Browse files
committed
minecraft-sound: Minecraft sound CLI with Nix-fetched sound pack
Add packages/minecraft/sound, a workspace crate whose `minecraft-sound` binary plays Minecraft sounds from the CLI (`play mob/zombie/death`, `list zombie`). The sounds are not vendored: sounds.nix is a fixed-output derivation that downloads the sound effects from Mojang's CDN at build time (music and record discs excluded to keep the closure ~75MB), pinned by sounds/lock.json (asset index + aggregate hash). Refresh with `nix run .#update-sounds`. The built pack is Mojang's proprietary content and is documented as do-not-upload to any shared/public cache. The binary is wrapped with MCSOUND_ASSETS (--set-default, overridable) so it plays sounds with zero config and no Minecraft install; assets.rs still falls back to a real Minecraft/MINECRAFT_HOME install otherwise. Surfaced via the overlay as pkgs.minecraft-sound. The shared rust workspace gets pkg-config + the ALSA pkg-config path on Linux so rodio/cpal/alsa-sys links there; darwin uses CoreAudio with no extra deps.
1 parent 793fa41 commit dae9a26

14 files changed

Lines changed: 1185 additions & 25 deletions

File tree

Cargo.lock

Lines changed: 586 additions & 25 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ members = [
88
"packages/ix-dev-diagnose",
99
"packages/mcp",
1010
"packages/minecraft/nbt",
11+
"packages/minecraft/sound",
1112
"packages/minecraft/sync-managed",
1213
"packages/nix-cargo-unit",
1314
"packages/nix-web-monitor/parser",
@@ -48,6 +49,7 @@ chrono = { version = "0.4", default-features = false }
4849
clap = "4"
4950
code-tokenizer = { path = "packages/code-tokenizer" }
5051
color-eyre = "0.6"
52+
dirs = "5"
5153
futures = "0.3"
5254
hegeltest = { version = "0.14", default-features = false }
5355
ignore = "0.4"
@@ -73,6 +75,7 @@ quartz_nbt = "0.2.9"
7375
repo-walker = { path = "packages/repo-walker" }
7476
rmcp = "1.7"
7577
rmp-serde = "1"
78+
rodio = { version = "0.19", default-features = false, features = ["vorbis"] }
7679
rustls = { version = "0.23", default-features = false, features = ["ring", "std", "tls12"] }
7780
rustls-native-certs = "0.8"
7881
schemars = "1"

flake.nix

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@
8888
tools = {
8989
ixShellSyncIgnored = ./tools/ix-shell-sync-ignored.py;
9090
mcSource = ./tools/mc-source.nu;
91+
updateSounds = ./tools/update-sounds.nu;
9192
updateIxCli = ./tools/update-ix-cli.py;
9293
updateLoaders = ./tools/update-loaders.py;
9394
updateMods = ./tools/update-mods.py;

lib/per-system.nix

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,12 @@ let
138138
meta.description = "Decompile a Minecraft server jar with Mojang mappings via Vineflower";
139139
};
140140

141+
updateSounds = ix.writeNushellApplication pkgs {
142+
name = "update-sounds";
143+
text = builtins.readFile paths.tools.updateSounds;
144+
meta.description = "Refresh the pinned Minecraft sound pack in packages/minecraft/sound";
145+
};
146+
141147
benchFilesystem = import paths.bench.filesystem { inherit ix pkgs; };
142148

143149
siteSrc = fs.toSource {
@@ -335,6 +341,7 @@ in
335341
update-ix-cli = updateIxCli;
336342
ix-shell-sync-ignored = ixShellSyncIgnored;
337343
mc-source = mcSource;
344+
update-sounds = updateSounds;
338345
}
339346
// repoFlakePackages
340347
// examplePackages

lib/rust-workspace.nix

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,14 @@ let
6262
];
6363
packageTestInputs.tui = [ workspacePkgs.vim ];
6464
packageTestInputs.ix-mcp = [ workspacePkgs.python3 ];
65+
# `rodio` (packages/minecraft/sound) pulls `cpal`/`alsa-sys`, whose build
66+
# script needs ALSA's pkg-config metadata to link `libasound` on Linux.
67+
# Scoped to the whole workspace because the unit graph compiles every
68+
# member on every system; darwin uses CoreAudio and needs nothing extra.
69+
nativeBuildInputs = lib.optional workspacePkgs.stdenv.hostPlatform.isLinux workspacePkgs.pkg-config;
70+
env = lib.optionalAttrs workspacePkgs.stdenv.hostPlatform.isLinux {
71+
PKG_CONFIG_PATH = "${workspacePkgs.alsa-lib.dev}/lib/pkgconfig";
72+
};
6573
# Every policy check runs once across the whole workspace. Selected
6674
# package outputs expose these as explicit tests instead of making
6775
# downstream binary builds depend on unrelated workspace policy.
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
[package]
2+
name = "minecraft-sound"
3+
version.workspace = true
4+
edition.workspace = true
5+
publish.workspace = true
6+
7+
[lints]
8+
workspace = true
9+
10+
[dependencies]
11+
anyhow.workspace = true
12+
clap = { workspace = true, features = ["derive"] }
13+
dirs.workspace = true
14+
rodio.workspace = true
15+
serde = { workspace = true, features = ["derive"] }
16+
serde_json.workspace = true
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
{
2+
ix,
3+
callPackage,
4+
symlinkJoin,
5+
makeWrapper,
6+
}:
7+
let
8+
bin = ix.cargoUnit.selectBinaryWithTests ix.rustWorkspace.units {
9+
binary = "minecraft-sound";
10+
meta.mainProgram = "minecraft-sound";
11+
};
12+
13+
# Mojang sound assets fetched at build time. See sounds.nix for the
14+
# licensing / do-not-upload constraints on this store path.
15+
sounds = callPackage ./sounds.nix { };
16+
in
17+
symlinkJoin {
18+
name = "minecraft-sound";
19+
paths = [ bin ];
20+
nativeBuildInputs = [ makeWrapper ];
21+
22+
# Bake the fetched sound pack in so the tool plays sounds with zero config and
23+
# no Minecraft install. `--set-default` keeps MCSOUND_ASSETS overridable at
24+
# runtime (e.g. to point at a real Minecraft / Prism install instead).
25+
postBuild = ''
26+
wrapProgram $out/bin/minecraft-sound \
27+
--set-default MCSOUND_ASSETS ${sounds}/sounds
28+
'';
29+
30+
inherit (bin) meta passthru;
31+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
id = "minecraft-sound";
3+
packageSet = true;
4+
flake = true;
5+
inRustWorkspace = true;
6+
passthruTests = true;
7+
overlay = true;
8+
}
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
# Minecraft sound asset pack.
2+
#
3+
# !!! LICENSING / DO NOT UPLOAD !!!
4+
# The `.ogg` files this derivation produces are Mojang's proprietary Minecraft
5+
# assets (governed by the Minecraft EULA). This is a fixed-output derivation
6+
# that DOWNLOADS them from Mojang's official CDN at build time on the building
7+
# machine; it does not vendor them in this repo. The resulting store path
8+
# therefore contains Mojang content and MUST NOT be pushed to any shared or
9+
# public binary cache (e.g. `indexable-inc.cachix.org`), nor included in any
10+
# image closure that gets cached or distributed. Each machine that wants
11+
# sounds builds this path locally from Mojang. Keep it out of CI cache pushes.
12+
{
13+
lib,
14+
stdenvNoCC,
15+
fetchurl,
16+
cacert,
17+
curl,
18+
jq,
19+
}:
20+
let
21+
lock = lib.importJSON ./sounds/lock.json;
22+
23+
# The asset index lists every sound object with its content hash. Pinned by
24+
# sha1 (the hash Mojang publishes for it), so this fetch is reproducible.
25+
assetIndex = fetchurl {
26+
inherit (lock.assetIndex) url hash;
27+
};
28+
29+
# Sound names whose leading path segment matches are dropped. Defaults to the
30+
# multi-megabyte background music and music-disc tracks, which a sound-effect
31+
# player never needs. Widen `excludePrefixes` in lock.json to trim more.
32+
excludeRegex = "^(" + lib.concatStringsSep "|" lock.excludePrefixes + ")/";
33+
in
34+
stdenvNoCC.mkDerivation {
35+
pname = "minecraft-sound-assets";
36+
version = lock.minecraftVersion;
37+
38+
dontUnpack = true;
39+
strictDeps = true;
40+
nativeBuildInputs = [
41+
curl
42+
jq
43+
];
44+
45+
SSL_CERT_FILE = "${cacert}/etc/ssl/certs/ca-bundle.crt";
46+
47+
# Fixed-output derivation: network access is permitted because the whole
48+
# assembled tree is content-addressed by `outputHash`, which is the integrity
49+
# guarantee for every downloaded file together. Refresh it with
50+
# `nix run .#update-sounds` after bumping the pinned Minecraft version.
51+
outputHashMode = "recursive";
52+
outputHashAlgo = "sha256";
53+
outputHash = lock.packHash;
54+
55+
buildPhase = ''
56+
runHook preBuild
57+
58+
list="$TMPDIR/sounds.tsv"
59+
jq -r --arg ex ${lib.escapeShellArg excludeRegex} '
60+
.objects
61+
| to_entries[]
62+
| select((.key | startswith("minecraft/sounds/")) and (.key | endswith(".ogg")))
63+
| (.key | ltrimstr("minecraft/sounds/")) as $name
64+
| select(($name | test($ex)) | not)
65+
| "\(.value.hash) \($name)"
66+
' ${assetIndex} > "$list"
67+
68+
echo "downloading $(wc -l < "$list") Minecraft sounds from Mojang's CDN..."
69+
70+
mkdir -p "$out/sounds"
71+
cut -d' ' -f2 "$list" | xargs -n1 dirname | sort -u | while read -r dir; do
72+
mkdir -p "$out/sounds/$dir"
73+
done
74+
75+
export out
76+
xargs -P 16 -n 2 bash -c '
77+
hash="$1"; name="$2"
78+
curl -sSfL --retry 3 \
79+
"https://resources.download.minecraft.net/''${hash:0:2}/$hash" \
80+
-o "$out/sounds/$name"
81+
' _ < "$list"
82+
83+
runHook postBuild
84+
'';
85+
86+
dontInstall = true;
87+
88+
meta = {
89+
description = "Minecraft sound effects (downloaded from Mojang's CDN at build time)";
90+
# License intentionally omitted: these are Mojang's proprietary assets.
91+
# See the DO NOT UPLOAD banner at the top of this file.
92+
};
93+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"minecraftVersion": "26.1.2",
3+
"assetIndexId": "30",
4+
"assetIndex": {
5+
"url": "https://piston-meta.mojang.com/v1/packages/2866ab0411f8507bf88d81513a08956a2474403f/30.json",
6+
"hash": "sha1-KGarBBH4UHv4jYFROgiVaiR0QD8="
7+
},
8+
"excludePrefixes": [
9+
"music",
10+
"records"
11+
],
12+
"packHash": "sha256-Km+bbcgu5+x7PYLHEZlJftq8bym+uYlXNCenQk/Zig8="
13+
}

0 commit comments

Comments
 (0)