def test_highLevelFunctions(self):
        remoteSubModVersionJSONString = """
		{
			"id" : "Onikakushi Ch.1/full",
			"files":[
				{"id": "cg",                  "version": "1.0.0"},
				{"id": "cgalt",               "version": "1.0.0"},
				{"id": "movie",               "version": "1.0.0"},
				{"id": "voices",              "version": "1.0.0"},
				{"id": "script",              "version": "6.1.0"},
				{"id": "movie-unix",          "version": "1.0.0"},
				{"id": "ui-windows",          "version": "1.0.0"},
				{"id": "ui-unix",             "version": "1.0.0"}
			]
		}
		"""
        remoteVersionObject = fileVersionManagement.SubModVersionInfo(
            json.loads(remoteSubModVersionJSONString))

        test_dir = tempfile.mkdtemp()

        modList = self.getModListFromDummyJSON()
        mod = modList[0]
        submod = mod['submods'][0]

        subModConfig = installConfiguration.SubModConfig(mod, submod)
        fullConfig = installConfiguration.FullInstallConfiguration(
            subModConfig, test_dir, True)

        originalModFileList = fullConfig.buildFileListSorted('datadir')

        # If there is no file present, all files should require download
        fileVersionManager = fileVersionManagement.VersionManager(
            subMod=subModConfig,
            modFileList=originalModFileList,
            localVersionFolder=test_dir,
            _testRemoteSubModVersion=remoteVersionObject)

        self.assertEqual(fileVersionManager.getFilesRequiringUpdate(),
                         originalModFileList)
        self.assertEqual(fileVersionManager.fullUpdateRequired(), True)

        fileVersionManager.saveVersionInstallFinished()

        # If there is a file present which is identical, no files should require download
        fileVersionManagerIdentical = fileVersionManagement.VersionManager(
            subMod=subModConfig,
            modFileList=originalModFileList,
            localVersionFolder=test_dir,
            _testRemoteSubModVersion=remoteVersionObject)

        self.assertEqual(fileVersionManagerIdentical.getFilesRequiringUpdate(),
                         [])
        self.assertEqual(fileVersionManagerIdentical.fullUpdateRequired(),
                         False)

        shutil.rmtree(test_dir)
	def __init__(self, fullInstallConfiguration, extractDirectlyToGameDirectory):
		# type: (installConfiguration.FullInstallConfiguration, bool) -> None

		"""
		Installer Init

		:param str directory: The directory of the game
		:param dict info: The info dictionary from server JSON file for the requested target
		"""
		self.info = fullInstallConfiguration
		self.directory = fullInstallConfiguration.installPath

		if common.Globals.IS_MAC:
			self.dataDirectory = path.join(self.directory, "Contents/Resources/Data")
		else:
			self.dataDirectory = path.join(self.directory, self.info.subModConfig.dataName)

		logger.getGlobalLogger().trySetSecondaryLoggingPath(
			os.path.join(self.dataDirectory, common.Globals.LOG_BASENAME)
		)

		self.assetsDir = path.join(self.dataDirectory, "StreamingAssets")

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

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

		self.downloadDir = self.info.subModConfig.modName + " Downloads"
		self.extractDir = self.directory if extractDirectlyToGameDirectory else (self.info.subModConfig.modName + " Extraction")

		self.fileVersionManager = fileVersionManagement.VersionManager(
			subMod=self.info.subModConfig,
			modFileList=self.info.buildFileListSorted(datadir=self.dataDirectory),
			localVersionFolder=self.directory)

		modFileList = self.fileVersionManager.getFilesRequiringUpdate()
		self.downloaderAndExtractor = common.DownloaderAndExtractor(modFileList=modFileList,
		                                                            downloadTempDir=self.downloadDir,
		                                                            extractionDir=self.extractDir)

		self.downloaderAndExtractor.buildDownloadAndExtractionList()

		parser = installConfiguration.ModOptionParser(self.info)

		for opt in parser.downloadAndExtractOptionsByPriority:
			self.downloaderAndExtractor.addItemManually(
				url=opt.url,
				extractionDir=os.path.join(self.extractDir, opt.relativeExtractionPath),
			)

		self.downloaderAndExtractor.printPreview()
Exemple #3
0
def getDownloadPreview(fullInstallConfig):
    ####### Preview which files are going to be downloaded #######
    # Higurashi installer needs datadirectory set to determine unity version
    dataDirectory = os.path.join(
        fullInstallConfig.installPath, "Contents/Resources/Data"
        if common.Globals.IS_MAC else fullInstallConfig.subModConfig.dataName)

    modFileList = fullInstallConfig.buildFileListSorted(
        datadir=dataDirectory)  # type: List[installConfiguration.ModFile]
    fileVersionManager = fileVersionManagement.VersionManager(
        subMod=fullInstallConfig.subModConfig,
        modFileList=modFileList,
        localVersionFolder=fullInstallConfig.installPath)

    # Generate rows for the normal/overridden files
    totalDownload = 0
    downloadItemsPreview = []
    for modFile in modFileList:
        updateNeeded, updateReason = fileVersionManager.updatesRequiredDict[
            modFile.id]
        downloadSize = common.Globals.URL_FILE_SIZE_LOOKUP_TABLE.get(
            modFile.url)
        downloadItemsPreview.append(
            (modFile.id, downloadSize, updateNeeded, updateReason))
        if updateNeeded and downloadSize:
            totalDownload += downloadSize

    # Generate rows for the mod option files
    parser = installConfiguration.ModOptionParser(fullInstallConfig)
    for option in parser.downloadAndExtractOptionsByPriority:
        downloadSize = common.Globals.URL_FILE_SIZE_LOOKUP_TABLE.get(
            option.url)
        downloadItemsPreview.append((option.name, downloadSize, True,
                                     'Mod options are always downloaded'))
        if downloadSize:
            totalDownload += downloadSize

    # The last row is the total download size of all the
    updateTypeString = 'Update Type: {}'.format(
        'Full Update' if fileVersionManager.fullUpdateRequired(
        ) else 'Partial Update')
    downloadItemsPreview.append(
        ("Total Download Size", totalDownload, None, updateTypeString))

    # Convert the size in bytes to a nicely formatted string
    downloadItemsPreview = [
        (row[0],
         'N/A' if row[1] is None else common.prettyPrintFileSize(row[1]),
         row[2], row[3]) for row in downloadItemsPreview
    ]

    return downloadItemsPreview, totalDownload, fileVersionManager.numUpdatesRequired, fileVersionManager.fullUpdateRequired(
    )
