Initial commit

This commit is contained in:
Richard Jones
2022-07-20 17:09:57 +01:00
commit c3e146e2ee
23 changed files with 2420 additions and 0 deletions

112
.gitignore vendored Normal file
View File

@@ -0,0 +1,112 @@
mame.json
mame/*
mfe.ini
mame.dat
mess.dat
mame_exclude.txt
.vscode/tags
# Mac Files
.DS_Store
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
.hypothesis/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
target/
# Jupyter Notebook
.ipynb_checkpoints
# pyenv
.python-version
# celery beat schedule file
celerybeat-schedule
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/

60
.vscode/launch.json vendored Normal file
View File

@@ -0,0 +1,60 @@
{
"version": "0.2.0",
"configurations": [{
"name": "Python",
"type": "python",
"request": "launch",
"stopOnEntry": false,
"pythonPath": "${config:python.pythonPath}",
"program": "${workspaceRoot}/mfe.py",
"cwd": "${workspaceRoot}",
"env": {},
"envFile": "${workspaceRoot}/.env",
"debugOptions": [
"RedirectOutput"
]
},
{
"name": "Integrated Terminal/Console",
"type": "python",
"request": "launch",
"stopOnEntry": true,
"pythonPath": "${config:python.pythonPath}",
"program": "${file}",
"cwd": "",
"console": "integratedTerminal",
"env": {},
"envFile": "${workspaceRoot}/.env",
"debugOptions": [
"WaitOnAbnormalExit",
"WaitOnNormalExit"
]
},
{
"name": "External Terminal/Console",
"type": "python",
"request": "launch",
"stopOnEntry": true,
"pythonPath": "${config:python.pythonPath}",
"program": "${file}",
"cwd": "",
"console": "externalTerminal",
"env": {},
"envFile": "${workspaceRoot}/.env",
"debugOptions": [
"WaitOnAbnormalExit",
"WaitOnNormalExit"
]
},
{
"name": "Attach (Remote Debug)",
"type": "python",
"request": "attach",
"localRoot": "${workspaceRoot}",
"remoteRoot": "${workspaceRoot}",
"port": 3000,
"secret": "my_secret",
"host": "localhost"
}
]
}

9
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,9 @@
{
"python.formatting.provider": "yapf",
"python.linting.flake8Enabled": true,
"python.linting.flake8Args": ["--ignore", "E501,E722,C0103"],
"editor.formatOnPaste": true,
"editor.formatOnSave": true,
"editor.formatOnType": true,
"python.pythonPath": "/Users/rich/.local/share/virtualenvs/mfe-K8QhrIKQ/bin/python",
}

22
Pipfile Normal file
View File

@@ -0,0 +1,22 @@
[[source]]
url = "https://pypi.org/simple"
verify_ssl = true
name = "pypi"
[packages]
configobj = ">=5.0.6"
appdirs = ">=1.4.3"
lxml = ">=3.8.0"
titlecase = ">=0.11.0"
#pygame = {path = "./../pygame/dist/pygame-1.9.5.dev0-py3.7-macosx-10.14-x86_64.egg"}
#Pygame = ">=1.9.3"
pygame = {file = "file:///Users/rich/git/pygame/dist/pygame-1.9.5.dev0-cp37-cp37m-macosx_10_14_x86_64.whl"}
[dev-packages]
pylint = "*"
flake8 = "*"
cx-freeze = {editable = true,git = "https://github.com/anthony-tuininga/cx_Freeze.git"}
yapf = "*"
[requires]
python_version = "3.7"

210
Pipfile.lock generated Normal file
View File

@@ -0,0 +1,210 @@
{
"_meta": {
"hash": {
"sha256": "d7ff85d4507b7eaaf2f2f6433e8450ffcf52bdd74a889e3051a38acd4cd3c62a"
},
"pipfile-spec": 6,
"requires": {
"python_version": "3.7"
},
"sources": [
{
"name": "pypi",
"url": "https://pypi.org/simple",
"verify_ssl": true
}
]
},
"default": {
"appdirs": {
"hashes": [
"sha256:9e5896d1372858f8dd3344faf4e5014d21849c756c8d5701f78f8a103b372d92",
"sha256:d8b24664561d0d34ddfaec54636d502d7cea6e29c3eaf68f3df6180863e2166e"
],
"index": "pypi",
"version": "==1.4.3"
},
"configobj": {
"hashes": [
"sha256:a2f5650770e1c87fb335af19a9b7eb73fc05ccf22144eb68db7d00cd2bcb0902"
],
"index": "pypi",
"version": "==5.0.6"
},
"lxml": {
"hashes": [
"sha256:02bc220d61f46e9b9d5a53c361ef95e9f5e1d27171cd461dddb17677ae2289a5",
"sha256:22f253b542a342755f6cfc047fe4d3a296515cf9b542bc6e261af45a80b8caf6",
"sha256:2f31145c7ff665b330919bfa44aacd3a0211a76ca7e7b441039d2a0b0451e415",
"sha256:36720698c29e7a9626a0dc802ef8885f8f0239bfd1689628ecd459a061f2807f",
"sha256:438a1b0203545521f6616132bfe0f4bca86f8a401364008b30e2b26ec408ce85",
"sha256:4815892904c336bbaf73dafd54f45f69f4021c22b5bad7332176bbf4fb830568",
"sha256:5be031b0f15ad63910d8e5038b489d95a79929513b3634ad4babf77100602588",
"sha256:5c93ae37c3c588e829b037fdfbd64a6e40c901d3f93f7beed6d724c44829a3ad",
"sha256:60842230678674cdac4a1cf0f707ef12d75b9a4fc4a565add4f710b5fcf185d5",
"sha256:62939a8bb6758d1bf923aa1c13f0bcfa9bf5b2fc0f5fa917a6e25db5fe0cfa4e",
"sha256:75830c06a62fe7b8fe3bbb5f269f0b308f19f3949ac81cfd40062f47c1455faf",
"sha256:81992565b74332c7c1aff6a913a3e906771aa81c9d0c68c68113cffcae45bc53",
"sha256:8c892fb0ee52c594d9a7751c7d7356056a9682674b92cc1c4dc968ff0f30c52f",
"sha256:9d862e3cf4fc1f2837dedce9c42269c8c76d027e49820a548ac89fdcee1e361f",
"sha256:a623965c086a6e91bb703d4da62dabe59fe88888e82c4117d544e11fd74835d6",
"sha256:a7783ab7f6a508b0510490cef9f857b763d796ba7476d9703f89722928d1e113",
"sha256:aab09fbe8abfa3b9ce62aaf45aca2d28726b1b9ee44871dbe644050a2fff4940",
"sha256:abf181934ac3ef193832fb973fd7f6149b5c531903c2ec0f1220941d73eee601",
"sha256:ae07fa0c115733fce1e9da96a3ac3fa24801742ca17e917e0c79d63a01eeb843",
"sha256:b9c78242219f674ab645ec571c9a95d70f381319a23911941cd2358a8e0521cf",
"sha256:bccb267678b870d9782c3b44d0cefe3ba0e329f9af8c946d32bf3778e7a4f271",
"sha256:c4df4d27f4c93b2cef74579f00b1d3a31a929c7d8023f870c4b476f03a274db4",
"sha256:caf0e50b546bb60dfa99bb18dfa6748458a83131ecdceaf5c071d74907e7e78a",
"sha256:d3266bd3ac59ac4edcd5fa75165dee80b94a3e5c91049df5f7c057ccf097551c",
"sha256:db0d213987bcd4e6d41710fb4532b22315b0d8fb439ff901782234456556aed1",
"sha256:dbbd5cf7690a40a9f0a9325ab480d0fccf46d16b378eefc08e195d84299bfae1",
"sha256:e16e07a0ec3a75b5ee61f2b1003c35696738f937dc8148fbda9fe2147ccb6e61",
"sha256:e175a006725c7faadbe69e791877d09936c0ef2cf49d01b60a6c1efcb0e8be6f",
"sha256:edd9c13a97f6550f9da2236126bb51c092b3b1ce6187f2bd966533ad794bbb5e",
"sha256:fa39ea60d527fbdd94215b5e5552f1c6a912624521093f1384a491a8ad89ad8b"
],
"index": "pypi",
"version": "==4.2.5"
},
"pygame": {
"file": "file:///Users/rich/git/pygame/dist/pygame-1.9.5.dev0-cp37-cp37m-macosx_10_14_x86_64.whl",
"hashes": [
"sha256:9b811036eebebca68f166683f21427d1682f62159cb6de2b6724dec2be322c2a",
"sha256:9fba43308b121628a6c70edc7fac4b07ae5e30ccfe5bf36b914b3a2130e1671a",
"sha256:a8e217e5c39c5ea2900f450f2e8acdb97487053a13447f680608200a956e8fe3",
"sha256:b48da4cd1b70487791767fcae7e9e3ce8eaf4d340667b5eea14d49a4104e23d1"
],
"version": "==1.9.5.dev0"
},
"six": {
"hashes": [
"sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9",
"sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb"
],
"version": "==1.11.0"
},
"titlecase": {
"hashes": [
"sha256:84de7a97fb702c400e5ba11c6b30849944b39db12e20fbf4515a23c7538a0611",
"sha256:95d643a0c08097c02933aced707adfe1c275c335019e8e514dea782a465c5b84"
],
"index": "pypi",
"version": "==0.12.0"
}
},
"develop": {
"astroid": {
"hashes": [
"sha256:35b032003d6a863f5dcd7ec11abd5cd5893428beaa31ab164982403bcb311f22",
"sha256:6a5d668d7dc69110de01cdf7aeec69a679ef486862a0850cc0fd5571505b6b7e"
],
"version": "==2.1.0"
},
"cx-freeze": {
"editable": true,
"git": "https://github.com/anthony-tuininga/cx_Freeze.git",
"ref": "9e06b761740a9e93431ee7ea8d0b10f786446a6a"
},
"flake8": {
"hashes": [
"sha256:6a35f5b8761f45c5513e3405f110a86bea57982c3b75b766ce7b65217abe1670",
"sha256:c01f8a3963b3571a8e6bd7a4063359aff90749e160778e03817cd9b71c9e07d2"
],
"index": "pypi",
"version": "==3.6.0"
},
"isort": {
"hashes": [
"sha256:1153601da39a25b14ddc54955dbbacbb6b2d19135386699e2ad58517953b34af",
"sha256:b9c40e9750f3d77e6e4d441d8b0266cf555e7cdabdcff33c4fd06366ca761ef8",
"sha256:ec9ef8f4a9bc6f71eec99e1806bfa2de401650d996c59330782b89a5555c1497"
],
"version": "==4.3.4"
},
"lazy-object-proxy": {
"hashes": [
"sha256:0ce34342b419bd8f018e6666bfef729aec3edf62345a53b537a4dcc115746a33",
"sha256:1b668120716eb7ee21d8a38815e5eb3bb8211117d9a90b0f8e21722c0758cc39",
"sha256:209615b0fe4624d79e50220ce3310ca1a9445fd8e6d3572a896e7f9146bbf019",
"sha256:27bf62cb2b1a2068d443ff7097ee33393f8483b570b475db8ebf7e1cba64f088",
"sha256:27ea6fd1c02dcc78172a82fc37fcc0992a94e4cecf53cb6d73f11749825bd98b",
"sha256:2c1b21b44ac9beb0fc848d3993924147ba45c4ebc24be19825e57aabbe74a99e",
"sha256:2df72ab12046a3496a92476020a1a0abf78b2a7db9ff4dc2036b8dd980203ae6",
"sha256:320ffd3de9699d3892048baee45ebfbbf9388a7d65d832d7e580243ade426d2b",
"sha256:50e3b9a464d5d08cc5227413db0d1c4707b6172e4d4d915c1c70e4de0bbff1f5",
"sha256:5276db7ff62bb7b52f77f1f51ed58850e315154249aceb42e7f4c611f0f847ff",
"sha256:61a6cf00dcb1a7f0c773ed4acc509cb636af2d6337a08f362413c76b2b47a8dd",
"sha256:6ae6c4cb59f199d8827c5a07546b2ab7e85d262acaccaacd49b62f53f7c456f7",
"sha256:7661d401d60d8bf15bb5da39e4dd72f5d764c5aff5a86ef52a042506e3e970ff",
"sha256:7bd527f36a605c914efca5d3d014170b2cb184723e423d26b1fb2fd9108e264d",
"sha256:7cb54db3535c8686ea12e9535eb087d32421184eacc6939ef15ef50f83a5e7e2",
"sha256:7f3a2d740291f7f2c111d86a1c4851b70fb000a6c8883a59660d95ad57b9df35",
"sha256:81304b7d8e9c824d058087dcb89144842c8e0dea6d281c031f59f0acf66963d4",
"sha256:933947e8b4fbe617a51528b09851685138b49d511af0b6c0da2539115d6d4514",
"sha256:94223d7f060301b3a8c09c9b3bc3294b56b2188e7d8179c762a1cda72c979252",
"sha256:ab3ca49afcb47058393b0122428358d2fbe0408cf99f1b58b295cfeb4ed39109",
"sha256:bd6292f565ca46dee4e737ebcc20742e3b5be2b01556dafe169f6c65d088875f",
"sha256:cb924aa3e4a3fb644d0c463cad5bc2572649a6a3f68a7f8e4fbe44aaa6d77e4c",
"sha256:d0fc7a286feac9077ec52a927fc9fe8fe2fabab95426722be4c953c9a8bede92",
"sha256:ddc34786490a6e4ec0a855d401034cbd1242ef186c20d79d2166d6a4bd449577",
"sha256:e34b155e36fa9da7e1b7c738ed7767fc9491a62ec6af70fe9da4a057759edc2d",
"sha256:e5b9e8f6bda48460b7b143c3821b21b452cb3a835e6bbd5dd33aa0c8d3f5137d",
"sha256:e81ebf6c5ee9684be8f2c87563880f93eedd56dd2b6146d8a725b50b7e5adb0f",
"sha256:eb91be369f945f10d3a49f5f9be8b3d0b93a4c2be8f8a5b83b0571b8123e0a7a",
"sha256:f460d1ceb0e4a5dcb2a652db0904224f367c9b3c1470d5a7683c0480e582468b"
],
"version": "==1.3.1"
},
"mccabe": {
"hashes": [
"sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42",
"sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"
],
"version": "==0.6.1"
},
"pycodestyle": {
"hashes": [
"sha256:cbc619d09254895b0d12c2c691e237b2e91e9b2ecf5e84c26b35400f93dcfb83",
"sha256:cbfca99bd594a10f674d0cd97a3d802a1fdef635d4361e1a2658de47ed261e3a"
],
"version": "==2.4.0"
},
"pyflakes": {
"hashes": [
"sha256:9a7662ec724d0120012f6e29d6248ae3727d821bba522a0e6b356eff19126a49",
"sha256:f661252913bc1dbe7fcfcbf0af0db3f42ab65aabd1a6ca68fe5d466bace94dae"
],
"version": "==2.0.0"
},
"pylint": {
"hashes": [
"sha256:689de29ae747642ab230c6d37be2b969bf75663176658851f456619aacf27492",
"sha256:771467c434d0d9f081741fec1d64dfb011ed26e65e12a28fe06ca2f61c4d556c"
],
"index": "pypi",
"version": "==2.2.2"
},
"six": {
"hashes": [
"sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9",
"sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb"
],
"version": "==1.11.0"
},
"wrapt": {
"hashes": [
"sha256:d4d560d479f2c21e1b5443bbd15fe7ec4b37fe7e53d335d3b9b0a7b1226fe3c6"
],
"version": "==1.10.11"
},
"yapf": {
"hashes": [
"sha256:8aa7f9abdb97b4da4d3227306b88477982daafef0a96cc41639754ca31f46d55",
"sha256:f2df5891481f94ddadfbf8ae8ae499080752cfb06005a31bbb102f3012f8b944"
],
"index": "pypi",
"version": "==0.25.0"
}
}
}

305
app.py Normal file
View File

@@ -0,0 +1,305 @@
import sys
# from pprint import pprint
import pygame
import csvfile
import gui as guimodule
import mame
import mess
from configfile import cfg, config_write, get_emu, get_emulators, set_emu
from utils import addscanlines, image_from_data, run_emulator
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
GREY = (70, 70, 70)
DARKGREY = (50, 50, 50)
class app(object):
def __init__(self):
if get_emu('EmulatorType') == 'MAME':
self.roms = mame.MameROMs(cfg['datadir'], cfg['HideMature'],
get_emu())
if get_emu('EmulatorType') == 'MESS':
self.roms = mess.MessROMs(cfg['datadir'], get_emu())
if get_emu('EmulatorType') == 'CSV':
self.roms = csvfile.csvROMs(cfg['datadir'], get_emu())
self.update_menus()
pygame.init()
pygame.font.init()
iconsurface = pygame.image.load("arcade.png")
pygame.display.set_icon(iconsurface)
if cfg['FullScreen']:
self.screen = pygame.display.set_mode(
(cfg['ResolutionX'], cfg['ResolutionY']), pygame.FULLSCREEN)
else:
self.screen = pygame.display.set_mode((cfg['ResolutionX'],
cfg['ResolutionY']))
self.font = pygame.font.Font(
pygame.font.match_font(cfg['Font']), cfg['FontSize'])
self.status_surface = pygame.Surface(( # pylint: disable=E1121
cfg['ResolutionX'], self.font.get_height()))
self.oldmouse = pygame.mouse.set_visible(False)
pygame.key.set_repeat(100, 30)
self.listwidth = int(cfg['ResolutionX'] / 2)
self.gui = guimodule.gui(self.screen)
self.gui.add_menu(
0,
0,
self.listwidth,
cfg['ResolutionY'] - self.font.get_height(),
border=False,
data=self.roms,
fg=(255, 255, 255),
bg=(0, 0, 0),
name='MainList',
startitem=get_emu('CurrentGame'),
itemattop=get_emu('GameAtTop'))
self.gui.render()
def update_menus(self):
rootmenu = [
'Show Artwork', 'Categories', 'Sort',
'Hide Clones' if get_emu('ShowClones') else 'Show Clones',
'Change Emulator', 'Quit'
]
sortmenu = ['Name Asc', 'Name Dec', 'Year Asc', 'Year Dec']
emulatormenu = get_emulators()
self.menus = {
'rootmenu': rootmenu,
'sortmenu': sortmenu,
'emulatormenu': emulatormenu,
'categorymenu': self.roms.categories
}
def run(self):
keyup = False
quitcount = 0
while True:
event = self.gui.waitevent()
if event:
if event.type == pygame.QUIT:
break
if event.type == pygame.KEYDOWN:
keyup = False
if event.key == pygame.K_ESCAPE and self.gui.currentobject == 1: # Main List
quitcount += 1
if quitcount == cfg['QuitKeyPresses']:
break
elif event.key == pygame.K_ESCAPE:
quitcount = 0
if self.gui.currentobject > 1:
self.gui.deletelastobject()
else:
quitcount = 0
if event.key in cfg[
'KeySelect'] and self.gui.currentobject == 1:
if sys.platform == 'darwin':
print('Running %s' %
self.roms[self.gui.getcurrentitem()].name)
run_emulator(
get_emu('EmulatorType'), get_emu('EXE'),
self.roms[self.gui.getcurrentitem()].name)
elif event.key in cfg[
'KeyGameHistory'] and self.gui.currentobject == 1:
history = self.roms.get_history(
self.roms[self.gui.getcurrentitem()])
if history:
self.gui.add_notepad(-1, -1, 800, 600, history,
True, WHITE, DARKGREY,
'history', 'Game History')
elif event.key in cfg[
'KeyGameInfo'] and self.gui.currentobject == 1:
info = self.roms.get_info(
self.roms[self.gui.getcurrentitem()])
if info:
self.gui.add_notepad(-1, -1, 800, 600, info, True,
WHITE, DARKGREY, 'info',
'Game Information')
elif event.key in cfg[
'KeyPopup'] and self.gui.currentobject == 1:
self.gui.add_menu(40, 40, -1, -1,
self.menus['rootmenu'], True, WHITE,
GREY, 'rootmenu', 0)
elif event.key in cfg[
'KeyPopup'] and self.gui.currentobject > 1:
self.gui.deletelastobject()
elif event.key in cfg['KeyShowArtwork']:
pass
# TODO: copy from below
elif event.key in cfg['KeySelect']:
currentitem = self.gui.getcurrentitem()
menuname = self.gui.getmenuname()
item = self.menus[menuname][currentitem]
if menuname == 'rootmenu':
if item == 'Quit':
break
elif item == 'Show Artwork':
print('hello')
aw = self.roms.get_artwork(
self.roms[self.gui.getcurrentitem(0)])
if aw != []:
self.gui.add_image_notepad(
-1, -1, 800, 600, aw, True, WHITE,
DARKGREY, 'artwork', 'Artwork')
elif item == 'Categories':
self.gui.add_menu(
80, 45, -1,
-(self.screen.get_height() - 45 -
(self.font.get_height() * 2) - 8),
self.menus['categorymenu'], True, WHITE,
GREY, 'categorymenu', 0)
elif item == 'Sort':
self.gui.add_menu(80, 45, -1, -1,
self.menus['sortmenu'], True,
WHITE, GREY, 'sortmenu', 0)
elif item == 'Show Clones':
set_emu('ShowClones', True)
self.update_menus()
self.roms.filter()
self.gui.deleteallmenus()
self.gui.setcurrentitem(0)
elif item == 'Hide Clones':
set_emu('ShowClones', False)
self.update_menus()
self.roms.filter()
self.gui.deleteallmenus()
self.gui.setcurrentitem(0)
elif item == 'Change Emulator':
self.gui.add_menu(
80, 45, -1,
-(self.screen.get_height() - 45 -
(self.font.get_height() * 2) - 8),
self.menus['emulatormenu'], True, WHITE,
GREY, 'emulatormenu', 0)
elif menuname == 'sortmenu':
set_emu('Sort', item)
self.roms.filter()
self.gui.deleteallmenus()
elif menuname == 'categorymenu':
set_emu('Category', item)
self.roms.filter()
self.gui.deleteallmenus()
self.gui.setcurrentitem(0)
elif menuname == 'emulatormenu':
cfg['Emulator'] = item
if get_emu('EmulatorType') == 'MAME':
self.roms = mame.MameROMs(
cfg['datadir'], cfg['HideMature'],
get_emu())
if get_emu('EmulatorType') == 'MESS':
self.roms = mess.MessROMs(
cfg['datadir'], get_emu())
if get_emu('EmulatorType') == 'CSV':
self.roms = csvfile.csvROMs(
cfg['datadir'], get_emu())
self.update_menus()
self.gui.deleteallmenus()
self.gui.deletelastobject()
self.gui.add_menu(
0,
0,
self.listwidth,
cfg['ResolutionY'] - self.font.get_height(),
border=False,
data=self.roms,
fg=(255, 255, 255),
bg=(0, 0, 0),
name='MainList',
startitem=get_emu('CurrentGame'),
itemattop=get_emu('GameAtTop'))
if event.type == pygame.KEYUP:
keyup = True
if self.gui.currentobject == 1 and self.roms:
currentitem = self.gui.getcurrentitem()
self.screen.fill(BLACK)
self.status_surface.fill((128, 128, 128))
if keyup:
pass
if cfg['AlwaysChangeSnap'] or keyup:
data = self.roms.get_snap(self.roms[currentitem], 'snap')
screenshot_surface = image_from_data(data, (508, 480))
if cfg['ScanLines']:
screenshot_surface = addscanlines(screenshot_surface)
self.screen.blit(
screenshot_surface,
(self.listwidth + 2 + (
(self.screen.get_width() - self.listwidth + 2) / 2)
- (screenshot_surface.get_width() / 2), 102))
data = self.roms.get_snap(self.roms[currentitem],
'marquee')
marquee_surface = image_from_data(data, (640, 100))
self.screen.blit(
marquee_surface,
(self.listwidth + 2 + (
(self.screen.get_width() - self.listwidth + 2) / 2)
- (marquee_surface.get_width() / 2), 0))
data = self.roms.get_snap(self.roms[currentitem], 'title')
title_surface = image_from_data(
data, (640, cfg['ResolutionY'] -
(screenshot_surface.get_height() +
marquee_surface.get_height() + 4 +
self.status_surface.get_height())))
self.screen.blit(
title_surface, (self.listwidth + 2 + (
(self.screen.get_width() - self.listwidth + 2) / 2)
- (title_surface.get_width() / 2),
(screenshot_surface.get_height() +
marquee_surface.get_height() + 4)))
size = self.font.size(
'%d / %d %s' % (currentitem + 1, len(self.roms),
get_emu('Sort')))[0]
self.status_surface.blit(
self.font.render(
'%d / %d %s' % (currentitem + 1, len(self.roms),
get_emu('Sort')), True, BLACK,
(128, 128, 128)), (cfg['ResolutionX'] - size - 5, 0))
self.status_surface.blit(
self.font.render(
'%s %s' % (self.roms[currentitem].year,
self.roms[currentitem].category), True,
BLACK, (128, 128, 128)), (5, 0))
try:
self.screen.blit(
self.status_surface,
(0, cfg['ResolutionY'] - self.font.get_height()))
except:
pass
self.gui.render()
pygame.mouse.set_visible(self.oldmouse)
pygame.key.set_repeat()
set_emu('CurrentGame', self.gui.getcurrentitem())
set_emu('GameAtTop', self.gui.getitemattop())
config_write()

BIN
arcade.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

5
cfg/___empty.cfg Normal file
View File

@@ -0,0 +1,5 @@
<?xml version="1.0"?>
<!-- This file is autogenerated; comments and unknown tags will be stripped -->
<mameconfig version="10">
<system name="___empty" />
</mameconfig>

5
cfg/default.cfg Normal file
View File

@@ -0,0 +1,5 @@
<?xml version="1.0"?>
<!-- This file is autogenerated; comments and unknown tags will be stripped -->
<mameconfig version="10">
<system name="default" />
</mameconfig>

137
configfile.py Normal file
View File

@@ -0,0 +1,137 @@
from __future__ import print_function
import os
from appdirs import user_data_dir
from configobj import ConfigObj, flatten_errors, Section
from validate import Validator
from exclude import MAME_EXCLUDE
from utils import get_pygame_keydict, get_keys
CFG = '''ResolutionX = integer(default=1024)
ResolutionY = integer(default=768)
FullScreen = boolean(default=False)
Font = string(default='microsoftsansserif')
FontSize = integer(default=13)
QuitKeyPresses = integer(min=1,default=3)
ScanLines = boolean(default=True)
Key_Up = list(default=list('K_UP'))
Key_Down = list(default=list('K_DOWN'))
Key_PgUp = list(default=list('K_LEFT','K_PAGEUP'))
Key_PgDn = list(default=list('K_RIGHT','K_PAGEDOWN'))
Key_Home = list(default=list('K_HOME'))
Key_End = list(default=list('K_END'))
Key_Select = list(default=list('K_RETURN','K_1'))
Key_GameInfo = list(default=list('K_5'))
Key_GameHistory = list(default=list('K_6'))
Key_Popup = list(default=list('K_2'))
Key_ShowArtwork = list(default=list())
AlwaysChangeSnap = boolean(default=True)
HideMature = boolean(default=True)
Emulator = string()
[__many__]
EXE = string()
Version = string(default=None)
ShowClones = boolean(default=True)
Category = string(default='All Games')
GameAtTop = integer(min=0,default=0)
CurrentGame = integer(min=0,default=0)
EmulatorType = option('MAME','MESS','CSV',default='MAME')
StatusFilter = list(default=list())
Sort = option('Name Asc','Name Dec','Year Asc','Year Dec',default='Name Asc')
SnapDir = string(default=None)
CSVFile = string(default=None)
ArtworkDirs = list(default=list())
'''
appname = 'MFE'
appauthor = 'RMJ'
datadir = user_data_dir(appname, appauthor)
datadir = os.getcwd()
if not os.path.isdir(datadir):
os.makedirs(datadir)
spec = CFG.split('\n')
cfg = ConfigObj(
os.path.join(datadir, 'mfe.ini'), configspec=spec, encoding='UTF8')
validator = Validator()
result = cfg.validate(validator, copy=True, preserve_errors=True)
stop = False
for entry in flatten_errors(cfg, result):
section_list, key, error = entry
if key is not None:
section_list.append(key)
else:
section_list.append('[missing section]')
section_string = ', '.join(section_list)
if error is False:
error = 'Missing value or section.'
print(section_string, ' = ', error)
stop = True
cfg.write()
if stop:
exit()
cfg["datadir"] = datadir
if not os.path.isfile(os.path.join(datadir, 'mame_exclude.txt')):
with open(os.path.join(datadir, "mame_exclude.txt"), "wt") as f:
f.write(MAME_EXCLUDE)
keys = get_pygame_keydict()
cfg['KeyUp'] = get_keys(keys, cfg['Key_Up'])
cfg['KeyDown'] = get_keys(keys, cfg['Key_Down'])
cfg['KeyPgUp'] = get_keys(keys, cfg['Key_PgUp'])
cfg['KeyPgDn'] = get_keys(keys, cfg['Key_PgDn'])
cfg['KeyHome'] = get_keys(keys, cfg['Key_Home'])
cfg['KeyEnd'] = get_keys(keys, cfg['Key_End'])
cfg['KeySelect'] = get_keys(keys, cfg['Key_Select'])
cfg['KeyGameInfo'] = get_keys(keys, cfg['Key_GameInfo'])
cfg['KeyGameHistory'] = get_keys(keys, cfg['Key_GameHistory'])
cfg['KeyPopup'] = get_keys(keys, cfg['Key_Popup'])
cfg['KeyShowArtwork'] = get_keys(keys, cfg['Key_ShowArtwork'])
def config_write():
Exclude = [
'KeyUp', 'KeyDown', 'KeyPgUp', 'KeyPgDn', 'KeySelect', 'KeyGameInfo',
'KeyGameHistory', 'KeyPopup', 'KeyHome', 'KeyEnd', 'KeyShowArtwork',
'datadir'
]
t = {}
for item in Exclude:
t[item] = cfg[item]
del cfg[item]
cfg.write()
for item in t:
cfg[item] = t[item]
def get_emu(option=None):
if option:
r = cfg[cfg['Emulator']][option]
else:
r = cfg[cfg['Emulator']]
return r
def set_emu(option, data):
cfg[cfg['Emulator']][option] = data
def get_emulators():
r = []
for k in cfg.keys():
if isinstance(cfg[k], Section):
r.append(k)
return r

143
csvfile.py Normal file
View File

@@ -0,0 +1,143 @@
import csv
import os
import zipfile
class csvROM(object):
''' One CSV ROM '''
def __init__(self):
self.name = ''
self.cloneof = None
self.description = ''
self.year = ''
self.manufacturer = ''
self.status = ''
self.category = ''
self.snap = None
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 csvROMs(object):
''' A Collection of CSV Roms '''
def __init__(self, data_dir, cfg):
self.all_roms = []
self.roms = []
self.all_categories = []
self.categories = []
self.catdict = {}
self.len = 0
self.data_dir = data_dir
self.cfg = cfg
self.emulator_dir = os.path.split(cfg['EXE'])[0]
self.parse()
self.filter()
self.snapdir = cfg['SnapDir']
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
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 self.cfg["Category"] == "All Games" or self.cfg["Category"] == 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)
self.categories = self.all_categories
def parse(self):
''' Parse csv file '''
if os.path.isfile(self.cfg['CSVFile']):
with open(self.cfg['CSVFile']) as f:
reader = csv.DictReader(f)
for game in reader:
rom = csvROM()
rom.name = game['romname']
rom.description = game[
'description'] if 'description' in game.keys(
) else game['romname']
rom.snap = game['snap'] if 'snap' in game.keys() else game[
'romname']
rom.year = game[
'year'] if 'year' in game.keys() else 'Unknown Year'
rom.category = game[
'category'] if 'category' in game.keys() else 'Unknown'
self.all_roms.append(rom)
self.all_categories.insert(0, 'All Games')
self.all_categories.append('Unknown')
return True
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 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_info(self, rom):
return
def get_history(self, rom):
return

99
exclude.py Normal file
View File

@@ -0,0 +1,99 @@
MAME_EXCLUDE = """3D Printer
Astrological Computer
Audio Sequencer
Bank-teller Terminal
Barcode Printer
Bridge Machine
Business Computer / Terminal
Calculator / Pocket Computer
Cash Counter
Chess Machine
Clock
Credit Card Terminal
DVD Player
DVD Reader/Writer
Dame Machine
Development Computer
Devices
Document Processors
Dot-Matrix Display
Drum Machine
EPROM Programmer
Educational Game
Electromechanical / Change Money
Electromechanical / Coin Pusher
Electromechanical / Misc.
Electromechanical / Pinball
Electromechanical / Redemption
Electromechanical / Reels
Electromechanical / Utilities
Electronic Board Game
Electronic Typewriter
Engine Control Unit
Gambling Board
Game Console
Game Console Expansion
Graphic Tablet
Graphics Display Controller
Handheld Child Computers
Handheld Game
Handheld Game Console
Home Computer
In Circuit Emulator
JukeBox
Kit Computer
Laptop / Notebook / Portable
Laser Printer
Matrix Printer
Microcomputer
Misc.
Misc. * Mature *
Mobile Phone
Modem
Multi-cart Board
Network Processor
Not Classified
Pinball
Pinball * Mature *
Pinball / Pachinko
Pinball / Pachinko * Mature *
Player
Pocket Device / Pad / PDA
Portable Media Player
Print Club
Printer Handbook
Programming Machine
Punched Card Computer
Quiz / Chinese
Quiz / French
Quiz / German
Quiz / Italian
Quiz / Japanese
Quiz / Japanese * Mature *
Quiz / Japanese - Music
Quiz / Korean
Quiz / Spanish
Rhythm / Dance
Rhythm / Instruments
Rhythm / Misc.
Robot Control
Satellite Receiver
Single Board Computer
Speech Synthesizer
Synthesizer
System / BIOS
System / Device
Talking Calculator
Telephone / ComputerPhone
Test ROM
Thermal Printer
Toy cars
Training Board
Utilities / Test
Utilities / Update
VTR Control
Virtual Environment
Wavetables Generator
Word-processing Machine
Workstation / Server
"""

413
gui.py Normal file
View File

@@ -0,0 +1,413 @@
import pygame
# pylint: disable=E0611
from pygame.locals import KEYDOWN
# pylint: enable=E0611
from configfile import cfg
from utils import wrap_multi_line, image_from_data
# pylint: disable=E1121,R0902,R0903,R0912,R0913
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
class baseobject(object):
def __init__(self,
surface,
x,
y,
width,
height,
data,
font,
border=False,
fg=WHITE,
bg=BLACK,
name=None,
title=None):
self.surface = surface
self.x = x
self.y = y
self.width = width
self.height = height
self.font = font
self.border = border
self.fontheight = self.font.get_height()
self.fg = fg
self.bg = bg
self.data = data
self.name = name
self.title = title
if self.width < 1:
for item in self.data:
size = self.font.size(item)[0]
if size > self.width:
self.width = size
if self.width > self.surface.get_width():
self.surface.get_width()
if self.border:
self.width += 8
if self.height < 1:
self.height = self.fontheight * (len(self.data))
if self.border:
self.height += 8
if self.height > self.surface.get_height():
if self.border:
self.height = self.surface.get_height() - 8 - self.y
else:
self.height = self.surface.get_height - self.y
if self.x == -1:
self.x = int((self.surface.get_width() / 2) - (self.width / 2))
if self.y == -1:
self.y = int((self.surface.get_height() / 2) - (self.height / 2))
if self.border:
if not self.title:
self.textx = self.x + 4
self.texty = self.y + 4
self.textwidth = self.width - 8
self.textheight = self.height - 8
else:
self.textx = self.x + 4
self.texty = self.y + 4 + self.fontheight
self.textwidth = self.width - 8
self.textheight = self.height - 8 - self.fontheight
else:
if not self.title:
self.textx = self.x
self.texty = self.y
self.textwidth = self.width
self.textheight = self.height
else:
self.textx = self.x
self.texty = self.y + self.fontheight
self.textwidth = self.width
self.textheight = self.height - self.fontheight
class menu(baseobject):
def __init__(self,
surface,
x,
y,
width,
height,
data,
font,
border=False,
fg=WHITE,
bg=BLACK,
name=None,
startitem=0,
itemattop=0):
baseobject.__init__(self, surface, x, y, width, height, data, font,
border, fg, bg, name)
self.currentitem = startitem
self.itemattop = itemattop
self.itemsperpage = int(self.textheight / self.fontheight)
self.isrom = True
if str(type(self.data[0])) == "<class 'str'>":
self.isrom = False
def processevent(self, event): # pylint: disable=R0912
if event.type == KEYDOWN:
if event.key in cfg["KeyUp"] and self.currentitem > 0:
if self.currentitem == self.itemattop:
self.itemattop -= 1
self.currentitem -= 1
if event.key in cfg["KeyDown"] and self.currentitem < len(
self.data) - 1:
self.currentitem += 1
if self.itemattop + self.itemsperpage <= self.currentitem:
self.itemattop += 1
if event.key in cfg["KeyPgUp"] and self.currentitem > 0:
self.currentitem -= self.itemsperpage - 1
while self.itemattop > self.currentitem:
self.itemattop -= 1
if self.itemattop < 0:
self.itemattop = 0
self.currentitem = 0
if event.key in cfg["KeyPgDn"] and self.currentitem < len(
self.data) - 1:
self.currentitem += self.itemsperpage - 1
while self.currentitem - self.itemattop >= self.itemsperpage:
self.itemattop += 1
if self.itemattop + self.itemsperpage > len(self.data):
self.itemattop = len(self.data) - self.itemsperpage
self.currentitem = len(self.data) - 1
if event.key in cfg["KeyHome"] and self.currentitem > 0:
self.currentitem = 0
self.itemattop = 0
if event.key in cfg["KeyEnd"] and self.currentitem < len(
self.data) - 1:
self.currentitem = len(self.data) - 1
self.itemattop = self.currentitem - self.itemsperpage + 1
def render(self):
pygame.Surface.fill(
self.surface,
self.bg,
rect=pygame.Rect(self.x, self.y, self.width, self.height))
if self.border:
pygame.draw.rect(self.surface, self.fg,
pygame.Rect(self.x + 1, self.y + 1,
self.width - 2, self.height - 2), 1)
for i in range(0, self.itemsperpage):
if i > len(self.data) - 1:
t = pygame.Surface((self.textwidth, self.fontheight))
t.fill(self.bg)
self.surface.blit(t, (self.textx,
self.texty + i * self.fontheight))
continue
if self.isrom:
item = self.data[self.itemattop + i].description
else:
item = self.data[self.itemattop + i]
if self.itemattop + i == self.currentitem:
s = self.font.render(item, True, self.bg, self.fg)
t = pygame.Surface((self.textwidth, self.fontheight))
t.fill(self.fg)
t.blit(s, (0, 0))
self.surface.blit(t, (self.textx,
self.texty + i * self.fontheight))
else:
s = self.font.render(item, True, self.fg, self.bg)
t = pygame.Surface((self.textwidth, self.fontheight))
t.fill(self.bg)
t.blit(s, (0, 0))
self.surface.blit(t, (self.textx,
self.texty + i * self.fontheight))
class notepad(baseobject):
def __init__(self,
surface,
x,
y,
width,
height,
data,
font,
border=False,
fg=WHITE,
bg=BLACK,
name="None",
title=None):
baseobject.__init__(self, surface, x, y, width, height, data, font,
border, fg, bg, name, title)
self.data = wrap_multi_line(data, self.font, self.textwidth)
self.lineattop = 0
self.linesperpage = int(self.textheight / self.fontheight)
self.lasttopline = int(len(self.data) - self.linesperpage)
def processevent(self, event):
if event.type == KEYDOWN:
if event.key in cfg["KeyDown"] and self.lineattop < self.lasttopline:
self.lineattop += 1
elif event.key in cfg["KeyUp"] and self.lineattop > 0:
self.lineattop -= 1
elif event.key in cfg["KeyPgDn"] and self.lineattop < self.lasttopline:
self.lineattop += self.linesperpage
if self.lineattop > self.lasttopline:
self.lineattop = self.lasttopline
elif event.key in cfg["KeyPgUp"] and self.lineattop > 0:
self.lineattop -= self.linesperpage
if self.lineattop < 0:
self.lineattop = 0
elif event.key in cfg["KeyHome"] and self.lineattop > 0:
self.lineattop = 0
elif event.key in cfg["KeyEnd"] and self.lineattop < self.lasttopline:
self.lineattop = self.lasttopline
def render(self):
pygame.Surface.fill(
self.surface,
self.bg,
rect=pygame.Rect(self.x, self.y, self.width, self.height))
if self.border:
pygame.draw.rect(self.surface, self.fg,
pygame.Rect(self.x + 1, self.y + 1,
self.width - 2, self.height - 2), 1)
if self.title:
x = (self.textwidth / 2) - (
self.font.size('%s' % self.title)[0] / 2)
title_surface = pygame.Surface((self.textwidth,
self.fontheight))
title_surface.fill(self.fg)
title_surface.blit(
self.font.render('%s' % self.title, True, BLACK, self.fg),
(x, 0))
self.surface.blit(title_surface,
(self.textx, self.texty - self.fontheight))
for c in range(0, self.linesperpage):
try:
itemsurface = self.font.render(self.data[c + self.lineattop],
True, self.fg, self.bg)
self.surface.blit(itemsurface,
(self.textx,
self.texty + c * self.fontheight))
except:
pass
class image_notepad(baseobject):
def __init__(self,
surface,
x,
y,
width,
height,
data,
font,
border=False,
fg=WHITE,
bg=BLACK,
name="None",
title=None):
baseobject.__init__(self, surface, x, y, width, height, data, font,
border, fg, bg, name, title)
self.currentitem = 0
self.data = data
def processevent(self, event):
if event.type == KEYDOWN:
if event.key in cfg["KeyPgDn"]:
if self.currentitem < len(self.data) - 1:
self.currentitem += 1
elif event.key in cfg["KeyPgUp"]:
if self.currentitem > 0:
self.currentitem -= 1
def render(self):
pygame.Surface.fill(
self.surface,
self.bg,
rect=pygame.Rect(self.x, self.y, self.width, self.height))
if self.border:
pygame.draw.rect(self.surface, self.fg,
pygame.Rect(self.x + 1, self.y + 1,
self.width - 2, self.height - 2), 1)
x = (self.textwidth / 2) - (self.font.size('%s' % self.title)[0] / 2)
title_surface = pygame.Surface((self.textwidth, self.fontheight))
title_surface.fill(self.fg)
title_surface.blit(
self.font.render('%s' % self.data[self.currentitem][0], True,
BLACK, self.fg), (x, 0))
self.surface.blit(title_surface, (self.textx,
self.texty - self.fontheight))
image_surface = image_from_data(self.data[self.currentitem][1],
(self.textwidth, self.textheight))
w, h = image_surface.get_width(), image_surface.get_height()
x = (self.textwidth / 2) - (w / 2) + self.textx
y = (self.textheight / 2) - (h / 2) + self.texty
self.surface.blit(image_surface, (x, y))
class gui():
def __init__(self, surface):
self.surface = surface
self.objects = []
self.font = pygame.font.Font(
pygame.font.match_font(cfg["Font"], bold=True), cfg["FontSize"])
# self.fontheight = self.font.get_height()
self.currentobject = 0
def waitevent(self):
event = pygame.event.wait()
if self.objects:
self.objects[-1].processevent(event)
return event
def add_menu(self,
x,
y,
width,
height,
data,
border=False,
fg=WHITE,
bg=BLACK,
name="None",
startitem=0,
itemattop=0):
self.currentobject += 1
self.objects.append(
menu(self.surface, x, y, width, height, data, self.font, border,
fg, bg, name, startitem, itemattop))
def add_notepad(self,
x,
y,
width,
height,
data,
border=False,
fg=WHITE,
bg=BLACK,
name="None",
title=None):
self.currentobject += 1
self.objects.append(
notepad(self.surface, x, y, width, height, data, self.font, border,
fg, bg, name, title))
def add_image_notepad(self,
x,
y,
width,
height,
data,
border=False,
fg=WHITE,
bg=BLACK,
name="None",
title=None):
self.currentobject += 1
self.objects.append(
image_notepad(self.surface, x, y, width, height, data, self.font,
border, fg, bg, name, title))
def deletelastobject(self):
del self.objects[-1]
self.currentobject -= 1
def getmenuname(self):
return self.objects[-1].name
def deleteallmenus(self):
while self.currentobject > 1:
self.deletelastobject()
def getcurrentitem(self, objnum=None):
if objnum is None:
return self.objects[-1].currentitem
return self.objects[objnum].currentitem
def getitemattop(self):
return self.objects[-1].itemattop
# TODO: Fix me for itemattop on normal menus
def setcurrentitem(self, selected_item):
self.objects[-1].itemattop = selected_item
self.objects[-1].currentitem = selected_item
def render(self):
for t in self.objects:
t.render()
pygame.display.update()

BIN
images/arcade.icns Normal file

Binary file not shown.

BIN
images/arcade.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 361 KiB

406
mame.py Normal file
View File

@@ -0,0 +1,406 @@
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

281
mess.py Normal file
View File

@@ -0,0 +1,281 @@
import glob
import os
import pickle
import re
import subprocess
from xml.sax.saxutils import unescape
import zipfile
from lxml import etree
class MessROM(object):
''' One Mame Software List ROM '''
def __init__(self):
self.name = ''
self.cloneof = None
self.description = ''
self.year = ''
self.manufacturer = ''
self.category = ''
def __str__(self):
return self.description
# return '["%s", "%s", "%s", "%s", "%s" "%s", "%s"]' % (
# self.name, self.cloneof, self.description, self.year,
# self.manufacturer, self.status, self.category)
def __repr__(self):
return '%s("%s", "%s", "%s", "%s", "%s", "%s")' % (
self.__class__.__name__, self.name, self.cloneof, self.description,
self.year, self.manufacturer, self.category)
class MessROMs(object):
''' A Collection of MAME Software List Roms '''
def __init__(self, data_dir, cfg):
self.all_roms = []
self.roms = []
self.categories = []
self.all_categories = []
self.catdict = {}
self.len = 0
self.data_dir = data_dir
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')
infofn = os.path.join(self.emulator_dir, 'messinfo.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()
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 self.cfg["Category"] == "All Games" or self.cfg["Category"] == rom.category:
if self.cfg["ShowClones"] or rom.cloneof is None:
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)
self.categories = self.all_categories
def parse(self):
""" Parse xml """
mess_version = self.get_mess_version()
datfile = os.path.join(self.data_dir, 'mess.dat')
if self.cfg['Version'] != mess_version:
self.cfg['Version'] = mess_version
if os.path.isfile(datfile):
os.unlink(datfile)
if os.path.isfile(datfile):
with open(datfile, 'rb') as i:
temp_mess_version = pickle.load(i)
if temp_mess_version == mess_version:
self.all_categories = pickle.load(i)
self.all_roms = pickle.load(i)
return True
else:
os.unlink(datfile)
self.all_categories = []
self.catdict = {}
files = glob.glob(os.path.join(self.emulator_dir, "hash", '*.xml'))
for file in files:
with open(file, 'rt', encoding='utf-8') as f:
line = None
while True:
line = f.readline().strip()
if line.startswith('<softwarelist name'):
break
self.all_categories.append(
unescape(line[line.find('ion=') + 5:-2]))
self.catdict[self.all_categories[-1]] = file
self.catdict[file] = self.all_categories[-1]
# TODO: Fix me for emulator names
# <softwarelist name="n64" description="Nintendo 64 cartridges">
self.all_categories.sort()
# pylint: disable=no-member
for xmlfile in files:
tree = etree.parse(xmlfile)
rom = None
for child in tree.getiterator():
if child.tag == 'software':
if rom:
rom.category = self.catdict[xmlfile]
self.all_roms.append(rom)
rom = None
if 'supported' not in child.attrib or child.attrib['supported'] != 'no':
rom = MessROM()
rom.name = child.attrib['name']
else:
rom = None
if rom and 'cloneof' in child.attrib:
rom.cloneof = child.attrib['cloneof']
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 == 'publisher':
rom.manufacturer = child.text
if rom:
rom.category = self.catdict[xmlfile]
self.all_roms.append(rom)
self.all_categories.insert(0, 'All Games')
self.all_categories.append('Unknown')
with open(datfile, 'wb') as output:
pickle.dump(mess_version, output)
pickle.dump(self.all_categories, output)
pickle.dump(self.all_roms, output)
return True
def get_mess_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_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 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_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):
print(rom.name)
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)

11
mfe.py Normal file
View File

@@ -0,0 +1,11 @@
import app
if __name__ == "__main__":
# try:
# startat = int(sys.argv[1])
# except:
# startat = None
application = app.app()
application.run()

34
mfe_mac.ini Normal file
View File

@@ -0,0 +1,34 @@
ResolutionX = 1024
ResolutionY = 768
FullScreen = False
Font = microsoftsansserif
FontSize = 13
QuitKeyPresses = 1
ScanLines = True
Key_Up = K_UP,
Key_Down = K_DOWN,
Key_PgUp = K_LEFT, K_PAGEUP
Key_PgDn = K_RIGHT, K_PAGEDOWN
Key_Home = K_HOME,
Key_End = K_END,
Key_Select = K_RETURN, K_1
Key_GameInfo = K_5,
Key_GameHistory = K_6,
Key_Popup = K_2,
AlwaysChangeSnap = True
HideMature = True
Emulator = M.A.M.E.
Key_ShowArtwork = ,
[M.A.M.E.]
EXE = /Users/rich/git/mfe/mame/mame64
Version = v0.190
ShowClones = False
Category = All Games
GameAtTop = 0
CurrentGame = 6
EmulatorType = MAME
StatusFilter = good,
Sort = Name Asc
SnapDir = None
CSVFile = None
ArtworkDirs = ,

4
readme.txt Normal file
View File

@@ -0,0 +1,4 @@
MFE
Mac Build
python3 setup.py bdist_mac --iconfile=images/arcade.icns

6
requirements.txt Normal file
View File

@@ -0,0 +1,6 @@
pygame>=1.9.3
configobj>=5.0.6
appdirs>=1.4.3
lxml>=3.8.0
titlecase>=0.11.0
cx_Freeze>=5.0.2

23
setup.py Normal file
View File

@@ -0,0 +1,23 @@
import os.path
import sys
from cx_Freeze import setup, Executable
build_exe_options = {
"packages": ["os", "pygame", "lxml"],
"excludes": ["tkinter", "numpy"],
"include_files": ["arcade.png"]
}
base = None
if sys.platform == "win32":
base = "Win32GUI"
setup(
name="mfe",
version="0.1",
description="MAME FrontEnd",
options={"build_exe": build_exe_options},
executables=[
Executable(
"mfe.py", base=base, icon=os.path.join('images', 'arcade.ico'))
])

135
utils.py Normal file
View File

@@ -0,0 +1,135 @@
from itertools import chain
import io
import os
import subprocess
import pygame
import pygame.locals
def get_pygame_keydict():
result = {}
pygame_keys = [
item for item in dir(pygame.locals) if item.startswith('K_')
]
for key in pygame_keys:
result[key] = getattr(pygame.locals, key)
return result
def get_keys(keys_dict, a):
result = []
for key in a:
result.append(keys_dict[key])
return result
def aspect_scale(img, bx, by):
""" Scales 'img' to fit into box bx/by.
This method will retain the original image's aspect ratio """
ix, iy = img.get_size()
if ix > iy:
# fit to width
scale_factor = bx / float(ix)
sy = scale_factor * iy
if sy > by:
scale_factor = by / float(iy)
sx = scale_factor * ix
sy = by
else:
sx = bx
else:
# fit to height
scale_factor = by / float(iy)
sx = scale_factor * ix
if sx > bx:
scale_factor = bx / float(ix)
sx = bx
sy = scale_factor * iy
else:
sy = by
return pygame.transform.scale(img, (int(sx), int(sy)))
def image_from_data(data, image_size):
if data:
with io.BytesIO(data) as f:
surface = pygame.image.load(f)
surface = aspect_scale(surface, image_size[0], image_size[1])
else:
surface = pygame.Surface(image_size) # pylint: disable=E1121
return surface
def addscanlines(surface):
width = surface.get_width()
for y in range(surface.get_height()):
if y % 2:
pygame.draw.line(surface, (0, 0, 0), (0, y), (width, y))
return surface
def truncline(text, font, maxwidth):
real = len(text)
stext = text
l = font.size(text)[0]
cut = 0
a = 0
done = 1
while l > maxwidth:
a = a + 1
n = text.rsplit(None, a)[0]
if stext == n:
cut += 1
stext = n[:-cut]
else:
stext = n
l = font.size(stext)[0]
real = len(stext)
done = 0
return real, done, stext
def wrapline(text, font, maxwidth):
done = 0
wrapped = []
while not done:
nl, done, stext = truncline(text, font, maxwidth)
wrapped.append(stext.strip())
text = text[nl:]
return wrapped
def wrap_multi_line(text, font, maxwidth):
""" returns text taking new lines into account.
"""
if type(text) is str:
lines = chain(*(wrapline(line, font, maxwidth)
for line in text.splitlines()))
else:
lines = chain(*(wrapline(line, font, maxwidth) for line in text))
return list(lines)
def run_emulator(emu_type, exe, rom):
old_path = os.getcwd()
os.chdir(os.path.split(exe)[0])
if emu_type == 'MAME':
subprocess.run([exe, rom])
elif emu_type == 'MESS':
subprocess.run([exe, rom])
elif emu_type == 'CSV':
subprocess.run([exe.replace('<romname>', rom)])
os.chdir(old_path)
return True