baseline
This commit is contained in:
177
flake.lock
generated
177
flake.lock
generated
@@ -44,6 +44,29 @@
|
|||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"chaotic": {
|
||||||
|
"inputs": {
|
||||||
|
"fenix": "fenix",
|
||||||
|
"flake-schemas": "flake-schemas",
|
||||||
|
"home-manager": "home-manager",
|
||||||
|
"jovian": "jovian",
|
||||||
|
"nixpkgs": "nixpkgs"
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1739212779,
|
||||||
|
"narHash": "sha256-7U7fOAOVy/AaOtw3HflnwEeXZJ9+ldxVU/Mx5tGN9A4=",
|
||||||
|
"owner": "chaotic-cx",
|
||||||
|
"repo": "nyx",
|
||||||
|
"rev": "175a7f545d07bd08c14709f0d0849a8cddaaf460",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "chaotic-cx",
|
||||||
|
"ref": "nyxpkgs-unstable",
|
||||||
|
"repo": "nyx",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
"crane": {
|
"crane": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1731098351,
|
"lastModified": 1731098351,
|
||||||
@@ -59,6 +82,28 @@
|
|||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"fenix": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs": [
|
||||||
|
"chaotic",
|
||||||
|
"nixpkgs"
|
||||||
|
],
|
||||||
|
"rust-analyzer-src": "rust-analyzer-src"
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1739082714,
|
||||||
|
"narHash": "sha256-cylMa750pId3Hqvzyurd86qJIYyyMWB0M7Gbh7ZB2tY=",
|
||||||
|
"owner": "nix-community",
|
||||||
|
"repo": "fenix",
|
||||||
|
"rev": "e84058a7fe56aa01f2db19373cce190098494698",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nix-community",
|
||||||
|
"repo": "fenix",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
"flake-compat": {
|
"flake-compat": {
|
||||||
"flake": false,
|
"flake": false,
|
||||||
"locked": {
|
"locked": {
|
||||||
@@ -145,6 +190,20 @@
|
|||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"flake-schemas": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1721999734,
|
||||||
|
"narHash": "sha256-G5CxYeJVm4lcEtaO87LKzOsVnWeTcHGKbKxNamNWgOw=",
|
||||||
|
"rev": "0a5c42297d870156d9c57d8f99e476b738dcd982",
|
||||||
|
"revCount": 75,
|
||||||
|
"type": "tarball",
|
||||||
|
"url": "https://api.flakehub.com/f/pinned/DeterminateSystems/flake-schemas/0.1.5/0190ef2f-61e0-794b-ba14-e82f225e55e6/source.tar.gz"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"type": "tarball",
|
||||||
|
"url": "https://flakehub.com/f/DeterminateSystems/flake-schemas/%3D0.1.5.tar.gz"
|
||||||
|
}
|
||||||
|
},
|
||||||
"flake-utils": {
|
"flake-utils": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"systems": [
|
"systems": [
|
||||||
@@ -209,7 +268,8 @@
|
|||||||
"home-manager": {
|
"home-manager": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"nixpkgs": [
|
"nixpkgs": [
|
||||||
"nixpkgs-unstable"
|
"chaotic",
|
||||||
|
"nixpkgs"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
@@ -247,6 +307,26 @@
|
|||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"home-manager_2": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs": [
|
||||||
|
"nixpkgs-unstable"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1738610386,
|
||||||
|
"narHash": "sha256-yb6a5efA1e8xze1vcdN2HBxqYr340EsxFMrDUHL3WZM=",
|
||||||
|
"owner": "nix-community",
|
||||||
|
"repo": "home-manager",
|
||||||
|
"rev": "066ba0c5cfddbc9e0dddaec73b1561ad38aa8abe",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nix-community",
|
||||||
|
"repo": "home-manager",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
"impermanence": {
|
"impermanence": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1737831083,
|
"lastModified": 1737831083,
|
||||||
@@ -265,7 +345,29 @@
|
|||||||
"jovian": {
|
"jovian": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"nix-github-actions": "nix-github-actions_2",
|
"nix-github-actions": "nix-github-actions_2",
|
||||||
"nixpkgs": "nixpkgs"
|
"nixpkgs": [
|
||||||
|
"chaotic",
|
||||||
|
"nixpkgs"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1738875499,
|
||||||
|
"narHash": "sha256-P3VbO2IkEW+0d0pJU7CuX8e+obSoiDw/YCVL1mnA26w=",
|
||||||
|
"owner": "Jovian-Experiments",
|
||||||
|
"repo": "Jovian-NixOS",
|
||||||
|
"rev": "4642ec1073a7417e6303484d8f2e7d29dc24a50f",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "Jovian-Experiments",
|
||||||
|
"repo": "Jovian-NixOS",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"jovian_2": {
|
||||||
|
"inputs": {
|
||||||
|
"nix-github-actions": "nix-github-actions_3",
|
||||||
|
"nixpkgs": "nixpkgs_2"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1739640234,
|
"lastModified": 1739640234,
|
||||||
@@ -310,7 +412,7 @@
|
|||||||
"manyfold": {
|
"manyfold": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"flake-utils": "flake-utils_2",
|
"flake-utils": "flake-utils_2",
|
||||||
"nixpkgs": "nixpkgs_2"
|
"nixpkgs": "nixpkgs_3"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1735525498,
|
"lastModified": 1735525498,
|
||||||
@@ -354,7 +456,7 @@
|
|||||||
},
|
},
|
||||||
"nix-darwin": {
|
"nix-darwin": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"nixpkgs": "nixpkgs_3"
|
"nixpkgs": "nixpkgs_4"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1739548217,
|
"lastModified": 1739548217,
|
||||||
@@ -393,6 +495,29 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"nix-github-actions_2": {
|
"nix-github-actions_2": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs": [
|
||||||
|
"chaotic",
|
||||||
|
"jovian",
|
||||||
|
"nixpkgs"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1729697500,
|
||||||
|
"narHash": "sha256-VFTWrbzDlZyFHHb1AlKRiD/qqCJIripXKiCSFS8fAOY=",
|
||||||
|
"owner": "zhaofengli",
|
||||||
|
"repo": "nix-github-actions",
|
||||||
|
"rev": "e418aeb728b6aa5ca8c5c71974e7159c2df1d8cf",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "zhaofengli",
|
||||||
|
"ref": "matrix-name",
|
||||||
|
"repo": "nix-github-actions",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nix-github-actions_3": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"nixpkgs": [
|
"nixpkgs": [
|
||||||
"jovian",
|
"jovian",
|
||||||
@@ -417,7 +542,7 @@
|
|||||||
"nixos-apple-silicon": {
|
"nixos-apple-silicon": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"flake-compat": "flake-compat_3",
|
"flake-compat": "flake-compat_3",
|
||||||
"nixpkgs": "nixpkgs_4",
|
"nixpkgs": "nixpkgs_5",
|
||||||
"rust-overlay": "rust-overlay_2"
|
"rust-overlay": "rust-overlay_2"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
@@ -527,6 +652,22 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"nixpkgs_2": {
|
"nixpkgs_2": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1738546358,
|
||||||
|
"narHash": "sha256-nLivjIygCiqLp5QcL7l56Tca/elVqM9FG1hGd9ZSsrg=",
|
||||||
|
"owner": "NixOS",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "c6e957d81b96751a3d5967a0fd73694f303cc914",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "NixOS",
|
||||||
|
"ref": "nixos-unstable",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nixpkgs_3": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1735291276,
|
"lastModified": 1735291276,
|
||||||
"narHash": "sha256-NYVcA06+blsLG6wpAbSPTCyLvxD/92Hy4vlY9WxFI1M=",
|
"narHash": "sha256-NYVcA06+blsLG6wpAbSPTCyLvxD/92Hy4vlY9WxFI1M=",
|
||||||
@@ -542,7 +683,7 @@
|
|||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"nixpkgs_3": {
|
"nixpkgs_4": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1736241350,
|
"lastModified": 1736241350,
|
||||||
"narHash": "sha256-CHd7yhaDigUuJyDeX0SADbTM9FXfiWaeNyY34FL1wQU=",
|
"narHash": "sha256-CHd7yhaDigUuJyDeX0SADbTM9FXfiWaeNyY34FL1wQU=",
|
||||||
@@ -558,7 +699,7 @@
|
|||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"nixpkgs_4": {
|
"nixpkgs_5": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1738410390,
|
"lastModified": 1738410390,
|
||||||
"narHash": "sha256-xvTo0Aw0+veek7hvEVLzErmJyQkEcRk6PSR4zsRQFEc=",
|
"narHash": "sha256-xvTo0Aw0+veek7hvEVLzErmJyQkEcRk6PSR4zsRQFEc=",
|
||||||
@@ -635,10 +776,11 @@
|
|||||||
"root": {
|
"root": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"authentik-nix": "authentik-nix",
|
"authentik-nix": "authentik-nix",
|
||||||
"home-manager": "home-manager",
|
"chaotic": "chaotic",
|
||||||
|
"home-manager": "home-manager_2",
|
||||||
"home-manager-stable": "home-manager-stable",
|
"home-manager-stable": "home-manager-stable",
|
||||||
"impermanence": "impermanence",
|
"impermanence": "impermanence",
|
||||||
"jovian": "jovian",
|
"jovian": "jovian_2",
|
||||||
"lanzaboote": "lanzaboote",
|
"lanzaboote": "lanzaboote",
|
||||||
"manyfold": "manyfold",
|
"manyfold": "manyfold",
|
||||||
"nix-darwin": "nix-darwin",
|
"nix-darwin": "nix-darwin",
|
||||||
@@ -649,6 +791,23 @@
|
|||||||
"sops-nix": "sops-nix"
|
"sops-nix": "sops-nix"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"rust-analyzer-src": {
|
||||||
|
"flake": false,
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1738997488,
|
||||||
|
"narHash": "sha256-jeNdFVtEDLypGIbNqBjURovfw9hMkVtlLR7j/5fRh54=",
|
||||||
|
"owner": "rust-lang",
|
||||||
|
"repo": "rust-analyzer",
|
||||||
|
"rev": "208bc52b5dc177badc081c64eb0584a313c73242",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "rust-lang",
|
||||||
|
"ref": "nightly",
|
||||||
|
"repo": "rust-analyzer",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
"rust-overlay": {
|
"rust-overlay": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"nixpkgs": [
|
"nixpkgs": [
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
# Chaotic-nix
|
# Chaotic-nix
|
||||||
# chaotic.url = "github:chaotic-cx/nyx/nyxpkgs-unstable";
|
chaotic.url = "github:chaotic-cx/nyx/nyxpkgs-unstable";
|
||||||
|
|
||||||
# Impermenance
|
# Impermenance
|
||||||
impermanence.url = "github:nix-community/impermanence";
|
impermanence.url = "github:nix-community/impermanence";
|
||||||
@@ -72,7 +72,7 @@
|
|||||||
nixpkgs-unstable,
|
nixpkgs-unstable,
|
||||||
# nixpkgs-unstable-small,
|
# nixpkgs-unstable-small,
|
||||||
nixpkgs-stable,
|
nixpkgs-stable,
|
||||||
# chaotic,
|
chaotic,
|
||||||
lanzaboote,
|
lanzaboote,
|
||||||
impermanence,
|
impermanence,
|
||||||
home-manager,
|
home-manager,
|
||||||
@@ -211,6 +211,8 @@
|
|||||||
sops-nix.nixosModules.sops
|
sops-nix.nixosModules.sops
|
||||||
|
|
||||||
jovian.nixosModules.jovian
|
jovian.nixosModules.jovian
|
||||||
|
|
||||||
|
chaotic.nixosModules.default
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{ pkgs, ... }:
|
{ pkgs, ... }:
|
||||||
let
|
let
|
||||||
kernel = pkgs.linuxPackages_latest;
|
kernel = pkgs.linuxPackages_cachyos;
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
# Configure bootloader with lanzaboot and secureboot
|
# Configure bootloader with lanzaboot and secureboot
|
||||||
|
|||||||
@@ -9,6 +9,8 @@
|
|||||||
[ # Include the results of the hardware scan.
|
[ # Include the results of the hardware scan.
|
||||||
./boot.nix
|
./boot.nix
|
||||||
./jovian.nix
|
./jovian.nix
|
||||||
|
./steam-shortcuts.nix
|
||||||
|
# ./steam-game
|
||||||
./hardware-configuration.nix
|
./hardware-configuration.nix
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -134,6 +136,8 @@
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
chaotic.mesa-git.enable = true;
|
||||||
|
|
||||||
# List packages installed in system profile. To search, run:
|
# List packages installed in system profile. To search, run:
|
||||||
# $ nix search wget
|
# $ nix search wget
|
||||||
environment = {
|
environment = {
|
||||||
@@ -141,6 +145,7 @@
|
|||||||
fuse
|
fuse
|
||||||
jq
|
jq
|
||||||
newt
|
newt
|
||||||
|
maliit-keyboard
|
||||||
onlyoffice-bin
|
onlyoffice-bin
|
||||||
python3
|
python3
|
||||||
rsync
|
rsync
|
||||||
@@ -160,6 +165,27 @@
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
# services.steamRomManager = {
|
||||||
|
# enable = true;
|
||||||
|
# user = "deck";
|
||||||
|
|
||||||
|
# romSources = [
|
||||||
|
# {
|
||||||
|
# name = "Nintendo Switch ROMs";
|
||||||
|
# path = "/home/deck/Emulation/roms/switch";
|
||||||
|
# platform = "nintendoswitch";
|
||||||
|
# emulator = "ryujinx";
|
||||||
|
# }
|
||||||
|
# ];
|
||||||
|
|
||||||
|
# emulators = [
|
||||||
|
# {
|
||||||
|
# name = "Ryujinx";
|
||||||
|
# executable = "${pkgs.ryujinx-greemdev}/bin/ryujinx";
|
||||||
|
# }
|
||||||
|
# ];
|
||||||
|
# };
|
||||||
|
|
||||||
# Some programs need SUID wrappers, can be configured further or are
|
# Some programs need SUID wrappers, can be configured further or are
|
||||||
# started in user sessions.
|
# started in user sessions.
|
||||||
# programs.mtr.enable = true;
|
# programs.mtr.enable = true;
|
||||||
|
|||||||
35
hosts/deck/steam-games.nix
Normal file
35
hosts/deck/steam-games.nix
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
{ pkgs, ... }:
|
||||||
|
{
|
||||||
|
# You'll need to get an API key from https://www.steamgriddb.com/profile/preferences/api
|
||||||
|
steamGridDbApiKey = "ada6729c6cd321a4e3792ddd0b83c1ba";
|
||||||
|
# List your non-Steam games here
|
||||||
|
games = [
|
||||||
|
{
|
||||||
|
name = "Ryujinx";
|
||||||
|
path = "${pkgs.ryujinx-greemdev}/bin/ryujinx";
|
||||||
|
extraLaunchOptions = "";
|
||||||
|
steamGridDbIds = {
|
||||||
|
grid = "121085";
|
||||||
|
hero = "63924";
|
||||||
|
logo = "29735";
|
||||||
|
list = "5517";
|
||||||
|
};
|
||||||
|
}
|
||||||
|
{
|
||||||
|
name = "Prismlauncher";
|
||||||
|
path = "${pkgs.prismlauncher}/bin/prismlauncher";
|
||||||
|
extraLaunchOptions = "";
|
||||||
|
steamGridDbIds = {
|
||||||
|
grid = "267141";
|
||||||
|
hero = "64303";
|
||||||
|
logo = "68299";
|
||||||
|
list = "23582";
|
||||||
|
};
|
||||||
|
}
|
||||||
|
{
|
||||||
|
name = "Mario Kart 8";
|
||||||
|
path = "${pkgs.ryujinx-greemdev}/bin/ryujinx";
|
||||||
|
extraLaunchOptions = "--fullscreen '/home/deck/Emulation/roms/switch/Mario Kart 8 Deluxe [B+U720896].nsp'";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
371
hosts/deck/steam-shortcuts.nix
Normal file
371
hosts/deck/steam-shortcuts.nix
Normal file
@@ -0,0 +1,371 @@
|
|||||||
|
{ config, lib, pkgs, ... }:
|
||||||
|
|
||||||
|
let
|
||||||
|
# Import the games configuration
|
||||||
|
gamesConfig = import ./steam-games.nix { inherit pkgs; };
|
||||||
|
|
||||||
|
# Create the VDF generator script
|
||||||
|
vdfGeneratorScript = pkgs.writeText "generate_vdf.py" ''
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
import sys
|
||||||
|
import json
|
||||||
|
import struct
|
||||||
|
import time
|
||||||
|
import hashlib
|
||||||
|
|
||||||
|
def generate_app_id(name):
|
||||||
|
"""Generate Steam app ID for a game using Steam's apparent format."""
|
||||||
|
# Get the current timestamp
|
||||||
|
timestamp = int(time.time())
|
||||||
|
|
||||||
|
# Create a hash of the name and timestamp
|
||||||
|
hash_input = f"{name}{timestamp}".encode('utf-8')
|
||||||
|
hash_obj = hashlib.md5(hash_input)
|
||||||
|
|
||||||
|
# Take first 6 bytes of hash and combine with a fixed pattern
|
||||||
|
hash_bytes = hash_obj.digest()[:6]
|
||||||
|
|
||||||
|
# Create a 64-bit number where:
|
||||||
|
# - First 6 bytes come from hash
|
||||||
|
# - Last 2 bytes are zeros (matching Steam's pattern)
|
||||||
|
full_bytes = hash_bytes + b'\x00\x00'
|
||||||
|
|
||||||
|
# Convert to 64-bit integer
|
||||||
|
app_id = struct.unpack(">Q", full_bytes)[0]
|
||||||
|
|
||||||
|
# Ensure the number is in the correct range by setting specific bits
|
||||||
|
app_id |= (1 << 62) # Set the second-to-highest bit
|
||||||
|
app_id &= ~(1 << 63) # Clear the highest bit
|
||||||
|
|
||||||
|
return app_id
|
||||||
|
|
||||||
|
def write_vdf(shortcuts):
|
||||||
|
"""Write VDF format matching the exact byte sequence."""
|
||||||
|
output = bytearray()
|
||||||
|
|
||||||
|
# Header
|
||||||
|
output.extend(b'\x00shortcuts\x00')
|
||||||
|
|
||||||
|
for i, shortcut in enumerate(shortcuts):
|
||||||
|
# Index
|
||||||
|
output.extend(f'\x00{i}\x00'.encode('utf-8'))
|
||||||
|
|
||||||
|
# AppName
|
||||||
|
output.extend(b'\x01appname\x00')
|
||||||
|
output.extend(shortcut['AppName'].encode('utf-8') + b'\x00')
|
||||||
|
|
||||||
|
# App ID (this is the key addition)
|
||||||
|
output.extend(b'\x02appid\x00')
|
||||||
|
output.extend(f'"{shortcut["Id"]}"'.encode('utf-8') + b'\x00')
|
||||||
|
|
||||||
|
# Exe
|
||||||
|
output.extend(b'\x01exe\x00')
|
||||||
|
output.extend(f'"{shortcut["ExePath"]}"'.encode('utf-8') + b'\x00')
|
||||||
|
|
||||||
|
# StartDir
|
||||||
|
output.extend(b'\x01StartDir\x00')
|
||||||
|
output.extend(b'"/home/deck"\x00')
|
||||||
|
|
||||||
|
# Icon
|
||||||
|
output.extend(b'\x01icon\x00\x00')
|
||||||
|
|
||||||
|
# ShortcutPath
|
||||||
|
output.extend(b'\x01ShortcutPath\x00\x00')
|
||||||
|
|
||||||
|
# LaunchOptions
|
||||||
|
output.extend(b'\x01LaunchOptions\x00')
|
||||||
|
output.extend(f'"{shortcut["LaunchOptions"]}"'.encode('utf-8') + b'\x00')
|
||||||
|
|
||||||
|
# IsHidden
|
||||||
|
output.extend(b'\x02IsHidden\x00\x00\x00\x00\x00')
|
||||||
|
|
||||||
|
# AllowDesktopConfig
|
||||||
|
output.extend(b'\x02AllowDesktopConfig\x00\x01\x00\x00\x00')
|
||||||
|
|
||||||
|
# AllowOverlay
|
||||||
|
output.extend(b'\x02AllowOverlay\x00\x01\x00\x00\x00')
|
||||||
|
|
||||||
|
# OpenVR
|
||||||
|
output.extend(b'\x02OpenVR\x00\x00\x00\x00\x00')
|
||||||
|
|
||||||
|
# Tags
|
||||||
|
output.extend(b'\x00tags\x00\x08')
|
||||||
|
|
||||||
|
# Entry terminator
|
||||||
|
output.extend(b'\x08')
|
||||||
|
|
||||||
|
# Final terminators
|
||||||
|
output.extend(b'\x08\x08')
|
||||||
|
|
||||||
|
return output
|
||||||
|
|
||||||
|
def main():
|
||||||
|
try:
|
||||||
|
shortcuts = json.load(sys.stdin)
|
||||||
|
vdf_data = write_vdf(shortcuts)
|
||||||
|
sys.stdout.buffer.write(vdf_data)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error: {e}", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
|
'';
|
||||||
|
|
||||||
|
# Create the SteamGridDB image downloader script
|
||||||
|
steamGridDbScript = pkgs.writeText "fetch_steamgriddb.py" ''
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
import sys
|
||||||
|
import json
|
||||||
|
import requests
|
||||||
|
import time
|
||||||
|
import hashlib
|
||||||
|
import struct
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
def generate_app_id(name):
|
||||||
|
"""Generate Steam app ID for a game using Steam's apparent format."""
|
||||||
|
# Get the current timestamp
|
||||||
|
timestamp = int(time.time())
|
||||||
|
|
||||||
|
# Create a hash of the name and timestamp
|
||||||
|
hash_input = f"{name}{timestamp}".encode('utf-8')
|
||||||
|
hash_obj = hashlib.md5(hash_input)
|
||||||
|
|
||||||
|
# Take first 6 bytes of hash and combine with a fixed pattern
|
||||||
|
hash_bytes = hash_obj.digest()[:6]
|
||||||
|
|
||||||
|
# Create a 64-bit number where:
|
||||||
|
# - First 6 bytes come from hash
|
||||||
|
# - Last 2 bytes are zeros (matching Steam's pattern)
|
||||||
|
full_bytes = hash_bytes + b'\x00\x00'
|
||||||
|
|
||||||
|
# Convert to 64-bit integer
|
||||||
|
app_id = struct.unpack(">Q", full_bytes)[0]
|
||||||
|
|
||||||
|
# Ensure the number is in the correct range by setting specific bits
|
||||||
|
app_id |= (1 << 62) # Set the second-to-highest bit
|
||||||
|
app_id &= ~(1 << 63) # Clear the highest bit
|
||||||
|
|
||||||
|
return app_id
|
||||||
|
|
||||||
|
def download_image(url, path, headers=None):
|
||||||
|
"""Download an image from a URL to a specified path."""
|
||||||
|
if url:
|
||||||
|
try:
|
||||||
|
response = requests.get(url) # Don't use auth headers for actual download
|
||||||
|
if response.status_code == 200:
|
||||||
|
path.write_bytes(response.content)
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
pass
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error downloading: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def fetch_images(api_key, game_name, grid_ids, output_dir):
|
||||||
|
"""Fetch images for a game from SteamGridDB."""
|
||||||
|
print(f"\nProcessing images for {game_name}")
|
||||||
|
headers = {"Authorization": f"Bearer {api_key}"}
|
||||||
|
base_url = "https://www.steamgriddb.com/api/v2"
|
||||||
|
app_id = generate_app_id(game_name)
|
||||||
|
|
||||||
|
# Create output directories
|
||||||
|
grid_dir = output_dir / "config/grid"
|
||||||
|
hero_dir = output_dir / "config/heroes"
|
||||||
|
logo_dir = output_dir / "config/logos"
|
||||||
|
|
||||||
|
print(f"Using directories:")
|
||||||
|
print(f"Grid: {grid_dir}")
|
||||||
|
print(f"Hero: {hero_dir}")
|
||||||
|
print(f"Logo: {logo_dir}")
|
||||||
|
|
||||||
|
grid_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
hero_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
logo_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
# Test API connection
|
||||||
|
test_response = requests.get(f"{base_url}/grids/game/1", headers=headers)
|
||||||
|
print(f"API test response: {test_response.status_code}")
|
||||||
|
if test_response.status_code == 401:
|
||||||
|
print("API authentication failed. Check your API key.")
|
||||||
|
return
|
||||||
|
|
||||||
|
image_types = {
|
||||||
|
"grid": (grid_dir / f"{app_id}.jpg", grid_ids.get("grid"), "grids"),
|
||||||
|
"hero": (hero_dir / f"{app_id}.jpg", grid_ids.get("hero"), "heroes"),
|
||||||
|
"logo": (logo_dir / f"{app_id}.png", grid_ids.get("logo"), "logos"),
|
||||||
|
"list": (grid_dir / f"{app_id}_list.jpg", grid_ids.get("list"), "grids")
|
||||||
|
}
|
||||||
|
|
||||||
|
for img_type, (output_path, specific_id, asset_type) in image_types.items():
|
||||||
|
if specific_id:
|
||||||
|
# Get direct file URL from the API
|
||||||
|
url = f"{base_url}/{asset_type}/{specific_id}"
|
||||||
|
try:
|
||||||
|
response = requests.get(url, headers=headers)
|
||||||
|
if response.status_code == 200:
|
||||||
|
data = response.json()
|
||||||
|
if data.get("success"):
|
||||||
|
image_url = data["data"]["url"]
|
||||||
|
if download_image(image_url, output_path):
|
||||||
|
print(f"Successfully downloaded {img_type} image")
|
||||||
|
else:
|
||||||
|
print(f"Failed to download {img_type} image")
|
||||||
|
else:
|
||||||
|
print(f"API error: {data}")
|
||||||
|
else:
|
||||||
|
print(f"API request failed: status {response.status_code}")
|
||||||
|
if response.headers.get('content-type', "").startswith('application/json'):
|
||||||
|
print(f"Response: {response.json()}")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error processing {img_type}: {e}")
|
||||||
|
time.sleep(1) # Rate limiting
|
||||||
|
|
||||||
|
def main():
|
||||||
|
if len(sys.argv) != 4:
|
||||||
|
print("Usage: script.py API_KEY game_data.json output_dir", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
api_key = sys.argv[1]
|
||||||
|
game_data_file = sys.argv[2]
|
||||||
|
output_dir = Path(sys.argv[3])
|
||||||
|
|
||||||
|
print(f"Loading game data from: {game_data_file}")
|
||||||
|
print(f"Output directory: {output_dir}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(game_data_file) as f:
|
||||||
|
games = json.load(f)
|
||||||
|
print(f"Loaded {len(games)} games")
|
||||||
|
|
||||||
|
for game in games:
|
||||||
|
if "steamGridDbIds" in game:
|
||||||
|
fetch_images(api_key, game["name"], game["steamGridDbIds"], output_dir)
|
||||||
|
else:
|
||||||
|
print(f"No steamGridDbIds for {game['name']}, skipping")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error: {e}", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
|
'';
|
||||||
|
|
||||||
|
# Python environment with required packages
|
||||||
|
pythonEnv = pkgs.python3.withPackages (ps: with ps; [
|
||||||
|
requests
|
||||||
|
]);
|
||||||
|
|
||||||
|
# Helper function to convert hex char to decimal
|
||||||
|
hexCharToInt = c:
|
||||||
|
let
|
||||||
|
hexChars = {
|
||||||
|
"0" = 0; "1" = 1; "2" = 2; "3" = 3; "4" = 4;
|
||||||
|
"5" = 5; "6" = 6; "7" = 7; "8" = 8; "9" = 9;
|
||||||
|
"a" = 10; "b" = 11; "c" = 12; "d" = 13; "e" = 14; "f" = 15;
|
||||||
|
"A" = 10; "B" = 11; "C" = 12; "D" = 13; "E" = 14; "F" = 15;
|
||||||
|
};
|
||||||
|
in hexChars.${c};
|
||||||
|
|
||||||
|
# Convert hex string to decimal number
|
||||||
|
hexToInt = str:
|
||||||
|
let
|
||||||
|
len = builtins.stringLength str;
|
||||||
|
chars = lib.stringToCharacters str;
|
||||||
|
go = pos: nums:
|
||||||
|
if pos == len then 0
|
||||||
|
else
|
||||||
|
let
|
||||||
|
char = builtins.elemAt chars pos;
|
||||||
|
num = hexCharToInt char;
|
||||||
|
exp = len - pos - 1;
|
||||||
|
factor = builtins.foldl' (a: b: a * 16) 1 (lib.range 1 exp);
|
||||||
|
in
|
||||||
|
(num * factor) + (go (pos + 1) nums);
|
||||||
|
in go 0 chars;
|
||||||
|
|
||||||
|
# Helper function for the ID generation - handling large numbers
|
||||||
|
generateAppId = name:
|
||||||
|
let
|
||||||
|
hash = builtins.hashString "md5" name;
|
||||||
|
# Instead of trying to create a massive number, we'll generate a unique pattern
|
||||||
|
# that will create a consistent large number when interpreted by Python
|
||||||
|
sixteenHex = builtins.substring 0 16 hash;
|
||||||
|
# Format it as a string that Python will interpret as a large number
|
||||||
|
shortcutAppId = "0x${sixteenHex}0000";
|
||||||
|
in shortcutAppId;
|
||||||
|
|
||||||
|
# Convert games to shortcuts format
|
||||||
|
gameToShortcut = game: {
|
||||||
|
Id = generateAppId game.name;
|
||||||
|
AppName = game.name;
|
||||||
|
ExePath = game.path;
|
||||||
|
LaunchOptions = game.extraLaunchOptions;
|
||||||
|
};
|
||||||
|
|
||||||
|
# Script to update shortcuts and fetch images
|
||||||
|
updateSteamShortcuts = pkgs.writeShellScriptBin "update-steam-shortcuts" ''
|
||||||
|
STEAM_DIR="$HOME/.local/share/Steam"
|
||||||
|
USERDATA_DIR="$STEAM_DIR/userdata"
|
||||||
|
CACHE_DIR="$HOME/.cache/steam-shortcuts"
|
||||||
|
|
||||||
|
if [ ! -d "$USERDATA_DIR" ]; then
|
||||||
|
echo "Steam userdata directory not found!"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Make sure Steam is not running
|
||||||
|
if pgrep -x "steam" > /dev/null; then
|
||||||
|
echo "Please close Steam before updating shortcuts"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir -p "$CACHE_DIR"
|
||||||
|
|
||||||
|
# Process each Steam user directory
|
||||||
|
for USER_DIR in "$USERDATA_DIR"/*; do
|
||||||
|
if [ -d "$USER_DIR" ]; then
|
||||||
|
USER_ID="$(basename "$USER_DIR")"
|
||||||
|
CONFIG_DIR="$USER_DIR/config"
|
||||||
|
mkdir -p "$CONFIG_DIR"
|
||||||
|
|
||||||
|
echo "Processing Steam user $USER_ID..."
|
||||||
|
|
||||||
|
# Create games JSON file
|
||||||
|
GAMES_JSON="$CACHE_DIR/games.json"
|
||||||
|
echo '${builtins.toJSON (map gameToShortcut gamesConfig.games)}' > "$GAMES_JSON"
|
||||||
|
|
||||||
|
# Generate shortcuts.vdf
|
||||||
|
cat "$GAMES_JSON" | ${pythonEnv}/bin/python3 ${vdfGeneratorScript} > "$CONFIG_DIR/shortcuts.vdf"
|
||||||
|
|
||||||
|
# Download images if API key is set
|
||||||
|
if [ "${gamesConfig.steamGridDbApiKey}" != "YOUR_API_KEY_HERE" ]; then
|
||||||
|
echo '${builtins.toJSON gamesConfig.games}' > "$CACHE_DIR/games_full.json"
|
||||||
|
${pythonEnv}/bin/python3 ${steamGridDbScript} "${gamesConfig.steamGridDbApiKey}" "$CACHE_DIR/games_full.json" "$USER_DIR"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Updated shortcuts and images for $USER_ID"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "Done! You can now start Steam."
|
||||||
|
'';
|
||||||
|
|
||||||
|
in {
|
||||||
|
# Add the update script to system packages
|
||||||
|
environment.systemPackages = [ updateSteamShortcuts ];
|
||||||
|
|
||||||
|
# Optional: Create a systemd user service to update shortcuts on login
|
||||||
|
systemd.user.services.update-steam-shortcuts = {
|
||||||
|
enable = true;
|
||||||
|
description = "Update Steam shortcuts";
|
||||||
|
wantedBy = [ "graphical-session.target" ];
|
||||||
|
serviceConfig = {
|
||||||
|
Type = "oneshot";
|
||||||
|
ExecStart = "${updateSteamShortcuts}/bin/update-steam-shortcuts";
|
||||||
|
RemainAfterExit = true;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user