4 Commits

Author SHA1 Message Date
Tickbase
642b68ce91 Update dlc_fetcher.py 2024-11-29 19:23:14 +01:00
Tickbase
eb9b3a1368 Update README.md 2024-11-29 19:22:38 +01:00
Tickbase
068e52c529 Update bug_report.md 2024-10-20 19:21:24 +02:00
Tickbase
7e19ad1ab9 Update README.md 2024-08-22 04:09:13 +02:00
3 changed files with 116 additions and 80 deletions

View File

@@ -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.

View File

@@ -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.

View File

@@ -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,44 +164,37 @@ 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()
try: with ThreadPoolExecutor() as executor:
futures = []
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)
if os.path.exists(install_path):
cream_installed = 'Cream installed' if 'cream.sh' in os.listdir(install_path) else ''
games[app_id] = (game_name, cream_installed, install_path)
log_debug(f"Found game: {game_name} (App ID: {app_id})")
except Exception as e:
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 future in futures:
for folder in scanned_folders: result = future.result()
log_error(f" - {folder}") if result:
app_id, game_name, install_path = result
cream_installed = 'Cream installed' if os.path.exists(os.path.join(install_path, 'cream.sh')) else ''
games[app_id] = (game_name, cream_installed, install_path)
return games return games
@@ -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: