Esempio n. 1
0
def ask_user_to_choose_in_a_list(text, file_list):
    """ Defines the container to use

    Print every *container* file in the
    given and make the user choose one

    :param containers_list: A list of identified to be containers

    :returns: The chosen container filename
    """
    max_container_number = len(file_list)
    min_container_number = 1
    choice_index = 0

    while choice_index == 0:
        Logger.logPrint('\nWhich container would you like to convert ?')
        print_list_elements(file_list)
        choice_index = input()
        try:
            choice_index = int(choice_index)
            verify_choice_input(choice_index, min_container_number,
                                max_container_number)
        except ValueError:
            choice_index = 0
            Logger.logPrint(
                f'Please use only values between {min_container_number} and {max_container_number}'
            )

    return file_list[choice_index - 1]
Esempio n. 2
0
def ask_for_multiple_choices(maximum_value) -> list:
    """ Let the user choose multiple numbers between 0 and a maximum value

    If the user choice is 0 then return an array with all values,
    the user choices are reduce by 1 in order to match future array indexes

    :Example:

    User choices:
    -  [1,2,4]  - returns 0, 1, 2
    -  [0]      - returns all choices

    :return: The list of numbers
    :exception: None (repeat until the choices are valid)
    """
    choices = []
    while not choices:
        choices = input()
        try:
            choices = process_multiple_choices_input(choices)
            verify_choices_input(choices, maximum_value)
        except ValueError:
            choices = []
            Logger.logPrint(
                f'Please use only values between 1 and {maximum_value} or 0 alone'
            )

    if choices == [-1]:
        return list(range(0, maximum_value))
    else:
        return choices
Esempio n. 3
0
    def __init__(self, container_file_path):
        """
        Extracts the saves from an Astroneer save container

        Reads the container file, divides it into chunks and
        regroups the chunks into save objects

        Arguments:
            container_file_path -- Path to the container file

        Returns:
            The AstroSaveContainer object 

        Exception:
            None
        """
        self.full_path = container_file_path
        self.save_list = []
        Logger.logPrint('full_path: {self.full_path}', "debug")

        with open(self.full_path, "rb") as container:
            # The Astroneer file type is contained in at least the first 2 bytes of the file
            self.header = container.read(2)

            if not self.is_valid_container_header(self.header):
                raise Exception(
                    f'The save container {self.full_path} is not valid (First two bytes:{self.header})'
                )
            # Skipping 2 bytes that may be part of the header
            container.read(2)

            # Next 4 bytes are the number of saves chunk
            self.chunk_count = int.from_bytes(container.read(4),
                                              byteorder='little')

            # Parsing saves chunks
            current_save_name = None
            for _ in range(self.chunk_count):
                current_chunk = container.read(CHUNK_METADATA_SIZE)
                current_chunk_name = self.extract_name_from_chunk(
                    current_chunk)

                if current_chunk_name != current_save_name:
                    if current_save_name != None:
                        # Parsing a new save, storing the current save
                        self.save_list.append(
                            AstroSave(current_save_name, current_chunks_names))

                    current_chunks_names = []
                    current_save_name = current_chunk_name

                current_chunks_names.append(
                    self.extract_chunk_file_name_from_chunk(current_chunk))

            # Saving the last save of the container file
            self.save_list.append(
                AstroSave(current_save_name, current_chunks_names))
Esempio n. 4
0
def backup_win_before_steam_export() -> str:
    Logger.logPrint(
        '\nFor safety reasons, we will now copy your current Microsoft Astroneer saves'
    )
    backup_path = ask_copy_target('MicrosoftAstroneerSavesBackup')

    astroneer_save_folder = AstroMicrosoftSaveFolder.backup_microsoft_save_folder(
        backup_path)

    return astroneer_save_folder
Esempio n. 5
0
def seek_microsoft_save_folder(appdata_path) -> str:
    folders = get_save_folders_from_path(appdata_path)

    if not folders:
        Logger.logPrint(f'No save folder found.', 'debug')
        raise FileNotFoundError
    elif len(folders) != 1:
        # We are not supposed to have more than one save folder
        Logger.logPrint(f'More than one save folders was found:\n {folders}', 'debug')
        raise MultipleFolderFoundError

    return folders[0]
Esempio n. 6
0
def ask_overwrite_if_file_exists(filename: str, target: str) -> bool:
    file_url = utils.join_paths(target, filename)

    if utils.is_path_exists(file_url):
        do_overwrite = None
        while do_overwrite not in ('y', 'n'):
            Logger.logPrint(
                f'\nFile {filename} already exists, overwrite it ? (y/n)')
            do_overwrite = input().lower()

        return do_overwrite == 'y'
    else:
        return True
Esempio n. 7
0
def ask_conversion_type() -> AstroConvType:
    Logger.logPrint(f'\nWhich conversion do you want to do ?')
    Logger.logPrint("\t1) Convert a Microsoft save into a Steam save")
    Logger.logPrint('\t2) Convert a Steam save into a Microsoft save')

    choice = input()
    while choice not in ('1', '2'):
        Logger.logPrint(f'\nPlease choose 1 or 2')
        choice = input()
        Logger.logPrint(f'convert_choice {choice}', 'debug')

    if choice == '1':
        return AstroConvType.WIN2STEAM
    else:
        return AstroConvType.STEAM2WIN
Esempio n. 8
0
def ask_rename_saves(saves_indexes, container):
    """ Guide the user in order to rename a save

    :param save_indexes: List of the saves in the container.save_list you want to rename
    :param container: Container from which to rename the save
    """

    do_rename = None
    while do_rename not in ('y', 'n'):
        Logger.logPrint('\nWould you like to rename a save ? (y/n)')
        do_rename = input().lower()

    if do_rename == 'y':
        for index in saves_indexes:
            save = container.save_list[index]
            rename_save(save)
Esempio n. 9
0
def ask_rename_saves(saves_indexes, save_list):
    """ Guide the user in order to rename a save

    :param save_indexes: List of the saves in the save_list you want to rename
    :param save_list: List of the save objects you may rename
    """

    do_rename = None
    while do_rename not in ('y', 'n'):
        Logger.logPrint('\nWould you like to rename a save ? (y/n)')
        do_rename = input().lower()

    if do_rename == 'y':
        for index in saves_indexes:
            save = save_list[index]
            rename_save(save)
Esempio n. 10
0
def rename_save(save):
    """ Rename a save
    Rename can be skipped by pressing Enter directly

    :param save: Save object to be renamed
    """
    new_name = None
    while new_name is None:
        new_name = input(
            f'\nNew name for {save.name.split("$")[0]}: [ENTER = unchanged] > '
        ).upper()
        if (new_name != ''):
            try:
                save.rename(new_name)
            except ValueError:
                new_name = None
                Logger.logPrint(f'Please use only alphanum and a length < 30')
Esempio n. 11
0
def get_save_folders_from_path(path) -> list:
    microsoft_save_folders = []

    for root, _, files in os.walk(path):
        for file in files:
            if re.search(r'^container\.', file):
                container_full_path = utils.join_paths(root, file)

                Logger.logPrint(f'Container file found:{container_full_path}', 'debug')

                container_text = read_container_text_from_path(container_full_path)

                if do_container_text_match_date(container_text):
                    Logger.logPrint(f'Matching save folder {root}', 'debug')
                    microsoft_save_folders.append(root)

    return microsoft_save_folders
Esempio n. 12
0
    def convert_to_xbox(self, source: str) -> Tuple[List[uuid.UUID], List[BytesIO]]:
        """Exports a save as a tuple in its Xbox file format

        The save is returned as a tuple (chunks names, chunk buffers) representing all of its chunks
        Each element of the list is a chunk of the save.
        The order of the elements matters.

        Arguments:
            source: In which folder to read the Steam save

        Returns:
            A tuple containing the names and the buffers uuid of the Xbox chunks
        """
        # TODO [enhance] this functionned could be renamed by something like load_save_from_steam_file
        #       and the whole AstroSave class modified to store the uuids list instead of chunk names list + to store the whole buffer of each chunk
        #       That would make more sense and the tuple wouldn't need to be returned
        #       The reading of the saves from a container would also be simplified by a lot (by building a uuid from the bytes read in the container)

        buffer_uuids = []
        buffers = []
        self.chunks_names = []

        len_read = XBOX_CHUNK_SIZE
        save_file_path = source

        with open(save_file_path, 'rb') as save_file:

            while len_read == XBOX_CHUNK_SIZE:
                buffer = BytesIO()
                file_uuid = uuid.uuid4()
                Logger.logPrint(f'UUID generated: {file_uuid}', "debug")

                buffer.write(save_file.read(XBOX_CHUNK_SIZE))

                len_read = len(buffer.getvalue())

                self.chunks_names.append(file_uuid.hex.upper())
                buffer_uuids.append(file_uuid)
                buffers.append(buffer)

        return (buffer_uuids, buffers)
