예제 #1
0
    def _getEntriesToExtract(
        self,
        tree: mobase.IFileTree,
        extensions: List[str] = ["png", "jpg", "jpeg", "gif", "bmp", "ini"],
    ) -> List[mobase.FileTreeEntry]:
        """
        Retrieve all the entries to extract from the given tree.

        Args:
            tree: The tree.
            extensions: The extensions of files.

        Returns:
            A list of entries corresponding to files with the given extensions.
        """
        entries = []

        def fn(path: str, entry: mobase.FileTreeEntry):
            if entry.isFile() and entry.hasSuffix(extensions):
                entries.append(entry)
            return mobase.IFileTree.CONTINUE

        tree.walk(fn)

        return entries
예제 #2
0
 def dataLooksValid(
     self, tree: mobase.IFileTree
 ) -> mobase.ModDataChecker.CheckReturn:
     if tree.exists("db") or tree.exists("appdata") or tree.exists("gamedata"):
         return mobase.ModDataChecker.VALID
     else:
         return mobase.ModDataChecker.INVALID
예제 #3
0
 def fix(self, tree: mobase.IFileTree) -> Optional[mobase.IFileTree]:
     if not isinstance(tree[0], mobase.IFileTree):
         return None
     entry = tree[0].find(tree[0].name() + ".pak")
     if entry is None:
         return None
     tree.copy(entry, "", mobase.IFileTree.InsertPolicy.FAIL_IF_EXISTS)
     return tree
    def fix(self, tree: mobase.IFileTree) -> mobase.IFileTree:
        ress, maps = self.get_resources_and_maps(tree)

        if ress:
            rfolder = tree.addDirectory("Resources")
            for r in ress:
                rfolder.insert(r, mobase.IFileTree.REPLACE)
        if maps:
            rfolder = tree.addDirectory("Maps")
            for r in maps:
                rfolder.insert(r, mobase.IFileTree.REPLACE)
        return tree
    def dataLooksValid(
        self, tree: mobase.IFileTree
    ) -> mobase.ModDataChecker.CheckReturn:
        # Check if we have a Resources / Maps folder or .ds2res/.ds2map
        ress, maps = self.get_resources_and_maps(tree)

        if not ress and not maps:
            if tree.exists("Resources") or tree.exists("Maps"):
                return mobase.ModDataChecker.VALID
            else:
                return mobase.ModDataChecker.INVALID

        return mobase.ModDataChecker.FIXABLE
예제 #6
0
    def fix(self, tree: mobase.IFileTree) -> mobase.IFileTree:
        lost_db = self.findLostData(tree)
        if lost_db:
            rfolder = tree.addDirectory("db").addDirectory("mods")
            for r in lost_db:
                rfolder.insert(r, mobase.IFileTree.REPLACE)

        return tree
예제 #7
0
    def fix(self, tree: mobase.IFileTree):
        toMove = []
        for entry in tree:
            if any([sub in entry.name().casefold() for sub in self._fileIgnore]):
                continue
            elif entry.suffix() == "chl":
                toMove.append((entry, "/Scripts/BW2/"))
            elif entry.suffix() == "bmp":
                toMove.append((entry, "/Data/"))
            elif entry.suffix() == "txt":
                toMove.append((entry, "/Scripts/"))
            else:
                toMove.append((entry, "/Data/landscape/BW2/"))

        for (entry, path) in toMove:
            tree.move(entry, path, policy=mobase.IFileTree.MERGE)

        return tree
예제 #8
0
    def __init__(self, tree: mobase.IFileTree):
        super().__init__(tree.name())
        self._tree = tree

        # We cannot perform lazy iteration on the tree in a Python way so we
        # have to list the files:

        self._files = []

        def fn(folder, entry) -> mobase.IFileTree.WalkReturn:
            self._files.append(entry.path())
            return mobase.IFileTree.CONTINUE

        self._tree.walk(fn)
예제 #9
0
    def dataLooksValid(
            self, tree: mobase.IFileTree) -> mobase.ModDataChecker.CheckReturn:

        folders: List[mobase.IFileTree] = []
        files: List[mobase.FileTreeEntry] = []

        for entry in tree:
            if isinstance(entry, mobase.IFileTree):
                folders.append(entry)
            else:
                files.append(entry)

        if len(folders) != 1:
            return mobase.ModDataChecker.INVALID

        folder = folders[0]
        pakfile = folder.name() + ".pak"
        if folder.exists(pakfile):
            if tree.exists(pakfile):
                return mobase.ModDataChecker.VALID
            else:
                return mobase.ModDataChecker.FIXABLE

        return mobase.ModDataChecker.INVALID
예제 #10
0
    def _getWizardArchiveBase(
        self, tree: mobase.IFileTree, data_name: str, checker: mobase.ModDataChecker
    ) -> Optional[mobase.IFileTree]:
        """
        Try to find the folder containing wizard.txt.

        Args:
            tree: Tree to look the data folder in.
            data_name: Name of the data folder (e.g., "data" for Bethesda games).
            checker: Checker to use to check if a tree is a data folder.

        Returns:
            The tree corresponding to the folder containing wizard.txt, or None.
        """

        entry = tree.find("wizard.txt", mobase.FileTreeEntry.FILE)

        if entry:
            return tree

        if len(tree) == 1 and isinstance(tree[0], mobase.IFileTree):
            return self._getWizardArchiveBase(tree[0], data_name, checker)

        return None
