1{ lib
2, alsa-lib
3, factorio-utils
4, fetchurl
5, libGL
6, libICE
7, libSM
8, libX11
9, libXcursor
10, libXext
11, libXi
12, libXinerama
13, libXrandr
14, libpulseaudio
15, libxkbcommon
16, makeDesktopItem
17, makeWrapper
18, releaseType
19, stdenv
20, wayland
21
22, mods-dat ? null
23, versionsJson ? ./versions.json
24, username ? ""
25, token ? "" # get/reset token at https://factorio.com/profile
26, experimental ? false # true means to always use the latest branch
27, ...
28} @ args:
29
30assert releaseType == "alpha"
31 || releaseType == "headless"
32 || releaseType == "demo";
33
34let
35
36 inherit (lib) importJSON;
37
38 mods = args.mods or [ ];
39
40 helpMsg = ''
41
42 ===FETCH FAILED===
43 Please ensure you have set the username and token with config.nix, or
44 /etc/nix/nixpkgs-config.nix if on NixOS.
45
46 Your token can be seen at https://factorio.com/profile (after logging in). It is
47 not as sensitive as your password, but should still be safeguarded. There is a
48 link on that page to revoke/invalidate the token, if you believe it has been
49 leaked or wish to take precautions.
50
51 Example:
52 {
53 packageOverrides = pkgs: {
54 factorio = pkgs.factorio.override {
55 username = "FactorioPlayer1654";
56 token = "d5ad5a8971267c895c0da598688761";
57 };
58 };
59 }
60
61 Alternatively, instead of providing the username+token, you may manually
62 download the release through https://factorio.com/download , then add it to
63 the store using e.g.:
64
65 releaseType=alpha
66 version=0.17.74
67 nix-prefetch-url file://\''$HOME/Downloads/factorio_\''${releaseType}_x64_\''${version}.tar.xz --name factorio_\''${releaseType}_x64-\''${version}.tar.xz
68
69 Note the ultimate "_" is replaced with "-" in the --name arg!
70 '';
71
72 desktopItem = makeDesktopItem {
73 name = "factorio";
74 desktopName = "Factorio";
75 comment = "A game in which you build and maintain factories.";
76 exec = "factorio";
77 icon = "factorio";
78 categories = [ "Game" ];
79 };
80
81 branch = if experimental then "experimental" else "stable";
82
83 # NB `experimental` directs us to take the latest build, regardless of its branch;
84 # hence the (stable, experimental) pairs may sometimes refer to the same distributable.
85 versions = importJSON versionsJson;
86 binDists = makeBinDists versions;
87
88 actual = binDists.${stdenv.hostPlatform.system}.${releaseType}.${branch} or (throw "Factorio ${releaseType}-${branch} binaries for ${stdenv.hostPlatform.system} are not available for download.");
89
90 makeBinDists = versions:
91 let
92 f = path: name: value:
93 if builtins.isAttrs value then
94 if value ? "name" then
95 makeBinDist value
96 else
97 builtins.mapAttrs (f (path ++ [ name ])) value
98 else
99 throw "expected attrset at ${toString path} - got ${toString value}";
100 in
101 builtins.mapAttrs (f [ ]) versions;
102 makeBinDist = { name, version, tarDirectory, url, sha256, needsAuth }: {
103 inherit version tarDirectory;
104 src =
105 if !needsAuth then
106 fetchurl { inherit name url sha256; }
107 else
108 (lib.overrideDerivation
109 (fetchurl {
110 inherit name url sha256;
111 curlOptsList = [
112 "--get"
113 "--data-urlencode"
114 "username@username"
115 "--data-urlencode"
116 "token@token"
117 ];
118 })
119 (_: {
120 # This preHook hides the credentials from /proc
121 preHook =
122 if username != "" && token != "" then ''
123 echo -n "${username}" >username
124 echo -n "${token}" >token
125 '' else ''
126 # Deliberately failing since username/token was not provided, so we can't fetch.
127 # We can't use builtins.throw since we want the result to be used if the tar is in the store already.
128 exit 1
129 '';
130 failureHook = ''
131 cat <<EOF
132 ${helpMsg}
133 EOF
134 '';
135 }));
136 };
137
138 configBaseCfg = ''
139 use-system-read-write-data-directories=false
140 [path]
141 read-data=$out/share/factorio/data/
142 [other]
143 check_updates=false
144 '';
145
146 updateConfigSh = ''
147 #! $SHELL
148 if [[ -e ~/.factorio/config.cfg ]]; then
149 # Config file exists, but may have wrong path.
150 # Try to edit it. I'm sure this is perfectly safe and will never go wrong.
151 sed -i 's|^read-data=.*|read-data=$out/share/factorio/data/|' ~/.factorio/config.cfg
152 else
153 # Config file does not exist. Phew.
154 install -D $out/share/factorio/config-base.cfg ~/.factorio/config.cfg
155 fi
156 '';
157
158 modDir = factorio-utils.mkModDirDrv mods mods-dat;
159
160 base = with actual; {
161 pname = "factorio-${releaseType}";
162 inherit version src;
163
164 preferLocalBuild = true;
165 dontBuild = true;
166
167 installPhase = ''
168 mkdir -p $out/{bin,share/factorio}
169 cp -a data $out/share/factorio
170 cp -a bin/${tarDirectory}/factorio $out/bin/factorio
171 patchelf \
172 --set-interpreter $(cat $NIX_CC/nix-support/dynamic-linker) \
173 $out/bin/factorio
174 '';
175
176 passthru.updateScript =
177 if (username != "" && token != "") then [
178 ./update.py
179 "--username=${username}"
180 "--token=${token}"
181 ] else null;
182
183 meta = {
184 description = "A game in which you build and maintain factories";
185 longDescription = ''
186 Factorio is a game in which you build and maintain factories.
187
188 You will be mining resources, researching technologies, building
189 infrastructure, automating production and fighting enemies. Use your
190 imagination to design your factory, combine simple elements into
191 ingenious structures, apply management skills to keep it working and
192 finally protect it from the creatures who don't really like you.
193
194 Factorio has been in development since spring of 2012, and reached
195 version 1.0 in mid 2020.
196 '';
197 homepage = "https://www.factorio.com/";
198 sourceProvenance = with lib.sourceTypes; [ binaryNativeCode ];
199 license = lib.licenses.unfree;
200 maintainers = with lib.maintainers; [ Baughn elitak erictapen priegger lukegb ];
201 platforms = [ "x86_64-linux" ];
202 };
203 };
204
205 releases = rec {
206 headless = base;
207 demo = base // {
208
209 nativeBuildInputs = [ makeWrapper ];
210 buildInputs = [ libpulseaudio ];
211
212 libPath = lib.makeLibraryPath [
213 alsa-lib
214 libGL
215 libICE
216 libSM
217 libX11
218 libXcursor
219 libXext
220 libXi
221 libXinerama
222 libXrandr
223 libpulseaudio
224 libxkbcommon
225 wayland
226 ];
227
228 installPhase = base.installPhase + ''
229 wrapProgram $out/bin/factorio \
230 --prefix LD_LIBRARY_PATH : /run/opengl-driver/lib:$libPath \
231 --run "$out/share/factorio/update-config.sh" \
232 --argv0 "" \
233 --add-flags "-c \$HOME/.factorio/config.cfg" \
234 ${lib.optionalString (mods!=[]) "--add-flags --mod-directory=${modDir}"}
235
236 # TODO Currently, every time a mod is changed/added/removed using the
237 # modlist, a new derivation will take up the entire footprint of the
238 # client. The only way to avoid this is to remove the mods arg from the
239 # package function. The modsDir derivation will have to be built
240 # separately and have the user specify it in the .factorio config or
241 # right along side it using a symlink into the store I think i will
242 # just remove mods for the client derivation entirely. this is much
243 # cleaner and more useful for headless mode.
244
245 # TODO: trying to toggle off a mod will result in read-only-fs-error.
246 # not much we can do about that except warn the user somewhere. In
247 # fact, no exit will be clean, since this error will happen on close
248 # regardless. just prints an ugly stacktrace but seems to be otherwise
249 # harmless, unless maybe the user forgets and tries to use the mod
250 # manager.
251
252 install -m0644 <(cat << EOF
253 ${configBaseCfg}
254 EOF
255 ) $out/share/factorio/config-base.cfg
256
257 install -m0755 <(cat << EOF
258 ${updateConfigSh}
259 EOF
260 ) $out/share/factorio/update-config.sh
261
262 mkdir -p $out/share/icons/hicolor/{64x64,128x128}/apps
263 cp -a data/core/graphics/factorio-icon.png $out/share/icons/hicolor/64x64/apps/factorio.png
264 cp -a data/core/graphics/factorio-icon@2x.png $out/share/icons/hicolor/128x128/apps/factorio.png
265 ln -s ${desktopItem}/share/applications $out/share/
266 '';
267 };
268 alpha = demo // {
269
270 installPhase = demo.installPhase + ''
271 cp -a doc-html $out/share/factorio
272 '';
273 };
274 };
275
276in
277stdenv.mkDerivation (releases.${releaseType})