Exemple #4
0
def mainUmineko(conf):
    # type: (installConfiguration.FullInstallConfiguration) -> None
    logger.getGlobalLogger().trySetSecondaryLoggingPath(
        os.path.join(conf.installPath, common.Globals.LOG_BASENAME))

    isQuestionArcs = 'question' in conf.subModConfig.modName.lower()

    print("CONFIGURATION:")
    print("Install path", conf.installPath)
    print("Mod Option", conf.subModConfig.modName)
    print("Sub Option", conf.subModConfig.subModName)
    print("Is Question Arcs", isQuestionArcs)
    print("Is Windows", common.Globals.IS_WINDOWS)
    print("Is Linux", common.Globals.IS_LINUX)
    print("Is Mac", common.Globals.IS_MAC)

    ####################################### VALIDATE AND PREPARE FOLDERS ###############################################
    # do a quick verification that the directory is correct before starting installer
    if not os.path.isfile(os.path.join(conf.installPath, "arc.nsa")):
        raise Exception(
            "ERROR - wrong game path. Installation Stopped.\n"
            "There is no 'arc.nsa' in the game folder. Are you sure the correct game folder was selected?"
        )

    for filename in os.listdir(conf.installPath):
        # Stop the user installing the mod on pirated versions of the game.
        # Use SHA256 hash of the lowercase filename to avoid listing the website names in our source code.
        if hashlib.sha256(filename.lower().encode('utf-8')).hexdigest() in [
                '2c02ec6f6de9281a68975257a477e8f994affe4eeaaf18b0b56b4047885461e0',
                '4fae41c555fe50034065e59ce33a643c1d93ee846221ecc5756f00e039035076',
        ]:
            raise Exception(
                "\nInstall Failed - The {} mod is not compatible with the pirated version of the game\n"
                "(Detected file [{}]) Please install the latest Steam or Mangagamer release."
                .format(conf.subModConfig.modName, filename))

        # Stop the user installing the mod on the old/original Japanese game.
        # This probably means the user placed a fake identifier (eg the game exe) in the old game's folder.
        if filename == 'snow.dll':
            raise Exception(
                "\nInstall Failed - The {} mod is not compatible with the old/original Japanese game.\n"
                "(Detected [{}]) Please install the latest Steam or Mangagamer release."
                .format(conf.subModConfig.modName, filename))

    # Create aliases for the temp directories, and ensure they exist beforehand
    downloadTempDir = conf.subModConfig.modName + " Downloads"

    if os.path.isdir(downloadTempDir):
        print(
            "Information: Temp directories already exist - continued or overwritten install"
        )

    common.makeDirsExistOK(downloadTempDir)

    ######################################## Query and Download Files ##################################################
    fileVersionManager = fileVersionManagement.VersionManager(
        subMod=conf.subModConfig,
        modFileList=conf.buildFileListSorted(),
        localVersionFolder=conf.installPath)

    filesRequiringUpdate = fileVersionManager.getFilesRequiringUpdate()
    print("Perform Full Install: {}".format(
        fileVersionManager.fullUpdateRequired()))
    downloaderAndExtractor = common.DownloaderAndExtractor(
        filesRequiringUpdate,
        downloadTempDir,
        conf.installPath,
        downloadProgressAmount=45,
        extractionProgressAmount=45)
    downloaderAndExtractor.buildDownloadAndExtractionList()

    parser = installConfiguration.ModOptionParser(conf)

    for opt in parser.downloadAndExtractOptionsByPriority:
        downloaderAndExtractor.addItemManually(
            url=opt.url,
            extractionDir=os.path.join(conf.installPath,
                                       opt.relativeExtractionPath),
        )

    downloaderAndExtractor.printPreview()

    # Delete all non-checksummed files from the download folder, if they exist
    print("Removing non-checksummed downloads:")
    deleteExtractablesFromFolder(
        downloadTempDir,
        [x for x in downloaderAndExtractor.extractList if not x.fromMetaLink])

    downloaderAndExtractor.download()

    # Treat the install as "started" once the "download" stage is complete
    fileVersionManager.saveVersionInstallStarted()

    ###################### Backup/clear the .exe and script files, and old graphics ####################################
    backupOrRemoveFiles(conf.installPath)

    if fileVersionManager.fullUpdateRequired():
        # Remove old graphics from a previous installation, as they can conflict with the voice-only patch
        graphicsPathsToDelete = [
            os.path.join(conf.installPath, x) for x in ['big', 'bmp', 'en']
        ]

        for folderPath in graphicsPathsToDelete:
            if os.path.exists(folderPath):
                print("Deleting {}".format(folderPath))
                try:
                    shutil.rmtree(folderPath)
                except:
                    print("WARNING: failed to remove folder {}".format(
                        folderPath))

    ######################################## Extract Archives ##########################################################
    downloaderAndExtractor.extract()

    ############################################# FIX .ARC FILE NAMING #################################################
    # Steam release has arc files labeled arc.nsa, arc1.nsa, arc2.nsa, arc3.nsa.
    # Mangagamer release has only one arc file labeled arc.nsa
    # Generate dummy arc1-arc3 nsa files if they don't already exist, so the game can find the arc4.nsa that we provide
    for i in range(1, 4):
        nsaPath = os.path.join(conf.installPath, 'arc{}.nsa'.format(i))
        if not os.path.exists(nsaPath):
            print(
                ".nsa archive check: Generating dummy [{}] as it does not already exist (Mangagamer)"
                .format(nsaPath))
            with open(nsaPath, 'wb') as dummyNSAFile:
                dummyNSAFile.write(bytes([0, 0, 0, 0, 0, 6]))
        else:
            print(".nsa archive check: [{}] already exists (Steam)".format(
                nsaPath))

    #################################### MAKE EXECUTABLE, WRITE HELPER SCRIPTS #########################################
    gameBaseName = "Umineko5to8"
    if isQuestionArcs:
        gameBaseName = "Umineko1to4"

    if common.Globals.IS_MAC:
        print("Un-quarantining game executable")
        subprocess.call([
            "xattr", "-d", "com.apple.quarantine",
            os.path.join(conf.installPath, gameBaseName + ".app")
        ])

    print("Creating debug mode batch files")
    # write batch file to let users launch game in debug mode
    with open(os.path.join(conf.installPath, gameBaseName + "_DebugMode.bat"),
              'w') as f:
        f.writelines([gameBaseName + ".exe --debug\n", "pause"])

    #make the following files executable, if they exist
    makeExecutableList = [
        os.path.join(conf.installPath, "Umineko1to4"),
        os.path.join(conf.installPath,
                     "Umineko1to4.app/Contents/MacOS/umineko4"),
        os.path.join(conf.installPath, "Umineko5to8"),
        os.path.join(conf.installPath,
                     "Umineko5to8.app/Contents/MacOS/umineko8")
    ]

    print("Making executables ... executable")
    for exePath in makeExecutableList:
        if os.path.exists(exePath):
            common.makeExecutable(exePath)

    # Patched game uses mysav folder, which Steam can't see so can't get incompatible saves by accident.
    # Add batch file which reverses this behaviour by making a linked folder from (saves->mysav)
    print("Creating EnableSteamSync.bat")
    with open(os.path.join(conf.installPath, "EnableSteamSync.bat"), 'w') as f:
        f.write("""
if exist saves (
    ren saves backup-saves
    mklink /J saves mysav
) else (
    mklink /J saves mysav
)
pause
""")

    # For now, don't copy save data

    steamGridExtractor.extractSteamGrid()
    fileVersionManager.saveVersionInstallFinished()

    if not parser.keepDownloads:
        print("Removing temporary downloads:")
        deleteExtractablesFromFolder(downloadTempDir,
                                     downloaderAndExtractor.extractList)

    commandLineParser.printSeventhModStatusUpdate(
        100, "Umineko install script completed!")