예제 #11
0
    def install(
        self,
        name: mobase.GuessedString,
        otree: mobase.IFileTree,
        version: str,
        modId: int,
    ) -> Union[mobase.InstallResult, mobase.IFileTree]:
        """
        Perform the actual installation.

        Args:
            name: The "name" of the mod. This can be updated to change the name of the
                mod.
            otree: The original archive tree.
            version: The original version of the mod.
            modId: The original ID of the mod.

        Returns: We either return the modified file-tree (if the installation was
            successful), or a InstallResult otherwise.

        Note: It is also possible to return a tuple (InstallResult, IFileTree, str, int)
            containing where the two last members correspond to the new version and ID
            of the mod, in case those were updated by the installer.
        """

        # Retrieve the name of the "data" folder:
        data_name = self._organizer.managedGame().dataDirectory().dirName()

        # Retrieve the mod-data-checker:
        checker: mobase.ModDataChecker = self._organizer.managedGame().feature(
            mobase.ModDataChecker  # type: ignore
        )

        # Retrive the "base" folder:
        base = self._getWizardArchiveBase(otree, data_name, checker)
        if not base or not checker:
            return mobase.InstallResult.NOT_ATTEMPTED

        wizard = base.find("wizard.txt")
        if wizard is None:
            return mobase.InstallResult.NOT_ATTEMPTED

        to_extract = self._getEntriesToExtract(otree)

        # Extract the script:
        paths = self._manager().extractFiles([wizard] + to_extract, silent=False)
        if len(paths) != len(to_extract) + 1:
            return mobase.InstallResult.FAILED

        interpreter = make_interpreter(base, self._organizer)

        script = paths[0]

        dialog = WizardInstallerDialog(
            self._organizer,
            interpreter,
            interpreter.make_top_level_context(Path(script), WizardRunnerState()),
            name,
            {
                Path(entry.path()): Path(path)
                for entry, path in zip(to_extract, paths[1:])
                if not path.endswith(".ini")
            },
            self._installerOptions,
            self._parentWidget(),
        )

        dialog.scriptButtonClicked.connect(lambda: os.startfile(script))  # type: ignore

        # Note: Unlike the official installer, we do not have a "silent" setting,
        # but it is really simple to add it.
        if dialog.exec() == QtWidgets.QDialog.Accepted:

            # We update the name with the user specified one:
            name.update(dialog.name(), mobase.GuessQuality.USER)

            # Create the tree with all the sub-packages:
            tree = otree.createOrphanTree()

            for subpackage in dialog.subpackages():
                entry = base.find(subpackage)

                # Should never happens since we fetch the subpackage for the archive:
                if not entry or not isinstance(entry, mobase.IFileTree):
                    print(
                        f"SubPackage {subpackage} not found in the archive.",
                        file=sys.stderr,
                    )
                    continue

                tree.merge(entry)

            # Handle renames:
            for original, new in dialog.renames().items():
                # Entry should be at the root:
                entry = tree.find(original)

                if not entry:
                    print(f"Plugin {original} not found, cannot rename.")
                    continue

                tree.move(entry, new)

            # TODO: INI Tweaks:
            alltweaks = dialog.tweaks()

            for filename, tweaks in alltweaks.items():

                # Find the original file (if any):
                o_entry = tree.find(filename)
                o_filename: Optional[str] = None
                if o_entry:
                    # Find the filepath from the list of extracted files:
                    index = to_extract.index(o_entry)

                    # +1 because the first one is the script.
                    o_filename = paths[index + 1]

                # If the file existed before, we keep the new one at the same
                # place:
                if o_entry or Path(filename).parts[0].lower() == "ini tweaks":
                    entry = tree.addFile(filename, replace_if_exists=True)

                # Otherwise we create it in INI Tweaks
                else:
                    entry = tree.addFile(
                        os.path.join("INI Tweaks", filename), replace_if_exists=True
                    )

                filepath = self._manager().createFile(entry)

                if not o_filename:
                    data = make_ini_tweaks(tweaks)
                else:
                    data = merge_ini_tweaks(tweaks, Path(o_filename))

                with open(filepath, "w") as fp:
                    fp.write(data)

            # Mark stuff for saving:
            self._installerUsed = True
            self._installerOptions = dict(dialog.selectedOptions())

            # Return the tree:
            return tree

        # If user requested a manual installation, we update the name (to keep it
        # in the manual installation dialog) and just notify the installation manager:
        elif dialog.isManualRequested():
            name.update(dialog.name(), mobase.GuessQuality.USER)
            return mobase.InstallResult.MANUAL_REQUESTED

        # If user canceled, we simply notify the installation manager:
        else:
            return mobase.InstallResult.CANCELED
예제 #12
0
 def getContentsFor(self, tree: mobase.IFileTree) -> List[int]:
     self.content = []
     tree.walk(self.walkContent, "/")
     return self.content