def get_microsoft_save_folder() -> str:
    """ Retrieves the microsoft save folders from %LocalAppdata%

    We know that the saves are stored along with a container.* file.
    We look for that specific container by checking if it contains a
    save date in order to return the whole path

    :return: The list of the microsoft save folder content found in %appdata%
    :exception: FileNotFoundError if no save folder is found
    :exception: MultipleFolderFoundError if multiple save folder are found
    """

    try:
        target = os.environ[
            'LOCALAPPDATA'] + '\\Packages\\SystemEraSoftworks*\\SystemAppData\\wgs'
    except KeyError:
        Logger.logPrint("Local Appdata are missing, maybe you're on linux ?")
        Logger.logPrint("Press any key to exit")
        utils.wait_and_exit(1)

    microsoft_save_paths = list(glob.iglob(target))

    for path in microsoft_save_paths:
        Logger.logPrint(f'SES path found in appadata: {path}', 'debug')

    SES_appdata_path = microsoft_save_paths[-1]

    microsoft_save_folder = seek_microsoft_save_folder(SES_appdata_path)

    return microsoft_save_folder
Esempio n. 14
0
def ask_custom_folder_path() -> str:
    Logger.logPrint(f'\nEnter your custom folder path:')
    path = input()
    Logger.logPrint(f'save_folder_path {path}', 'debug')

    if utils.is_folder_a_dir(path):
        return path
    else:
        Logger.logPrint(
            f'\nWrong path for save folder, please enter a valid path : ')
        return ask_custom_folder_path()
Esempio n. 15
0
def ask_saves_to_export(save_list):
    Logger.logPrint('Extracted save list :')
    print_save_from_container(save_list)
    Logger.logPrint(
        '\nWhich saves would you like to convert ? (Choose 0 for all of them)')
    Logger.logPrint('(Multi-convert is supported. Ex: "1,2,4")')

    maximum_save_number = len(save_list)
    saves_to_export = ask_for_multiple_choices(maximum_save_number)

    return saves_to_export
Esempio n. 16
0
def ask_saves_to_export(save_list: List[AstroSave]) -> List[int]:
    """TODO [doc] explain that this function returns the indexes in the save list and not a sublist of the save_list
    """
    Logger.logPrint('Extracted save list :')
    print_save_from_container(save_list)
    Logger.logPrint(
        '\nWhich saves would you like to convert ? (Choose 0 for all of them)')
    Logger.logPrint('(Multi-convert is supported. Ex: "1,2,4")')

    maximum_save_number = len(save_list)
    saves_to_export = ask_for_multiple_choices(maximum_save_number)

    return saves_to_export
def get_steam_save_folder() -> str:
    """ Retrieves the Steam save folders from %LocalAppdata%

    :return: The Steam save folder content found in %appdata%
    :exception: FileNotFoundError if no save folder is found
    :exception: MultipleFolderFoundError if multiple save folder are found
    """

    try:
        target = os.environ['LOCALAPPDATA'] + '\\Astro\\Saved\\SaveGames'
    except KeyError:
        Logger.logPrint("Local Appdata are missing, maybe you're on linux ?")
        Logger.logPrint("Press any key to exit")
        utils.wait_and_exit(1)

    steam_save_paths = list(glob.iglob(target))

    for path in steam_save_paths:
        Logger.logPrint(f'SES path found in appadata: {path}', 'debug')

    return steam_save_paths[0]