Exemple #5
0
	def __init__(self, fullInstallConfiguration, extractDirectlyToGameDirectory, modOptionParser, forcedExtractDirectory=None):
		# type: (installConfiguration.FullInstallConfiguration, bool, installConfiguration.ModOptionParser, Optional[str]) -> None

		"""
		Installer Init

		:param str directory: The directory of the game
		:param dict info: The info dictionary from server JSON file for the requested target
		"""
		self.forcedExtractDirectory = forcedExtractDirectory
		self.info = fullInstallConfiguration
		self.directory = fullInstallConfiguration.installPath
		self.dataDirectory = self.getDataDirectory(self.directory)
		self.clearScripts = False  # If true, will clear CompiledUpdateScripts before extraction stage
		self.languagePatchIsEnabled = False  # True if at least one language patch will be installed

		logger.getGlobalLogger().trySetSecondaryLoggingPath(
			os.path.join(self.dataDirectory, common.Globals.LOG_BASENAME)
		)

		self.assetsDir = path.join(self.dataDirectory, "StreamingAssets")

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

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

		self.downloadDir = self.info.subModConfig.modName + " Downloads"
		self.extractDir = self.directory if extractDirectlyToGameDirectory else (self.info.subModConfig.modName + " Extraction")
		if forcedExtractDirectory is not None:
			self.extractDir = forcedExtractDirectory

		self.fileVersionManager = fileVersionManagement.VersionManager(
			subMod=self.info.subModConfig,
			modFileList=self.info.buildFileListSorted(datadir=self.dataDirectory),
			localVersionFolder=self.directory)

		modFileList = self.fileVersionManager.getFilesRequiringUpdate()

		for modFile in modFileList:
			if modFile.name == 'script':
				self.clearScripts = True

		self.info.subModConfig.printEnabledOptions()
		self.downloaderAndExtractor = common.DownloaderAndExtractor(modFileList=modFileList,
		                                                            downloadTempDir=self.downloadDir,
		                                                            extractionDir=self.extractDir)

		self.downloaderAndExtractor.buildDownloadAndExtractionList()

		self.optionParser = modOptionParser

		for opt in self.optionParser.downloadAndExtractOptionsByPriority:
			self.downloaderAndExtractor.addItemManually(
				url=opt.url,
				extractionDir=os.path.join(self.extractDir, opt.relativeExtractionPath),
			)
			if opt.group == 'Alternate Languages':
				self.clearScripts = True
				self.languagePatchIsEnabled = True

		self.downloaderAndExtractor.printPreview()
