Exemplo n.º 1
0
def findPossibleGamePathsWindows():
	"""
	Blindly retrieve all game folders in the `Steam\steamappps\common` folder (no filtering is performed)
	TODO: scan other locations than just the steamapps folder
	:return: a list of absolute paths, which are the folders in the `Steam\steamappps\common` folder
	:rtype: list[str]
	"""
	try:
		import winreg
	except ImportError:
		import _winreg as winreg

	allSteamPaths = []
	try:
		try:
			registryKey = winreg.OpenKey(winreg.HKEY_CURRENT_USER, r'Software\Valve\Steam')
		except WindowsError:
			# I installed Steam on a Win 10 64-bit machine and it used this alternate registry key location. Not sure why.
			registryKey = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, r'Software\Wow6432Node\Valve\Steam')

		defaultSteamPath, _regType = winreg.QueryValueEx(registryKey, 'SteamPath')
		allSteamPaths.append(defaultSteamPath)
		winreg.CloseKey(registryKey)
	except WindowsError:
		print("findPossibleGamePaths: Couldn't read Steam registry key - Steam not installed?")
		return []

	# now that we know the steam path, search the "Steam\config\config.vdf" file for extra install paths
	# this is a purely optional step, so it's OK if it fails
	try:
		import io
		baseInstallFolderRegex = re.compile(r'^\s*"BaseInstallFolder_\d+"\s*"([^"]+)"', re.MULTILINE)
		steamConfigVDFLocation = os.path.join(defaultSteamPath, r'config\config.vdf')
		with io.open(steamConfigVDFLocation, 'r', encoding='UTF-8') as configVDFFile:
			allSteamPaths += baseInstallFolderRegex.findall(configVDFFile.read())
	except Exception as e:
		traceback.print_exc()

	logger.printNoTerminal("Will scan the following steam install locations: {}".format(allSteamPaths))

	# normpath added so returned paths have consistent slash directions (registry key has forward slashes on Win...)
	try:
		allPossibleGamePaths = []

		for steamCommonPath in (os.path.join(steamPath, r'steamapps\common') for steamPath in allSteamPaths):
			for gameFolderName in os.listdir(steamCommonPath):
				gameFolderPath = os.path.join(steamCommonPath, gameFolderName)
				if os.path.isdir(gameFolderPath):
					allPossibleGamePaths.append(
						os.path.normpath(
							gameFolderPath
						)
					)

		return allPossibleGamePaths
	except:
		print("findPossibleGamePaths: Couldn't open registry key folder - Steam folder deleted?")
		return []

	return []
Exemplo n.º 2
0
def getSubModConfigList(modList):
    subModconfigList = []
    for mod in modList:
        for submod in mod['submods']:
            conf = installConfiguration.SubModConfig(mod, submod)
            logger.printNoTerminal(conf)
            subModconfigList.append(conf)
    return subModconfigList
