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
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
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
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
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
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)
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
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
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
def getContentsFor(self, tree: mobase.IFileTree) -> List[int]: self.content = [] tree.walk(self.walkContent, "/") return self.content