Exemple #6
0
def getDownloadPreview(fullInstallConfig):
    #type: (installConfiguration.FullInstallConfiguration) -> Any
    ####### Preview which files are going to be downloaded #######
    # Higurashi installer needs datadirectory set to determine unity version
    dataDirectory = os.path.join(
        fullInstallConfig.installPath, "Contents/Resources/Data"
        if common.Globals.IS_MAC else fullInstallConfig.subModConfig.dataName)

    modFileList = fullInstallConfig.buildFileListSorted(
        datadir=dataDirectory)  # type: List[installConfiguration.ModFile]
    fileVersionManager = fileVersionManagement.VersionManager(
        subMod=fullInstallConfig.subModConfig,
        modFileList=modFileList,
        localVersionFolder=fullInstallConfig.installPath)

    # Check for partial re-install (see https://github.com/07th-mod/python-patcher/issues/93)
    if fullInstallConfig.subModConfig.family == 'higurashi':
        installTimeProbePath = os.path.join(dataDirectory, 'Managed',
                                            'UnityEngine.dll')
    elif fullInstallConfig.subModConfig.family == 'umineko':
        installTimeProbePath = os.path.join(fullInstallConfig.installPath,
                                            'fonts', 'oldface0.ttf')
    else:
        installTimeProbePath = None

    if installTimeProbePath is None:
        partialReinstallDetected = False
    else:
        partialReinstallDetected = fileVersionManager.userDidPartialReinstall(
            installTimeProbePath)

    # Generate rows for the normal/overridden files
    totalDownload = 0
    downloadItemsPreview = []
    for modFile in modFileList:
        updateNeeded, updateReason = fileVersionManager.updatesRequiredDict[
            modFile.id]
        downloadSize = common.Globals.URL_FILE_SIZE_LOOKUP_TABLE.get(
            modFile.url)
        downloadItemsPreview.append(
            (modFile.id, downloadSize, updateNeeded, updateReason))
        if updateNeeded and downloadSize:
            totalDownload += downloadSize

    # Generate rows for the mod option files
    parser = installConfiguration.ModOptionParser(fullInstallConfig)
    for option in parser.downloadAndExtractOptionsByPriority:
        downloadSize = common.Globals.URL_FILE_SIZE_LOOKUP_TABLE.get(
            option.url)
        downloadItemsPreview.append((option.name, downloadSize, True,
                                     'Mod options are always downloaded'))
        if downloadSize:
            totalDownload += downloadSize

    # The last row is the total download size of all the
    updateTypeString = 'Update Type: {}'.format(
        'Full Update' if fileVersionManager.fullUpdateRequired(
        ) else 'Partial Update')
    downloadItemsPreview.append(
        ("Total Download Size", totalDownload, None, updateTypeString))

    # Convert the size in bytes to a nicely formatted string
    downloadItemsPreview = [
        (row[0],
         'N/A' if row[1] is None else common.prettyPrintFileSize(row[1]),
         row[2], row[3]) for row in downloadItemsPreview
    ]

    return downloadItemsPreview, totalDownload, fileVersionManager.numUpdatesRequired, fileVersionManager.fullUpdateRequired(
    ), partialReinstallDetected