Exemplo n.º 3
0
            def startInstallHandler(requestData):
                # this is not a 'proper' submod - just a handle returned form getSubModHandlesRequestHandler()
                webSubModHandle = requestData['subMod']
                webModOptionGroups = webSubModHandle['modOptionGroups']
                id = webSubModHandle['id']
                validateOnly = requestData.get('validateOnly', False)
                deleteVersionInformation = requestData.get(
                    'deleteVersionInformation', False)
                installSteamGrid = requestData.get('installSteamGrid', False)

                subMod = self.idToSubMod[id]

                updateModOptionsFromWebFormat(subMod.modOptions,
                                              webModOptionGroups)
                logger.printNoTerminal("\nUser selected options for install:")
                for modOption in subMod.modOptions:
                    logger.printNoTerminal(modOption)

                installPath = requestData.get('installPath', None)
                installValid, fullInstallConfiguration = self.try_start_install(
                    subMod, installPath, validateOnly, installSteamGrid)
                retval = {'installStarted': installValid}
                if installValid:
                    if deleteVersionInformation:
                        fileVersionManagement.VersionManager.tryDeleteLocalVersionFile(
                            fullInstallConfiguration.installPath)

                    downloadItemsPreview, totalDownloadSize, numUpdatesRequired, fullUpdateRequired, partialReinstallDetected = getDownloadPreview(
                        fullInstallConfiguration)
                    haveEnoughFreeSpace, freeSpaceAdvisoryString = common.checkFreeSpace(
                        installPath=fullInstallConfiguration.installPath,
                        recommendedFreeSpaceBytes=totalDownloadSize *
                        common.Globals.DOWNLOAD_TO_EXTRACTION_SCALING)
                    CWDHaveEnoughFreeSpaceInstallerPath, CWDFreeSpaceAdvisoryStringInstallerPath = common.checkFreeSpace(
                        installPath=os.getcwd(),
                        recommendedFreeSpaceBytes=totalDownloadSize *
                        common.Globals.DOWNLOAD_TO_EXTRACTION_SCALING)

                    retval[
                        'validatedInstallPath'] = fullInstallConfiguration.installPath
                    retval['haveEnoughFreeSpace'] = haveEnoughFreeSpace
                    retval['freeSpaceAdvisoryString'] = freeSpaceAdvisoryString
                    retval[
                        'CWDHaveEnoughFreeSpace'] = CWDHaveEnoughFreeSpaceInstallerPath
                    retval[
                        'CWDFreeSpaceAdvisoryString'] = CWDFreeSpaceAdvisoryStringInstallerPath
                    retval['downloadItemsPreview'] = downloadItemsPreview
                    retval['numUpdatesRequired'] = numUpdatesRequired
                    retval['fullUpdateRequired'] = fullUpdateRequired
                    retval[
                        'partialReinstallDetected'] = partialReinstallDetected
                return retval
Exemplo n.º 4
0
	def __init__(self, subMod, modFileList, localVersionFolder, _testRemoteSubModVersion=None):
		#type: (installConfiguration.SubModConfig, List[installConfiguration.ModFile], str, Optional[SubModVersionInfo]) -> None
		self.targetID = subMod.modName + '/' + subMod.subModName
		self.unfilteredModFileList = modFileList
		self.localVersionFilePath = os.path.join(localVersionFolder, VersionManager.localVersionFileName)

		# Get remote and local versions
		try:
			self.localVersionInfo = getLocalVersion(self.localVersionFilePath)
		except Exception as error:
			self.localVersionInfo = None
			print("VersionManager: Error while retrieving version information: {}".format(error))

		# allow overriding the remote sub mod version for testing purposes
		if _testRemoteSubModVersion is not None:
			self.remoteVersionInfo = _testRemoteSubModVersion
		else:
			try:
				self.remoteVersionInfo = getRemoteVersion(self.targetID)
			except Exception as error:
				self.remoteVersionInfo = None
				print("VersionManager: Error while retrieving remote version information {}".format(error))

		logger.printNoTerminal("\nLocal Version: {}".format(self.localVersionInfo))
		logger.printNoTerminal("Remote Version: {}".format(self.remoteVersionInfo))

		# If can't retrieve version info, mark everything as needing update
		if self.localVersionInfo is None:
			self.updatesRequiredDict = {}
			for file in self.unfilteredModFileList:
				self.updatesRequiredDict[file.id] = (True, "No local version information - Assuming update is required")
		elif self.remoteVersionInfo is None:
			self.updatesRequiredDict = {}
			for file in self.unfilteredModFileList:
				self.updatesRequiredDict[file.id] = (True, "Failed to retrieve remote version information")
		else:
			# Mark files which need update
			self.updatesRequiredDict = getFilesNeedingUpdate(self.unfilteredModFileList, self.localVersionInfo, self.remoteVersionInfo)

			print("\nInstaller Update Information:")
			for fileID, (needsUpdate, updateReason) in self.updatesRequiredDict.items():
				print("[{}]: status: [{}] because [{}]".format(fileID, needsUpdate, updateReason))

		# Check how many updates are required
		updatesRequiredList = self.updatesRequiredDict.values()
		self.totalNumUpdates = len(updatesRequiredList)
		self.numUpdatesRequired = sum([needsUpdate for (needsUpdate, _) in updatesRequiredList])
		print("Full Update: {} ({}/{}) excluding mod options".format(self.fullUpdateRequired(), self.numUpdatesRequired, self.totalNumUpdates))
Exemplo n.º 5
0
        def handleInstallerData(body_string):
            # type: (str) -> str
            requestType, requestData = _decodeJSONRequest(body_string)
            if requestType != 'statusUpdate':
                logger.printNoTerminal('Got Request [{}] Data [{}]'.format(
                    requestType, requestData))

            # requestData: set which game the user selected by specifying the mods->name field from the json, eg "Onikakushi Ch.1"
            # responseData: a dictionary indicating if it's a valid selection (true, false)
            def setModName(requestData):
                userSelectedModToInstall = requestData['modName']
                modNames = [config.modName for config in self.allSubModConfigs]
                modNameValid = userSelectedModToInstall in modNames
                if modNameValid:
                    self.selectedModName = userSelectedModToInstall

                return {'valid': modNameValid, 'modNames': modNames}

            # requestData: leave as null. will be ignored.
            # responseData: A dictionary containing basic information about each subModConfig, along with it's index.
            #               Most important is the index, which must be submitted in the 'getGamePaths' request.
            def getSubModHandlesRequestHandler(requestData):
                # a list of 'handles' to each submod.
                # This contains just enough information about each submod so that the python script knows
                # which config was chosen, and which
                subModHandles = []
                for subModConfig in self.allSubModConfigs:
                    subModHandles.append({
                        'id':
                        subModConfig.id,
                        'modName':
                        subModConfig.modName,
                        'subModName':
                        subModConfig.subModName,
                        'descriptionID':
                        subModConfig.descriptionID,
                        'modOptionGroups':
                        modOptionsToWebFormat(subModConfig.modOptions),
                        'family':
                        subModConfig.family,
                        'identifiers':
                        subModConfig.identifiers,
                    })

                return {
                    'selectedMod': self.selectedModName,
                    'subModHandles': subModHandles,
                    'logFilePath':
                    os.path.abspath(common.Globals.LOG_FILE_PATH),
                    'os': common.Globals.OS_STRING,
                }

            # requestData: A dictionary, which contains a field 'id' containing the ID of the subMod to install, or None to get ALL possible games
            # responseData: A dictionary containing basic information about each fullConfig. Most important is the path
            #               which must be submitted in the final install step.
            # NOTE: the idOfSubMod is not unique in the returned list. You must supply both a submod ID
            #       and a path to the next stage
            def getGamePathsHandler(requestData):
                id = requestData['id']
                selectedSubMods = [
                    self.idToSubMod[id]
                ] if id is not None else self.allSubModConfigs
                fullInstallConfigs = gameScanner.scanForFullInstallConfigs(
                    selectedSubMods)
                fullInstallConfigHandles = []
                for fullConfig in fullInstallConfigs:
                    fullInstallConfigHandles.append({
                        'id':
                        fullConfig.subModConfig.id,
                        'modName':
                        fullConfig.subModConfig.modName,
                        'subModName':
                        fullConfig.subModConfig.subModName,
                        'path':
                        fullConfig.installPath,
                        'isSteam':
                        fullConfig.isSteam,
                    })
                return fullInstallConfigHandles

            #TODO: for security reasons, can't get full path from browser. Either need to copy paste, or open a
            # tk window . Adding a tk window would then require tk dependencies (no problem except requring tk on linux)

            # requestData: The submod ID and install path. If the install path is not specified, then the tkinter
            #               window chooser will be used
            # responseData: If the path is valid:
            #               If the path is invalid: null is returned
            def startInstallHandler(requestData):
                # this is not a 'proper' submod - just a handle returned form getSubModHandlesRequestHandler()
                webSubModHandle = requestData['subMod']
                webModOptionGroups = webSubModHandle['modOptionGroups']
                id = webSubModHandle['id']
                validateOnly = requestData.get('validateOnly', False)
                deleteVersionInformation = requestData.get(
                    'deleteVersionInformation', False)

                subMod = self.idToSubMod[id]

                updateModOptionsFromWebFormat(subMod.modOptions,
                                              webModOptionGroups)
                logger.printNoTerminal("\nUser selected options for install:")
                for modOption in subMod.modOptions:
                    logger.printNoTerminal(modOption)

                installPath = requestData.get('installPath', None)
                installValid, fullInstallConfiguration = self.try_start_install(
                    subMod, installPath, validateOnly)
                retval = {'installStarted': installValid}
                if installValid:
                    if deleteVersionInformation:
                        fileVersionManagement.VersionManager.tryDeleteLocalVersionFile(
                            fullInstallConfiguration.installPath)

                    downloadItemsPreview, totalDownloadSize, numUpdatesRequired, fullUpdateRequired = getDownloadPreview(
                        fullInstallConfiguration)
                    haveEnoughFreeSpace, freeSpaceAdvisoryString = common.checkFreeSpace(
                        installPath=fullInstallConfiguration.installPath,
                        recommendedFreeSpaceBytes=totalDownloadSize *
                        common.Globals.DOWNLOAD_TO_EXTRACTION_SCALING)
                    CWDHaveEnoughFreeSpaceInstallerPath, CWDFreeSpaceAdvisoryStringInstallerPath = common.checkFreeSpace(
                        installPath=os.getcwd(),
                        recommendedFreeSpaceBytes=totalDownloadSize *
                        common.Globals.DOWNLOAD_TO_EXTRACTION_SCALING)

                    retval[
                        'validatedInstallPath'] = fullInstallConfiguration.installPath
                    retval['haveEnoughFreeSpace'] = haveEnoughFreeSpace
                    retval['freeSpaceAdvisoryString'] = freeSpaceAdvisoryString
                    retval[
                        'CWDHaveEnoughFreeSpace'] = CWDHaveEnoughFreeSpaceInstallerPath
                    retval[
                        'CWDFreeSpaceAdvisoryString'] = CWDFreeSpaceAdvisoryStringInstallerPath
                    retval['downloadItemsPreview'] = downloadItemsPreview
                    retval['numUpdatesRequired'] = numUpdatesRequired
                    retval['fullUpdateRequired'] = fullUpdateRequired
                return retval

            # requestData: Not necessary - will be ignored
            # responseData: Returns a list of dictionaries. Each dictionary may have different fields depending on the
            #               type of status returned.
            #               Please check the _loggerMessageToStatusDict() function for a full list of fields.
            def statusUpdate(requestData):
                return [
                    _loggerMessageToStatusDict(x)
                    for x in logger.getGlobalLogger().threadSafeReadAll()
                ]

            def getNews(requestData):
                return common.tryGetRemoteNews(requestData)

            def getDonationStatus(requestData):
                monthsRemaining, progressPercent = common.getDonationStatus()
                return {
                    'monthsRemaining': monthsRemaining,
                    'progressPercent': progressPercent,
                }

            def getInstallerMetaInfo(requestData):
                return {
                    'buildInfo': common.Globals.BUILD_INFO,
                    'lockFileExists': common.lockFileExists(
                    ),  # Indicate if it looks like install already in progress
                    'operatingSystem': common.Globals.OS_STRING,
                    'installAlreadyInProgress':
                    self.installAlreadyInProgress(),
                }

            # This causes a TKInter window to open allowing the user to choose a game path.
            # The request data should be the submod ID.
            # This is required so that the correct file filter can be applied to the tkinter file chooser.
            # The function returns None (Javascript null) if the user failed to select a path by pressing 'cancel'.
            def showFileChooser(requestDataSubModID):
                subMod = self.idToSubMod[requestDataSubModID]
                selectedPath = _TKAskPath(subMod)
                return {'path': selectedPath if selectedPath else None}

            def unknownRequestHandler(requestData):
                return 'Invalid request type [{}]. Should be one of [{}]'.format(
                    requestType, requestTypeToRequestHandlers.items())

            # This function takes identical arguments to 'startInstallHandler(...)'
            # TODO: Add correct paths for Linux and Mac
            def troubleshoot(requestData):
                action = requestData['action']

                id = requestData['subMod']['id']
                subMod = self.idToSubMod[id]

                # If the requestData included the install path, use that. Otherwise, open a dialog to choose the path
                # returns the empty string if user cancels selecting a path
                def _getInstallPath():
                    _installPath = requestData.get('installPath', None)
                    if _installPath is None:
                        userSelectedPath = os.path.dirname(_TKAskPath(subMod))
                        fullInstallConfigs, errorMessage = gameScanner.scanUserSelectedPath(
                            [subMod], userSelectedPath)
                        _installPath = '' if not fullInstallConfigs else fullInstallConfigs[
                            0].installPath
                    return _installPath

                if action == 'getLogsZip':
                    installPath = _getInstallPath()
                    higurashi_log_file_name = 'output_log.txt'
                    gameLogPath = os.path.join(installPath, subMod.dataName,
                                               higurashi_log_file_name)
                    gameLogExists = os.path.exists(gameLogPath)
                    with zipfile.ZipFile(
                            os.path.join(workingDirectory,
                                         common.Globals.LOGS_ZIP_FILE_PATH),
                            'w') as myzip:
                        for filename in os.listdir(common.Globals.LOG_FOLDER):
                            path = os.path.join(common.Globals.LOG_FOLDER,
                                                filename)
                            myzip.write(path, os.path.basename(path))

                        if gameLogExists:
                            myzip.write(gameLogPath, higurashi_log_file_name)

                    print('Game Log [{}] {}'.format(
                        gameLogPath,
                        "was found" if gameLogExists else "WAS NOT FOUND"))

                    return {
                        'filePath': common.Globals.LOGS_ZIP_FILE_PATH,
                        'gameLogFound': gameLogExists
                    }
                elif action == 'showLogs':
                    installPath = _getInstallPath()

                    logsPath = installPath

                    if subMod.family == 'higurashi':
                        if common.Globals.IS_MAC:
                            logsPath = os.path.join(installPath,
                                                    "Contents/Resources/Data")
                        else:
                            logsPath = os.path.join(installPath,
                                                    subMod.dataName)

                    if os.path.exists(logsPath):
                        print('Trying to open [{}]'.format(logsPath))
                        common.trySystemOpen(logsPath, normalizePath=True)
                    else:
                        return {
                            'error':
                            'Cant open Logs Folder [{}] as it doesnt exist!'.
                            format(logsPath)
                        }

                    return {}
                elif action == 'openSaveFolder':
                    if subMod.family == 'higurashi':
                        result = re.findall(r'\d\d', subMod.dataName)
                        if result:
                            saveFolderName = os.path.expandvars(
                                '%appdata%\Mangagamer\higurashi' + result[0])
                        else:
                            return {
                                'error':
                                'Sorry, cant figure out higurashi episode number :('
                            }
                    elif subMod.family == 'umineko':
                        saveFolderName = os.path.join(_getInstallPath(),
                                                      'mysav')
                    elif subMod.family == 'umineko_nscripter':
                        # For now just open the all users profile folder
                        # The actual save folder will be set according to the ';gameid' defined at the top of the script file
                        saveFolderName = os.path.expandvars(
                            '%AllUsersProfile%')
                    else:
                        return {
                            'error':
                            'Cant open save folder: Unknown game family {}'.
                            format(subMod.family)
                        }

                    if os.path.exists(saveFolderName):
                        print('Trying to open [{}]'.format(saveFolderName))
                        common.trySystemOpen(saveFolderName,
                                             normalizePath=True)
                    else:
                        return {
                            'error':
                            'Save Folder [{}] doesnt exist! Have you made any saves yet?'
                            .format(saveFolderName)
                        }

                    return {}

            requestTypeToRequestHandlers = {
                'setModName': setModName,
                'subModHandles': getSubModHandlesRequestHandler,
                'gamePaths': getGamePathsHandler,
                'startInstall': startInstallHandler,
                'statusUpdate': statusUpdate,
                'getNews': getNews,
                'getDonationStatus': getDonationStatus,
                'troubleshoot': troubleshoot,
                'showFileChooser': showFileChooser,
                'getInstallerMetaInfo': getInstallerMetaInfo,
            }

            requestHandler = requestTypeToRequestHandlers.get(
                requestType, None)

            # Check for unknown request
            if not requestHandler:
                return _makeJSONResponse('unknownRequest',
                                         unknownRequestHandler(requestData))

            # Try and execute the request. If an exception is thrown, display the reason to the user on the web GUI
            try:
                responseDataJson = requestHandler(requestData)
            except Exception as exception:
                print('Exception Thrown handling request {}: {}'.format(
                    requestType, exception))
                traceback.print_exc()
                return _makeJSONResponse(
                    'error', {
                        'errorReason':
                        'Exception handling [{}] request: {}'.format(
                            requestType, traceback.format_exc())
                    })

            return _makeJSONResponse(responseType=requestType,
                                     responseDataJson=responseDataJson)
