-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #3 from JonLiuFYI/master
Linux+Proton support
- Loading branch information
Showing
10 changed files
with
199 additions
and
56 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
biggestcookie | ||
JonLiuFYI |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
# Changelog | ||
|
||
## 1.2 (2023-05-15) | ||
* Added Linux support | ||
|
||
## 1.1 (2020-07-04) | ||
* Added main menu | ||
* Added saving and loading settings | ||
|
||
## 1.0 (2020-06-24) | ||
Initial release |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
"""Custom exceptions""" | ||
|
||
class SaveDataNotFound(Exception): | ||
"""SaveData folder doesn't exist.""" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
"""Enum representing the choice of which game to configure""" | ||
from enum import Enum | ||
|
||
|
||
class Game_Choice(Enum): | ||
"""Enum representing the choice of which game to configure""" | ||
|
||
BL2 = "Borderlands 2" | ||
TPS = "Borderlands The Pre-Sequel" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,62 +1,64 @@ | ||
from ctypes import windll | ||
import os | ||
import string | ||
from typing import List | ||
from pathlib import Path | ||
|
||
from config import CONFIG | ||
from exceptions import SaveDataNotFound | ||
from util import try_input | ||
from user_os import UserOS | ||
|
||
|
||
def input_to_game_path(input: str) -> str: | ||
save_path = f"{input}\\SaveData\\" | ||
if os.path.isdir(save_path): | ||
CONFIG.set_data("path", save_path) | ||
profile_path = f"{get_latest_directory(save_path)}\\profile.bin" | ||
if os.path.isfile(profile_path): | ||
return profile_path | ||
save_path = Path(input) / "SaveData" | ||
if save_path.is_dir(): | ||
CONFIG.set_data("path", str(save_path)) | ||
profile_path: Path = get_latest_directory(save_path) / "profile.bin" | ||
if profile_path.is_file(): | ||
return str(profile_path) | ||
raise Exception("profile.bin not found in given path.") | ||
|
||
|
||
def get_drives() -> List[str]: | ||
drives: List[str] = [] | ||
bitmask = windll.kernel32.GetLogicalDrives() | ||
for letter in string.ascii_uppercase: | ||
if bitmask & 1: | ||
drives.append(letter) | ||
bitmask >>= 1 | ||
return drives | ||
def get_latest_directory(path: Path | None) -> Path: | ||
"""Return the most recently modified folder within `path`. | ||
Exceptions that may bubble up: | ||
* FileNotFoundError | ||
* TypeError - `path` is None, has no subdirectories, or doesn't exist""" | ||
if path is None: | ||
raise TypeError | ||
|
||
def get_latest_directory(path: str) -> str: | ||
return max([os.path.join(path, d) for d in os.listdir(path)], key=os.path.getmtime,) | ||
subdirs = filter(lambda p: p.is_dir(), path.iterdir()) | ||
return max(subdirs, key=lambda p: p.stat().st_mtime) | ||
|
||
|
||
def get_profile_path() -> str: | ||
save_path = CONFIG.get_data("path") | ||
game_choice = CONFIG.game_choice.value | ||
def get_profile_path(user_os: UserOS) -> str | Path: | ||
save_path_init: str | None = CONFIG.get_data("path") | ||
if save_path_init is not None: | ||
save_path = Path(save_path_init) | ||
else: | ||
try: | ||
save_path = user_os.find_savedata() | ||
except SaveDataNotFound as errpath: | ||
save_path = None | ||
print( | ||
"It seems like your SaveData folder isn't in the usual location,", | ||
errpath, | ||
) | ||
else: | ||
CONFIG.set_data("path", str(save_path)) | ||
|
||
profile_path = "" | ||
if not save_path: | ||
path = "{0}:\\Users\\{1}\\Documents\\My Games\\{2}\\WillowGame\\SaveData\\" | ||
user = os.environ["USERNAME"] | ||
for drive in get_drives(): | ||
save_path = path.format(drive, user, game_choice) | ||
if os.path.exists(save_path): | ||
CONFIG.set_data("path", save_path) | ||
break | ||
# Sentinel value that almost certainly isn't valid a valid Path on a normal | ||
# person's PC. This is extremely cursed and hacky. (Linear A Sign A661) | ||
profile_path = Path("𐜳") | ||
try: | ||
profile_path = f"{get_latest_directory(save_path)}\\profile.bin" | ||
except FileNotFoundError: | ||
if not os.path.isfile(profile_path): | ||
text = [ | ||
f"\nCould not find '{game_choice}\\WillowGame\\SaveData'.", | ||
f"Please enter the full path to WillowGame, this can usually be found in 'Documents\\My Games\\{game_choice}'.", | ||
f"Ex. C:\\Users\\CL4P-TP\\Documents\\My Games\\{game_choice}\\WillowGame", | ||
] | ||
latest_subdir = get_latest_directory(save_path) | ||
profile_path = Path(latest_subdir) / "profile.bin" | ||
except (TypeError, FileNotFoundError): | ||
if not profile_path.is_file(): | ||
profile_path = try_input( | ||
input_to_game_path, | ||
text=text, | ||
text=user_os.savedata_not_found, | ||
error=f"Couldn't find WillowGame at that path. Please try entering your path again: ", | ||
) | ||
|
||
print(f"\nFound latest profile.bin at {profile_path}") | ||
return profile_path |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
"""Platform-specific behaviors that must be implemented for each supported OS""" | ||
|
||
from abc import ABC, abstractmethod | ||
import os | ||
from pathlib import Path | ||
|
||
from exceptions import SaveDataNotFound | ||
from game_choice import Game_Choice | ||
|
||
|
||
class UserOS(ABC): | ||
"""Base class for platform-specific things needed to find profile.bin""" | ||
|
||
def __init__(self, game_choice: Game_Choice) -> None: | ||
self.game: str = game_choice.value | ||
|
||
@property | ||
@abstractmethod | ||
def savedata_not_found(self) -> list[str]: | ||
"""Error message if the path to SaveData wasn't found""" | ||
pass | ||
|
||
@abstractmethod | ||
def find_savedata(self) -> Path: | ||
"""Return an absolute path to the chosen game's default SaveData folder | ||
(where profile.bin is), or raise an exception if it wasn't found. | ||
Exceptions: | ||
* SaveDataNotFound - The SaveData folder doesn't exist at its default location. | ||
""" | ||
pass | ||
|
||
|
||
class Linux(UserOS): | ||
def __init__(self, game_choice: Game_Choice) -> None: | ||
super().__init__(game_choice) | ||
steam_ids = { | ||
"Borderlands 2": "49520", | ||
"Borderlands The Pre-Sequel": "261640", | ||
} | ||
self._steamid = steam_ids[self.game] | ||
self._errmsg = [ | ||
f"\nCould not find '{self.game}/WillowGame/SaveData' for the Proton version of {self.game}.", | ||
f"Please enter the full path to WillowGame, this can usually be found in", | ||
f"'steamapps/compatdata/{self._steamid}/pfx/drive_c/users/steamuser/Documents/My Games/{self.game}'.", | ||
f" ({'^' * len(self._steamid)} the Steam ID of {self.game})", | ||
f"Ex. /home/CL4P-TP/.steam/steam/steamapps/...<ETC>.../{self.game}/WillowGame", | ||
] | ||
|
||
@property | ||
def savedata_not_found(self) -> list[str]: | ||
return self._errmsg | ||
|
||
def find_savedata(self) -> Path: | ||
save_path = ( | ||
Path.home() | ||
/ f".steam/steam/steamapps/compatdata/{self._steamid}/pfx/drive_c/users/steamuser/Documents/My Games/{self.game}/WillowGame/SaveData/" | ||
) | ||
if not save_path.exists(): | ||
raise SaveDataNotFound(str(save_path)) | ||
return save_path | ||
|
||
|
||
class Windows(UserOS): | ||
def __init__(self, game_choice: Game_Choice) -> None: | ||
super().__init__(game_choice) | ||
self._errmsg = [ | ||
f"\nCould not find '{self.game}\\WillowGame\\SaveData'.", | ||
f"Please enter the full path to WillowGame, this can usually be found in 'Documents\\My Games\\{self.game}'.", | ||
f"Ex. C:\\Users\\CL4P-TP\\Documents\\My Games\\{self.game}\\WillowGame", | ||
] | ||
|
||
@staticmethod | ||
def get_drives() -> list[str]: | ||
from ctypes import windll # type: ignore | ||
import string | ||
|
||
drives: list[str] = [] | ||
bitmask = windll.kernel32.GetLogicalDrives() | ||
for letter in string.ascii_uppercase: | ||
if bitmask & 1: | ||
drives.append(letter) | ||
bitmask >>= 1 | ||
return drives | ||
|
||
@property | ||
def savedata_not_found(self) -> list[str]: | ||
return self._errmsg | ||
|
||
def find_savedata(self) -> Path: | ||
user = os.environ["USERNAME"] | ||
for drive in self.get_drives(): | ||
save_path = ( | ||
Path(f"{drive}:/Users") | ||
/ f"{user}/Documents/My Games/{self.game}/WillowGame/SaveData" | ||
) | ||
if os.path.exists(save_path): | ||
return save_path | ||
|
||
raise SaveDataNotFound |