Exemple #7
0
def main(conf):
    # type: (installConfiguration.FullInstallConfiguration) -> None
    logger.getGlobalLogger().trySetSecondaryLoggingPath(
        os.path.join(conf.installPath, common.Globals.LOG_BASENAME))

    print("CONFIGURATION:")
    print("Install path", conf.installPath)
    print("Mod Option", conf.subModConfig.modName)
    print("Sub Option", conf.subModConfig.subModName)
    print("Is Windows", common.Globals.IS_WINDOWS)
    print("Is Linux", common.Globals.IS_LINUX)
    print("Is Mac", common.Globals.IS_MAC)

    if not common.Globals.IS_WINDOWS:
        raise Exception(
            "Error: Umineko Hane Mod (onscripter version) is not supported on Mac or Linux!"
        )

    ####################################### VALIDATE AND PREPARE FOLDERS ###############################################
    # do a quick verification that the directory is correct before starting installer
    if not os.path.isfile(os.path.join(conf.installPath, "arc.nsa")):
        raise Exception(
            "ERROR - wrong game path. Installation Stopped.\n"
            "There is no 'arc.nsa' in the game folder. Are you sure the correct game folder was selected?"
        )

    # Create aliases for the temp directories, and ensure they exist beforehand
    downloadTempDir = conf.subModConfig.modName + " Downloads"

    if os.path.isdir(downloadTempDir):
        print(
            "Information: Temp directories already exist - continued or overwritten install"
        )

    common.makeDirsExistOK(downloadTempDir)

    ######################################## DOWNLOAD, BACKUP, THEN EXTRACT ############################################
    fileVersionManager = fileVersionManagement.VersionManager(
        subMod=conf.subModConfig,
        modFileList=conf.buildFileListSorted(),
        localVersionFolder=conf.installPath)

    filesRequiringUpdate = fileVersionManager.getFilesRequiringUpdate()
    conf.subModConfig.printEnabledOptions()
    downloaderAndExtractor = common.DownloaderAndExtractor(
        filesRequiringUpdate,
        downloadTempDir,
        conf.installPath,
        downloadProgressAmount=45,
        extractionProgressAmount=45)
    downloaderAndExtractor.buildDownloadAndExtractionList()

    parser = installConfiguration.ModOptionParser(conf)

    for opt in parser.downloadAndExtractOptionsByPriority:
        downloaderAndExtractor.addItemManually(
            url=opt.url,
            extractionDir=os.path.join(conf.installPath,
                                       opt.relativeExtractionPath),
        )

    downloaderAndExtractor.printPreview()

    # Download files
    # Delete all non-checksummed files from the download folder, if they exist
    for extractableItem in downloaderAndExtractor.extractList:
        extractableItemPath = os.path.join(downloadTempDir,
                                           extractableItem.filename)
        if not extractableItem.fromMetaLink and os.path.exists(
                extractableItemPath):
            print("Removing existing non-checksummed download: [{}]".format(
                extractableItemPath))
            os.remove(extractableItemPath)

    downloaderAndExtractor.download()

    # Extract files
    fileVersionManager.saveVersionInstallStarted()
    downloaderAndExtractor.extract()

    fileVersionManager.saveVersionInstallFinished()
    commandLineParser.printSeventhModStatusUpdate(
        100, "Umineko Hane install script completed!")