Esempio n. 18
0
def steam_to_windows_conversion(original_save_path: str) -> None:
    Logger.logPrint('\n\n/!\\ WARNING /!\\')
    Logger.logPrint(
        '/!\\ Astroneer needs to be closed longer than 20 seconds before we can start exporting your saves /!\\'
    )
    Logger.logPrint(
        '/!\\ More info and save restoring procedure are available on Github (cf. README) /!\\'
    )
    loading_bar = LoadingBar(15)
    loading_bar.start_loading()

    xbox_astroneer_save_folder = Scenario.backup_win_before_steam_export()

    steamsave_files_list = AstroSave.get_steamsaves_list(original_save_path)

    saves_list = AstroSave.init_saves_list_from(steamsave_files_list)

    original_saves_name = []
    for save in saves_list:
        original_saves_name.append(save.name)

    saves_indexes_to_export = Scenario.ask_saves_to_export(saves_list)

    Scenario.ask_rename_saves(saves_indexes_to_export, saves_list)

    Logger.logPrint(
        f'\nExtracting saves {str([i+1 for i in saves_indexes_to_export])}')
    Logger.logPrint(
        f'Working folder: {original_save_path} Export to: {xbox_astroneer_save_folder}',
        "debug")

    for save_index in saves_indexes_to_export:
        save = saves_list[save_index]
        original_save_full_path = utils.join_paths(
            original_save_path, original_saves_name[save_index] + '.savegame')
        Scenario.export_save_to_xbox(save, original_save_full_path,
                                     xbox_astroneer_save_folder)

        Logger.logPrint(f"\nSave {save.name} has been exported succesfully.")
Esempio n. 19
0
def windows_to_steam_conversion(original_save_path: str) -> None:
    containers_list = Container.get_containers_list(original_save_path)

    Logger.logPrint('\nContainers found:' + str(containers_list))
    container_name = Scenario.ask_for_containers_to_convert(
        containers_list) if len(containers_list) > 1 else containers_list[0]
    container_url = utils.join_paths(original_save_path, container_name)

    Logger.logPrint('\nInitializing Astroneer save container...')
    container = Container(container_url)
    Logger.logPrint(f'Detected chunks: {container.chunk_count}')

    Logger.logPrint('Container file loaded successfully !\n')

    saves_to_export = Scenario.ask_saves_to_export(container.save_list)

    Scenario.ask_rename_saves(saves_to_export, container.save_list)

    to_path = utils.join_paths(original_save_path, 'Steam saves')
    utils.make_dir_if_doesnt_exists(to_path)

    Logger.logPrint(
        f'\nExtracting saves {str([i+1 for i in saves_to_export])}')
    Logger.logPrint(f'Container: {container.full_path} Export to: {to_path}',
                    "debug")

    for save_index in saves_to_export:
        save = container.save_list[save_index]

        Scenario.ask_overwrite_save_while_file_exists(save, to_path)
        Scenario.export_save_to_steam(save, original_save_path, to_path)

        Logger.logPrint(f"\nSave {save.name} has been exported succesfully.")
Esempio n. 20
0
        f'Working folder: {original_save_path} Export to: {xbox_astroneer_save_folder}',
        "debug")

    for save_index in saves_indexes_to_export:
        save = saves_list[save_index]
        original_save_full_path = utils.join_paths(
            original_save_path, original_saves_name[save_index] + '.savegame')
        Scenario.export_save_to_xbox(save, original_save_full_path,
                                     xbox_astroneer_save_folder)

        Logger.logPrint(f"\nSave {save.name} has been exported succesfully.")


