mirror of
https://github.com/Novattz/creamlinux-installer.git
synced 2026-01-29 06:42:51 -05:00
Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
642b68ce91 | ||
|
|
eb9b3a1368 | ||
|
|
068e52c529 | ||
|
|
7e19ad1ab9 |
5
.github/ISSUE_TEMPLATE/bug_report.md
vendored
5
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -7,6 +7,11 @@ assignees: Novattz
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
||||||
|
**Before submitting, have you tried:**
|
||||||
|
- Using smokeapi with Proton? [ ] Yes [ ] No
|
||||||
|
- Checking if `LD_PRELOAD` is blocked on your system? [ ] Yes [ ] No
|
||||||
|
|
||||||
**Describe the bug**
|
**Describe the bug**
|
||||||
- A clear and concise description of what the bug is.
|
- A clear and concise description of what the bug is.
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
|
|
||||||
# Steam DLC Fetcher and installer for Linux
|
# Steam DLC Fetcher and installer for Linux
|
||||||
- Python script designed for linux to automate fetching of DLC id's for steam games and the installation of creamlinux automatically. [Demo](https://www.youtube.com/watch?v=22LDDUoBvus&ab_channel=Nova)
|
- Python script designed for linux to automate fetching of DLC id's for steam games and the installation of creamlinux automatically. [Demo/Tutorial](https://www.youtube.com/watch?v=Y1E15rUsdDw)
|
||||||
### Features
|
### Features
|
||||||
- Automatically fetches and lists DLC's for selected steam game(s) installed on the computer.
|
- Automatically fetches and lists DLC's for selected steam game(s) installed on the computer.
|
||||||
- Automatically installs creamlinux and its components into selected steam games, excluding and handling specific config files.
|
- Automatically installs creamlinux and its components into selected steam games, excluding and handling specific config files.
|
||||||
@@ -31,6 +31,7 @@ git clone https://github.com/Novattz/creamlinux-installer;cd creamlinux-installe
|
|||||||
- [ ] Check if the game already has dlc files installed
|
- [ ] Check if the game already has dlc files installed
|
||||||
- [ ] Gui?
|
- [ ] Gui?
|
||||||
- [ ] Add checker for configs already applied to games. (i.e script will check for new dlc id's for already applied games.)
|
- [ ] Add checker for configs already applied to games. (i.e script will check for new dlc id's for already applied games.)
|
||||||
|
- [ ] Add a way to check if a game blocks LD_PRELOAD.
|
||||||
|
|
||||||
### Issues?
|
### Issues?
|
||||||
- Open a issue and attach all relevant errors/logs.
|
- Open a issue and attach all relevant errors/logs.
|
||||||
|
|||||||
188
dlc_fetcher.py
188
dlc_fetcher.py
@@ -5,10 +5,11 @@ import zipfile
|
|||||||
import time
|
import time
|
||||||
import stat
|
import stat
|
||||||
import subprocess
|
import subprocess
|
||||||
import psutil
|
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
import logging
|
import logging
|
||||||
import argparse
|
import argparse
|
||||||
|
from concurrent.futures import ThreadPoolExecutor
|
||||||
|
from tqdm import tqdm
|
||||||
|
|
||||||
LOG_FILE = 'script.log'
|
LOG_FILE = 'script.log'
|
||||||
DEBUG_FILE = 'debug_script.log'
|
DEBUG_FILE = 'debug_script.log'
|
||||||
@@ -122,19 +123,16 @@ def find_steam_library_folders(manual_path=""):
|
|||||||
|
|
||||||
library_folders = []
|
library_folders = []
|
||||||
try:
|
try:
|
||||||
if manual_path != "" and manual_path != None:
|
if manual_path:
|
||||||
log_debug(f"Manual game path set to \"{manual_path}\" skipping path lookup")
|
log_debug(f"Manual game path set to \"{manual_path}\" skipping path lookup")
|
||||||
library_folders.append(manual_path)
|
library_folders.append(manual_path)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
steam_binary_path = find_steam_binary()
|
steam_binary_path = find_steam_binary()
|
||||||
if steam_binary_path and steam_binary_path not in search_list:
|
if steam_binary_path and steam_binary_path not in search_list:
|
||||||
log_debug(f"Found Steam Binary path! adding it to search paths: {steam_binary_path}")
|
|
||||||
search_list.append(steam_binary_path)
|
search_list.append(steam_binary_path)
|
||||||
|
|
||||||
steam_install_path = read_steam_registry()
|
steam_install_path = read_steam_registry()
|
||||||
if steam_install_path and steam_install_path not in search_list:
|
if steam_install_path and steam_install_path not in search_list:
|
||||||
log_debug(f"Found Steam Binary path! adding it to search paths: {steam_install_path}")
|
|
||||||
search_list.append(steam_install_path)
|
search_list.append(steam_install_path)
|
||||||
|
|
||||||
log_debug(f"Paths that will be searched: {search_list}")
|
log_debug(f"Paths that will be searched: {search_list}")
|
||||||
@@ -142,7 +140,6 @@ def find_steam_library_folders(manual_path=""):
|
|||||||
for search_path in search_list:
|
for search_path in search_list:
|
||||||
if os.path.exists(search_path):
|
if os.path.exists(search_path):
|
||||||
log_debug(f"Scanning path: {search_path}")
|
log_debug(f"Scanning path: {search_path}")
|
||||||
|
|
||||||
steamapps_path = str(os.path.normpath(f"{search_path}/steamapps"))
|
steamapps_path = str(os.path.normpath(f"{search_path}/steamapps"))
|
||||||
if os.path.exists(steamapps_path):
|
if os.path.exists(steamapps_path):
|
||||||
library_folders.append(steamapps_path)
|
library_folders.append(steamapps_path)
|
||||||
@@ -160,7 +157,6 @@ def find_steam_library_folders(manual_path=""):
|
|||||||
|
|
||||||
if not library_folders:
|
if not library_folders:
|
||||||
raise FileNotFoundError("No Steam library folders found.")
|
raise FileNotFoundError("No Steam library folders found.")
|
||||||
log_debug(f"Total Steam library folders found: {len(library_folders)}")
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log_error(f"Error finding Steam library folders: {e}")
|
log_error(f"Error finding Steam library folders: {e}")
|
||||||
log_error("Scanned paths:")
|
log_error("Scanned paths:")
|
||||||
@@ -168,45 +164,38 @@ def find_steam_library_folders(manual_path=""):
|
|||||||
log_error(f" - {path}")
|
log_error(f" - {path}")
|
||||||
return library_folders
|
return library_folders
|
||||||
|
|
||||||
|
def process_acf_file(folder, item):
|
||||||
|
try:
|
||||||
|
app_id, game_name, install_dir = parse_acf(os.path.join(folder, item))
|
||||||
|
if app_id and game_name:
|
||||||
|
install_path = os.path.join(folder, 'common', install_dir)
|
||||||
|
if os.path.exists(install_path):
|
||||||
|
return app_id, game_name, install_path
|
||||||
|
except Exception as e:
|
||||||
|
log_error(f"Error processing {item}: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
def find_steam_apps(library_folders):
|
def find_steam_apps(library_folders):
|
||||||
acf_pattern = re.compile(r'^appmanifest_(\d+)\.acf$')
|
acf_pattern = re.compile(r'^appmanifest_(\d+)\.acf$')
|
||||||
games = {}
|
games = {}
|
||||||
scanned_folders = []
|
|
||||||
start_time = time.time()
|
with ThreadPoolExecutor() as executor:
|
||||||
|
futures = []
|
||||||
try:
|
|
||||||
for folder in library_folders:
|
for folder in library_folders:
|
||||||
if time.time() - start_time > TIMEOUT:
|
|
||||||
log_error("Script timeout reached. Stopping Steam app scan.")
|
|
||||||
return games
|
|
||||||
scanned_folders.append(folder)
|
|
||||||
if os.path.exists(folder):
|
if os.path.exists(folder):
|
||||||
log_debug(f"Scanning folder for ACF files: {folder}")
|
|
||||||
acf_count = 0
|
|
||||||
for item in os.listdir(folder):
|
for item in os.listdir(folder):
|
||||||
if acf_pattern.match(item):
|
if acf_pattern.match(item):
|
||||||
acf_count += 1
|
futures.append(
|
||||||
try:
|
executor.submit(process_acf_file, folder, item)
|
||||||
app_id, game_name, install_dir = parse_acf(os.path.join(folder, item))
|
)
|
||||||
if app_id and game_name:
|
|
||||||
install_path = os.path.join(folder, 'common', install_dir)
|
for future in futures:
|
||||||
if os.path.exists(install_path):
|
result = future.result()
|
||||||
cream_installed = 'Cream installed' if 'cream.sh' in os.listdir(install_path) else ''
|
if result:
|
||||||
games[app_id] = (game_name, cream_installed, install_path)
|
app_id, game_name, install_path = result
|
||||||
log_debug(f"Found game: {game_name} (App ID: {app_id})")
|
cream_installed = 'Cream installed' if os.path.exists(os.path.join(install_path, 'cream.sh')) else ''
|
||||||
except Exception as e:
|
games[app_id] = (game_name, cream_installed, install_path)
|
||||||
log_error(f"Error parsing {item}: {e}")
|
|
||||||
log_debug(f"Found {acf_count} ACF files in {folder}")
|
|
||||||
if not games:
|
|
||||||
raise FileNotFoundError("No Steam games found.")
|
|
||||||
log_debug(f"Total games found: {len(games)}")
|
|
||||||
except Exception as e:
|
|
||||||
log_error(f"Error finding Steam apps: {e}")
|
|
||||||
|
|
||||||
log_error("Scanned folders:")
|
|
||||||
for folder in scanned_folders:
|
|
||||||
log_error(f" - {folder}")
|
|
||||||
|
|
||||||
return games
|
return games
|
||||||
|
|
||||||
def parse_acf(file_path):
|
def parse_acf(file_path):
|
||||||
@@ -227,36 +216,41 @@ def fetch_dlc_details(app_id):
|
|||||||
response = requests.get(base_url)
|
response = requests.get(base_url)
|
||||||
data = response.json()
|
data = response.json()
|
||||||
if app_id not in data or "data" not in data[app_id]:
|
if app_id not in data or "data" not in data[app_id]:
|
||||||
log_error("Error: Unable to fetch game details.")
|
|
||||||
return []
|
return []
|
||||||
|
|
||||||
game_data = data[app_id]["data"]
|
game_data = data[app_id]["data"]
|
||||||
dlcs = game_data.get("dlc", [])
|
dlcs = game_data.get("dlc", [])
|
||||||
dlc_details = []
|
dlc_details = []
|
||||||
for dlc_id in dlcs:
|
|
||||||
try:
|
with tqdm(total=len(dlcs), desc="Fetching DLC details") as pbar:
|
||||||
time.sleep(0.3)
|
for dlc_id in dlcs:
|
||||||
dlc_url = f"https://store.steampowered.com/api/appdetails?appids={dlc_id}"
|
try:
|
||||||
dlc_response = requests.get(dlc_url)
|
time.sleep(0.3)
|
||||||
if dlc_response.status_code == 200:
|
dlc_url = f"https://store.steampowered.com/api/appdetails?appids={dlc_id}"
|
||||||
dlc_data = dlc_response.json()
|
dlc_response = requests.get(dlc_url)
|
||||||
if str(dlc_id) in dlc_data and "data" in dlc_data[str(dlc_id)]:
|
|
||||||
dlc_name = dlc_data[str(dlc_id)]["data"].get("name", "Unknown DLC")
|
if dlc_response.status_code == 200:
|
||||||
dlc_details.append({"appid": dlc_id, "name": dlc_name})
|
dlc_data = dlc_response.json()
|
||||||
else:
|
if str(dlc_id) in dlc_data and "data" in dlc_data[str(dlc_id)]:
|
||||||
log_error(f"Data missing for DLC {dlc_id}")
|
dlc_name = dlc_data[str(dlc_id)]["data"].get("name", "Unknown DLC")
|
||||||
elif dlc_response.status_code == 429:
|
dlc_details.append({"appid": dlc_id, "name": dlc_name})
|
||||||
log_error("Rate limited! Please wait before trying again.")
|
elif dlc_response.status_code == 429:
|
||||||
time.sleep(10)
|
time.sleep(10)
|
||||||
else:
|
|
||||||
log_error(f"Failed to fetch details for DLC {dlc_id}, Status Code: {dlc_response.status_code}")
|
pbar.update(1)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log_error(f"Exception for DLC {dlc_id}: {str(e)}")
|
log_error(f"Exception for DLC {dlc_id}: {str(e)}")
|
||||||
|
|
||||||
return dlc_details
|
return dlc_details
|
||||||
|
|
||||||
except requests.exceptions.RequestException as e:
|
except requests.exceptions.RequestException as e:
|
||||||
log_error(f"Failed to fetch DLC details for {app_id}: {e}")
|
log_error(f"Failed to fetch DLC details for {app_id}: {e}")
|
||||||
return []
|
return []
|
||||||
|
|
||||||
def install_files(app_id, game_install_dir, dlcs, game_name):
|
def install_files(app_id, game_install_dir, dlcs, game_name):
|
||||||
|
GREEN = '\033[92m'
|
||||||
|
YELLOW = '\033[93m'
|
||||||
|
RESET = '\033[0m'
|
||||||
zip_url = "https://github.com/anticitizn/creamlinux/releases/latest/download/creamlinux.zip"
|
zip_url = "https://github.com/anticitizn/creamlinux/releases/latest/download/creamlinux.zip"
|
||||||
zip_path = os.path.join(game_install_dir, 'creamlinux.zip')
|
zip_path = os.path.join(game_install_dir, 'creamlinux.zip')
|
||||||
try:
|
try:
|
||||||
@@ -276,12 +270,29 @@ def install_files(app_id, game_install_dir, dlcs, game_name):
|
|||||||
with open(cream_api_path, 'w') as f:
|
with open(cream_api_path, 'w') as f:
|
||||||
f.write(f"APPID = {app_id}\n[config]\nissubscribedapp_on_false_use_real = true\n[methods]\ndisable_steamapps_issubscribedapp = false\n[dlc]\n{dlc_list}")
|
f.write(f"APPID = {app_id}\n[config]\nissubscribedapp_on_false_use_real = true\n[methods]\ndisable_steamapps_issubscribedapp = false\n[dlc]\n{dlc_list}")
|
||||||
print(f"Custom cream_api.ini has been written to {game_install_dir}.")
|
print(f"Custom cream_api.ini has been written to {game_install_dir}.")
|
||||||
print(f"Installation complete. Set launch options in Steam: 'sh ./cream.sh %command%' for {game_name}.")
|
print(f"\n{GREEN}Installation complete!{RESET}")
|
||||||
|
print(f"\n{YELLOW}Set launch options in Steam:{RESET}")
|
||||||
|
print(f"{GREEN}'sh ./cream.sh %command%'{RESET} for {game_name}")
|
||||||
else:
|
else:
|
||||||
log_error("Failed to download the files needed for installation.")
|
log_error("Failed to download the files needed for installation.")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log_error(f"Failed to install files for {game_name}: {e}")
|
log_error(f"Failed to install files for {game_name}: {e}")
|
||||||
|
|
||||||
|
def uninstall_creamlinux(install_path, game_name):
|
||||||
|
YELLOW = '\033[93m'
|
||||||
|
GREEN = '\033[92m'
|
||||||
|
RESET = '\033[0m'
|
||||||
|
try:
|
||||||
|
files_to_remove = ['cream.sh', 'cream_api.ini', 'cream_api.so', 'lib32Creamlinux.so', 'lib64Creamlinux.so']
|
||||||
|
for file in files_to_remove:
|
||||||
|
file_path = os.path.join(install_path, file)
|
||||||
|
if os.path.exists(file_path):
|
||||||
|
os.remove(file_path)
|
||||||
|
print(f"\n{GREEN}Successfully uninstalled CreamLinux from {game_name}{RESET}")
|
||||||
|
print(f"\n{YELLOW}Make sure to remove{RESET} {GREEN}'sh ./cream.sh %command%'{RESET} {YELLOW}from launch options{RESET}")
|
||||||
|
except Exception as e:
|
||||||
|
log_error(f"Failed to uninstall CreamLinux: {e}")
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
parser = argparse.ArgumentParser(description="Steam DLC Fetcher")
|
parser = argparse.ArgumentParser(description="Steam DLC Fetcher")
|
||||||
parser.add_argument("--manual", metavar='steamapps_path', help="Sets the steamapps path for faster operation", required=False)
|
parser.add_argument("--manual", metavar='steamapps_path', help="Sets the steamapps path for faster operation", required=False)
|
||||||
@@ -293,41 +304,60 @@ def main():
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
library_folders = find_steam_library_folders(args.manual)
|
library_folders = find_steam_library_folders(args.manual)
|
||||||
if library_folders == [] or library_folders == None:
|
if not library_folders:
|
||||||
print("Falling back to Manual Method since no library folder was found")
|
print("Falling back to Manual Method since no library folder was found")
|
||||||
steamapps_path = input("Steamapps Path: ")
|
steamapps_path = input("Steamapps Path: ")
|
||||||
|
|
||||||
if len(steamapps_path) > 3:
|
if len(steamapps_path) > 3:
|
||||||
library_folders = [ steamapps_path ]
|
library_folders = [steamapps_path]
|
||||||
|
|
||||||
else:
|
else:
|
||||||
print("Invalid path! Closing the program...")
|
print("Invalid path! Closing the program...")
|
||||||
return
|
return
|
||||||
|
|
||||||
games = find_steam_apps(library_folders)
|
games = find_steam_apps(library_folders)
|
||||||
if games:
|
if games:
|
||||||
print("Select the game for which you want to fetch DLCs:")
|
print("\nSelect the game for which you want to fetch DLCs:")
|
||||||
games_list = list(games.items())
|
games_list = list(games.items())
|
||||||
GREEN = '\033[92m'
|
GREEN = '\033[92m'
|
||||||
RESET = '\033[0m'
|
RESET = '\033[0m'
|
||||||
|
|
||||||
for idx, (app_id, (name, cream_status, _)) in enumerate(games_list, 1):
|
for idx, (app_id, (name, cream_status, _)) in enumerate(games_list, 1):
|
||||||
|
status = []
|
||||||
if cream_status:
|
if cream_status:
|
||||||
print(f"{idx}. {GREEN}{name} (App ID: {app_id}) - Cream installed{RESET}")
|
status.append(f"{GREEN}Cream installed{RESET}")
|
||||||
|
|
||||||
|
status_str = f" ({', '.join(status)})" if status else ""
|
||||||
|
print(f"{idx}. {name} (App ID: {app_id}){status_str}")
|
||||||
|
|
||||||
|
choice = int(input("\nEnter the number of the game: ")) - 1
|
||||||
|
if 0 <= choice < len(games_list):
|
||||||
|
selected_app_id, (selected_game_name, cream_status, selected_install_dir) = games_list[choice]
|
||||||
|
|
||||||
|
if cream_status:
|
||||||
|
print("\nCreamLinux is installed. What would you like to do?")
|
||||||
|
print("1. Fetch DLC IDs")
|
||||||
|
print("2. Uninstall CreamLinux")
|
||||||
|
action = input("Enter your choice (1-2): ")
|
||||||
|
|
||||||
|
if action == "1":
|
||||||
|
print(f"\nSelected: {selected_game_name} (App ID: {selected_app_id})")
|
||||||
|
dlcs = fetch_dlc_details(selected_app_id)
|
||||||
|
if dlcs:
|
||||||
|
install_files(selected_app_id, selected_install_dir, dlcs, selected_game_name)
|
||||||
|
else:
|
||||||
|
print("No DLCs found for the selected game.")
|
||||||
|
elif action == "2":
|
||||||
|
uninstall_creamlinux(selected_install_dir, selected_game_name)
|
||||||
|
else:
|
||||||
|
print("Invalid choice.")
|
||||||
else:
|
else:
|
||||||
print(f"{idx}. {name} (App ID: {app_id})")
|
print(f"\nSelected: {selected_game_name} (App ID: {selected_app_id})")
|
||||||
|
dlcs = fetch_dlc_details(selected_app_id)
|
||||||
choice = int(input("Enter the number of the game: ")) - 1
|
if dlcs:
|
||||||
if choice < 0 or choice >= len(games_list):
|
install_files(selected_app_id, selected_install_dir, dlcs, selected_game_name)
|
||||||
raise ValueError("Invalid selection.")
|
else:
|
||||||
selected_app_id, (selected_game_name, _, selected_install_dir) = games_list[choice]
|
print("No DLCs found for the selected game.")
|
||||||
print(f"You selected: {selected_game_name} (App ID: {selected_app_id})")
|
|
||||||
|
|
||||||
dlcs = fetch_dlc_details(selected_app_id)
|
|
||||||
if dlcs:
|
|
||||||
print("DLC IDs found:", [dlc['appid'] for dlc in dlcs]) # Only print app IDs for clarity
|
|
||||||
install_files(selected_app_id, selected_install_dir, dlcs, selected_game_name)
|
|
||||||
else:
|
else:
|
||||||
print("No DLCs found for the selected game.")
|
print("Invalid selection.")
|
||||||
else:
|
else:
|
||||||
print("No Steam games found on this computer or connected drives.")
|
print("No Steam games found on this computer or connected drives.")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|||||||
Reference in New Issue
Block a user