Exemplo n.º 6
0
        def send_head(self):
            """
			Copy and pasted from  SimpleHTTPRequestHandler class because it's difficult
			to alter the headers without modifying the function directly

			Common code for GET and HEAD commands.

			This sends the response code and MIME headers.

			Return value is either a file object (which has to be copied
			to the outputfile by the caller unless the command was HEAD,
			and must be closed by the caller under all circumstances), or
			None, in which case the caller has nothing further to do.

			"""
            originalPath = self.translate_path(self.path)
            # --------- THE FOLLOWING WAS ADDED ---------
            # Python 3 has the ability to change web directory built-in, but Python 2 does not.
            relativePath = os.path.relpath(originalPath, os.getcwd())
            path = os.path.join(
                working_directory, relativePath
            )  # working_directory is captured from outer scope!
            logger.printNoTerminal(
                'Browser requested [{}], Trying to deliver [{}]'.format(
                    self.path, path))
            # --------- END ADDED SECTION ---------
            f = None
            if os.path.isdir(path):
                parts = urlparse.urlsplit(self.path)
                if not parts.path.endswith('/'):
                    # redirect browser - doing basically what apache does
                    self.send_response(301)
                    new_parts = (parts[0], parts[1], parts[2] + '/', parts[3],
                                 parts[4])
                    new_url = urlparse.urlunsplit(new_parts)
                    self.send_header("Location", new_url)
                    self.end_headers()
                    return None
                for index in "index.html", "index.htm":
                    index = os.path.join(path, index)
                    if os.path.exists(index):
                        path = index
                        break
                else:
                    return self.list_directory(path)
            ctype = self.guess_type(path)
            try:
                # Always read in binary mode. Opening files in text mode may cause
                # newline translations, making the actual size of the content
                # transmitted *less* than the content-length!
                f = open(path, 'rb')
            except IOError:
                self.send_error(404, "File not found")
                logger.printNoTerminal(
                    '404 Error: Cant deliver [{}] - file not found!\n'.format(
                        path))
                return None
            try:
                self.send_response(200)
                self.send_header("Content-type", ctype)
                fs = os.fstat(f.fileno())
                self.send_header("Content-Length", str(fs[6]))
                self.send_header("Last-Modified",
                                 self.date_time_string(fs.st_mtime))
                # --------- THE FOLLOWING WAS ADDED ---------
                self.send_header('Cache-Control',
                                 'no-cache, no-store, must-revalidate')
                self.send_header('Pragma', 'no-cache')
                self.send_header('Expires', '0')
                # --------- END ADDED SECTION ---------
                self.end_headers()
                return f
            except:
                f.close()
                raise
