Skip to content

Instantly share code, notes, and snippets.

@eyJhb
Forked from danbst/README.adoc
Last active August 14, 2023 09:41
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save eyJhb/623c723ddf068a1c8de26e1ca467f002 to your computer and use it in GitHub Desktop.
Save eyJhb/623c723ddf068a1c8de26e1ca467f002 to your computer and use it in GitHub Desktop.
Pure Nix Minecraft launcher. For every MC version!

Pure Nix Minecraft launchers

  1. Download .nix file

  2. Run:

    $ nix run -f all-minecrafts.nix versions.v1_8_9.client -c minecraft
  3. Enjoy (…​power of fixed-output derivations)!

Internally, minecraft consists of four entities: client .jar, libraries, native libraries and assets. The list of required files is distributed through https://launchermeta.mojang.com/mc/game/version_manifest.json file.

What is cool, is that this file (and it’s references) include hash (SHA1), which allows us to use fixed-output derivations without any pregeneration. We still rely on import-from-derivation, however it is called differently now — fromJson (readFile (fetchurl …​)).

It is possible to remove IFD using offline argument. You should download 3 essential files though. Check source if you want to go this route.

{ pkgs ? import <nixpkgs> {}
, lib ? pkgs.lib
, OS ? "linux" }:
with lib;
let
mcManifest = pkgs.fetchurl {
url = "https://piston-meta.mojang.com/mc/game/version_manifest_v2.json";
sha256 = "sha256-YUjob6jjXWpU+LpOjz64HYrc2xYEt4zSvKfSlfOc0xY=";
};
getJSON = data: builtins.fromJSON (builtins.readFile data);
# builtins.fetchurl doesn'support 'sha1' hash. Which is strange, because it should
# I've tried to overcome this with:
# # fetchurlPath = builtins.toPath pkgs.nix + "/share/nix/corepkgs/fetchurl.nix";
# # builtins_fetchurl = import fetchurlPath;
# but looks like builtins.fetchurl wants some other kind of hash. So we stick with pkgs.fetchurl
builtins_fetchurl = pkgs.fetchurl;
buildMc = versionInfo: assetsIndex:
let
client = builtins_fetchurl {
inherit (versionInfo.downloads.client) url sha1;
};
isAllowed = artifact:
let
lemma1 = acc: rule:
if rule.action == "allow"
then if rule ? os then rule.os.name == OS else true
else if rule ? os then rule.os.name != OS else false;
in if artifact ? rules
then foldl' lemma1 false artifact.rules
else true;
artifacts = lib.filter isAllowed versionInfo.libraries;
libPath = lib.makeLibraryPath [
pkgs.libpulseaudio
pkgs.xorg.libXcursor
pkgs.xorg.libXrandr
pkgs.xorg.libXxf86vm # Needed only for versions <1.13
pkgs.libGL
];
in pkgs.runCommand "minecraft-client-${versionInfo.id}" {
version = versionInfo.id;
buildInputs = [
pkgs.unzip
pkgs.makeWrapper
];
} ''
mkdir -p $out/bin $out/assets/indexes $out/libraries $out/natives
ln -s ${client} $out/libraries/client.jar
# Java libraries
${concatMapStringsSep "\n" (artif:
let library = builtins_fetchurl {
inherit (artif.downloads.artifact) url sha1;
};
in ''
mkdir -p $out/libraries/${builtins.dirOf artif.downloads.artifact.path}
ln -s ${library} $out/libraries/${artif.downloads.artifact.path}
'') (filter (x: !(x.downloads ? "classifiers")) artifacts)}
# Native libraries
${concatMapStringsSep "\n" (artif:
let library = builtins_fetchurl {
inherit (artif.downloads.classifiers.${artif.natives.${OS}}) url sha1;
};
in ''
unzip ${library} -d $out/natives && rm -rf $out/natives/META-INF
'') (filter (x: (x.downloads ? "classifiers")) artifacts)}
# assets
${concatStringsSep "\n" (builtins.attrValues (flip mapAttrs assetsIndex.objects (name: a:
let asset = builtins_fetchurl {
sha1 = a.hash;
url = "https://resources.download.minecraft.net/" + hashTwo;
};
hashTwo = builtins.substring 0 2 a.hash + "/" + a.hash;
outPath = if versionInfo.assets == "legacy"
then "$out/assets/virtual/legacy/${name}"
else "$out/assets/objects/${hashTwo}";
in ''
mkdir -p ${builtins.dirOf outPath}
ln -sf ${asset} ${outPath}
'')))}
ln -s ${builtins.toFile "assets.json" (builtins.toJSON assetsIndex)} \
$out/assets/indexes/${versionInfo.assets}.json
# Launcher
makeWrapper ${pkgs.jre}/bin/java $out/bin/minecraft \
--add-flags "\$JRE_OPTIONS" \
--add-flags "-Djava.library.path='$out/natives'" \
--add-flags "-cp '$(find $out/libraries -name '*.jar' | tr -s '\n' ':')'" \
--add-flags "${versionInfo.mainClass}" \
--add-flags "--version ${versionInfo.id}" \
--add-flags "--assetsDir ${if versionInfo.assets == "legacy" then "$out/assets/virtual/legacy" else "$out/assets"}" \
--add-flags "--assetIndex ${versionInfo.assets}" \
--add-flags "--accessToken foobarbaz" \
--prefix LD_LIBRARY_PATH : "${libPath}"
'';
prepareMc = v: rec {
versionDoc = v;
versionInfo = getJSON (pkgs.fetchurl {
url = versionDoc.url;
sha1 = versionDoc.sha1;
});
assetsIndex = getJSON (pkgs.fetchurl {
url = versionInfo.assetIndex.url;
sha1 = versionInfo.assetIndex.sha1;
});
client = buildMc versionInfo assetsIndex;
server = pkgs.fetchurl {
url = versionInfo.downloads.server.url;
sha1 = versionInfo.downloads.server.sha1;
};
};
in rec {
manifest = getJSON mcManifest;
versions = builtins.listToAttrs (map (x: {
name = "v" + replaceStrings ["."] ["_"] x.id;
value = prepareMc x;
}) manifest.versions);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment