def __init__(self, games, exoDosDir, gamesDosDir, outputDir, metadataHandler, logger): self.games = games self.exoDosDir = exoDosDir self.logger = logger self.gamesDosDir = gamesDosDir self.outputDir = outputDir self.metadataHandler = metadataHandler self.confConverter = ConfConverter(self.games, self.exoDosDir, self.outputDir, self.logger)
def __init__(self, game, genre, outputDir, collectionVersion, useGenreSubFolders, metadata, conversionType, conversionConf, exoCollectionDir, fullnameToGameDir, scriptDir, logger): self.game = game self.collectionVersion = collectionVersion self.genre = genre self.outputDir = outputDir self.useGenreSubFolders = useGenreSubFolders self.metadata = metadata self.conversionType = conversionType self.exoCollectionDir = exoCollectionDir self.logger = logger self.fullnameToGameDir = fullnameToGameDir self.conversionConf = conversionConf self.scriptDir = scriptDir self.confConverter = ConfConverter(self)
class GameGenerator: def __init__(self, game, genre, outputDir, collectionVersion, useGenreSubFolders, metadata, conversionType, conversionConf, exoCollectionDir, fullnameToGameDir, scriptDir, logger): self.game = game self.collectionVersion = collectionVersion self.genre = genre self.outputDir = outputDir self.useGenreSubFolders = useGenreSubFolders self.metadata = metadata self.conversionType = conversionType self.exoCollectionDir = exoCollectionDir self.logger = logger self.fullnameToGameDir = fullnameToGameDir self.conversionConf = conversionConf self.scriptDir = scriptDir self.confConverter = ConfConverter(self) ##### Utils functions #### # Checks if collection is win3x or dos def isWin3x(self): return util.isWin3x(self.collectionVersion) # Returns local parent output dir of the generated game def getLocalParentOutputDir(self): return os.path.join( self.outputDir, self.genre) if self.useGenreSubFolders else self.outputDir # returns local game ouput dir of the generated game def getLocalGameOutputDir(self): return os.path.join(self.getLocalParentOutputDir(), self.game + ".pc") # returns local game data output dir of the generated game def getLocalGameDataOutputDir(self): return self.getLocalGameOutputDir() if self.isWin3x( ) else os.path.join(self.getLocalGameOutputDir(), self.game) ################################ ##### Generation functions ##### # Converts game def convertGame(self): self.copyGameFiles() self.confConverter.process(self) self.postConversion() # Copy game files and game dosbox.conf to output dir def copyGameFiles(self): self.logger.log(" copy dosbox conf") # Copy dosbox.conf in game.pc shutil.copy2( os.path.join( util.getCollectionGamesConfDir(self.exoCollectionDir, self.collectionVersion), self.game, "dosbox.conf"), os.path.join(self.getLocalGameDataOutputDir(), "dosbox.conf")) # Create blank file with full game name f = open(os.path.join(self.getLocalGameOutputDir(), util.getCleanGameID(self.metadata, '.txt')), 'w', encoding='utf8') f.write(self.metadata.desc) f.close() # Handle first-game-of-a-serie dependencies needsFirstGame = { 'roadware': ['Roadwar 2000 (1987).zip'], # @mount a .\Games\roadwar -t floppy 'eob2': ['Eye of the Beholder (1991).zip' ], # mount a .\Games\eob1\ -t floppy 'bardtal2': ["Bard's Tale 1, The - Tales Of The Unknown (1987).zip"], # mount a .\Games\bardtal1 -t floppy 'bardtal3': [ "Bard's Tale 1, The - Tales Of The Unknown (1987).zip", # mount a .\Games\bardtal1 -t floppy "Bard's Tale 2, The - The Destiny Knight (1988).zip" ], # @mount b .\Games\bardtal2 -t floppy 'MM2': ['Might and Magic - Book 1 (1986).zip' ], # mount a .\Games\MM1\ -t floppy 'vengexca': ['Spirit of Excalibur (1990).zip' ], # @mount a .\Games\spirexc -t floppy 'WC2DLX': ['Wing Commander (1990).zip'], # mount a .\Games\WC\WING\GAMEDAT\ 'darkdes2': ['Dark Designs I - Grelminars Staff (1990).zip' ], # mount a .\Games\darkdes1 -t floppy 'whalvoy2': ["Whale's Voyage (1993).zip"] # @mount e .\Games\whalvoy1\WVCD } if self.game in needsFirstGame: for previousGameZip in needsFirstGame[self.game]: # unzip game dependency with ZipFile( os.path.join( util.getCollectionGamesDir(self.exoCollectionDir, self.collectionVersion), previousGameZip), 'r') as zipFile: # Extract all the contents of zip file in current directory self.logger.log(" unzipping previous game" + previousGameZip) zipFile.extractall(path=util.getCollectionGamesDir( self.exoCollectionDir, self.collectionVersion)) # copy its directory or directory part to the inside of the second game dir shutil.move( os.path.join( util.getCollectionGamesDir(self.exoCollectionDir, self.collectionVersion), self.fullnameToGameDir.get( os.path.splitext(previousGameZip)[0])), os.path.join(self.getLocalGameOutputDir())) ###################################### ###### Post-conversion functions ##### # Post-conversion operations for a given game for various conversion types def postConversion(self): if self.conversionType == util.retropie: self.postConversionForRetropie() elif self.conversionType in [util.esoteric, util.simplemenu]: self.postConversionForOpenDingux() elif self.conversionType == util.mister: self.postConversionForMister() elif self.conversionType == util.recalbox: self.postConversionForRecalbox() elif self.conversionType == util.batocera: self.postConversionForBatocera() elif self.conversionType == util.emuelec: self.postConversionForEmuelec() # Post-conversion for Emuelec for a given game def postConversionForEmuelec(self): self.logger.log(" Emuelec post-conversion") # create pcdata and pc subfolders in outputdir if not os.path.exists(os.path.join(self.outputDir, 'pcdata')): os.mkdir(os.path.join(self.outputDir, 'pcdata')) if not os.path.exists(os.path.join(self.outputDir, 'pc')): os.mkdir(os.path.join(self.outputDir, 'pc')) # move *.pc folder to pcdata folder shutil.move(os.path.join(self.getLocalGameOutputDir()), os.path.join(self.outputDir, 'pcdata')) os.rename(os.path.join(self.outputDir, 'pcdata', self.game + '.pc'), os.path.join(self.outputDir, 'pcdata', self.game)) # move *.bat *.map and *.cfg to pc/*.pc folder and rename *.cfg to dosbox-SDL2.conf emuelecConfOutputDir = os.path.join( self.outputDir, 'pc', self.genre, self.game + ".pc") if self.useGenreSubFolders else os.path.join( self.outputDir, 'pc', self.game + ".pc") if not os.path.exists(emuelecConfOutputDir): os.makedirs(emuelecConfOutputDir) shutil.move( os.path.join(self.outputDir, 'pcdata', self.game, 'dosbox.bat'), emuelecConfOutputDir) shutil.move( os.path.join(self.outputDir, 'pcdata', self.game, 'dosbox.cfg'), os.path.join(emuelecConfOutputDir, 'dosbox-SDL2.conf')) shutil.copy2( os.path.join(self.outputDir, 'pcdata', self.game, util.getCleanGameID(self.metadata, '.txt')), emuelecConfOutputDir) if os.path.exists( os.path.join(self.outputDir, 'pcdata', self.game, 'mapper.map')): shutil.move( os.path.join(self.outputDir, 'pcdata', self.game, 'mapper.map'), emuelecConfOutputDir) # modify dosbox-SDL2.conf to add mount c /storage/roms/pcdata/game at the beginning of autoexec.bat dosboxCfg = open( os.path.join(emuelecConfOutputDir, 'dosbox-SDL2.conf'), 'a') # add mount c at end of dosbox.cfg romsFolder = util.getRomsFolderPrefix(self.conversionType, self.conversionConf) emuelecGameDir = romsFolder + "/" + self.genre + "/" + self.game if self.useGenreSubFolders else romsFolder + "/" + self.game dosboxCfg.write("mount c " + emuelecGameDir + "\n") dosboxCfg.write("c:\n") # copy all instructions from dosbox.bat to end of dosbox.cfg dosboxBat = open(os.path.join(emuelecConfOutputDir, "dosbox.bat"), 'r') # retroarch dosbox.bat for cmdLine in dosboxBat.readlines(): dosboxCfg.write(cmdLine) # delete dosbox.bat dosboxCfg.close() dosboxBat.close() # delete dosbox.bat os.remove(os.path.join(emuelecConfOutputDir, "dosbox.bat")) # Post-conversion for Recalbox for a given game def postConversionForRecalbox(self): self.logger.log(" Recalbox post-conversion") p2kTemplate = open( os.path.join(self.scriptDir, 'data', 'P2K.template.txt'), 'r') p2kFile = open(os.path.join(self.getLocalParentOutputDir(), self.game + '.pc.p2k.cfg'), 'w', encoding='utf-8') for line in p2kTemplate.readlines(): p2kFile.write(line.replace('{GameID}', self.metadata.name)) p2kFile.close() p2kTemplate.close() def postConversionForBatocera(self): self.logger.log(" Batocera post-conversion") pad2key = os.path.join(self.scriptDir, 'data', 'padto.keys') shutil.copy2(pad2key, self.getLocalGameOutputDir()) # TODO handle mapper type joy / nojoy # TODO load keyb2joypad file # TODO convert pad2key chosen file from json to python object # TODO replace modified controls in pad2key file # TODO needs to add mapping for ctrl + F4 in pad2key # Post-conversion for MiSTeR for a given game def postConversionForMister(self): self.logger.log(" MiSTer post-conversion") # Remove any C: from dosbox.bat, rename to launch.bat, remove dosbox.cfg os.remove(os.path.join(self.getLocalGameOutputDir(), 'dosbox.cfg')) # Move CDs to cdgames/gamefolder and rename commands mister.batsAndMounts(self) shutil.move( os.path.join(self.getLocalGameOutputDir(), util.getCleanGameID(self.metadata, '.txt')), os.path.join(self.getLocalGameOutputDir(), '2_About.txt')) # Remove unused CDs mister.removeUnusedCds(self.game, self.getLocalGameDataOutputDir(), self.logger) # Remove any COMMAND.COM and CHOICE.EXE files, as they are not compatible with MiSTeR if self.isWin3x: tobeRemoved = [ file for file in os.listdir(self.getLocalGameOutputDir()) if file.lower() in ['command.com', 'choice.exe'] ] for fileToRemove in tobeRemoved: self.logger.log(" remove non-compatible file %s" % fileToRemove) os.remove( os.path.join(self.getLocalGameOutputDir(), fileToRemove)) else: tobeRemoved = [ file for file in os.listdir( os.path.join(self.getLocalGameDataOutputDir())) if file.lower() in ['command.com', 'choice.exe'] ] for fileToRemove in tobeRemoved: self.logger.log(" remove non-compatible file %s" % fileToRemove) os.remove( os.path.join(self.getLocalGameDataOutputDir(), fileToRemove)) # Create about.jpg combining About.txt and pic of the game if self.metadata.frontPic is not None: cover = os.path.join( self.getLocalGameOutputDir(), '5_About' + os.path.splitext(self.metadata.frontPic)[-1]) shutil.move( os.path.join(self.outputDir, 'downloaded_images', ntpath.basename(self.metadata.frontPic)), cover) aboutTxt = open(os.path.join(self.getLocalGameOutputDir(), '2_About.txt'), 'r', encoding='utf-8') mister.text2png( self.scriptDir, aboutTxt.read(), cover, os.path.join(self.getLocalGameOutputDir(), '2_About.jpg')) aboutTxt.close() os.remove(os.path.join(self.getLocalGameOutputDir(), '2_About.txt')) os.remove( os.path.join( self.getLocalGameOutputDir(), '5_About' + os.path.splitext(self.metadata.frontPic)[-1])) misterCleanName = util.getCleanGameID(self.metadata, '').replace('+', '').replace("'", '').replace('µ', 'mu') \ .replace('¿', '').replace('é', 'e').replace('á', '').replace('ō', 'o').replace('#', '').replace('½', '') \ .replace('$', '').replace('à', 'a').replace('&', 'and').replace(',', '') util.misterCleanNameToGameDir[misterCleanName] = self.game if not os.path.exists(os.path.join(self.outputDir, 'games')): os.mkdir(os.path.join(self.outputDir, 'games')) if self.conversionConf['preExtractGames']: # Create zero sized zip as the game will be pre-extracted open( os.path.join(self.outputDir, 'games', misterCleanName + '.zip'), 'w').close() # Move game.pc folder to games-data if not os.path.exists(os.path.join(self.outputDir, 'games-data')): os.mkdir(os.path.join(self.outputDir, 'games-data')) shutil.move( os.path.join(self.getLocalGameOutputDir()), os.path.join(self.outputDir, 'games-data', misterCleanName)) else: # Zip internal game dir to longgamename.zip self.logger.log(' Rezipping game to %s.zip' % misterCleanName) shutil.make_archive( os.path.join(self.getLocalParentOutputDir(), misterCleanName), 'zip', self.getLocalGameOutputDir()) # Delete everything unrelated shutil.rmtree(os.path.join(self.getLocalGameOutputDir())) # Move archive to games folder shutil.move( os.path.join(self.getLocalParentOutputDir(), misterCleanName + '.zip'), os.path.join(self.outputDir, 'games')) # Post-conversion for openDingux for a given game def postConversionForOpenDingux(self): self.logger.log(" opendingux post-conversion") openDinguxPicDir = '.previews' if self.conversionType == util.esoteric else '.media' # Copy image to opendingux img folder for game.pc distPicPath = os.path.join(self.getLocalParentOutputDir(), openDinguxPicDir) if not os.path.exists(distPicPath): os.mkdir(distPicPath) shutil.copy2(self.metadata.frontPic, os.path.join(distPicPath, self.game + '.pc.png')) # Resize image util.resize(os.path.join(distPicPath, self.game + '.pc.png')) # Copy image to opendingux img folder for game.pc/dosbox.bat dosboxBatPicPath = os.path.join(self.getLocalGameOutputDir(), openDinguxPicDir) if not os.path.exists(dosboxBatPicPath): os.mkdir(dosboxBatPicPath) shutil.copy2(os.path.join(distPicPath, self.game + '.pc.png'), os.path.join(dosboxBatPicPath, 'dosbox.png')) # Generate RG350 mapper mapper = open(os.path.join(self.getLocalGameOutputDir(), "mapper.map"), 'w') mapper.write('key_space "key 308"\n') mapper.write('key_lshift "key 32"\n') mapper.write('key_lctrl "key 304"\n') mapper.write('key_lalt "key 306"\n') mapper.write('key_esc "key 27"\n') mapper.write('key_enter "key 13"\n') mapper.write('key_up "key 273"\n') mapper.write('key_down "key 274"\n') mapper.write('key_right "key 275"\n') mapper.write('key_left "key 276"\n') mapper.write('key_n "key 9"\n') mapper.write('key_y "key 8"\n') mapper.close() # POst-conversion for Retropie for a given game def postConversionForRetropie(self): self.logger.log(" retropie post-conversion") dosboxCfg = open( os.path.join(self.getLocalGameOutputDir(), "dosbox.cfg"), 'a') # add mount c at end of dosbox.cfg romsFolder = util.getRomsFolderPrefix(self.conversionType, self.conversionConf) retropieGameDir = romsFolder + "/" + self.genre + "/" + self.game + ".pc" if self.useGenreSubFolders else romsFolder + "/" + self.game + ".pc" dosboxCfg.write("mount c " + retropieGameDir + "\n") dosboxCfg.write("c:\n") # copy all instructions from dosbox.bat to end of dosbox.cfg dosboxBat = open( os.path.join(self.getLocalGameOutputDir(), "dosbox.bat"), 'r') # retroarch dosbox.bat for cmdLine in dosboxBat.readlines(): dosboxCfg.write(cmdLine) # delete dosbox.bat dosboxCfg.close() dosboxBat.close() os.remove(os.path.join(self.getLocalGameOutputDir(), "dosbox.bat")) # move dosbox.cfg to {game}.conf at top level shutil.move( os.path.join(self.getLocalGameOutputDir(), "dosbox.cfg"), os.path.join(self.getLocalParentOutputDir(), util.getCleanGameID(self.metadata, '.conf')))
class ExoDOSConverter(): def __init__(self, games, exoDosDir, gamesDosDir, outputDir, metadataHandler, logger): self.games = games self.exoDosDir = exoDosDir self.logger = logger self.gamesDosDir = gamesDosDir self.outputDir = outputDir self.metadataHandler = metadataHandler self.confConverter = ConfConverter(self.games, self.exoDosDir, self.outputDir, self.logger) def convertGames(self): self.logger.log("Loading metadatas...") self.metadataHandler.parseXmlMetadata() self.logger.log("") if not os.path.exists(os.path.join(self.outputDir, 'downloaded_images')): os.mkdir(os.path.join(self.outputDir, 'downloaded_images')) if not os.path.exists(os.path.join(self.outputDir, 'manuals')): os.mkdir(os.path.join(self.outputDir, 'manuals')) gamelist = self.metadataHandler.initXml(self.outputDir) count = 1 total = len(self.games) errors = dict() for game in self.games: try: self.convertGame(game, gamelist, total, count) except: self.logger.log('Error %s while converting game %s' % (sys.exc_info()[0], game)) excInfo = traceback.format_exc() errors[game] = excInfo count = count + 1 self.metadataHandler.writeXml(self.outputDir, gamelist) self.logger.log('\n<--------- Finished Process --------->') if len(errors.keys()) > 0: self.logger.log('\n<--------- Errors rundown --------->') self.logger.log('%i errors were found during process' % len(errors.keys())) self.logger.log('See error log in your outputDir') logFile = open(os.path.join(self.outputDir, 'error_log.txt'), 'w') for key in list(errors.keys()): logFile.write("Found error when processing %s" % key + " :\n") logFile.write(errors.get(key)) logFile.write("\n") logFile.close() elif os.path.exists(os.path.join(self.outputDir, 'error_log.txt')): # Delete log from previous runs os.remove(os.path.join(self.outputDir, 'error_log.txt')) def convertGame(self, game, gamelist, total, count): genre = self.metadataHandler.buildGenre( self.metadataHandler.metadatas.get(game)) if not os.path.exists(os.path.join(self.outputDir, genre, game + ".pc")): self.logger.log(">>> %i/%i >>> %s: starting conversion" % (count, total, game)) self.metadataHandler.processGame(game, gamelist, genre, self.outputDir) if not os.path.exists(os.path.join(self.exoDosDir, "Games", game)): self.logger.log(" needs installation...") #automatic F and N subprocess.call("cmd /C (echo Y&echo F&echo N) | Install.bat", cwd=os.path.join(self.gamesDosDir, game), shell=False) self.logger.log(" installed") else: self.logger.log(" already installed") self.copyGameFiles(game, genre) self.confConverter.process(game, genre) else: self.logger.log( ">>> %i/%i >>> %s: already converted in output folder" % (count, total, game)) self.metadataHandler.processGame(game, gamelist, genre, self.outputDir) self.logger.log("") def copyGameFiles(self, game, genre): dest = os.path.join(self.outputDir, genre, game + ".pc", game) self.logger.log(" copy game data") # Copy game files in game.pc/game shutil.copytree(os.path.join(self.exoDosDir, "Games", game), dest) self.logger.log(" copy dosbox conf") # Copy dosbox.conf in game.pc shutil.copy2( os.path.join(self.exoDosDir, "Games", "!dos", game, "dosbox.conf"), os.path.join(dest, "dosbox.conf"))