if __name__ == "__main__":
    try:
        Logger.setup_logging(os.getcwd())

        try:
            os.system(
                "title AstroSaveConverter 2.0 - Convert your Astroneer saves between Microsoft and Steam"
            )
        except:
            pass

        args = get_args()

        conversion_type = Scenario.ask_conversion_type()

        try:
            if not args.savesPath:
                original_save_path = Scenario.ask_for_save_folder(
Esempio n. 21
0
def ask_for_save_folder(conversion_type: AstroConvType) -> str:
    """ Obtains the save folder

    Lets the user pick between automatic save retrieving/copying or
    a custom save folder

    Arguments:
        conversion_type : Type of save conversion (for automatic folder retrieval purpose)

    Returns:
        The save folder path
    """
    while 1:
        try:
            Logger.logPrint("Which  folder would you like to work with ?")
            Logger.logPrint(
                "\t1) Automatically detect and copy my save folder (Please close Astroneer first)"
            )
            Logger.logPrint("\t2) Chose a custom folder")

            work_choice = input()
            while work_choice not in ('1', '2'):
                Logger.logPrint(f'\nPlease choose 1 or 2')
                work_choice = input()
                Logger.logPrint(f'folder_type {work_choice}', 'debug')

            if work_choice == '1':
                if conversion_type == AstroConvType.WIN2STEAM:
                    astroneer_save_folder = AstroMicrosoftSaveFolder.get_microsoft_save_folder(
                    )
                    Logger.logPrint(
                        f'Microsoft folder path: {astroneer_save_folder}',
                        'debug')
                else:
                    astroneer_save_folder = AstroSteamSaveFolder.get_steam_save_folder(
                    )
                    Logger.logPrint(
                        f'Steam folder path: {astroneer_save_folder}', 'debug')

                save_path = ask_copy_target('AstroSaveFolder')
                utils.copy_files(astroneer_save_folder, save_path)

                Logger.logPrint(f'Save files copied to: {save_path}')

            elif work_choice == '2':
                save_path = ask_custom_folder_path()

            return save_path

        except MultipleFolderFoundError:
            Logger.logPrint(
                f'\nToo many save folders found ! Please use custom folder mode.'
            )
        except FileNotFoundError as e:
            Logger.logPrint('\nNo container found in path: ' + save_path)
            Logger.logPrint(e, 'exception')
Esempio n. 22
0
def print_list_elements(elements):
    for i, container in elements:
        Logger.logPrint(f'\t {i+1}) {container}')
Esempio n. 23
0
def steam_to_windows_conversion(original_save_path: str) -> None:
    Logger.logPrint('\n\n/!\\ WARNING /!\\')
    Logger.logPrint(
        '/!\\ Astroneer needs to be closed longer than 20 seconds before we can start exporting your saves /!\\'
    )
    Logger.logPrint(
        '/!\\ More info and save restoring procedure are available on Github (cf. README) /!\\'
    )
    # TODO Elfou loading bar => "safety bar" (15 sec)

    xbox_astroneer_save_folder = Scenario.backup_win_before_steam_export()

    steamsave_files_list = AstroSave.get_steamsaves_list(original_save_path)

    saves_list = AstroSave.init_saves_list_from(steamsave_files_list)

    saves_indexes_to_export = Scenario.ask_saves_to_export(saves_list)

    Scenario.ask_rename_saves(saves_indexes_to_export, saves_list)

    Logger.logPrint(
        f'\nExtracting saves {str([i+1 for i in saves_indexes_to_export])}')
    Logger.logPrint(
        f'Working folder: {original_save_path} Export to: {xbox_astroneer_save_folder}',
        "debug")

    for save_index in saves_indexes_to_export:
        save = saves_list[save_index]
        Scenario.export_save_to_xbox(save, original_save_path,
                                     xbox_astroneer_save_folder)

        # TODO
        # Retrieve the real Microsoft gamepass astro container full path
        # Export save objects to container (prepare chunks in hexa and concat them to the container)
        # If export failed, delete all the chunk files written to disk

        Logger.logPrint(f"\nSave {save.name} has been exported succesfully.")
Esempio n. 24
0
def print_save_from_container(save_list):
    """ Displays the human readable saves of a container """
    for i, save in enumerate(save_list):
        Logger.logPrint(f'\t {str(i+1)}) {save.name}')
Esempio n. 25
0
def export_save_to_xbox(save: AstroSave, from_file: str, to_path: str) -> None:
    chunk_uuids, converted_chunks = save.convert_to_xbox(from_file)

    chunk_count = len(chunk_uuids)

    if chunk_count >= 10:
        Logger.logPrint(
            f'The selected save contains {chunk_count} which is over the 9 chunks limit AstroSaveconverter can handle yet'
        )
        Logger.logPrint(
            f'Congrats for having such a huge save, please open an issue on the GitHub :D'
        )

    for i in range(chunk_count):

        # The file name is the HEX upper form of the uuid
        chunk_name = save.chunks_names[i]
        Logger.logPrint(f'UUID as file name: {chunk_name}', "debug")

        target_full_path = utils.join_paths(to_path, chunk_name)
        Logger.logPrint(f'Chunk file written to: {target_full_path}', "debug")

        # Regenerating chunk name if it already exists. Very, very unlikely
        while utils.is_path_exists(target_full_path):
            Logger.logPrint(f'UUID: {chunk_name} already exists ! (omg)',
                            "debug")

            chunk_uuids[i] = save.regenerate_uuid(i)
            chunk_name = save.chunks_names[i]

            Logger.logPrint(f'Regenerated UUID: {chunk_name}', "debug")
            target_full_path = utils.join_paths(to_path, chunk_name)

        # TODO [enhance] raise exception if can't write, catch it then delete all the chunks already written and exit
        utils.write_buffer_to_file(target_full_path, converted_chunks[i])

    # Container is updated only after all the chunks of the save have been written successfully
    container_file_name = Container.get_containers_list(to_path)[0]

    container_full_path = utils.join_paths(to_path, container_file_name)

    with open(container_full_path, "r+b") as container:
        container.read(4)

        current_container_chunk_count = int.from_bytes(container.read(4),
                                                       byteorder='little')

        new_container_chunk_count = current_container_chunk_count + chunk_count

        container.seek(-4, 1)
        container.write(
            new_container_chunk_count.to_bytes(4, byteorder='little'))

    chunks_buffer = BytesIO()
    for i in range(chunk_count):

        total_written_len = 0

        encoded_save_name = save.name.encode('utf-16le', errors='ignore')
        total_written_len += chunks_buffer.write(encoded_save_name)

        if chunk_count > 1:
            # Multi-chunks save. Adding metadata, format: '$${i}${chunk_count}$1'
            chunk_metadata = f'$${i}${chunk_count}$1'

            encoded_metadata = chunk_metadata.encode('utf-16le',
                                                     errors='ignore')
            total_written_len += chunks_buffer.write(encoded_metadata)

        chunks_buffer.write(b"\00" * (144 - total_written_len))

        chunks_buffer.write(chunk_uuids[i].bytes_le)

    Logger.logPrint(f'Editing container: {container_full_path}', "debug")
    utils.append_buffer_to_file(container_full_path, chunks_buffer)
Esempio n. 26
0
def ask_copy_target():
    Logger.logPrint('Where would you like to copy your save folder ?')
    Logger.logPrint('\t1) New folder on my desktop')
    Logger.logPrint("\t2) New folder in a custom path")

    choice = input()
    while choice not in ('1', '2'):
        Logger.logPrint(f'\nPlease choose 1 or 2')
        choice = input()
        Logger.logPrint(f'copy_choice {choice}', 'debug')

    if choice == '1':
        # Winpath is needed here because Windows user can have a custom Desktop location
        save_path = utils.get_windows_desktop_path()
    elif choice == '2':
        Logger.logPrint(f'\nEnter your custom folder path:')
        save_path = input()
        Logger.logPrint(f'save_path {save_path}', 'debug')

    return utils.join_paths(save_path,
                            utils.create_folder_name('AstroSaveFolder'))
Esempio n. 27
0
def steam_to_windows_conversion(original_save_path: str) -> None:
    Logger.logPrint('\nThis conversion is not supported yet. Exiting...')
    utils.wait_and_exit(0)
Esempio n. 28
0
def ask_copy_target(folder_main_name: str):
    ''' Requests a target folder to the user
    TODO [doc] to explain the folder name format
    Arguments:
        folder_main_name:

    Returns
        ...
    '''
    Logger.logPrint('Where would you like to copy your save folder ?')
    Logger.logPrint('\t1) New folder on my desktop')
    Logger.logPrint("\t2) New folder in a custom path")

    choice = input()
    while choice not in ('1', '2'):
        Logger.logPrint(f'\nPlease choose 1 or 2')
        choice = input()
        Logger.logPrint(f'copy_choice {choice}', 'debug')

    if choice == '1':
        # Winpath is needed here because Windows user can have a custom Desktop location
        save_path = utils.get_windows_desktop_path()
    elif choice == '2':
        Logger.logPrint(f'\nEnter your custom folder path:')
        save_path = input()
        Logger.logPrint(f'save_path {save_path}', 'debug')

    return utils.join_paths(save_path,
                            utils.create_folder_name(folder_main_name))