diff --git a/docs/troubleshooting/index.md b/docs/troubleshooting/index.md index e4c49bb..0e11236 100644 --- a/docs/troubleshooting/index.md +++ b/docs/troubleshooting/index.md @@ -146,6 +146,33 @@ MO2-LINT currently only supports Proton 10.0. Ensure your game is configured to Launch the game at least once through Steam/Heroic before installing MO2-LINT. This allows the launcher to set up the Proton prefix with default dependencies. +### Preserving Save Data Across a Prefix Reset + +If you need to delete and recreate your game's prefix, your save files will be lost unless you back them up first. Save data is stored inside the prefix under the `users` folder: + +- **Steam**: `/pfx/drive_c/users/` +- **GOG / Epic (Heroic)**: `/drive_c/users/` + +To find your prefix path, run: +```bash +mo2-lint list +``` +and note the launcher for your instance, then refer to your launcher's library settings for the exact prefix location. + +**To preserve your saves:** + +1. Copy the `users` folder somewhere safe before deleting the prefix: + ```bash + cp -r /path/to/prefix/drive_c/users ~/.tmp/mo2-lint/users-backup + ``` +2. Delete the old prefix and launch the game once to generate a fresh one, then exit. +3. Restore your saves by copying the `users` folder back: + ```bash + cp -r ~/.tmp/mo2-lint/users-backup/. /path/to/new/prefix/drive_c/users/ + ``` + +> **Note:** Overwriting the entire `users` folder should be safe for save data, but be aware that some per-user configuration inside the prefix (e.g. registry hives under `users//`) may carry over stale settings from the old prefix. + ## Instance Issues ### Instance Not Found diff --git a/src/mo2-lint/step/configure_prefix.py b/src/mo2-lint/step/configure_prefix.py index 5bb0add..f0b9923 100644 --- a/src/mo2-lint/step/configure_prefix.py +++ b/src/mo2-lint/step/configure_prefix.py @@ -1,9 +1,7 @@ #!/usr/bin/env python3 -from datetime import datetime from loguru import logger from pathlib import Path -from shutil import copytree, move from step.load_game_info import get_launcher from util import lang, variables as var, state_file as state from util.heroic.find_library import get_data as get_heroic_data @@ -16,8 +14,6 @@ default_tricks = [ yes = ("", "y", "yes") -current_time = datetime.now().strftime("%Y%m%d-%H%M%S") - def load_prefix() -> Path: """ @@ -75,56 +71,6 @@ def load_prefix() -> Path: return prefix -def archive_prefix(prefix: Path): - """ - Archives the existing game prefix by renaming it with a timestamp suffix.\n - Sets the global 'archive' variable to the new archive path. - - Parameters - ---------- - prefix : Path - Path to the existing game prefix to be archived. - """ - if prefix.exists() and prefix.is_dir(): - archive_path = prefix.with_suffix(f".{current_time}").expanduser() - move(prefix, archive_path) - logger.trace(f"Backed up existing prefix from {prefix} to {archive_path}") - var.archived_prefix = archive_path - - -def restore_archived_prefix(prefix: Path): - """ - Restores the `users` directory from the archived prefix to the new prefix,\n - preserving personal data such as saved games. - - Parameters - ---------- - prefix : Path - Path to the new game prefix where personal data will be restored. - """ - match state.current_instance.launcher: - case "steam": - subpath = Path("pfx") / "drive_c" / "users" - src = var.archived_prefix / subpath - case "gog" | "epic" | _: - subpath = Path("drive_c") / "users" - src = var.archived_prefix / subpath - dst = prefix / subpath - logger.trace( - f"Restoring personal data from archived prefix. Source: {src}, Destination: {dst}" - ) - - copytree(src, dst.with_suffix(".bak")) - logger.trace( - f"Copied personal data to temporary backup location: {dst.with_suffix('.bak')}" - ) - copytree(src, dst.with_suffix(".old")) - move(dst.with_suffix(".old"), dst) - logger.trace( - f"Restored personal data to new prefix at {dst}. Original data backed up at {dst.with_suffix('.bak')}" - ) - - def configure(): """ Run the necessary winetricks/protontricks for the selected game launcher. @@ -141,40 +87,18 @@ def configure(): def prompt(): """ - Prompts the user to archive their existing game prefix and create a clean one. - - Raises - ------ - SystemExit - If the user declines to create a clean prefix. + Prompts the user to confirm their prefix is set up, then configures it. """ - - # Prompt user to archive prefix prefix = load_prefix() for suffix in ["users", "drive_c", "pfx"]: if prefix.name == suffix: prefix = prefix.parent var.prefix = prefix logger.trace(f"Resolved prefix path for prompting: {prefix}") - logger.debug(f"Prompting user to archive existing prefix at {prefix}") - if lang.prompt_archive(): - logger.trace( - "User agreed to archive existing prefix. Proceeding with archival." - ) - archive_prefix(prefix) - else: - logger.warning( - "User declined to archive existing prefix. Proceeding without archival." - ) - var.archived_prefix = None - # Prompt user to create a clean prefix - if not lang.prompt_archive_init(): + if not lang.prompt_prefix_init(): logger.warning( - "User declined that instructions were followed to create a clean prefix. No support will be provided if errors occur." + "User did not confirm prefix setup. No support will be provided if errors occur." ) configure() - if var.archived_prefix is not None: - restore_archived_prefix(prefix) - print(lang.prompt_archive_done.format(directory=var.archived_prefix)) diff --git a/src/mo2-lint/util/lang.py b/src/mo2-lint/util/lang.py index 4fa72c6..a38ed1f 100644 --- a/src/mo2-lint/util/lang.py +++ b/src/mo2-lint/util/lang.py @@ -17,11 +17,6 @@ help_pin = """Pin the Mod Organizer 2 installation in the specified directory, p help_unpin = """Unpin the Mod Organizer 2 installation in the specified directory, allowing updates.""" help_update = """Update the Mod Organizer 2 installation in the specified directory, as well as the launch option for the game.""" -prompt_archive_done = """Your prefix has been archived and can be found at: {directory} - -Personal data from the archived prefix (e.g. saved games) has been preserved and restored to the new prefix. -Feel free to delete the archive if you no longer need it after confirming your data is intact.""" - def list_instances(instance_list: list) -> list: """ @@ -44,41 +39,9 @@ def list_instances(instance_list: list) -> list: return instances -def prompt_archive() -> bool: +def prompt_prefix_init() -> bool: """ - Prompts the user to archive the existing game prefix and create a clean one. - - Returns - ------- - bool - True if the user agrees to archive prefix, False otherwise. - """ - logger.debug("Prompting user to archive existing prefix") - if var.unattended: - logger.debug("Unattended mode: skipping archive prompt, defaulting to False") - return False - - message = """It is recommended to archive your existing game prefix and create a clean one for Mod Organizer 2 to avoid potential conflicts. - - This process will maintain your personal data (save games, settings) while providing a fresh environment for proton-/winetricks to set up the necessary dependencies. - The archived prefix will be renamed with a timestamp suffix for easy identification. - - Do you want to proceed with archiving the existing prefix and creating a clean one?""" - - msg = { - "type": "confirm", - "message": message, - "name": "archive_clean", - "default": False, - } - result = prompt([msg]) - logger.debug(f"User chose to archive prefix: {result['archive_clean']}") - return result["archive_clean"] - - -def prompt_archive_init() -> bool: - """ - Prompts the user to initialize a clean prefix + Prompts the user to confirm their game prefix has been set up. Returns ------- @@ -86,7 +49,7 @@ def prompt_archive_init() -> bool: True if the user indicates they have followed the instructions, False otherwise. """ logger.debug( - f"Prompting user to initialize clean prefix for launcher: {state.current_instance.launcher}" + f"Prompting user to confirm prefix setup for launcher: {state.current_instance.launcher}" ) if var.unattended: logger.debug("Unattended mode: skipping prefix init prompt, defaulting to True") @@ -94,16 +57,16 @@ def prompt_archive_init() -> bool: match state.current_instance.launcher: case "steam": - instructions = """To create a clean Steam prefix, please follow these steps: + instructions = """Before continuing, please ensure your Steam prefix is set up correctly: 1. Right-click on the game in your Steam library. 2. Select 'Properties', and navigate to the 'Compatibility' tab. 3. Check the box for 'Force the use of a specific Steam Play compatibility tool', if it's not already checked. 4. From the dropdown menu, select your preferred Proton version. Proton 10.0 is the supported and recommended version. - 5. Close the properties window and launch the game once to allow Steam to set up the prefix. - 6. Exit the game completely. Do not launch it until the installation process is finished.""" + 5. If you haven't already, launch the game once to allow Steam to set up the prefix, then exit completely. + 6. Do not launch the game again until the installation process is finished.""" case "gog" | "epic": - instructions = """To create a clean prefix for GOG/Epic via Heroic, please follow these steps: + instructions = """Before continuing, please ensure your Heroic prefix is set up correctly: 1. Right-click on the game in your Heroic library. 2. Select 'Settings', and navigate to the 'WINE' tab. @@ -112,20 +75,18 @@ def prompt_archive_init() -> bool: * If Proton versions are not available, you will need to enable "Allow using Valve Proton builds to run games" in Heroic's Settings, under the 'Advanced' tab. Then, ensure that Proton is downloaded and installed in Steam. 4. Optional: Navigate to the 'OTHER' tab and check 'Use Steam Runtime'. This is recommended and may help with compatibility. - 5. Launch the game once to allow Heroic to set up the prefix. - 6. Exit the game completely. Do not launch it until the installation process is finished.""" + 5. If you haven't already, launch the game once to allow Heroic to set up the prefix, then exit completely. + 6. Do not launch the game again until the installation process is finished.""" msg = { "type": "confirm", - "message": f"{instructions}\n\n Have you completed these instructions?", - "name": "clean_prefix_done", + "message": f"{instructions}\n\n Have you completed these steps?", + "name": "prefix_init_done", "default": False, } result = prompt([msg]) - logger.debug( - f"User completed clean prefix initialization: {result['clean_prefix_done']}" - ) - return result["clean_prefix_done"] + logger.debug(f"User confirmed prefix setup: {result['prefix_init_done']}") + return result["prefix_init_done"] def prompt_install_mo2_checksum_fail(mo2_path: str) -> bool: diff --git a/src/mo2-lint/util/variables.py b/src/mo2-lint/util/variables.py index 2433420..c31d3d7 100755 --- a/src/mo2-lint/util/variables.py +++ b/src/mo2-lint/util/variables.py @@ -863,16 +863,6 @@ prefix: Path = None Path to game's Wine/Proton prefix directory. """ -archived_prefix: Path = None -""" -Path to archived Wine/Proton prefix directory. -""" - -archived_prefixes: list[Path] = [] -""" -List of previously archived Wine/Proton prefix directories. -""" - heroic_config: tuple[str, str | int, Path, Path, Path] = () game_install_path: Path = None