{ stdenv, lib, fetchHumbleBundle, unzip, fetchurl, writeText, SDL2, libGLU, libGL , xorg, makeDesktopItem }: let binaryDeps = { starbound.deps = [ SDL2 libGLU libGL xorg.libX11 xorg.libICE xorg.libSM xorg.libXext ]; starbound.needsBootconfig = true; starbound_server.name = "starbound-server"; starbound_server.needsBootconfig = true; asset_packer.name = "starbound-asset-packer"; asset_unpacker.name = "starbound-asset-unpacker"; dump_versioned_json.name = "starbound-dump-versioned-json"; make_versioned_json.name = "starbound-make-versioned-json"; planet_mapgen.name = "starbound-planet-mapgen"; }; desktopItem = makeDesktopItem { name = "starbound"; exec = "starbound"; icon = fetchurl { url = "http://i1305.photobucket.com/albums/s544/ClockworkBarber/" + "logo_zps64c4860d.png"; sha256 = "1zmpmpp0856vis71b1na402js8mnc7g89r8lv0safyk4y9khwm4q"; }; comment = "An extraterrestrial sandbox adventure game"; desktopName = "Starbound"; genericName = "starbound"; categories = "Game;"; }; patchBinary = bin: attrs: '' mkdir -p "patched/$(dirname "${bin}")" cp -t "patched/$(dirname "${bin}")" "linux/${bin}" chmod +x "patched/$(basename "${bin}")" ${lib.optionalString (attrs.needsBootconfig or false) '' for offset in $( grep -abz '^sbinit\.config''$' "patched/$(basename "${bin}")" \ | cut -d: -f1 -z | xargs -0 ); do for i in $(seq 13); do printf '\x07'; done \ | dd of="patched/$(basename "${bin}")" \ obs=1 seek="$offset" \ count=13 conv=notrunc done patchelf \ --add-needed "$lib/lib/libstarbound-preload.so" \ "patched/$(basename "${bin}")" ''} patchelf \ --set-interpreter "$(cat $NIX_CC/nix-support/dynamic-linker)" \ --set-rpath "${lib.makeLibraryPath (attrs.deps or [])}" \ "patched/$(basename "${bin}")" if ldd "patched/$(basename "${bin}")" | grep -F 'not found' \ | grep -v 'libstarbound-preload\.so\|libsteam_api\.so'; then exit 1; fi ''; preloaderSource = writeText "starbound-preloader.c" '' #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #define MAGIC "\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07" static char *getXdgDataHome(void) { int envlen; char *result; const char *env; if ((env = getenv("XDG_DATA_HOME")) != NULL) return strdup(env); if ((env = getenv("HOME")) == NULL) return NULL; envlen = strlen(env); if ((result = malloc(envlen + 14)) == NULL) return NULL; strncpy(result, env, envlen); strncpy(result + envlen, "/.local/share", 14); return result; } static char *mkJsonString(const char *str) { char *result, *out; if ((result = malloc(strlen(str) * 6 + 3)) == NULL) return NULL; out = result; *out++ = '"'; for (; *str != '\0'; str++) { switch (*str) { case '"': *out++ = '\\'; *out++ = '"'; break; case '\\': *out++ = '\\'; *out++ = '\\'; break; case '\b': *out++ = '\\'; *out++ = 'b'; break; case '\f': *out++ = '\\'; *out++ = 'f'; break; case '\n': *out++ = '\\'; *out++ = 'n'; break; case '\r': *out++ = '\\'; *out++ = 'r'; break; case '\t': *out++ = '\\'; *out++ = 't'; break; default: if (*str >= 0 && *str <= 31) { *out++ = '\\'; *out++ = 'u'; snprintf(out, 5, "%04x", *str); out += 4; } else { *out++ = *str; } break; } } *out++ = '"'; *out = 0; return result; } static char *mkDataDir(const char *dataHome, const char *append) { char *buf, *out; int homeLen = strlen(dataHome); int appendLen = strlen(append); if ((buf = malloc(homeLen + appendLen + 2)) == NULL) return NULL; snprintf(buf, homeLen + appendLen + 2, "%s/%s", dataHome, append); out = mkJsonString(buf); free(buf); return out; } static int makedirs(const char *path) { char *buf, *p; if (strlen(path) == 0) return 1; if ((buf = strdup(path)) == NULL) return 1; for (p = buf + 1; *p != '\0'; p++) { if (*p != '/') continue; *p = '\0'; mkdir(buf, 0777); *p = '/'; } free(buf); return 0; } static int writeSBInit(FILE *sbinit) { char *buf, *dataHome; int homeLen, ret; if ((dataHome = getXdgDataHome()) == NULL) return 1; homeLen = strlen(dataHome); if ((buf = malloc(homeLen + 12)) == NULL) goto errout; strncpy(buf, dataHome, homeLen); strncpy(buf + homeLen, "/starbound/", 12); ret = makedirs(buf); free(buf); if (ret != 0) goto errout; fputs("{\"assetDirectories\":[", sbinit); if ((buf = mkJsonString(STARBOUND_ASSET_DIR)) == NULL) goto errout; fputs(buf, sbinit); free(buf); fputc(',', sbinit); if ((buf = mkDataDir(dataHome, "starbound/mods/")) == NULL) goto errout; fputs(buf, sbinit); free(buf); fputs("],\"storageDirectory\":", sbinit); if ((buf = mkDataDir(dataHome, "starbound/")) == NULL) goto errout; fputs(buf, sbinit); free(buf); fputs("}", sbinit); free(dataHome); return 0; errout: free(dataHome); return 1; } static FILE *fakeSBInitHandle = NULL; static int fakeSBInit(void) { fakeSBInitHandle = tmpfile(); if (writeSBInit(fakeSBInitHandle) != 0) { fclose(fakeSBInitHandle); fakeSBInitHandle = NULL; return -1; } rewind(fakeSBInitHandle); return fileno(fakeSBInitHandle); } int open(const char *path, int flags, ...) { va_list ap; mode_t mode; static int (*_open) (const char *, int, mode_t) = NULL; if (_open == NULL) _open = dlsym(RTLD_NEXT, "open"); va_start(ap, flags); mode = va_arg(ap, mode_t); va_end(ap); if (strncmp(path, MAGIC, 14) != 0) return _open(path, flags, mode); return fakeSBInit(); } int close(int fd) { int ret; static int (*_close) (int) = NULL; if (_close == NULL) _close = dlsym(RTLD_NEXT, "close"); if (fakeSBInitHandle != NULL) { ret = fclose(fakeSBInitHandle); fakeSBInitHandle = NULL; } else { ret = _close(fd); } return ret; } int SteamAPI_Init(void) { return 0; } void SteamAPI_RegisterCallback(void *dummy1, int dummy2) {} void SteamAPI_RunCallbacks(void) {} ''; in stdenv.mkDerivation rec { name = "starbound-${version}"; version = "1.4.4"; src = fetchHumbleBundle { name = "starbound-linux-${version}.zip"; machineName = "starbound_linux"; md5 = "3e9b86e622afbf72f78b160cbb65708b"; }; outputs = [ "out" "lib" "assets" ]; nativeBuildInputs = [ unzip ]; buildPhase = with lib; '' cc -Wall -shared "${preloaderSource}" -o preload.so -ldl -fPIC \ -DSTARBOUND_ASSET_DIR="\"$assets\"" ${concatStrings (mapAttrsToList patchBinary binaryDeps)} patchelf --remove-needed libsteam_api.so patched/starbound ''; doCheck = true; checkPhase = '' checkFailed= for i in linux/*; do [ -f "$i" ] || continue case "$(basename "$i")" in sbinit.config) continue;; *.s[ho]) continue;; esac [ ! -e "patched/$(basename "$i")" ] || continue echo "Found missing binary $i from the upstream tree." checkFailed=1 done [ -z "$checkFailed" ] ''; installPhase = '' install -vsD preload.so "$lib/lib/libstarbound-preload.so" ${lib.concatStrings (lib.mapAttrsToList (bin: attrs: let basename = builtins.baseNameOf bin; in '' install -vD "patched/${basename}" "$out/bin/${attrs.name or basename}" '') binaryDeps)} install -m 0644 -vD "${desktopItem}/share/applications/starbound.desktop" \ "$out/share/applications/starbound.desktop" cp -vr assets "$assets" ''; dontStrip = true; }