game:
arch: win64
exe: $GAMEDIR/prefix/drive_c/ANTHOLOGY/Anomaly-1.5.3-Anthology 2.1/AnomalyLauncher.exe
prefix: $GAMEDIR/prefix
working_dir: $GAMEDIR/prefix/drive_c/ANTHOLOGY/Anomaly-1.5.3-Anthology 2.1
installer:
- mkdir: $GAMEDIR/install-media
- write_file:
content: "import hashlib\nimport json\nimport shutil\nimport sys\nimport time\n\
from pathlib import Path\nfrom urllib.parse import urlencode\nfrom urllib.request\
\ import Request, urlopen\n\n# Official 2.1 Final public folder, listed on AP-PRO.ru.\n\
SHARE = \"https://disk.yandex.ru/d/zBo4EybWJyuplA\"\nAPI = \"https://cloud-api.yandex.net/v1/disk/public/resources\"\
\nUSER_AGENT = \"Lutris-Anomaly-Anthology/0.2\"\nREQUIRED = (\n \"ANTHOLOGY2_1_Final_setup.exe\"\
,\n \"Anthology_Game.bin\",\n \"Anthology_Modpack.bin\",\n)\nCHUNK = 8\
\ * 1024 * 1024\n\n\ndef api(endpoint=\"\", **params):\n url = API + endpoint\
\ + \"?\" + urlencode(params)\n req = Request(url, headers={\"User-Agent\"\
: USER_AGENT})\n with urlopen(req, timeout=75) as response:\n return\
\ json.load(response)\n\n\ndef list_official_files():\n # The public share\
\ can be a folder with nested subfolders.\n found = {}\n queue = [\"\"\
]\n while queue and len(found) < len(REQUIRED):\n folder = queue.pop(0)\n\
\ offset = 0\n while True:\n parameters = {\"public_key\"\
: SHARE, \"limit\": 100, \"offset\": offset}\n if folder:\n \
\ parameters[\"path\"] = folder\n data = api(**parameters)\n\
\ entries = data.get(\"_embedded\", {}).get(\"items\", [])\n \
\ for item in entries:\n path = folder + \"/\" + item[\"\
name\"]\n if item.get(\"type\") == \"dir\":\n \
\ queue.append(path)\n elif item.get(\"type\") == \"file\"\
\ and item[\"name\"] in REQUIRED:\n if item[\"name\"] in\
\ found:\n raise RuntimeError(\"Duplicate required file:\
\ \" + item[\"name\"])\n size = item.get(\"size\")\n \
\ if not isinstance(size, int) or size <= 0:\n \
\ raise RuntimeError(\"Invalid file size: \" + item[\"name\"])\n \
\ found[item[\"name\"]] = (path, size, item.get(\"md5\"))\n\
\ offset += len(entries)\n if not entries or offset >=\
\ data.get(\"_embedded\", {}).get(\"total\", offset):\n break\n\
\ missing = set(REQUIRED) - found.keys()\n if missing:\n raise\
\ RuntimeError(\"Official share does not contain expected files: \" + \", \"\
.join(sorted(missing)))\n return found\n\n\ndef md5_file(path):\n h =\
\ hashlib.md5()\n with path.open(\"rb\") as stream:\n for chunk in\
\ iter(lambda: stream.read(CHUNK), b\"\"):\n h.update(chunk)\n \
\ return h.hexdigest()\n\n\ndef file_ok(path, expected_size, expected_md5):\n\
\ if not path.is_file() or path.stat().st_size != expected_size:\n \
\ return False\n return not expected_md5 or md5_file(path).lower() == expected_md5.lower()\n\
\n\ndef fetch_one(dest, name, path, size, expected_md5):\n final = dest /\
\ name\n partial = dest / (name + \".part\")\n if file_ok(final, size,\
\ expected_md5):\n print(\"Verified existing \" + name, flush=True)\n\
\ return\n if final.exists():\n final.unlink()\n if partial.exists()\
\ and partial.stat().st_size > size:\n partial.unlink()\n\n print(\"\
Downloading \" + name, flush=True)\n for attempt in range(8):\n downloaded\
\ = partial.stat().st_size if partial.exists() else 0\n if downloaded\
\ == size:\n break\n try:\n link = api(\"/download\"\
, public_key=SHARE, path=path)[\"href\"]\n headers = {\"User-Agent\"\
: USER_AGENT}\n if downloaded:\n headers[\"Range\"\
] = \"bytes=\" + str(downloaded) + \"-\"\n with urlopen(Request(link,\
\ headers=headers), timeout=180) as response:\n if downloaded:\n\
\ if response.status != 206 or not response.headers.get(\"\
Content-Range\", \"\").startswith(\"bytes \" + str(downloaded) + \"-\"):\n \
\ # The server did not honor our partial download. Restart\
\ safely.\n partial.unlink(missing_ok=True)\n \
\ raise IOError(\"Host does not support resuming this request\"\
)\n next_report = downloaded + 512 * 1024 * 1024\n \
\ with partial.open(\"ab\" if downloaded else \"wb\") as output:\n \
\ while True:\n chunk = response.read(CHUNK)\n\
\ if not chunk:\n break\n\
\ output.write(chunk)\n downloaded\
\ += len(chunk)\n if downloaded >= next_report:\n \
\ print(\" \" + name + \": \" + f\"{100 * downloaded\
\ / size:.1f}%\", flush=True)\n next_report = downloaded\
\ + 512 * 1024 * 1024\n if partial.stat().st_size != size:\n \
\ raise IOError(\"Transfer incomplete\")\n break\n \
\ except (OSError, ValueError, KeyError) as error:\n if attempt\
\ == 7:\n raise RuntimeError(\"Unable to download \" + name +\
\ \" from the official Yandex share\") from error\n print(\" Download\
\ interrupted: \" + str(error) + \"; retrying\", flush=True)\n time.sleep(min(2\
\ ** attempt, 30))\n\n if not file_ok(partial, size, expected_md5):\n \
\ raise RuntimeError(\"File integrity check failed: \" + name)\n partial.replace(final)\n\
\ print(\"Finished and verified \" + name, flush=True)\n\n\ndef main():\n\
\ dest = Path(sys.argv[1]).resolve()\n dest.mkdir(parents=True, exist_ok=True)\n\
\ print(\"Checking official Anomaly Anthology 2.1 Final download folder\"\
, flush=True)\n files = list_official_files()\n remaining = 0\n for\
\ name in REQUIRED:\n _, size, checksum = files[name]\n final\
\ = dest / name\n partial = dest / (name + \".part\")\n if file_ok(final,\
\ size, checksum):\n continue\n remaining += size - min(partial.stat().st_size\
\ if partial.exists() else 0, size)\n\n # Both downloaded installation media\
\ and unpacked game reside under $GAMEDIR.\n # The full game and modpack\
\ require about 113 GB after extraction.\n required_space = remaining + 113_000_000_000\n\
\ available = shutil.disk_usage(dest).free\n if available < required_space:\n\
\ deficit = (required_space - available + 999_999_999) // 1_000_000_000\n\
\ raise RuntimeError(f\"Not enough free disk space. Need about {deficit}\
\ GB more for the full installation\")\n\n for name in REQUIRED:\n \
\ path, size, checksum = files[name]\n fetch_one(dest, name, path, size,\
\ checksum)\n print(\"All three installation files are ready.\", flush=True)\n\
\n\nif __name__ == \"__main__\":\n main()\n"
file: $GAMEDIR/download-anthology.py
- execute:
command: python3 "$GAMEDIR/download-anthology.py" "$GAMEDIR/install-media"
disable_runtime: true
working_dir: $GAMEDIR
- task:
arch: win64
name: create_prefix
prefix: $GAMEDIR/prefix
- task:
app: vcrun2022
arch: win64
description: Installing Microsoft Visual C++ 2015-2022 runtime
name: winetricks
prefix: $GAMEDIR/prefix
- task:
arch: win64
description: Install Anthology into C:\ANTHOLOGY
executable: $GAMEDIR/install-media/ANTHOLOGY2_1_Final_setup.exe
name: wineexec
prefix: $GAMEDIR/prefix
working_dir: $GAMEDIR/install-media
wine:
dxvk: true
description: Automatically downloads and installs S.T.A.L.K.E.R. Anomaly Anthology
2.1 Final, including the standalone game and optional modpack, using the official
release files hosted on Yandex Disk. Creates an isolated 64-bit Wine prefix and
installs Microsoft Visual C++ 2022.
game_slug: stalker-anomaly-anthology
gogslug: ''
humblestoreid: ''
installer_slug: stalker-anomaly-anthology-21-final
name: S.T.A.L.K.E.R. Anomaly Anthology
notes: "Requires approximately 180\u2013200 GB of free disk space during installation.\
\ The installer downloads the three official release files using Python 3 and the\
\ Yandex Disk public API. No external download manager or torrent client is required.\r\
\n\r\nDuring the Windows setup wizard, select C:\\ANTHOLOGY as the installation\
\ destination. The Lutris script creates a 64-bit Wine prefix and installs vcrun2022\
\ to resolve a known Wine concrt140.dll compatibility issue.\r\n\r\nDirectX 11 with\
\ DXVK is recommended. To use the modpack, select \"Play with modpack\" in the Anthology\
\ launcher, then launch Anomaly (DX11) or Anomaly (DX11-AVX) through Mod Organizer\
\ 2.\r\n\r\nTested on CachyOS Linux with Proton-CachyOS and an NVIDIA RTX 4070 SUPER.\
\ Compatibility with other Wine runners and Linux distributions has not yet been\
\ verified."
runner: wine
script:
game:
arch: win64
exe: $GAMEDIR/prefix/drive_c/ANTHOLOGY/Anomaly-1.5.3-Anthology 2.1/AnomalyLauncher.exe
prefix: $GAMEDIR/prefix
working_dir: $GAMEDIR/prefix/drive_c/ANTHOLOGY/Anomaly-1.5.3-Anthology 2.1
installer:
- mkdir: $GAMEDIR/install-media
- write_file:
content: "import hashlib\nimport json\nimport shutil\nimport sys\nimport time\n\
from pathlib import Path\nfrom urllib.parse import urlencode\nfrom urllib.request\
\ import Request, urlopen\n\n# Official 2.1 Final public folder, listed on\
\ AP-PRO.ru.\nSHARE = \"https://disk.yandex.ru/d/zBo4EybWJyuplA\"\nAPI = \"\
https://cloud-api.yandex.net/v1/disk/public/resources\"\nUSER_AGENT = \"Lutris-Anomaly-Anthology/0.2\"\
\nREQUIRED = (\n \"ANTHOLOGY2_1_Final_setup.exe\",\n \"Anthology_Game.bin\"\
,\n \"Anthology_Modpack.bin\",\n)\nCHUNK = 8 * 1024 * 1024\n\n\ndef api(endpoint=\"\
\", **params):\n url = API + endpoint + \"?\" + urlencode(params)\n \
\ req = Request(url, headers={\"User-Agent\": USER_AGENT})\n with urlopen(req,\
\ timeout=75) as response:\n return json.load(response)\n\n\ndef list_official_files():\n\
\ # The public share can be a folder with nested subfolders.\n found\
\ = {}\n queue = [\"\"]\n while queue and len(found) < len(REQUIRED):\n\
\ folder = queue.pop(0)\n offset = 0\n while True:\n\
\ parameters = {\"public_key\": SHARE, \"limit\": 100, \"offset\"\
: offset}\n if folder:\n parameters[\"path\"] =\
\ folder\n data = api(**parameters)\n entries = data.get(\"\
_embedded\", {}).get(\"items\", [])\n for item in entries:\n \
\ path = folder + \"/\" + item[\"name\"]\n if\
\ item.get(\"type\") == \"dir\":\n queue.append(path)\n\
\ elif item.get(\"type\") == \"file\" and item[\"name\"] in\
\ REQUIRED:\n if item[\"name\"] in found:\n \
\ raise RuntimeError(\"Duplicate required file: \" + item[\"name\"\
])\n size = item.get(\"size\")\n if\
\ not isinstance(size, int) or size <= 0:\n raise RuntimeError(\"\
Invalid file size: \" + item[\"name\"])\n found[item[\"\
name\"]] = (path, size, item.get(\"md5\"))\n offset += len(entries)\n\
\ if not entries or offset >= data.get(\"_embedded\", {}).get(\"\
total\", offset):\n break\n missing = set(REQUIRED) - found.keys()\n\
\ if missing:\n raise RuntimeError(\"Official share does not contain\
\ expected files: \" + \", \".join(sorted(missing)))\n return found\n\n\
\ndef md5_file(path):\n h = hashlib.md5()\n with path.open(\"rb\") as\
\ stream:\n for chunk in iter(lambda: stream.read(CHUNK), b\"\"):\n\
\ h.update(chunk)\n return h.hexdigest()\n\n\ndef file_ok(path,\
\ expected_size, expected_md5):\n if not path.is_file() or path.stat().st_size\
\ != expected_size:\n return False\n return not expected_md5 or\
\ md5_file(path).lower() == expected_md5.lower()\n\n\ndef fetch_one(dest,\
\ name, path, size, expected_md5):\n final = dest / name\n partial =\
\ dest / (name + \".part\")\n if file_ok(final, size, expected_md5):\n\
\ print(\"Verified existing \" + name, flush=True)\n return\n\
\ if final.exists():\n final.unlink()\n if partial.exists() and\
\ partial.stat().st_size > size:\n partial.unlink()\n\n print(\"\
Downloading \" + name, flush=True)\n for attempt in range(8):\n \
\ downloaded = partial.stat().st_size if partial.exists() else 0\n \
\ if downloaded == size:\n break\n try:\n link\
\ = api(\"/download\", public_key=SHARE, path=path)[\"href\"]\n \
\ headers = {\"User-Agent\": USER_AGENT}\n if downloaded:\n \
\ headers[\"Range\"] = \"bytes=\" + str(downloaded) + \"-\"\n\
\ with urlopen(Request(link, headers=headers), timeout=180) as\
\ response:\n if downloaded:\n if response.status\
\ != 206 or not response.headers.get(\"Content-Range\", \"\").startswith(\"\
bytes \" + str(downloaded) + \"-\"):\n # The server\
\ did not honor our partial download. Restart safely.\n \
\ partial.unlink(missing_ok=True)\n raise IOError(\"\
Host does not support resuming this request\")\n next_report\
\ = downloaded + 512 * 1024 * 1024\n with partial.open(\"ab\"\
\ if downloaded else \"wb\") as output:\n while True:\n\
\ chunk = response.read(CHUNK)\n \
\ if not chunk:\n break\n \
\ output.write(chunk)\n downloaded += len(chunk)\n\
\ if downloaded >= next_report:\n \
\ print(\" \" + name + \": \" + f\"{100 * downloaded / size:.1f}%\"\
, flush=True)\n next_report = downloaded + 512\
\ * 1024 * 1024\n if partial.stat().st_size != size:\n \
\ raise IOError(\"Transfer incomplete\")\n break\n \
\ except (OSError, ValueError, KeyError) as error:\n if attempt\
\ == 7:\n raise RuntimeError(\"Unable to download \" + name\
\ + \" from the official Yandex share\") from error\n print(\"\
\ Download interrupted: \" + str(error) + \"; retrying\", flush=True)\n \
\ time.sleep(min(2 ** attempt, 30))\n\n if not file_ok(partial,\
\ size, expected_md5):\n raise RuntimeError(\"File integrity check\
\ failed: \" + name)\n partial.replace(final)\n print(\"Finished and\
\ verified \" + name, flush=True)\n\n\ndef main():\n dest = Path(sys.argv[1]).resolve()\n\
\ dest.mkdir(parents=True, exist_ok=True)\n print(\"Checking official\
\ Anomaly Anthology 2.1 Final download folder\", flush=True)\n files =\
\ list_official_files()\n remaining = 0\n for name in REQUIRED:\n \
\ _, size, checksum = files[name]\n final = dest / name\n \
\ partial = dest / (name + \".part\")\n if file_ok(final, size,\
\ checksum):\n continue\n remaining += size - min(partial.stat().st_size\
\ if partial.exists() else 0, size)\n\n # Both downloaded installation\
\ media and unpacked game reside under $GAMEDIR.\n # The full game and\
\ modpack require about 113 GB after extraction.\n required_space = remaining\
\ + 113_000_000_000\n available = shutil.disk_usage(dest).free\n if\
\ available < required_space:\n deficit = (required_space - available\
\ + 999_999_999) // 1_000_000_000\n raise RuntimeError(f\"Not enough\
\ free disk space. Need about {deficit} GB more for the full installation\"\
)\n\n for name in REQUIRED:\n path, size, checksum = files[name]\n\
\ fetch_one(dest, name, path, size, checksum)\n print(\"All three\
\ installation files are ready.\", flush=True)\n\n\nif __name__ == \"__main__\"\
:\n main()\n"
file: $GAMEDIR/download-anthology.py
- execute:
command: python3 "$GAMEDIR/download-anthology.py" "$GAMEDIR/install-media"
disable_runtime: true
working_dir: $GAMEDIR
- task:
arch: win64
name: create_prefix
prefix: $GAMEDIR/prefix
- task:
app: vcrun2022
arch: win64
description: Installing Microsoft Visual C++ 2015-2022 runtime
name: winetricks
prefix: $GAMEDIR/prefix
- task:
arch: win64
description: Install Anthology into C:\ANTHOLOGY
executable: $GAMEDIR/install-media/ANTHOLOGY2_1_Final_setup.exe
name: wineexec
prefix: $GAMEDIR/prefix
working_dir: $GAMEDIR/install-media
wine:
dxvk: true
slug: stalker-anomaly-anthology-21-final
steamid: null
version: 2.1 Final
year: 2023
{
"game_slug": "stalker-anomaly-anthology",
"version": "2.1 Final",
"description": "Automatically downloads and installs S.T.A.L.K.E.R. Anomaly Anthology 2.1 Final, including the standalone game and optional modpack, using the official release files hosted on Yandex Disk. Creates an isolated 64-bit Wine prefix and installs Microsoft Visual C++ 2022.",
"notes": "Requires approximately 180\u2013200 GB of free disk space during installation. The installer downloads the three official release files using Python 3 and the Yandex Disk public API. No external download manager or torrent client is required.\r\n\r\nDuring the Windows setup wizard, select C:\\ANTHOLOGY as the installation destination. The Lutris script creates a 64-bit Wine prefix and installs vcrun2022 to resolve a known Wine concrt140.dll compatibility issue.\r\n\r\nDirectX 11 with DXVK is recommended. To use the modpack, select \"Play with modpack\" in the Anthology launcher, then launch Anomaly (DX11) or Anomaly (DX11-AVX) through Mod Organizer 2.\r\n\r\nTested on CachyOS Linux with Proton-CachyOS and an NVIDIA RTX 4070 SUPER. Compatibility with other Wine runners and Linux distributions has not yet been verified.",
"name": "S.T.A.L.K.E.R. Anomaly Anthology",
"year": 2023,
"steamid": null,
"gogslug": "",
"humblestoreid": "",
"runner": "wine",
"slug": "stalker-anomaly-anthology-21-final",
"installer_slug": "stalker-anomaly-anthology-21-final",
"script": {
"game": {
"arch": "win64",
"exe": "$GAMEDIR/prefix/drive_c/ANTHOLOGY/Anomaly-1.5.3-Anthology 2.1/AnomalyLauncher.exe",
"prefix": "$GAMEDIR/prefix",
"working_dir": "$GAMEDIR/prefix/drive_c/ANTHOLOGY/Anomaly-1.5.3-Anthology 2.1"
},
"installer": [
{
"mkdir": "$GAMEDIR/install-media"
},
{
"write_file": {
"content": "import hashlib\nimport json\nimport shutil\nimport sys\nimport time\nfrom pathlib import Path\nfrom urllib.parse import urlencode\nfrom urllib.request import Request, urlopen\n\n# Official 2.1 Final public folder, listed on AP-PRO.ru.\nSHARE = \"https://disk.yandex.ru/d/zBo4EybWJyuplA\"\nAPI = \"https://cloud-api.yandex.net/v1/disk/public/resources\"\nUSER_AGENT = \"Lutris-Anomaly-Anthology/0.2\"\nREQUIRED = (\n \"ANTHOLOGY2_1_Final_setup.exe\",\n \"Anthology_Game.bin\",\n \"Anthology_Modpack.bin\",\n)\nCHUNK = 8 * 1024 * 1024\n\n\ndef api(endpoint=\"\", **params):\n url = API + endpoint + \"?\" + urlencode(params)\n req = Request(url, headers={\"User-Agent\": USER_AGENT})\n with urlopen(req, timeout=75) as response:\n return json.load(response)\n\n\ndef list_official_files():\n # The public share can be a folder with nested subfolders.\n found = {}\n queue = [\"\"]\n while queue and len(found) < len(REQUIRED):\n folder = queue.pop(0)\n offset = 0\n while True:\n parameters = {\"public_key\": SHARE, \"limit\": 100, \"offset\": offset}\n if folder:\n parameters[\"path\"] = folder\n data = api(**parameters)\n entries = data.get(\"_embedded\", {}).get(\"items\", [])\n for item in entries:\n path = folder + \"/\" + item[\"name\"]\n if item.get(\"type\") == \"dir\":\n queue.append(path)\n elif item.get(\"type\") == \"file\" and item[\"name\"] in REQUIRED:\n if item[\"name\"] in found:\n raise RuntimeError(\"Duplicate required file: \" + item[\"name\"])\n size = item.get(\"size\")\n if not isinstance(size, int) or size <= 0:\n raise RuntimeError(\"Invalid file size: \" + item[\"name\"])\n found[item[\"name\"]] = (path, size, item.get(\"md5\"))\n offset += len(entries)\n if not entries or offset >= data.get(\"_embedded\", {}).get(\"total\", offset):\n break\n missing = set(REQUIRED) - found.keys()\n if missing:\n raise RuntimeError(\"Official share does not contain expected files: \" + \", \".join(sorted(missing)))\n return found\n\n\ndef md5_file(path):\n h = hashlib.md5()\n with path.open(\"rb\") as stream:\n for chunk in iter(lambda: stream.read(CHUNK), b\"\"):\n h.update(chunk)\n return h.hexdigest()\n\n\ndef file_ok(path, expected_size, expected_md5):\n if not path.is_file() or path.stat().st_size != expected_size:\n return False\n return not expected_md5 or md5_file(path).lower() == expected_md5.lower()\n\n\ndef fetch_one(dest, name, path, size, expected_md5):\n final = dest / name\n partial = dest / (name + \".part\")\n if file_ok(final, size, expected_md5):\n print(\"Verified existing \" + name, flush=True)\n return\n if final.exists():\n final.unlink()\n if partial.exists() and partial.stat().st_size > size:\n partial.unlink()\n\n print(\"Downloading \" + name, flush=True)\n for attempt in range(8):\n downloaded = partial.stat().st_size if partial.exists() else 0\n if downloaded == size:\n break\n try:\n link = api(\"/download\", public_key=SHARE, path=path)[\"href\"]\n headers = {\"User-Agent\": USER_AGENT}\n if downloaded:\n headers[\"Range\"] = \"bytes=\" + str(downloaded) + \"-\"\n with urlopen(Request(link, headers=headers), timeout=180) as response:\n if downloaded:\n if response.status != 206 or not response.headers.get(\"Content-Range\", \"\").startswith(\"bytes \" + str(downloaded) + \"-\"):\n # The server did not honor our partial download. Restart safely.\n partial.unlink(missing_ok=True)\n raise IOError(\"Host does not support resuming this request\")\n next_report = downloaded + 512 * 1024 * 1024\n with partial.open(\"ab\" if downloaded else \"wb\") as output:\n while True:\n chunk = response.read(CHUNK)\n if not chunk:\n break\n output.write(chunk)\n downloaded += len(chunk)\n if downloaded >= next_report:\n print(\" \" + name + \": \" + f\"{100 * downloaded / size:.1f}%\", flush=True)\n next_report = downloaded + 512 * 1024 * 1024\n if partial.stat().st_size != size:\n raise IOError(\"Transfer incomplete\")\n break\n except (OSError, ValueError, KeyError) as error:\n if attempt == 7:\n raise RuntimeError(\"Unable to download \" + name + \" from the official Yandex share\") from error\n print(\" Download interrupted: \" + str(error) + \"; retrying\", flush=True)\n time.sleep(min(2 ** attempt, 30))\n\n if not file_ok(partial, size, expected_md5):\n raise RuntimeError(\"File integrity check failed: \" + name)\n partial.replace(final)\n print(\"Finished and verified \" + name, flush=True)\n\n\ndef main():\n dest = Path(sys.argv[1]).resolve()\n dest.mkdir(parents=True, exist_ok=True)\n print(\"Checking official Anomaly Anthology 2.1 Final download folder\", flush=True)\n files = list_official_files()\n remaining = 0\n for name in REQUIRED:\n _, size, checksum = files[name]\n final = dest / name\n partial = dest / (name + \".part\")\n if file_ok(final, size, checksum):\n continue\n remaining += size - min(partial.stat().st_size if partial.exists() else 0, size)\n\n # Both downloaded installation media and unpacked game reside under $GAMEDIR.\n # The full game and modpack require about 113 GB after extraction.\n required_space = remaining + 113_000_000_000\n available = shutil.disk_usage(dest).free\n if available < required_space:\n deficit = (required_space - available + 999_999_999) // 1_000_000_000\n raise RuntimeError(f\"Not enough free disk space. Need about {deficit} GB more for the full installation\")\n\n for name in REQUIRED:\n path, size, checksum = files[name]\n fetch_one(dest, name, path, size, checksum)\n print(\"All three installation files are ready.\", flush=True)\n\n\nif __name__ == \"__main__\":\n main()\n",
"file": "$GAMEDIR/download-anthology.py"
}
},
{
"execute": {
"command": "python3 \"$GAMEDIR/download-anthology.py\" \"$GAMEDIR/install-media\"",
"disable_runtime": true,
"working_dir": "$GAMEDIR"
}
},
{
"task": {
"arch": "win64",
"name": "create_prefix",
"prefix": "$GAMEDIR/prefix"
}
},
{
"task": {
"app": "vcrun2022",
"arch": "win64",
"description": "Installing Microsoft Visual C++ 2015-2022 runtime",
"name": "winetricks",
"prefix": "$GAMEDIR/prefix"
}
},
{
"task": {
"arch": "win64",
"description": "Install Anthology into C:\\ANTHOLOGY",
"executable": "$GAMEDIR/install-media/ANTHOLOGY2_1_Final_setup.exe",
"name": "wineexec",
"prefix": "$GAMEDIR/prefix",
"working_dir": "$GAMEDIR/install-media"
}
}
],
"wine": {
"dxvk": true
}
}
}