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()
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( )
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!")
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()
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
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!")