import os import pickle import re import subprocess import zipfile from glob import glob from lxml import etree from titlecase import titlecase class MameROM(object): ''' One Mame ROM ''' def __init__(self): self.name = '' self.cloneof = None self.description = '' self.year = '' self.manufacturer = '' self.status = '' self.category = '' def __str__(self): return self.description def __repr__(self): return '%s("%s", "%s", "%s", "%s", "%s", "%s", "%s")' % ( self.__class__.__name__, self.name, self.cloneof, self.description, self.year, self.manufacturer, self.status, self.category) class MameROMs(object): ''' A Collection of MAME Roms ''' def __init__(self, data_dir, hide_mature, cfg): self.all_roms = [] self.roms = [] self.all_categories = [] self.categories = [] self.catdict = {} self.len = 0 self.data_dir = data_dir self.hide_mature = hide_mature self.cfg = cfg self.emulator_dir = os.path.split(cfg['EXE'])[0] self.parse() self.filter() self.snapdir = os.path.join(self.emulator_dir, 'snap') if os.path.isfile(os.path.join(self.snapdir, 'snap.zip')): self.snapfile = zipfile.ZipFile( os.path.join(self.snapdir, "snap.zip")) else: self.snapfile = None self.snapdir = os.path.join(self.snapdir, 'snap') self.marqueesdir = os.path.join(self.emulator_dir, 'marquees') if os.path.isfile(os.path.join(self.marqueesdir, 'marquees.zip')): self.marqueesfile = zipfile.ZipFile( os.path.join(self.marqueesdir, "marquees.zip")) else: self.marqueesfile = None self.marqueesdir = os.path.join(self.marqueesdir, 'marquees') self.titlesdir = os.path.join(self.emulator_dir, 'titles') if os.path.isfile(os.path.join(self.titlesdir, 'titles.zip')): self.titlesfile = zipfile.ZipFile( os.path.join(self.titlesdir, "titles.zip")) else: self.titlesfile = None self.titlesdir = os.path.join(self.titlesdir, 'titles') infofn = os.path.join(self.emulator_dir, 'mameinfo.dat') if not os.path.isfile(infofn): self.info = None else: with open(infofn, 'rt', encoding='latin1') as f: self.info = f.read() historyfn = os.path.join(self.emulator_dir, 'history.dat') if not os.path.isfile(historyfn): self.history = None else: with open(historyfn, 'rt', encoding='utf-8') as f: self.history = f.read() self.artwork_dirs = {} for item in cfg['ArtworkDirs']: directory = os.path.join(self.emulator_dir, item) if os.path.isdir(directory): if os.path.isfile(os.path.join(directory, '%s.zip' % item)): self.artwork_dirs[titlecase(item)] = { "dof": os.path.join(directory, '%s.zip' % item), 'type': 'file' } else: self.artwork_dirs[titlecase(item)] = { "dof": directory, 'type': 'dir' } def __len__(self): return self.len def __getitem__(self, item): return self.roms[item] def filter(self): self.roms = [] for rom in self.all_roms: if rom.status in self.cfg['StatusFilter']: if self.cfg["Category"] == "All Games" or self.cfg["Category"] == rom.category: if self.cfg["ShowClones"] or rom.cloneof is None: if not self.hide_mature or '* Mature *' not in rom.category: self.roms.append(rom) if self.cfg["Sort"] == "Name Asc": self.roms.sort(key=lambda x: x.description.lower(), reverse=False) elif self.cfg["Sort"] == "Name Dec": self.roms.sort(key=lambda x: x.description.lower(), reverse=True) elif self.cfg["Sort"] == "Year Asc": self.roms.sort(key=lambda x: x.year.lower(), reverse=False) elif self.cfg["Sort"] == "Year Dec": self.roms.sort(key=lambda x: x.year.lower(), reverse=True) self.len = len(self.roms) if self.hide_mature: self.categories = [] for category in self.all_categories: if '* Mature *' not in category: self.categories.append(category) else: self.categories = self.all_categories def parse(self): ''' Parse xml ''' mame_version = self.get_mame_version() xmlfile = os.path.join(self.emulator_dir, 'mame.xml') datfile = os.path.join(self.data_dir, 'mame.dat') if self.cfg['Version'] != mame_version: self.cfg['Version'] = mame_version if os.path.isfile(xmlfile): os.unlink(xmlfile) if os.path.isfile(datfile): with open(datfile, 'rb') as i: temp_mame_version = pickle.load(i) if temp_mame_version == mame_version: self.all_categories = pickle.load(i) self.all_roms = pickle.load(i) return True else: os.unlink(datfile, 'mame.dat') tempcat = {} catfile = open(os.path.join(self.emulator_dir, 'catver.ini')) found = False for line in catfile: line = line.strip() if line == '[VerAdded]': break if line == '[Category]': found = True if found and line != '' and line[0] != ';' and line[0] != '[': # zwackery=Platform / Run Jump game, category = line.split('=') tempcat[game] = category if category not in self.all_categories: self.all_categories.append(category) self.catdict[category] = 0 self.all_categories.sort() self.all_categories.insert(0, 'All Games') self.all_categories.append('Unknown') self.catdict['All Games'] = 1 self.catdict['Unknown'] = 0 if not os.path.isfile(xmlfile): try: with open(xmlfile, 'w') as out: retcode = subprocess.call( [self.cfg['EXE'], '-listxml'], stdout=out) if retcode != 0: try: os.unlink(xmlfile) except: pass return False except OSError: try: os.unlink(xmlfile) except: pass return False # pylint: disable=no-member with open(os.path.join(self.data_dir, 'mame_exclude.txt'), 'rt') as f: exclude = f.readlines() exclude = [x.strip() for x in exclude] tree = etree.parse(xmlfile) rom = None for child in tree.getiterator(): if child.tag == 'machine': if rom: if rom.category not in exclude: self.all_roms.append(rom) self.catdict[rom.category] += 1 rom = None if 'runnable' in child.attrib and child.attrib['runnable'] == 'yes': rom = MameROM() rom.name = child.attrib['name'] else: rom = None if rom and 'cloneof' in child.attrib: rom.cloneof = child.attrib['cloneof'] if rom and rom.name in tempcat: rom.category = tempcat[rom.name] elif rom and rom.cloneof in tempcat: rom.category = tempcat[rom.cloneof] elif rom: rom.category = 'Unknown' elif rom and child.tag == 'description': rom.description = child.text elif rom and child.tag == 'year': rom.year = child.text elif rom and child.tag == 'manufacturer': rom.manufacturer = child.text elif rom and child.tag == 'driver': rom.status = child.attrib['status'] if rom: if rom.category not in exclude: self.all_roms.append(rom) for cat in self.catdict: if self.catdict[cat] == 0: del self.all_categories[self.all_categories.index(cat)] with open(datfile, 'wb') as output: pickle.dump(mame_version, output) pickle.dump(self.all_categories, output) pickle.dump(self.all_roms, output) return True def get_mame_version(self): mamerun = subprocess.run( [self.cfg['EXE'], '-?'], stdout=subprocess.PIPE) output = mamerun.stdout.decode('utf-8') output = output[output.find('v'):] output = output[:output.find(' ')] return output def get_info(self, rom): if not self.info: return search = re.search("\\$info=%s" % rom.name, self.info) if not search: search = re.search("\\$info=%s" % rom.cloneof, self.info) if not search: return None start = search.start() info = self.info[start:self.info.find('$end', start)].splitlines() for i in range(len(info) - 1, -1, -1): if info[i].startswith('$'): del info[i] while info[0].strip() == '': del info[0] while info[len(info) - 1].strip() == '': del info[len(info) - 1] return '\n'.join(info) def get_history(self, rom): if not self.history: return search = re.search("\\$info=%s" % rom.name, self.history) if not search: search = re.search("\\$info=%s" % rom.cloneof, self.history) if not search: return None start = search.start() info = self.history[start:self.history.find('$end', start)].splitlines() for i in range(len(info) - 1, -1, -1): if info[i].startswith('$'): del info[i] while info[0].strip() == '': del info[0] while info[len(info) - 1].strip() == '': del info[len(info) - 1] return '\n'.join(info) def get_snap(self, rom, type_of_image): # pylint : disable=R0912 data = None image_file = None image_dir = None if type_of_image == 'snap': image_dir = self.snapdir image_file = self.snapfile if type_of_image == 'title': image_dir = self.titlesdir image_file = self.titlesfile if type_of_image == 'marquee': image_dir = self.marqueesdir image_file = self.marqueesfile if image_file: try: data = image_file.read(rom.name + ".png") except: try: data = image_file.read(rom.cloneof + '.png') except: data = None elif image_dir: fn = os.path.join(image_dir, rom.name + '.png') if os.path.isfile(fn): with open(fn, 'rb') as f: data = f.read() elif rom.cloneof: fn = os.path.join(image_dir, rom.cloneof + '.png') if os.path.isfile(fn): with open(fn, 'rb') as f: data = f.read() else: data = None else: data = None else: data = None return data def get_artwork(self, rom): result = [] for item in self.artwork_dirs: title = item dof = self.artwork_dirs[title]['dof'] if self.artwork_dirs[title]['type'] == 'dir': fn = None if glob('%s/**/%s.*' % (dof, rom.name), recursive=True) != []: fn = glob( '%s/**/%s.*' % (dof, rom.name), recursive=True)[0] elif glob( '%s/**/%s.*' % (dof, rom.cloneof), recursive=True) != []: fn = glob( '%s/**/%s.*' % (dof, rom.cloneof), recursive=True)[0] if fn: data = None if os.path.splitext(fn)[1].lower() in [ '.png', '.jpg', '.jpeg' ]: with open(fn, 'rb') as f: data = f.read() if data: result.append([title, data]) else: data = None try: with zipfile.ZipFile(dof) as f: data = f.read('%s.png' % rom.name) except: try: with zipfile.ZipFile(dof) as f: data = f.read('%s.png' % rom.cloneof) except: data = None if data: result.append([title, data]) return result