Skip to content
This repository was archived by the owner on May 11, 2026. It is now read-only.

Commit 2b02809

Browse files
committed
add rich to prettier output
1 parent 63ad413 commit 2b02809

8 files changed

Lines changed: 173 additions & 143 deletions

File tree

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ license-files = ["LICEN[CS]E*"]
88
requires-python = ">=3.13"
99
dependencies = [
1010
"requests>=2.33.1",
11+
"rich>=15.0.0",
1112
]
1213
classifiers = [
1314
"Development Status :: 3 - Alpha",

steamlayer/bootstrap/defender.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -53,19 +53,19 @@ def check_defender_exclusion(path: str) -> bool:
5353

5454

5555
def warn_about_defender_if_needed(vendors_path: str) -> None:
56-
if not is_realtime_protection_on():
57-
return
58-
59-
if check_defender_exclusion(vendors_path):
60-
return
61-
6256
section = "defender"
6357
shown = state.get(section, "warning_count", 0)
6458

6559
if shown >= MAX_WARNINGS:
6660
log.debug("Defender warning suppressed (shown %d times).", shown)
6761
return
6862

63+
if not is_realtime_protection_on():
64+
return
65+
66+
if check_defender_exclusion(vendors_path):
67+
return
68+
6969
log.warning(
7070
"\n"
7171
"┌─ Windows Defender Warning ──────────────────────────────────────────┐\n"

steamlayer/core.py

Lines changed: 30 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
GameRestorer,
2828
)
2929
from steamlayer.http_client import HTTPClient
30-
from steamlayer.logging_utils import configure_logging
30+
from steamlayer.logging_utils import configure_logging, spinner, success
3131

3232
log = logging.getLogger("steamlayer.core")
3333

@@ -47,15 +47,15 @@ def _run_discovery_and_patch(
4747
strict=args.strict,
4848
)
4949
game.appid = result.appid
50-
log.info(f"Found appid '{game.appid}'")
5150

5251
if game.appid is not None:
5352
dlc_cache_path = args.cache_dir / f"dlcs_{game.appid}.json"
54-
game.dlcs = discoverer.fetch_dlcs(
55-
game.appid,
56-
cache_path=dlc_cache_path,
57-
allow_network=not args.no_network,
58-
)
53+
with spinner("Fetching DLC metadata..."):
54+
game.dlcs = discoverer.fetch_dlcs(
55+
game.appid,
56+
cache_path=dlc_cache_path,
57+
allow_network=not args.no_network,
58+
)
5959

6060
config = GoldbergConfig().set_dlcs(game.dlcs)
6161
patcher = GamePatcher(
@@ -171,8 +171,11 @@ def main() -> None:
171171

172172
emulator = Goldberg(path=VENDORS_PATH / "goldberg")
173173
if args.restore:
174-
restorer = GameRestorer(game=game, emulator=emulator, dry_run=args.dry_run)
175-
restorer.run()
174+
with spinner("Restoring the patch..."):
175+
restorer = GameRestorer(game=game, emulator=emulator, dry_run=args.dry_run)
176+
restorer.run()
177+
if not args.dry_run:
178+
success(f"Restored '{game.path.name}' successfully.")
176179
return
177180

178181
if args.no_network:
@@ -186,14 +189,20 @@ def main() -> None:
186189
with HTTPClient() as http:
187190
try:
188191
if not args.no_defender_check:
189-
warn_about_defender_if_needed(str(VENDORS_PATH))
192+
with spinner("Checking if real-time protection is on..."):
193+
warn_about_defender_if_needed(str(VENDORS_PATH))
190194

191195
if args.unpack:
192-
SteamlessBootstrapper(VENDORS_PATH / "steamless", http=http).ensure(
193-
allow_network=not args.no_network
194-
)
195-
SevenZipBootstrapper(VENDORS_PATH / "7zip", http).ensure(allow_network=not args.no_network)
196-
GoldbergBootstrapper(VENDORS_PATH / "goldberg", http).ensure(allow_network=not args.no_network)
196+
with spinner("Bootstrapping Steamless..."):
197+
SteamlessBootstrapper(VENDORS_PATH / "steamless", http=http).ensure(
198+
allow_network=not args.no_network
199+
)
200+
201+
with spinner("Bootstrapping 7-Zip..."):
202+
SevenZipBootstrapper(VENDORS_PATH / "7zip", http).ensure(allow_network=not args.no_network)
203+
204+
with spinner("Bootstrapping Goldberg..."):
205+
GoldbergBootstrapper(VENDORS_PATH / "goldberg", http).ensure(allow_network=not args.no_network)
197206

198207
except RuntimeError as e:
199208
log.error(str(e))
@@ -202,6 +211,12 @@ def main() -> None:
202211
discoverer = DiscoveryFacade(http=http, allow_network=not args.no_network)
203212
_run_discovery_and_patch(args, game, emulator, discoverer)
204213

214+
dlc_count = len(game.dlcs)
215+
success(
216+
f"{'[DRY RUN] ' if args.dry_run else ''}Patched '{game.path.name}' "
217+
f"(AppID {game.appid}, {dlc_count} DLC{'s' if dlc_count != 1 else ''})"
218+
)
219+
205220
duration = round(time.time() - start_time, 2)
206221
log.debug(f"Done in: {duration}s")
207222

steamlayer/discovery/resolver.py

Lines changed: 29 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
import logging
44
import pathlib
55

6+
from steamlayer.logging_utils import spinner
7+
68
from .decision import DecisionPolicy
79
from .interaction import InteractiveSelector
810
from .local import LocalDiscovery
@@ -129,38 +131,41 @@ def resolve(
129131
confidence=1.0,
130132
)
131133

132-
original_name = game_path.name
133-
local_id = self.local.find(game_path)
134-
if local_id:
135-
return DiscoveryResult(appid=local_id, source=DiscoverySource.LOCAL, confidence=1.0)
134+
with spinner("Looking in the local game files..."):
135+
original_name = game_path.name
136+
local_id = self.local.find(game_path)
137+
if local_id:
138+
return DiscoveryResult(appid=local_id, source=DiscoverySource.LOCAL, confidence=1.0)
136139

137-
index_res = self._find_local_index(original_name)
138-
all_candidates: list[DiscoveryResult] = []
139-
if index_res.appid is not None:
140-
if index_res.confidence >= 0.85:
141-
log.info(f"Resolved via local index: {index_res.appid}")
142-
return index_res
140+
with spinner("Looking in the local index..."):
141+
index_res = self._find_local_index(original_name)
142+
all_candidates: list[DiscoveryResult] = []
143+
if index_res.appid is not None:
144+
if index_res.confidence >= 0.85:
145+
log.info(f"Resolved via local index: {index_res.appid}")
146+
return index_res
143147

144-
elif index_res.confidence > 0.4:
145-
all_candidates.append(index_res)
148+
elif index_res.confidence > 0.4:
149+
all_candidates.append(index_res)
146150

147151
if not allow_network:
148152
log.warning("AppID discovery failed. Network is disabled.")
149153
return DiscoveryResult(source=DiscoverySource.NONE)
150154

151155
log.info(f"Searching Steam for: '{original_name}'...")
152-
queries = self.query_strategy.generate(original_name)
153-
for query in queries:
154-
log.debug(f"[QUERY] Trying: '{query}'")
155-
web_results = self._find_web(query)
156-
157-
for res in web_results:
158-
res.confidence = self.matcher.calculate_confidence(original_name, res.game_name or "")
159-
if res.confidence > 0.4:
160-
all_candidates.append(res)
161-
162-
if any(c.confidence >= 1.0 for c in all_candidates):
163-
break
156+
with spinner(f"Searching steam for {original_name}..."):
157+
queries = self.query_strategy.generate(original_name)
158+
for query in queries:
159+
log.debug(f"[QUERY] Trying: '{query}'")
160+
web_results = self._find_web(query)
161+
162+
for res in web_results:
163+
res.confidence = self.matcher.calculate_confidence(original_name, res.game_name or "")
164+
if res.confidence > 0.4:
165+
all_candidates.append(res)
166+
167+
if any(c.confidence >= 1.0 for c in all_candidates):
168+
break
164169

165170
if all_candidates:
166171
all_candidates.sort(key=lambda x: x.confidence, reverse=True)

steamlayer/game.py

Lines changed: 41 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
from steamlayer.emulators import Emulator, EmulatorConfig
88
from steamlayer.fileops import BackedUpFile, SteamAPIDll
9+
from steamlayer.logging_utils import spinner
910
from steamlayer.steamstub import SteamlessCLI, SteamStubScanner
1011

1112
log = logging.getLogger("steamlayer." + __name__)
@@ -85,17 +86,9 @@ def _handle_steamstub(self, dlls: list[SteamAPIDll]) -> None:
8586
return
8687

8788
if not self.unpack:
88-
log.warning("─" * 60)
89-
log.warning("⚠ STEAMSTUB (SteamDRM) DETECTED")
90-
log.warning(
91-
"\nOne or more executables are wrapped with SteamStub. Replacing the DLL might not be enough."
92-
)
9389
for exe, variant in sorted(wrapped.items()):
9490
rel = exe.relative_to(self.game.path) if exe.is_relative_to(self.game.path) else exe
95-
log.warning(f" {rel} [{variant}]")
96-
97-
log.warning("\nRECOMMENDED: Run with '--unpack' to automate DRM removal via Steamless.")
98-
log.warning("─" * 60)
91+
log.warning(f"SteamStub detected on '{rel}' ({variant}) — re-run with --unpack to strip it.")
9992
return
10093

10194
dry_prefix = "[DRY RUN] " if self.dry_run else ""
@@ -153,45 +146,49 @@ def run(self) -> None:
153146
if not dlls:
154147
raise FileNotFoundError("No Steam API DLLs found in game directory.")
155148

156-
self._handle_steamstub(dlls)
149+
with spinner("Handling steamstub..."):
150+
self._handle_steamstub(dlls)
157151

158-
for dll in dlls:
159-
relative_path = dll.file.relative_to(self.game.path)
160-
vault_dest = self.vault_root / relative_path
161-
dll.set_backup_destination(vault_dest)
162-
163-
if self.dry_run:
164-
patched_dlls = dlls # used for logging at the end
152+
with spinner("Patching the game..."):
165153
for dll in dlls:
166-
log.info(f"[DRY RUN] Would vault original to: {dll.backup_path}")
167-
log.info(f"[DRY RUN] Would overwrite: {dll.file}")
168-
169-
for target_dir in {d.file.parent for d in dlls}:
170-
log.info(
171-
f"[DRY RUN] Would configure '{target_dir}' using the following flags: "
172-
f"(APPID={self.game.appid} DLLS={dlls} DLCS={self.game.dlcs}). "
173-
f"User-specific information would also be correctly written."
174-
)
175-
176-
else:
177-
patched_dlls = self.emulator.patch_game(dlls=dlls)
178-
try:
179-
self.emulator.create_config_files(
180-
config=self.config,
181-
appid=self.game.appid,
182-
game_path=self.game.path,
183-
dll_paths=[d.file for d in patched_dlls],
184-
)
185-
except Exception as e:
186-
log.error(
187-
f"Config creation failed: {e}. The DLL patch was still applied "
188-
"— the game may still work with default settings."
189-
)
154+
relative_path = dll.file.relative_to(self.game.path)
155+
vault_dest = self.vault_root / relative_path
156+
dll.set_backup_destination(vault_dest)
190157

191-
dlc_count = len(self.game.dlcs) if self.game.dlcs else 0
192-
dll_count = len(patched_dlls)
158+
if self.dry_run:
159+
patched_dlls = dlls # used for logging at the end
160+
for dll in dlls:
161+
log.info(f"[DRY RUN] Would vault original to: {dll.backup_path}")
162+
log.info(f"[DRY RUN] Would overwrite: {dll.file}")
163+
164+
for target_dir in {d.file.parent for d in dlls}:
165+
log.info(
166+
f"[DRY RUN] Would configure '{target_dir}' using the following flags: "
167+
f"(APPID={self.game.appid} DLLS={dlls} DLCS={self.game.dlcs}). "
168+
f"User-specific information would also be correctly written."
169+
)
193170

194-
log.info(f"{dry_prefix}Patch completed successfully (AppID={appid}, DLLs={dll_count}, DLCs={dlc_count})")
171+
else:
172+
patched_dlls = self.emulator.patch_game(dlls=dlls)
173+
try:
174+
self.emulator.create_config_files(
175+
config=self.config,
176+
appid=self.game.appid,
177+
game_path=self.game.path,
178+
dll_paths=[d.file for d in patched_dlls],
179+
)
180+
except Exception as e:
181+
log.error(
182+
f"Config creation failed: {e}. The DLL patch was still applied "
183+
"— the game may still work with default settings."
184+
)
185+
186+
dlc_count = len(self.game.dlcs) if self.game.dlcs else 0
187+
dll_count = len(patched_dlls)
188+
189+
log.info(
190+
f"{dry_prefix}Patch completed successfully (AppID={appid}, DLLs={dll_count}, DLCs={dlc_count})"
191+
)
195192

196193

197194
class GameRestorer:

0 commit comments

Comments
 (0)