Exemplo n.º 7
0
def scanForFullInstallConfigs(subModConfigList,
                              possiblePaths=None,
                              scanExtraPaths=True):
    # type: (List[installConfiguration.SubModConfig], [str], bool) -> [installConfiguration.FullInstallConfiguration, List[str]]
    """
	This function has two purposes:
		- When given a specific game path ('possiblePaths' argument), it checks if any of the given SubModConfig
		  can be installed into that path. Each SubModConfig which can be installed into that path will be returned
		  as a FullInstallConfiguration object.

		- When not given a specific game path, it searches the computer for valid installations where the given
		  SubModConfig could be installed to. Each valid (installation + SubModConfig) combination will be returned
		  as a FullInstallConfiguration object.

	:param subModConfigList: A **list** of SubModConfig which are to be searched for on disk
	:param possiblePaths: (Optional) Specify folders to check if the given SubModConfig can be installed into that path.
	:return:    1. A list of FullInstallConfig, each representing a valid install path that the
				given SubModConfig(s) couldbe installed into.
				2. A list of games which were "partially uninstalled" by Steam - steam deletes game files, but not the mod
				files. The user should be notified to delete these files manually.
	"""

    returnedFullConfigs = []
    returnedPartiallyUninstalledPaths = []
    pathsToBeScanned = possiblePaths

    if not pathsToBeScanned:
        pathsToBeScanned = getMaybeGamePaths()

    # Build a mapping from subModIdentifier -> List[subMod]
    # This tells us, for each identifier, which subMods are compatible with that identifier (can be installed)
    # In all our games, the identifiers are the same for each subMod (but different for each Mod),
    # but it is easier to work with in the installer if we work with subMods

    from collections import defaultdict
    subModConfigDictionary = defaultdict(
        list)  #type: defaultdict[List[installConfiguration.SubModConfig]]
    for subMod in subModConfigList:
        # If autodetection is disabled, and autodetection requested, do not scan for this submod
        if not subMod.autodetect and possiblePaths is None:
            continue

        for identifier in subMod.identifiers:
            subModConfigDictionary[identifier].append(subMod)

    # If there are no identifiers to be matched, give up immediately as we'll never find a match
    if not subModConfigDictionary:
        return [], []

    if scanExtraPaths:
        extraPaths = []
        for gamePath in pathsToBeScanned:
            # MacOS: Any subpath with '.app' is also checked in case the containing path was manually entered
            extraPaths.extend(glob.glob(os.path.join(gamePath, "*.app")))
            # GOG Linux: Higurashi might be inside a 'game' subfolder
            extraPaths.extend(glob.glob(os.path.join(gamePath, "game")))

        pathsToBeScanned += extraPaths

    logger.printNoTerminal("Scanning:\n\t- " + "\n\t- ".join(pathsToBeScanned))

    for gamePath in pathsToBeScanned:
        possibleIdentifiers = getPossibleIdentifiersFromFolder(gamePath)
        subModConfigsInThisGamePath = set()

        possibleSteamPaths = [
            os.path.join(gamePath, "steam_api.dll"),
            os.path.join(gamePath, "Contents/Plugins/CSteamworks.bundle"),
            os.path.join(gamePath, "libsteam_api.so")
        ]

        isSteam = False
        for possibleSteamPath in possibleSteamPaths:
            if os.path.exists(possibleSteamPath):
                isSteam = True

        if gamePathIsPartiallyUninstalled(gamePath):
            returnedPartiallyUninstalledPaths.append(gamePath)

        for possibleIdentifier in possibleIdentifiers:
            try:
                # Add each submod which is compatible with the found identifier, unless it has already been detected at this path.
                for subModConfig in subModConfigDictionary[possibleIdentifier]:
                    if subModConfig not in subModConfigsInThisGamePath:
                        subModConfigsInThisGamePath.add(subModConfig)
                        returnedFullConfigs.append(
                            installConfiguration.FullInstallConfiguration(
                                subModConfig, gamePath, isSteam))
                        print("Found Game [{}] at [{}] id [{}]".format(
                            subModConfig.modName, gamePath,
                            possibleIdentifier))

            except KeyError:
                pass

    return returnedFullConfigs, returnedPartiallyUninstalledPaths
Exemplo n.º 8
0
    def __init__(self,
                 subMod,
                 modFileList,
                 localVersionFolder,
                 _testRemoteSubModVersion=None,
                 verbosePrinting=True):
        #type: (installConfiguration.SubModConfig, List[installConfiguration.ModFile], str, Optional[SubModVersionInfo], bool) -> None
        self.verbosePrinting = verbosePrinting
        self.targetID = subMod.modName + '/' + subMod.subModName
        self.unfilteredModFileList = modFileList
        self.localVersionFilePath = os.path.join(
            localVersionFolder, VersionManager.localVersionFileName)

        # Get remote and local versions
        try:
            self.localVersionInfo = getLocalVersion(self.localVersionFilePath)
        except Exception as error:
            self.localVersionInfo = None
            print(
                "VersionManager: Error while retrieving version information: {}"
                .format(error))

        # allow overriding the remote sub mod version for testing purposes
        if _testRemoteSubModVersion is not None:
            self.remoteVersionInfo = _testRemoteSubModVersion
        else:
            try:
                self.remoteVersionInfo = getRemoteVersion(self.targetID)
            except Exception as error:
                self.remoteVersionInfo = None
                print(
                    "VersionManager: Error while retrieving remote version information {}"
                    .format(error))

        if verbosePrinting:
            logger.printNoTerminal("\nLocal Version: {}".format(
                self.localVersionInfo))
            logger.printNoTerminal("Remote Version: {}".format(
                self.remoteVersionInfo))

        # If can't retrieve version info, mark everything as needing update
        if self.localVersionInfo is None:
            self.updatesRequiredDict = {}
            for file in self.unfilteredModFileList:
                self.updatesRequiredDict[file.id] = (
                    True,
                    "No local version information - Assuming update is required"
                )
        elif self.remoteVersionInfo is None:
            self.updatesRequiredDict = {}
            for file in self.unfilteredModFileList:
                self.updatesRequiredDict[file.id] = (
                    True, "Failed to retrieve remote version information")
        else:
            # Mark files which need update
            self.updatesRequiredDict = getFilesNeedingUpdate(
                self.unfilteredModFileList, self.localVersionInfo,
                self.remoteVersionInfo)

            if verbosePrinting:
                print("\nInstaller Update Information:")
                for fileID, (needsUpdate,
                             updateReason) in self.updatesRequiredDict.items():
                    print("[{}]: status: [{}] because [{}]".format(
                        fileID, needsUpdate, updateReason))

        # If file has 'skipIfModNewerThan' property, disable it if the mod install is older than the given date
        for file in self.unfilteredModFileList:
            if file.skipIfModNewerThan is not None and self.updatesRequiredDict[
                    file.id][0]:
                installIsNewer, reason = installNewerThanDate(
                    self.localVersionFilePath, file.skipIfModNewerThan)
                if installIsNewer:
                    msg = "Not installing {} because: ({})".format(
                        file.id, reason)
                    self.updatesRequiredDict[file.id] = (
                        False,
                        "Not installing because you already have these files")
                    print(msg)
                else:
                    msg = "{} - Will install because: ({})".format(
                        self.updatesRequiredDict[file.id][1], reason)
                    self.updatesRequiredDict[file.id] = (
                        self.updatesRequiredDict[file.id][0],
                        "You are missing these files (judging from your last mod install date)"
                    )
                    print(msg)

        # Check how many updates are required
        updatesRequiredList = self.updatesRequiredDict.values()
        self.totalNumUpdates = len(updatesRequiredList)
        self.numUpdatesRequired = sum(
            [needsUpdate for (needsUpdate, _) in updatesRequiredList])
        if verbosePrinting:
            print("Full Update: {} ({}/{}) excluding mod options".format(
                self.fullUpdateRequired(), self.numUpdatesRequired,
                self.totalNumUpdates))