diff -Nru fs-uae-arcade-2.9.6/arcade/gamecentersettings.py fs-uae-arcade-2.9.7/arcade/gamecentersettings.py --- fs-uae-arcade-2.9.6/arcade/gamecentersettings.py 2017-05-25 10:07:54.000000000 +0000 +++ fs-uae-arcade-2.9.7/arcade/gamecentersettings.py 2017-10-16 21:40:40.000000000 +0000 @@ -1,4 +1,25 @@ +import sys + +from fsbc import settings +from fsgs.option import Option + + +class ArcadeSettings: + def __init__(self): + # FIXME: Add fs game context as parameter and check settings through + # that instead. + pass + + def search(self): + if "--disable-search" in sys.argv: + return False + return settings.get(Option.ARCADE_SEARCH) != "0" + + def shutdown_command(self): + return settings.get(Option.ARCADE_SHUTDOWN) + + class GameCenterSettings: @classmethod def get_shutdown_command(cls): - pass + return ArcadeSettings().shutdown_command() diff -Nru fs-uae-arcade-2.9.6/arcade/glui/inputmenu.py fs-uae-arcade-2.9.7/arcade/glui/inputmenu.py --- fs-uae-arcade-2.9.6/arcade/glui/inputmenu.py 2017-05-25 10:07:54.000000000 +0000 +++ fs-uae-arcade-2.9.7/arcade/glui/inputmenu.py 2017-10-16 21:40:40.000000000 +0000 @@ -10,7 +10,7 @@ from arcade.glui.topmenu import GameCenterItem from fsgs.drivers.gamedriver import GameDriver from fsgs.input.inputdevice import InputDevice -from fsgs.input.manager import DeviceManager +from fsgs.input.devicemanager import DeviceManager from .launchmenu import LaunchMenu diff -Nru fs-uae-arcade-2.9.6/arcade/glui/input.py fs-uae-arcade-2.9.7/arcade/glui/input.py --- fs-uae-arcade-2.9.6/arcade/glui/input.py 2017-05-25 10:07:54.000000000 +0000 +++ fs-uae-arcade-2.9.7/arcade/glui/input.py 2017-10-16 21:40:40.000000000 +0000 @@ -6,7 +6,7 @@ from fsgs.input.eventlistener import EventListener from fsgs.input.inputdevice import InputDevice from fsgs.input.keyboard import Keyboard -from fsgs.input.manager import DeviceManager +from fsgs.input.devicemanager import DeviceManager REPEAT_THRESHOLD = 0.300 REPEAT_INTERVAL = 0.075 @@ -336,7 +336,7 @@ @classmethod def get_device_manager(cls): - return cls.event_listener.manager + return cls.event_listener.device_manager @classmethod def reinit_joysticks(cls): diff -Nru fs-uae-arcade-2.9.6/arcade/glui/notification.py fs-uae-arcade-2.9.7/arcade/glui/notification.py --- fs-uae-arcade-2.9.6/arcade/glui/notification.py 2017-05-25 10:07:54.000000000 +0000 +++ fs-uae-arcade-2.9.7/arcade/glui/notification.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,73 +0,0 @@ -from arcade.glui.opengl import gl, fs_emu_blending, fs_emu_texturing -from arcade.glui.render import Render -from arcade.notification import Notification - - -# noinspection PyAttributeOutsideInit -class NotificationRender(object): - font = None - y = 0 - - @classmethod - def init(cls): - pass - # import pygame.font - # font_path = resources.resource_filename( - # "LiberationSans-Bold.ttf") - # cls.font = pygame.font.Font( - # font_path, int(0.022 * Render.get().display_height)) - # #Notification("New device connected:\nLogitech Gamepad F310", - # duration=60) - # #Notification("Test 2", duration=10) - # #Notification("Test 3") - - @classmethod - def update(cls): - pass - # if Notification.new: - # Notification.new = False - # Render.get().dirty = True - # # post wake-up event - # pygame.event.post(pygame.event.Event(pygame.NUMEVENTS - 1)) - - @classmethod - def render(cls): - notifications = Notification.all() - if len(notifications) == 0: - return - Render.get().hd_perspective() - cls.y = 0 - for i, notification in enumerate(notifications): - cls.render_one(notification) - Render.get().dirty = True - # showing max two notifications - if i == 1: - break - - @classmethod - def render_one(cls, notification): - notification.show() - w = 500 - h = 100 - x = 1920 - w - y = cls.y - z = 0.9 - fs_emu_texturing(False) - fs_emu_blending(True) - gl.glBegin(gl.GL_QUADS) - gl.glColor(1.0, 1.0, 0.5, 0.8) - gl.glVertex3f(x, y, z) - gl.glVertex3f(x + w, y, z) - gl.glVertex3f(x + w, y + h, z) - gl.glVertex3f(x, y + h, z) - gl.glEnd() - - lines = notification.text.split("\n") - y += h - 23 - for line in lines: - tw, th = Render.get().measure_text(line, cls.font) - y -= th - Render.get().text(line, cls.font, x + 23, y, - color=(0.2, 0.2, 0.0, 1.0)) - y -= 2 - cls.y += h + 15 diff -Nru fs-uae-arcade-2.9.6/arcade/glui/notificationrender.py fs-uae-arcade-2.9.7/arcade/glui/notificationrender.py --- fs-uae-arcade-2.9.6/arcade/glui/notificationrender.py 1970-01-01 00:00:00.000000000 +0000 +++ fs-uae-arcade-2.9.7/arcade/glui/notificationrender.py 2017-10-16 21:40:40.000000000 +0000 @@ -0,0 +1,83 @@ +from arcade.glui.opengl import gl, fs_emu_blending, fs_emu_texturing +from arcade.glui.render import Render +from arcade.notification import Notification + + +START_X = 20 +START_Y = 1080 - 60 - 20 + + +# noinspection PyAttributeOutsideInit +class NotificationRender(object): + font = None + y = START_Y + + @classmethod + def init(cls): + pass + # import pygame.font + # font_path = resources.resource_filename( + # "LiberationSans-Bold.ttf") + # cls.font = pygame.font.Font( + # font_path, int(0.022 * Render.get().display_height)) + # #Notification("New device connected:\nLogitech Gamepad F310", + # duration=60) + # #Notification("Test 2", duration=10) + # #Notification("Test 3") + + @classmethod + def update(cls): + pass + # if Notification.new: + # Notification.new = False + # Render.get().dirty = True + # # post wake-up event + # pygame.event.post(pygame.event.Event(pygame.NUMEVENTS - 1)) + + @classmethod + def render(cls): + notifications = Notification.all() + if len(notifications) == 0: + return + Render.get().hd_perspective() + cls.y = START_Y + for i, notification in enumerate(notifications): + cls.render_one(notification) + Render.get().dirty = True + # showing max four notifications + if i > 4: + break + + @classmethod + def render_one(cls, notification): + notification.show() + w = 500 + h = 100 + x = START_X + y = cls.y + z = 0.9 + + y -= h + + gl.glDisable(gl.GL_DEPTH_TEST) + fs_emu_texturing(False) + fs_emu_blending(True) + gl.glBegin(gl.GL_QUADS) + gl.glColor(1.0, 1.0, 0.5, 0.8) + gl.glVertex3f(x, y, z) + gl.glVertex3f(x + w, y, z) + gl.glVertex3f(x + w, y + h, z) + gl.glVertex3f(x, y + h, z) + gl.glEnd() + + lines = notification.text.split("\n") + y += h - 23 + for line in lines: + tw, th = Render.get().measure_text(line, cls.font) + y -= th + Render.get().text(line, cls.font, x + 23, y, + color=(0.2, 0.2, 0.0, 1.0)) + y -= 2 + # cls.y += h + 15 + cls.y -= h + 20 + gl.glEnable(gl.GL_DEPTH_TEST) diff -Nru fs-uae-arcade-2.9.6/arcade/glui/texture.py fs-uae-arcade-2.9.7/arcade/glui/texture.py --- fs-uae-arcade-2.9.6/arcade/glui/texture.py 2017-05-25 10:07:54.000000000 +0000 +++ fs-uae-arcade-2.9.7/arcade/glui/texture.py 2017-10-16 21:40:40.000000000 +0000 @@ -60,8 +60,10 @@ item_background = None top_item_background = None logo_32 = None + stretch = None aspect = None + square_pixels = None def __init__(self, name, target=gl.GL_TEXTURE_2D, **kwargs): # print(repr(type(name))) diff -Nru fs-uae-arcade-2.9.6/arcade/glui/topmenu.py fs-uae-arcade-2.9.7/arcade/glui/topmenu.py --- fs-uae-arcade-2.9.6/arcade/glui/topmenu.py 2017-05-25 10:07:54.000000000 +0000 +++ fs-uae-arcade-2.9.7/arcade/glui/topmenu.py 2017-10-16 21:40:40.000000000 +0000 @@ -113,24 +113,46 @@ class AspectItem(TopMenuItem): def __init__(self): super().__init__() - self.normal_texture = Texture.stretch - self.selected_texture = Texture.stretch + # self.normal_texture = Texture.stretch + # self.selected_texture = Texture.stretch + self.normal_texture = Texture.aspect + self.selected_texture = Texture.aspect self.update_texture() + # def update_texture(self): + # # TODO: Ideally, this class should listen for settings changes. + # if Settings.instance()["keep_aspect"] in ["0", ""]: + # texture = Texture.stretch + # else: + # texture = Texture.aspect + # self.normal_texture = texture + # self.selected_texture = texture + # + # def activate(self, menu): + # if Settings.instance()["keep_aspect"] == "1": + # Settings.instance()["keep_aspect"] = "" + # else: + # Settings.instance()["keep_aspect"] = "1" + # self.update_texture() + def update_texture(self): # TODO: Ideally, this class should listen for settings changes. - if Settings.instance()["keep_aspect"] in ["0", ""]: + if Settings.instance()["stretch"] == "1": texture = Texture.stretch + elif Settings.instance()["stretch"] == "0": + texture = Texture.square_pixels else: texture = Texture.aspect self.normal_texture = texture self.selected_texture = texture def activate(self, menu): - if Settings.instance()["keep_aspect"] == "1": - Settings.instance()["keep_aspect"] = "" + if Settings.instance()["stretch"] == "1": + Settings.instance()["stretch"] = "0" + elif Settings.instance()["stretch"] == "0": + Settings.instance()["stretch"] = "" else: - Settings.instance()["keep_aspect"] = "1" + Settings.instance()["stretch"] = "1" self.update_texture() diff -Nru fs-uae-arcade-2.9.6/arcade/glui/window.py fs-uae-arcade-2.9.7/arcade/glui/window.py --- fs-uae-arcade-2.9.6/arcade/glui/window.py 2017-05-25 10:07:54.000000000 +0000 +++ fs-uae-arcade-2.9.7/arcade/glui/window.py 2017-10-16 21:40:40.000000000 +0000 @@ -4,6 +4,7 @@ import time from collections import deque +from arcade.gamecentersettings import ArcadeSettings from fsbc import settings from fsgs.FSGSDirectories import FSGSDirectories @@ -28,7 +29,7 @@ from arcade.glui.animation import AnimateValueBezier from arcade.glui.state import State from arcade.glui.render import Render -from arcade.glui.notification import NotificationRender +from arcade.glui.notificationrender import NotificationRender from arcade.glui.font import Font, BitmapFont from arcade.glui.items import MenuItem, AllMenuItem, NoItem, PlatformItem from arcade.glui.items import ListItem @@ -44,10 +45,14 @@ ALWAYS_RENDER = True # IDLE = Config.get_bool("Menu/Idle", 1) IDLE = 1 +if "--no-idle" in sys.argv: + IDLE = 0 # Render.get().display_fps = pyapp.user.ini.get_bool("Menu/ShowFPS", False) # LIGHTING = Config.get_bool("Menu/Lighting", False) LIGHTING = False RENDER_DEBUG_SQUARES = 0 +if "--render-debug-squares" in sys.argv: + RENDER_DEBUG_SQUARES = 1 SEARCH_CHARS = \ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789 -:.," CONFIG_SEPARATION = 0.15 @@ -456,6 +461,10 @@ # vera_font_path, int(0.06 * Render.get().display_height)) # Font.header_font.set_size(int(0.06 * Render.get().display_height)) + if NotificationRender.font is None: + NotificationRender.font = Font( + liberation_sans_bold_path, int(0.020 * Render.get().display_height)) + char_buffer = "" char_buffer_last_updated = 0 @@ -498,30 +507,32 @@ print("returning false") return False elif char == "BACKSPACE" or char == u"\b": - char_buffer = char_buffer[:-1] - if len(char_buffer) <= 2: - char_buffer = "" - handle_search_menu_on_character_press(char_buffer) + if ArcadeSettings().search(): + char_buffer = char_buffer[:-1] + if len(char_buffer) <= 2: + char_buffer = "" + handle_search_menu_on_character_press(char_buffer) return True elif len(char) == 1: - char_buffer += char - handle_search_menu_on_character_press(char_buffer) - if 1 <= len(char_buffer) <= 2: - jump_to_item = -1 - for i, item in enumerate(current_menu.items): - # FIXME: a bit hack-y this, should check sort_name - # instead (need to store this in items then) - # also, should use binary search and not sequential search... - searches = [char_buffer, "the " + char_buffer, - "a " + char_buffer] - check = item.name.lower() - for s in searches: - if check.startswith(s): - jump_to_item = i - break - if jump_to_item >= 0: - current_menu.set_selected_index(jump_to_item, immediate=True) - + if ArcadeSettings().search(): + char_buffer += char + handle_search_menu_on_character_press(char_buffer) + if 1 <= len(char_buffer) <= 2: + jump_to_item = -1 + for i, item in enumerate(current_menu.items): + # FIXME: a bit hack-y this, should check sort_name + # instead (need to store this in items then) + # also, should use binary search and not sequential search + searches = [char_buffer, "the " + char_buffer, + "a " + char_buffer] + check = item.name.lower() + for s in searches: + if check.startswith(s): + jump_to_item = i + break + if jump_to_item >= 0: + current_menu.set_selected_index( + jump_to_item, immediate=True) return True else: raise Exception(repr(char)) @@ -1262,8 +1273,10 @@ Texture.item_background = Texture("item_background.png") Texture.top_item_background = Texture("top_item_background.png") Texture.logo_32 = Texture("logo-32.png") + Texture.stretch = Texture("stretch.png") - Texture.aspect = Texture("aspect.png") + Texture.aspect = Texture("stretch-aspect.png") + Texture.square_pixels = Texture("stretch-none.png") # # FIXME: TEMPORARY - FOR TESTING, ONLY # path = "c:\\git\\fs-game-database\\Backdrops\\ffx.png" Binary files /tmp/tmpYweXHu/uxFP7mr62z/fs-uae-arcade-2.9.6/arcade/res/themes/blue/aspect.png and /tmp/tmpYweXHu/yyd8WCnHx7/fs-uae-arcade-2.9.7/arcade/res/themes/blue/aspect.png differ Binary files /tmp/tmpYweXHu/uxFP7mr62z/fs-uae-arcade-2.9.6/arcade/res/themes/blue/shutdown.png and /tmp/tmpYweXHu/yyd8WCnHx7/fs-uae-arcade-2.9.7/arcade/res/themes/blue/shutdown.png differ Binary files /tmp/tmpYweXHu/uxFP7mr62z/fs-uae-arcade-2.9.6/arcade/res/themes/blue/shutdown_selected.png and /tmp/tmpYweXHu/yyd8WCnHx7/fs-uae-arcade-2.9.7/arcade/res/themes/blue/shutdown_selected.png differ Binary files /tmp/tmpYweXHu/uxFP7mr62z/fs-uae-arcade-2.9.6/arcade/res/themes/blue/stretch-aspect.png and /tmp/tmpYweXHu/yyd8WCnHx7/fs-uae-arcade-2.9.7/arcade/res/themes/blue/stretch-aspect.png differ Binary files /tmp/tmpYweXHu/uxFP7mr62z/fs-uae-arcade-2.9.6/arcade/res/themes/blue/stretch-none.png and /tmp/tmpYweXHu/yyd8WCnHx7/fs-uae-arcade-2.9.7/arcade/res/themes/blue/stretch-none.png differ diff -Nru fs-uae-arcade-2.9.6/arcade/ui/arcade_window.py fs-uae-arcade-2.9.7/arcade/ui/arcade_window.py --- fs-uae-arcade-2.9.6/arcade/ui/arcade_window.py 2017-05-25 10:07:54.000000000 +0000 +++ fs-uae-arcade-2.9.7/arcade/ui/arcade_window.py 2017-10-16 21:40:40.000000000 +0000 @@ -15,8 +15,12 @@ CURSOR_SHOW_DURATION = 5.0 -def check_argument(name): +def check_argument(name, options=None): name = name.replace("_", "-") + if options is not None: + for option in options: + if "--" + name + "=" + option in sys.argv: + return option if "--" + name in sys.argv: return "1" if "--" + name + "=1" in sys.argv: @@ -29,12 +33,12 @@ def fullscreen(): - # If we have explicitly used --window or --maximized as arguments, do + # If we have explicitly used --window as arguments, do # not enable fullscreen regardless of settings. - if check_argument("window") == "1": - return False - if check_argument("maximize") == "1": + if check_argument("window", ["maximize"]) in ["1", "maximize"]: return False + # if check_argument("maximize") == "1": + # return False value = check_argument("fullscreen") if not value: value = Settings.instance().get("arcade_fullscreen") @@ -42,12 +46,17 @@ def maximized(): - if check_argument("window") == "1": - return False - value = check_argument("maximize") - if not value: + # if check_argument("fullscreen") == "1": + # return False + # if check_argument("window") == "1": + # return False + # value = check_argument("window") == "maximize" + value = check_argument("window", ["maximize"]) == "maximize" + if value: + return True + else: value = Settings.instance().get("arcade_maximized") - return value != "0" + return value == "1" def monitor(): diff -Nru fs-uae-arcade-2.9.6/debian/changelog fs-uae-arcade-2.9.7/debian/changelog --- fs-uae-arcade-2.9.6/debian/changelog 2017-06-12 07:21:14.000000000 +0000 +++ fs-uae-arcade-2.9.7/debian/changelog 2017-12-17 20:43:19.000000000 +0000 @@ -1,3 +1,9 @@ +fs-uae-arcade (1:2.9.7-1~retrogames) xenial; urgency=low + + * New dev release + + -- Aleksey Samoilov Mon, 18 Dec 2017 00:43:19 +0400 + fs-uae-arcade (1:2.9.6-1~retrogames) xenial; urgency=low * Build for Retrogames diff -Nru fs-uae-arcade-2.9.6/dist/macosx/Info.plist fs-uae-arcade-2.9.7/dist/macosx/Info.plist --- fs-uae-arcade-2.9.6/dist/macosx/Info.plist 2017-05-25 10:07:53.000000000 +0000 +++ fs-uae-arcade-2.9.7/dist/macosx/Info.plist 2017-10-16 21:40:40.000000000 +0000 @@ -19,11 +19,11 @@ CFBundlePackageType APPL CFBundleShortVersionString - 2.9.6 + 2.9.7 CFBundleSignature ???? CFBundleVersion - 2.9.6 + 2.9.7 LSHasLocalizedDisplayName LSMinimumSystemVersion diff -Nru fs-uae-arcade-2.9.6/fsbc/debug.py fs-uae-arcade-2.9.7/fsbc/debug.py --- fs-uae-arcade-2.9.6/fsbc/debug.py 2017-05-25 10:07:54.000000000 +0000 +++ fs-uae-arcade-2.9.7/fsbc/debug.py 2017-10-16 21:40:41.000000000 +0000 @@ -24,7 +24,7 @@ _let_thread_exceptions_be_unhandled() return False - print("enabling except hook") + # print("enabling except hook") sys.excepthook = _handle_exception _enable_thread_exception_handler() return True @@ -45,7 +45,7 @@ def _enable_thread_exception_handler(): - print("enable tread exception handler") + # print("enable tread exception handler") def run(): self = threading.current_thread() diff -Nru fs-uae-arcade-2.9.6/fsbc/init.py fs-uae-arcade-2.9.7/fsbc/init.py --- fs-uae-arcade-2.9.6/fsbc/init.py 2017-05-25 10:07:54.000000000 +0000 +++ fs-uae-arcade-2.9.7/fsbc/init.py 2017-10-16 21:40:41.000000000 +0000 @@ -25,11 +25,11 @@ if name is not None: Application.app_name = name - print(name) + # print(name) if version is not None: Application.app_version = version - print(version) + # print(version) if enable_exception_handler: fsbc.debug.enable_exception_handler() @@ -42,5 +42,5 @@ # fsbc.unicode.patch_all() unicode_patched = True - print(platform.uname()) - print(sys.argv) + # print(platform.uname()) + # print(sys.argv) diff -Nru fs-uae-arcade-2.9.6/fsbc/logging.py fs-uae-arcade-2.9.7/fsbc/logging.py --- fs-uae-arcade-2.9.6/fsbc/logging.py 2017-05-25 10:07:54.000000000 +0000 +++ fs-uae-arcade-2.9.7/fsbc/logging.py 2017-10-16 21:40:41.000000000 +0000 @@ -81,10 +81,11 @@ from fsgs.FSGSDirectories import FSGSDirectories logs_dir = FSGSDirectories.get_logs_dir() log_file = os.path.join(logs_dir, log_name) + print("[LOGGING] Logging to", log_file) try: f = open(log_file, "wb") except Exception: - print("could not open log file") + print("[LOGGING] Could not open log file") # use MultiplexedOutput here too, for the mutex handling sys.stdout = MultiplexedOutput(sys.stdout) sys.stderr = MultiplexedOutput(sys.stderr) diff -Nru fs-uae-arcade-2.9.6/fsbc/settings.py fs-uae-arcade-2.9.7/fsbc/settings.py --- fs-uae-arcade-2.9.6/fsbc/settings.py 2017-05-25 10:07:54.000000000 +0000 +++ fs-uae-arcade-2.9.7/fsbc/settings.py 2017-10-16 21:40:41.000000000 +0000 @@ -20,7 +20,6 @@ return cls._instance def __init__(self, app=None, path: str = None) -> None: - print("[SETTINGS] Constructor", self) self.app = app self.path = path self.values = {} # type: Dict[str, str] @@ -28,6 +27,7 @@ self._loaded = False self._loading = False self._atexit_registered = False + self.verbose = True def set_path(self, path): self.path = path @@ -47,7 +47,8 @@ self.log_key_value(key, value, extra="(unchanged)") return if not self._loading and not self._atexit_registered: - print("[SETTINGS] Register atexit save path =", self.path) + if self.verbose: + print("[SETTINGS] Register atexit save path =", self.path) atexit.register(self.save) self._atexit_registered = True self.log_key_value(key, value) @@ -65,14 +66,15 @@ if (not self._loaded) or force: try: self._loading = True - self._provider.load(self) + self._provider.load(self, verbose=self.verbose) finally: self._loading = False self._loaded = True # print("[SETTINGS] Loaded, path is", self.path) def save(self, extra: Dict[str, str] = None) -> None: - print("[SETTINGS] Save", self) + if self.verbose: + print("[SETTINGS] Save", self) self._provider.save(self, extra) def log_key_value(self, key, value, extra=""): @@ -81,7 +83,8 @@ value = "*CENSORED*" if extra: extra = " " + extra - print("[SETTINGS] Set {} = {}{}".format(key, value, extra)) + if self.verbose: + print("[SETTINGS] Set {} = {}{}".format(key, value, extra)) class SettingsProvider: @@ -89,26 +92,31 @@ this and replace the provider on the Settings instance if you want customized loading/saving.""" - def load(self, settings): + def load(self, settings, verbose=True): cp = ConfigParser(interpolation=None) cp.optionxform = str path = settings.path if settings.app and not path: path = settings.app.get_settings_path() if not path: - print("[SETTINGS] No settings path specified") + if verbose: + print("[SETTINGS] No settings path specified") path = os.path.join(fsboot.base_dir(), "Data", "Settings.ini") - print("[SETTINGS] Using default", path) + if verbose: + print("[SETTINGS] Using default", path) if os.path.exists(path): - print("[SETTINGS] Loading from", path) + if verbose: + print("[SETTINGS] Loading from", path) else: - print("[SETTINGS] File", path, "does not exist") + if verbose: + print("[SETTINGS] File", path, "does not exist") # Write current settings path back to Settings instance settings.path = path try: cp.read([path], encoding="UTF-8") except Exception as e: - print("[SETTINGS] Error loading", repr(e)) + if verbose: + print("[SETTINGS] Error loading", repr(e)) return try: keys = cp.options("settings") @@ -118,7 +126,8 @@ values = {} for key in sorted(keys): if key.startswith("__"): - print("[SETTINGS] Ignoring", key) + if verbose: + print("[SETTINGS] Ignoring", key) continue value = cp.get("settings", key) values[key] = value diff -Nru fs-uae-arcade-2.9.6/fsbc/system.py fs-uae-arcade-2.9.7/fsbc/system.py --- fs-uae-arcade-2.9.6/fsbc/system.py 2017-05-25 10:07:54.000000000 +0000 +++ fs-uae-arcade-2.9.7/fsbc/system.py 2017-10-16 21:40:41.000000000 +0000 @@ -1,4 +1,5 @@ import sys +import platform as _platform windows = sys.platform == "win32" linux = sys.platform.startswith("linux") @@ -19,3 +20,11 @@ linux = linux macos = macosx platform = platform + + x86_64 = False + if _platform.machine().lower() in ["x86_64", "x86-64", "amd64", + "i386", "i486", "i586", "i686"]: + if _platform.architecture()[0] == "64bit": + x86_64 = True + # else: + # x86 = True diff -Nru fs-uae-arcade-2.9.6/fsgs/amiga/fsuaeamigadriver.py fs-uae-arcade-2.9.7/fsgs/amiga/fsuaeamigadriver.py --- fs-uae-arcade-2.9.6/fsgs/amiga/fsuaeamigadriver.py 2017-05-25 10:07:53.000000000 +0000 +++ fs-uae-arcade-2.9.7/fsgs/amiga/fsuaeamigadriver.py 2017-10-16 21:40:40.000000000 +0000 @@ -1,50 +1,62 @@ import os from fsbc.settings import Settings +from fsgs import Option from fsgs.amiga.fsuae import FSUAE from fsgs.amiga.launchhandler import LaunchHandler from fsgs.drivers.gamedriver import GameDriver +AMIGA_JOYSTICK = { + "type": "joystick", + "description": "Joystick", + "mapping_name": "amiga", +} +AMIGA_MOUSE = { + "type": "mouse", + "description": "Mouse", + "mapping_name": "amiga_mouse", # FIXME ... +} +CD32_CONTROLLER = { + "type": "cd32 gamepad", + "description": "CD32 Gamepad", + "mapping_name": "cd32", +} +# FIXME: NO_DEVICE +AMIGA_PORTS = [ + { + "description": "Joystick Port", + "types": [AMIGA_JOYSTICK, AMIGA_MOUSE, CD32_CONTROLLER], + "type_option": "joystick_port_1_mode", + "device_option": "joystick_port_1", + }, { + "description": "Mouse Port", + "types": [AMIGA_JOYSTICK, AMIGA_MOUSE, CD32_CONTROLLER], + "type_option": "joystick_port_0_mode", + "device_option": "joystick_port_0", + }, + # FIXME: ADDITIONAL PORTS... +] + class FSUAEAmigaDriver(GameDriver): - JOYSTICK = { - "type": "joystick", - "description": "Joystick", - "mapping_name": "amiga", - } - - MOUSE = { - "type": "mouse", - "description": "Mouse", - "mapping_name": "amiga_mouse", # FIXME ... - } - - PORTS = [ - { - "description": "Joystick Port", - "types": [JOYSTICK] - }, { - "description": "Mouse Port", - "types": [MOUSE, JOYSTICK] - }, - ] + PORTS = AMIGA_PORTS - def __init__(self, fsgs): - super().__init__(fsgs) + def __init__(self, fsgc): + super().__init__(fsgc) self.temp_config_file = None self.launch_handler = None def prepare(self): - print("AmigaRunner.prepare") + print("FSUAEAmigaDriver.prepare") - # self.temp_dir = self.fsgs.temp_dir("amiga") + # self.temp_dir = self.fsgc.temp_dir("amiga") # self.change_handler = GameChangeHandler(self.temp_dir) # self.firmware_dir = self.prepare_firmware("Amiga Firmware") - # config = self.fsgs.config.copy() + # config = self.fsgc.config.copy() - model = self.config["amiga_model"] + model = self.options[Option.AMIGA_MODEL] if model.startswith("CD32"): platform = "CD32" elif model == "CDTV": @@ -52,23 +64,26 @@ else: platform = "Amiga" # name = Settings.get("config_name") - # name = self.fsgs.game.name + # name = self.fsgc.game.name # uuid = Config.get("x_game_uuid") # uuid = None - from fsgs.SaveStateHandler import SaveStateHandler - save_state_handler = SaveStateHandler( - self.fsgs, self.get_name(), platform) + from fsgs.saves import SaveHandler + save_state_handler = SaveHandler(self.fsgc, self.get_name(), platform) - self.config["joystick_port_0"] = self.ports[1].device_id or "" - self.config["joystick_port_1"] = self.ports[0].device_id or "" + print("[INPUT] joystick_port_1", self.options["joystick_port_1"], + "->", self.ports[0].device_id or "none") + print("[INPUT] joystick_port_0", self.options["joystick_port_0"], + "->", self.ports[1].device_id or "none") + self.options["joystick_port_1"] = self.ports[0].device_id or "none" + self.options["joystick_port_0"] = self.ports[1].device_id or "none" self.launch_handler = LaunchHandler( - self.fsgs, self.get_name(), self.config, save_state_handler, + self.fsgc, self.get_name(), self.options, save_state_handler, temp_dir=self.cwd.path) - # self.change_handler.init(self.fsgs.get_game_state_dir(), + # self.change_handler.init(self.fsgc.get_game_state_dir(), # ignore=["*.uss", "*.sdf"]) # self.launch_handler.config["joystick_port_0"] = \ @@ -98,7 +113,7 @@ # def configure(self): # print("AmigaGameHandler.configure") # - # #temp_dir = self.fsgs.temp.dir("amiga-config") + # #temp_dir = self.fsgc.temp.dir("amiga-config") # #config_file = os.path.join(temp_dir, "amiga-config.fs-uae") # #with open(config_file, "wb") as f: # # #self._configure_emulator(f) @@ -121,22 +136,27 @@ # f.write("fsaa = {0}\n".format(str(self.get_option("fsaa")))) def run(self): - print("AmigaGameHandler.run, cwd =", self.cwd.path) + print("FSUAEAmigaDriver.run, cwd =", self.cwd.path) config = self.launch_handler.create_config() self.emulator.process, self.temp_config_file = FSUAE.start_with_config( config, cwd=self.cwd.path) def finish(self): - print("removing", self.temp_config_file) + print("FSUAEAmigaDriver.finish: Removing", self.temp_config_file) if self.temp_config_file is not None: try: os.remove(self.temp_config_file) except Exception: - print("could not remove config file", self.temp_config_file) + print("Could not remove config file", self.temp_config_file) self.launch_handler.update_changes() self.launch_handler.cleanup() - # self.change_handler.update(self.fsgs.get_game_state_dir()) + # self.change_handler.update(self.fsgc.get_game_state_dir()) + + def get_game_refresh_rate(self): + # FIXME: Now assuming that all games are PAL / 50 Hz + # - make configurable? + return 50.0 def get_supported_filters(self): supported = [] @@ -169,53 +189,47 @@ # ] return supported - def set_input_options(self, f): - # FIXME - - input_mapping = [{ - "1": "JOY2_FIRE_BUTTON", - "2": "JOY2_2ND_BUTTON", - "3": "JOY2_3RD_BUTTON", - "left": "JOY2_LEFT", - "right": "JOY2_RIGHT", - "up": "JOY2_UP", - "down": "JOY2_DOWN", - }, { - "1": "JOY1_FIRE_BUTTON", - "2": "JOY1_2ND_BUTTON", - "3": "JOY1_3RD_BUTTON", - "left": "JOY1_LEFT", - "right": "JOY1_RIGHT", - "up": "JOY1_UP", - "down": "JOY1_DOWN", - }] - - f.write("\n[input]\n") - - for i, input in enumerate(self.inputs): - if not input.device: - continue - if i == 0: - key = "joystick_port_1" - else: - key = "joystick_port_0" - if input.type == "amiga_mouse": - value = "mouse" - else: - value = input.device.id - print("configure device:", key, value) - f.write("{0} = {1}\n".format(key, value)) - - def find_kickstart(self, bios_patterns): - print("find_kickstart", bios_patterns) - for name in os.listdir(self.firmware_dir): - for bios in bios_patterns: - if bios in name: - return os.path.join(self.firmware_dir, name) - raise Exception("Could not find Amiga bios/kickstart " + - repr(bios_patterns)) - - def get_game_refresh_rate(self): - # FIXME: Now assuming that all games are PAL / 50 Hz - # - make configurable? - return 50.0 + # def set_input_options(self, f): + # # FIXME + # input_mapping = [{ + # "1": "JOY2_FIRE_BUTTON", + # "2": "JOY2_2ND_BUTTON", + # "3": "JOY2_3RD_BUTTON", + # "left": "JOY2_LEFT", + # "right": "JOY2_RIGHT", + # "up": "JOY2_UP", + # "down": "JOY2_DOWN", + # }, { + # "1": "JOY1_FIRE_BUTTON", + # "2": "JOY1_2ND_BUTTON", + # "3": "JOY1_3RD_BUTTON", + # "left": "JOY1_LEFT", + # "right": "JOY1_RIGHT", + # "up": "JOY1_UP", + # "down": "JOY1_DOWN", + # }] + # + # f.write("\n[input]\n") + # + # for i, port in enumerate(self.ports): + # if not port.device: + # continue + # if i == 0: + # key = "joystick_port_1" + # else: + # key = "joystick_port_0" + # if port.type == "amiga_mouse": + # value = "mouse" + # else: + # value = port.device.id + # print("[INPUT] Configure device:", key, value) + # f.write("{0} = {1}\n".format(key, value)) + + # def find_kickstart(self, bios_patterns): + # print("find_kickstart", bios_patterns) + # for name in os.listdir(self.firmware_dir): + # for bios in bios_patterns: + # if bios in name: + # return os.path.join(self.firmware_dir, name) + # raise Exception("Could not find Amiga bios/kickstart " + + # repr(bios_patterns)) diff -Nru fs-uae-arcade-2.9.6/fsgs/amiga/valueconfigloader.py fs-uae-arcade-2.9.7/fsgs/amiga/valueconfigloader.py --- fs-uae-arcade-2.9.6/fsgs/amiga/valueconfigloader.py 2017-05-25 10:07:53.000000000 +0000 +++ fs-uae-arcade-2.9.7/fsgs/amiga/valueconfigloader.py 2017-10-16 21:40:40.000000000 +0000 @@ -4,7 +4,7 @@ import json from fsbc.paths import Paths from fsgs.amiga.amiga import Amiga -from fsgs.FileDatabase import FileDatabase +from fsgs.filedatabase import FileDatabase from fsgs.network import openretro_url_prefix from fsgs.option import Option from fsgs.amiga import whdload @@ -164,6 +164,9 @@ def set_name_and_uuid(self): # self.config["x_config_uuid"] = self.root.get("uuid", "") + self.config["game_uuid"] = self.values.get("game_uuid", "") + self.config["variant_uuid"] = self.values.get("variant_uuid", "") + game_name = self.values.get("game_name", "") platform_name = self.values.get("platform", "") variant_name = self.values.get("variant_name", "") diff -Nru fs-uae-arcade-2.9.6/fsgs/Archive.py fs-uae-arcade-2.9.7/fsgs/Archive.py --- fs-uae-arcade-2.9.6/fsgs/Archive.py 2017-05-25 10:07:53.000000000 +0000 +++ fs-uae-arcade-2.9.7/fsgs/Archive.py 2017-10-16 21:40:40.000000000 +0000 @@ -1,6 +1,9 @@ import os import io import traceback + +from io import BytesIO + from fsbc.zipfile import ZipFile archive_extensions = [".zip", ".rp9"] @@ -9,7 +12,7 @@ from lhafile import LhaFile except ImportError: traceback.print_exc() - print("LhaFile module import problem") + print("[ARCHIVE] LhaFile module import problem") LhaFile = None else: archive_extensions.append(".lha") @@ -18,7 +21,7 @@ from fsbc.seven_zip_file import SevenZipFile except ImportError: traceback.print_exc() - print("SevenZipFile module import problem") + print("[ARCHIVE] SevenZipFile module import problem") SevenZipFile = None else: archive_extensions.append(".7z") @@ -166,7 +169,55 @@ return [] def open(self, path): - return open(path, "rb") + return filter_open(path) + + +def filter_open(path, stream=None): + if stream is None: + stream = open(path.rsplit("#?", 1)[0], "rb") + if "#?" in path: + if path.endswith("#?Filter=Skip(16)"): + return SkipFilter(stream, 16) + elif path.endswith("#?Filter=ByteSwapWords"): + return ByteSwapWordsFilter(stream) + else: + raise Exception( + "Unrecognized file filter: " + path.split("#?")[-1]) + return stream + + +class SkipFilter: + def __init__(self, stream, count): + print("[ARCHIVE] Skip({}) filter for".format(count), stream) + self.stream = stream + self.count = count + self.strip_left = count + + def read(self, n=-1): + if self.strip_left: + data = self.stream.read(self.strip_left) + if len(data) == 0: + # No data returned, end of stream + return b"" + self.strip_left -= len(data) + return self.stream.read(n) + + +class ByteSwapWordsFilter: + def __init__(self, stream): + print("[ARCHIVE] ByteSwapWords filter for", stream) + self.stream = stream + + def read(self, n=-1): + data = self.stream.read(n) + # FIXME: Support odd length reads + assert len(data) % 2 == 0 + io = BytesIO() + for i in range(0, len(data), 2): + io.write(data[i + 1:i + 2]) + io.write(data[i:i + 1]) + io.seek(0) + return io.getvalue() class Archive(object): @@ -184,7 +235,7 @@ return os.path.dirname(path) def split_path(self, path): - print("split_path", path) + print("[ARCHIVE] Split path", path) if "#/" in path: parts = path.rsplit("#/", 1) archive = parts[0] @@ -207,7 +258,7 @@ def get_handler(self): if self._handler is not None: return self._handler - print("get_handler", self.path) + print("[ARCHIVE] get_handler", self.path) name, ext = os.path.splitext(self.path) ext = ext.lower() if ext == ".7z" and SevenZipFile is not None: @@ -218,18 +269,18 @@ self._handler = ZipHandler(self.path) except Exception as e: if ext == ".zip": - print(repr(e)) + print("[ARCHIVE]", repr(e)) try: self._handler = LhaHandler(self.path) except Exception as e: if ext == ".lha": - print(repr(e)) + print("[ARCHIVE]", repr(e)) self._handler = NullHandler(self.path) return self._handler def list_files(self): result = [] - print(self.get_handler()) + print("[ARCHIVE]", self.get_handler()) for item in self.get_handler().list_files(self.sub_path): # result.append(os.path.join(self.path, item)) result.append(self.path + "#/" + item) @@ -240,18 +291,23 @@ # print(path, self.path) assert path == self.path if not sub_path: - # print("os.path.exists", path) + if "#?" in path: + path = path.rsplit("#?", 1)[0] return os.path.exists(path) + if "#?" in sub_path: + sub_path = sub_path.rsplit("#?", 1)[0] return self.get_handler().exists(sub_path) def open(self, path): - # print("open", repr(path)) + print("[Archive] Open", repr(path)) path, sub_path = self.split_path(path) # print(path, self.path) assert path == self.path if not sub_path: - return open(path, "rb") - return self.get_handler().open(sub_path) + return filter_open(path) + if "#?" in sub_path: + sub_path = sub_path.rsplit("#?", 1)[0] + return filter_open(path, self.get_handler().open(sub_path)) def copy(self, path, dest): ifo = self.open(path) diff -Nru fs-uae-arcade-2.9.6/fsgs/Database.py fs-uae-arcade-2.9.7/fsgs/Database.py --- fs-uae-arcade-2.9.6/fsgs/Database.py 2017-05-25 10:07:53.000000000 +0000 +++ fs-uae-arcade-2.9.7/fsgs/Database.py 2017-10-16 21:40:40.000000000 +0000 @@ -1,13 +1,14 @@ import os import re from fsbc.application import app +from fsbc.settings import Settings from fsgs.BaseDatabase import BaseDatabase from fsgs.FSGSDirectories import FSGSDirectories import threading thread_local = threading.local() -VERSION = 38 -RESET_VERSION = 38 +VERSION = 40 +RESET_VERSION = 39 QUOTED_TERMS_RE = re.compile("[\"].*?[\"]") @@ -224,7 +225,7 @@ def find_game_variants_new(self, game_uuid="", have=3): include_unpublished = False - if app.settings["database_show_unpublished"] == "1": + if Settings.instance()["database_show_unpublished"] == "1": include_unpublished = True cursor = self.internal_cursor() print("FIXME: not looking up ratings yet!") @@ -619,11 +620,13 @@ def get_last_file_event_stamps(self): cursor = self.internal_cursor() cursor.execute( - "SELECT last_file_insert, last_file_delete FROM metadata") + "SELECT last_file_insert, last_file_delete, " + "database_locker FROM metadata") row = cursor.fetchone() result = { "last_file_insert": row[0], - "last_file_delete": row[0], + "last_file_delete": row[1], + "database_locker": row[2], } return result @@ -638,13 +641,17 @@ cursor.execute( "UPDATE metadata set last_file_delete = ?", (stamps["last_file_delete"],)) + if stamps["database_locker"] != last_stamps["database_locker"]: + cursor.execute( + "UPDATE metadata set database_locker = ?", + (stamps["database_locker"],)) def get_game_lists(self): cursor = self.internal_cursor() cursor.execute("SELECT uuid, name FROM game_list") return cursor.fetchall() - def update_database_to_version_38(self): + def update_database_to_version_39(self): cursor = self.internal_cursor() cursor.execute("""CREATE TABLE game ( id INTEGER PRIMARY KEY, @@ -722,3 +729,8 @@ )""") cursor.execute("""CREATE INDEX game_list_game_list_uuid ON game_list_game(list_uuid)""") + + def update_database_to_version_40(self): + cursor = self.internal_cursor() + cursor.execute( + "ALTER TABLE metadata ADD COLUMN database_locker INTEGER") diff -Nru fs-uae-arcade-2.9.6/fsgs/drivers/dolphindriver.py fs-uae-arcade-2.9.7/fsgs/drivers/dolphindriver.py --- fs-uae-arcade-2.9.6/fsgs/drivers/dolphindriver.py 1970-01-01 00:00:00.000000000 +0000 +++ fs-uae-arcade-2.9.7/fsgs/drivers/dolphindriver.py 2017-10-16 21:40:40.000000000 +0000 @@ -0,0 +1,185 @@ +import os + +from fsgs.drivers.gamedriver import GameDriver, Emulator +from fsgs.input.mapper import InputMapper +from fsbc.system import System + + +class DolphinDriver(GameDriver): + def __init__(self, fsgs): + super().__init__(fsgs) + self.emulator = Emulator("dolphin-emu") + self.emulator.allow_system_emulator = True + + def prepare(self): + # configure dolphin.ini + temp_dir = self.temp_dir("dolphin") + dolphin_config_file = os.path.join( + temp_dir.path, "user", "Config", "Dolphin.ini") + if not os.path.exists(os.path.dirname(dolphin_config_file)): + os.makedirs(os.path.dirname(dolphin_config_file)) + + # dest = os.path.join(temp_dir, "User", "Wii", "title") + # dest = os.path.join(temp_dir, "user") + # if not os.path.exists(dest): + # os.makedirs(dest) + # plugin = pyapp.plugins.get_plugin("no.fengestad.emulator.dolphin") + # GameCenterUtil.copy_folder_tree(os.path.join(plugin.get_bin_dir(), + # "user"), dest) + # self.changes = ChangeHandler(dest) + # self.changes.init(self.context.game.state_dir) + + with open(dolphin_config_file, "w", encoding="UTF-8") as f: + self.configure(f) + + # media options + rom_path = self.get_game_file() + self.emulator.args.extend(["--exec=" + rom_path]) + + def finish(self): + pass + + def configure(self, f): + temp_dir = self.temp_dir("dolphin") + + f.write("[Interface]\n") + f.write("HideCursor = True\n") + f.write("[Display]\n") + f.write("RenderToMain = True\n") + f.write("FullscreenResolution = {w}x{h}\n".format( + w=self.screen_size()[0], h=self.screen_size()[1])) + if self.use_fullscreen(): + f.write("Fullscreen = True\n") + else: + f.write("Fullscreen = False\n") + + f.write("[Core]\n") + if System.windows: + f.write("GFXPlugin = Plugin_VideoDX9.dll\n") + + # Force Interpreter (Cached) core. + f.write("CPUCore = 0\n") + + self.dolphin_configure_core(f) + self.dolphin_configure_input() + + # graphics options + if self.use_vsync(): + vsync = True + else: + vsync = False + if System.windows: + graphics_config_file = os.path.join( + temp_dir, "user", "Config", "gfx_dx9.ini") + with open(graphics_config_file, "w") as f: + f.write("[Hardware]\n") + if vsync: + f.write("VSync = True\n") + else: + f.write("VSync = False\n") + f.write("[Settings]\n") + f.write("DisableFog = True\n") + f.write("[Hacks]\n") + f.write("EFBToTextureEnable = True\n") + else: + graphics_config_file = os.path.join( + temp_dir.path, "user", "Config", "gfx_opengl.ini") + with open(graphics_config_file, "w") as f: + f.write("[Hardware]\n") + if vsync: + f.write("VSync = True\n") + else: + f.write("VSync = False\n") + f.write("[Settings]\n") + + # def run(self): + # plugin = pyapp.plug.get_plugin("no.fengestad.emulator.dolphin") + # if fs.windows: + # temp_dir = self.context.temp.dir("dolphin") + # GameCenterUtil.copy_folder_tree(plugin.get_bin_dir(), temp_dir) + # args = self.args[:] + # args.insert(0, os.path.join(temp_dir, "Dolphin.exe")) + # return subprocess.Popen(args, env=self.env, cwd=temp_dir, + # close_fds=True) + # else: + # temp_dir = self.context.temp.dir("dolphin") + # GameCenterUtil.copy_folder_tree(plugin.get_bin_dir(), temp_dir) + # args = self.args[:] + # args.insert(0, os.path.join(temp_dir, "dolphin-emu")) + # return subprocess.Popen(args, env=self.env, cwd=temp_dir, + # close_fds=True) + + # def cleanup(self): + # # remove some dirs and recopy config files, etc to + # # clean up the list of changed files + # temp_dir = self.temp_dir("dolphin") + # paths = [ + # os.path.join(temp_dir, "user", "Config"), + # os.path.join(temp_dir, "user", "GC"), + # os.path.join(temp_dir, "user", "Logs"), + # ] + # for p in paths: + # if os.path.exists(p): + # shutil.rmtree(p) + # plugin = pyapp.plug.get_plugin("no.fengestad.emulator.dolphin") + # GameCenterUtil.copy_folder_tree(os.path.join(plugin.get_bin_dir(), + # "user"), + # os.path.join(temp_dir, "user")) + # # now take backup of the real changes + # self.changes.update(self.context.game.state_dir) + + def dolphin_configure_core(self, f): + pass + + def dolphin_configure_input(self): + pass + + +class DolphinInputMapper(InputMapper): + def axis(self, axis, positive): + dir_str = "+" if positive else "-" + return "Axis " + str(axis) + dir_str + + def hat(self, hat, direction): + dir_str = { + "left": "W", + "right": "E", + "up": "N", + "down": "S", + }[direction] + return "Hat " + str(hat) + " " + dir_str + + def button(self, button): + return "Button " + str(button) + + # Mouse values + # elif value.startswith("M/"): + # if value == "M/UP": return "Cursor Y-" + # if value == "M/DOWN": return "Cursor Y+" + # if value == "M/LEFT": return "Cursor X-" + # if value == "M/RIGHT": return "Cursor X+" + # if value == "M/00": return "Click 0" + # if value == "M/01": return "Click 1" + # if value == "M/02": return "Click 2" + # raise Exception("unknown mouse value " + value) + # else: + # #if fs.windows: + # # return key_mapping[value] + # #else: + # # return value + # return "" + + def key(self, key): + if System.windows: + return key.dinput_name[4:] + else: + return key.sdl_name[5:] + + def mouse(self, button, axis, positive): + if button: + return "Click " + str(button - 1) + else: + if axis == 0: + return "Cursor X+" if positive else "Cursor X-" + if axis == 1: + return "Cursor Y+" if positive else "Cursor Y-" diff -Nru fs-uae-arcade-2.9.6/fsgs/drivers/gamedriver.py fs-uae-arcade-2.9.7/fsgs/drivers/gamedriver.py --- fs-uae-arcade-2.9.6/fsgs/drivers/gamedriver.py 2017-05-25 10:07:53.000000000 +0000 +++ fs-uae-arcade-2.9.7/fsgs/drivers/gamedriver.py 2017-10-16 21:40:40.000000000 +0000 @@ -13,8 +13,8 @@ from fsbc.task import current_task from fsgs.FSGSDirectories import FSGSDirectories from fsgs.amiga.fsuae import FSUAE -from fsgs.option import Option -from fsgs.plugins.plugin_manager import PluginManager +from launcher.option import Option +from fsgs.plugins.plugin_manager import PluginManager, Executable from fsgs.refreshratetool import RefreshRateTool from fsgs.util.gamenameutil import GameNameUtil @@ -26,52 +26,31 @@ def __init__(self, fsgc): self.fsgc = fsgc self.files = GameFiles(fsgc) - self.emulator = GameEmulator() - self._allow_gsync = True - self._model_name = "" + self.emulator = Emulator("no-emulator") self.options = defaultdict(str) - for key, value in Settings.instance().values.items(): - # FIXME: re-enable this check? - # if key in Config.config_keys: - # print("... ignoring config key from settings:", key) - # continue - self.options[key] = value - for key, value in self.fsgc.config.items(): - self.options[key] = value + self.init_options() self.ports = [] - for port_info in self.PORTS: - port = Port(port_info["description"]) - port.types = port_info["types"] - self.ports.append(port) + self.init_ports() + self._allow_gsync = True + self._model_name = "" + self._emulator_skin_prepared = {} self.__vsync = False self.__game_temp_file = None + self.temp_root = TemporaryItem( root=None, prefix="fsgs-", suffix="tmp", directory=True) - # # Default current working directory for the emulator. self.cwd = self.temp_dir("cwd") # # Fake home directory for the emulator. self.home = self.temp_dir("home") - self.home._path = os.path.join(FSGSDirectories.get_cache_dir(), "Home") if not os.path.exists(self.home.path): os.makedirs(self.home.path) + # noinspection PyProtectedMember self.cwd._path = self.home._path - # self.cwd._path = os.path.join(self.home._path, "cwd") - # if not os.path.exists(self.cwd.path): - # os.makedirs(self.cwd.path) - - # Deprecated compatibility name - self.args = self.emulator.args - # Deprecated compatibility name - self.config = self.options - # Deprecated compatibility name - self.env = self.emulator.env - # Deprecated compatibility name - self.fsgs = self.fsgc def __del__(self): print("GameDriver.__del__") @@ -85,9 +64,11 @@ def run(self): executable = PluginManager.instance().find_executable( self.emulator.name) + if executable is None and self.emulator.allow_system_emulator: + executable_path = shutil.which(self.emulator.name) + if executable_path is not None: + executable = Executable(executable_path) if executable is None: - # if self.emulator.allow_system_emulator: - # pass raise LookupError( "Could not find emulator " + repr(self.emulator.name)) self.emulator.process = self.start_emulator(executable) @@ -98,21 +79,60 @@ def finish(self): pass - def set_env(self, name, value): - warnings.warn("set_env is deprecated", DeprecationWarning) - self.env[name] = value + def init_options(self): + for key, value in Settings.instance().values.items(): + # FIXME: re-enable this check? + # if key in Config.config_keys: + # print("... ignoring config key from settings:", key) + # continue + self.options[key] = value + for key, value in self.fsgc.config.items(): + self.options[key] = value - def add_arg(self, *args): - warnings.warn("add_arg is deprecated", DeprecationWarning) - self.args.extend(args) + def init_ports(self): + for i, port_info in enumerate(self.PORTS): + self.init_port(i, port_info) + + def init_port(self, index, port_info): + port = Port(port_info["description"]) + # FIXME: MOVE TO CONSTRUCTOR + port.number = index + 1 + port.types = port_info["types"] + port.type_option = port_info.get("type_option", "") + port.device_option = port_info.get("device_option", "") + # Set correct (port type index) based on type option + # FIXME: MOVE CODE TO port CLASS + if port.type_option: + type_value = self.options[port.type_option] + if type_value: + print("[INPUT] Port option", port.type_option, + "was set to", type_value) + else: + print("[INPUT] No value specified for", port.type_option) - # def set_emulator(self, emulator): - # self._emulator = emulator + # Hack / Workaround for Amiga + # FIXME: Move to driver subclasses? + # if not type_value: + # if self.options[Option.AMIGA_MODEL].lower() == "cd32": + # type_value = "cd32 pad" - # self.inputs.append(self.create_input( - # name='Controller {0}'.format(i + 1), - # type='megadrive', - # description='Gamepad')) + if not type_value: + # FIXME: RETRIEVE DEFAULT VALUE FOR TYPE OPTION + try: + type_value = Option.get(port.type_option)["default"] + except Exception: + print("[INPUT] Could not find default for", + port.type_option) + for j, port_type in enumerate(port.types): + print("[INPUT]", j, port_type["type"]) + if type_value == port_type["type"]: + print("[INPUT] Set index", j, "for type", type_value) + port.index = j + break + else: + print("[INPUT] WARNING: Could not find index for type", + type_value) + self.ports.append(port) def set_allow_gsync(self, allow): self._allow_gsync = False @@ -129,7 +149,16 @@ return False return True + def fullscreen_window_mode(self): + fullscreen_mode = self.options[Option.FULLSCREEN_MODE] + return fullscreen_mode == "window" + + def g_sync(self): + return self.options[Option.G_SYNC] == "1" + def use_vsync(self): + if self.g_sync(): + return False # if "--no-vsync" in sys.argv: # return False # return True @@ -198,7 +227,8 @@ CRT_EFFECT = "crt" HQ2X_EFFECT = "hq2x" SCALE2X_EFFECT = "scale2x" - DEFAULT_EFFECT = NO_EFFECT + DOUBLE_EFFECT = "2x" + DEFAULT_EFFECT = DOUBLE_EFFECT def effect(self): if self.options[Option.EFFECT] == self.NO_EFFECT: @@ -211,6 +241,9 @@ return self.SCALE2X_EFFECT return self.DEFAULT_EFFECT + def bezel(self): + return self.options[Option.BEZEL] != "0" + # FIXME: REMOVE def use_smoothing(self): return True @@ -253,7 +286,7 @@ def screenshots_name(self): # FIXME: REMOVE? - return GameNameUtil.create_fs_name(self.get_name()) + return GameNameUtil.create_fs_name(self.game_name()) # def get_game_name(self): # return self.options["game_name"] @@ -315,8 +348,6 @@ refresh_rate_tool = RefreshRateTool() screens = refresh_rate_tool.screens_xrandr() rect = cls.screen_rect_for_monitor(monitor) - # from pprint import pprint - # pprint(screens) for screen in screens: print("Screen {} refresh rate (Xrandr) = {}".format( screen, screens[screen]["refresh_rate"])) @@ -441,14 +472,6 @@ f.write(data) return self.__game_temp_file.path - def create_temp_dir(self, suffix): - warnings.warn("create_temp_dir is deprecated", DeprecationWarning) - return self.temp_dir(suffix) - - def create_temp_file(self, suffix): - warnings.warn("create_temp_file is deprecated", DeprecationWarning) - return self.temp_file(suffix) - def temp_dir(self, name): return TemporaryNamedItem( root=self.temp_root, name=name, directory=True) @@ -557,6 +580,10 @@ if not self.emulator.allow_home_access: env["HOME"] = self.home.path + if self.options[Option.G_SYNC] == "0": + print("[VIDEO] Disabling G-Sync (Might only work on Linux)") + env["__GL_GSYNC_ALLOWED"] = "0" + if not self._allow_gsync: # DOSBox-FS and Fuse-FS does not work nicely with G-SYNC yet. # Enabling G-SYNC causes stuttering. @@ -615,15 +642,47 @@ # print("window position", env["SDL_VIDEO_WINDOW_POS"]) # os.environ["SDL_VIDEO_WINDOW_POS"] = "{0},{1}".format(x, y) - def prepare_emulator_skin(self, env): - path = self.temp_file("left.png").path - with open(path, "wb") as f: + def emulator_skin_paths(self): + left = self.temp_file("left.png").path + left_overlay = self.temp_file("left-overlay.png").path + right = self.temp_file("right.png").path + right_overlay = self.temp_file("right-overlay.png").path + return { + "left": left, + "left-overlay": left_overlay, + "right": right, + "right-overlay": right_overlay, + } + + def prepare_emulator_skin(self, env=None, paths=None): + if self._emulator_skin_prepared: + return self._emulator_skin_prepared + if paths is None: + paths = self.emulator_skin_paths() + with open(paths["left"], "wb") as f: f.write(Resources("fsgs").stream("res/emu/left.png").read()) - env["FSGS_SKIN_LEFT"] = path - path = self.temp_file("right.png").path - with open(path, "wb") as f: + if env is not None: + env["FSGS_BEZEL_LEFT"] = paths["left"] + env["FSGS_SKIN_LEFT"] = paths["left"] + if "left-overlay" in paths: + with open(paths["left-overlay"], "wb") as f: + f.write(Resources("fsgs").stream( + "res/emu/left-overlay.png").read()) + if env is not None: + env["FSGS_BEZEL_LEFT_OVERLAY"] = paths["left-overlay"] + with open(paths["right"], "wb") as f: f.write(Resources("fsgs").stream("res/emu/right.png").read()) - env["FSGS_SKIN_RIGHT"] = path + if env is not None: + env["FSGS_BEZEL_RIGHT"] = paths["right"] + env["FSGS_SKIN_RIGHT"] = paths["right"] + if "right-overlay" in paths: + with open(paths["right-overlay"], "wb") as f: + f.write(Resources("fsgs").stream( + "res/emu/right-overlay.png").read()) + if env is not None: + env["FSGS_BEZEL_RIGHT_OVERLAY"] = paths["right-overlay"] + self._emulator_skin_prepared = paths + return self._emulator_skin_prepared def start_emulator( self, emulator, args=None, env_vars=None, executable=None, @@ -652,7 +711,8 @@ env = os.environ.copy() FSUAE.add_environment_from_settings(env) self.update_environment(env) - self.prepare_emulator_skin(env) + if self.bezel(): + self.prepare_emulator_skin(env) if env_vars: env.update(env_vars) print("") @@ -816,13 +876,64 @@ def abort(self): print("GameRunner.abort - WARNING: not implemented") + def cheats_file(self, name): + try: + plugin = PluginManager.instance().plugin("Cheats") + except LookupError: + return None + path = plugin.data_file_path(name) + if os.path.exists(path): + return path + return None + + @property + def args(self): + warnings.warn("deprecated", DeprecationWarning) + return self.emulator.args + + @property + def config(self): + warnings.warn("deprecated", DeprecationWarning) + return self.options + + @property + def env(self): + warnings.warn("deprecated", DeprecationWarning) + return self.emulator.env + + @property + def fsgs(self): + warnings.warn("deprecated", DeprecationWarning) + return self.fsgc + + def set_env(self, name, value): + warnings.warn("deprecated", DeprecationWarning) + self.emulator.env[name] = value + + def add_arg(self, *args): + warnings.warn("deprecated", DeprecationWarning) + self.emulator.args.extend(args) + + def create_temp_dir(self, suffix): + warnings.warn("create_temp_dir is deprecated", DeprecationWarning) + return self.temp_dir(suffix) + + def create_temp_file(self, suffix): + warnings.warn("create_temp_file is deprecated", DeprecationWarning) + return self.temp_file(suffix) + class Port(object): def __init__(self, name): + self.number = 0 self.name = name self.types = [] self.index = 0 self.device = None + # Name of config option for device type + self.type_option = "" + # Name of config option for device + self.device_option = "" # FIXME: remove self.device_id = None @@ -905,13 +1016,13 @@ return self._path -class GameEmulator: - def __init__(self): - self.name = "no-emulator" +class Emulator: + def __init__(self, name): + self.name = name self.args = [] self.env = {} self.process = None - # self.allow_system_emulator = False + self.allow_system_emulator = False self.allow_home_access = False diff -Nru fs-uae-arcade-2.9.6/fsgs/drivers/mamedriver.py fs-uae-arcade-2.9.7/fsgs/drivers/mamedriver.py --- fs-uae-arcade-2.9.6/fsgs/drivers/mamedriver.py 2017-05-25 10:07:53.000000000 +0000 +++ fs-uae-arcade-2.9.7/fsgs/drivers/mamedriver.py 2017-10-16 21:40:40.000000000 +0000 @@ -1,9 +1,17 @@ +import filecmp import os +import shutil + from fsbc.system import windows from fsbc.task import current_task -from fsgs.drivers.gamedriver import GameDriver +from fsgs.option import Option +from fsgs.FSGSDirectories import FSGSDirectories +from fsgs.drivers.gamedriver import GameDriver, Emulator from fsgs.input.mapper import InputMapper +from fsgs.knownfiles import KnownFilePath +from fsgs.plugins.plugin_manager import PluginManager + """ FIXME: Check screenshot saving @@ -13,15 +21,31 @@ action? And then also map a joystick button back? """ + +class MameCheatFile(KnownFilePath): + @property + def path(self): + return os.path.join( + FSGSDirectories.get_data_dir(), "MAME", "Cheats", "cheat.7z") + + +class MameArtworkDirectory(KnownFilePath): + # FIXME: KnownDirectory + @property + def path(self): + return os.path.join( + FSGSDirectories.get_data_dir(), "MAME", "Artwork") + + +mame_cheat_file = MameCheatFile() +mame_artwork_dir = MameArtworkDirectory() + + # noinspection PyAttributeOutsideInit class MameDriver(GameDriver): def __init__(self, fsgs): super().__init__(fsgs) - # self.emulator.name = "multiemu-fs" - self.emulator.name = "arcade-fs" - # To prevent slow startup on Linux due to scanning all system - # fonts. - self.emulator.allow_home_access = True + self.emulator = Emulator("mame-fs") self.mame_init() def mame_emulator_name(self): @@ -40,10 +64,96 @@ if windows: self.input_device_order = "DINPUT8" + def install_mame_hash_file(self, name): + # FIXME: Better to find data file based on path/provides rather than + # hardcoding plugin name, but... + plugin = PluginManager.instance().plugin("MAME-FS") + src = plugin.data_file_path("hash/" + name) + hash_dir = os.path.join(self.cwd.path, "hash") + if not os.path.exists(hash_dir): + os.makedirs(hash_dir) + dst = os.path.join(hash_dir, name) + # FIXME: Check if file is the same (no need to copy) + if os.path.exists(dst) and filecmp.cmp(src, dst): + print("[A5200] Hash file", name, "already in place") + return + print("[A5200] Installing hash/" + name) + shutil.copy2(src, dst) + def prepare(self): self.configure() self.mame_prepare() + def create_mame_layout(self): + paths = self.emulator_skin_paths() + artwork = os.path.join(self.cwd.path, "artwork", self.romset) + if not os.path.exists(artwork): + os.makedirs(artwork) + + if self.stretching() == self.STRETCH_FILL_SCREEN or not self.bezel(): + shutil.rmtree(os.path.join(self.cwd.path, "artwork")) + return + + # FIXME: filecmp? + self.prepare_emulator_skin(paths={ + "left": os.path.join(artwork, "left.png"), + "right": os.path.join(artwork, "right.png"), + }) + # shutil.move(paths["left"], ) + # shutil.move(paths["right"], ) + + # FIXME: With no bezel, we should still use a black bezel to + # hide screen stretching + + # FIXME: On taller displays than 16:9, the bezel can cause the + # graphics to shrink (fit-in). Is it possible to display the + # bezel + + if self.options["orientation"] == "vertical": + # noinspection SpellCheckingInspection + layout = ( + """ + + + + + + + + + + + + + + + + + """) + else: + # noinspection SpellCheckingInspection + layout = ( + """ + + + + + + + + + + + + + + + + + """) + with open(os.path.join(artwork, "default.lay"), "w") as f: + f.write(layout) + def mame_init(self): # override in subclasses pass @@ -63,7 +173,7 @@ \n""".format(self.romset)] - self.add_arg("-skip_gameinfo") + self.emulator.args.extend(["-skip_gameinfo"]) # state_dir = self.get_state_dir() state_dir = self.emulator_state_dir(self.mame_emulator_name()) state_dir = state_dir + os.sep @@ -76,7 +186,8 @@ system_rom_path = os.path.join(rom_path, self.romset) os.makedirs(system_rom_path) - for sha1, name in self.romset_files: + # for sha1, name in self.romset_files: + for name, sha1 in self.romset_files: file_uri = self.fsgs.file.find_by_sha1(sha1) current_task.set_progress("Preparing ROM {name}".format(name=name)) input_stream = self.fsgs.file.open(file_uri) @@ -109,15 +220,17 @@ game_cfg_file = os.path.join( self.cfg_dir.path, "{romset}.cfg".format(romset=self.romset)) - self.add_arg("-cfg_directory", self.cfg_dir.path) - self.add_arg("-nvram_directory", state_dir) + self.emulator.args.extend(["-cfg_directory", self.cfg_dir.path]) + self.emulator.args.extend(["-nvram_directory", state_dir]) # self.add_arg("-memcard_directory", state_dir) # self.add_arg("-hiscore_directory", state_dir) - self.add_arg("-state_directory", state_dir) - self.add_arg("-diff_directory", state_dir) + self.emulator.args.extend(["-state_directory", state_dir]) + self.emulator.args.extend(["-diff_directory", state_dir]) - self.add_arg("-snapshot_directory", self.screenshots_dir()) - self.add_arg("-snapname", "{0}-%i".format(self.screenshots_name())) + self.emulator.args.extend( + ["-snapshot_directory", self.screenshots_dir()]) + self.emulator.args.extend( + ["-snapname", "{0}-%i".format(self.screenshots_name())]) # self.change_handler = GameChangeHandler(self.cfg_dir.path) # self.change_handler.init( @@ -154,8 +267,20 @@ else: self.add_arg("-nofilter") + cheats_file_path = mame_cheat_file.path + if not os.path.exists(cheats_file_path): + # Data/MAME/Cheat/cheat.7z not found, trying plugin instead. + cheats_file_path = self.cheats_file("MAME/cheat.7z") + if cheats_file_path: + print("[MAME] Using cheats file:".format(cheats_file_path)) + self.emulator.args.extend( + ["-cheatpath", cheats_file_path[:-3]]) # Stripping .7z + self.emulator.args.extend(["-cheat"]) + else: + print("[MAME] No cheats file found") + + self.emulator.args.append(self.romset) self.mame_configure() - self.args.append(self.romset) def mame_configure(self): # override in subclasses @@ -181,7 +306,7 @@ for i, port in enumerate(self.ports): input_mapping = self.mame_input_mapping(i) - mapper = MAMEInputMapper(port, input_mapping) + mapper = MameInputMapper(port, input_mapping) for key, value in mapper.items(): print("---->", key, value) if isinstance(key, tuple): @@ -257,6 +382,7 @@ def mame_shortcuts(self): shortcuts = { + "TOGGLE_KEEP_ASPECT": ["KEYCODE_LALT KEYCODE_A"], "UI_CANCEL": ["KEYCODE_LALT KEYCODE_Q"], "UI_PAUSE": ["KEYCODE_LALT KEYCODE_P"], "UI_CONFIGURE": ["KEYCODE_F12"], @@ -278,19 +404,31 @@ if self.use_stretching(): self.emulator.args.extend(["-resolution", "960x540"]) - if self.use_stretching(): + if self.stretching() == self.STRETCH_FILL_SCREEN: self.emulator.args.extend(["-nokeepaspect"]) else: self.emulator.args.extend(["-keepaspect"]) + # FIXME: Square pixels and no stretch... + self.game_xml.append(' \n') # effect = 'none' @@ -328,21 +466,20 @@ video_override = "mame_video_options" if windows and False: video_args.append('-triplebuffer') - # print("mame_video_options_vsync", self.config[video_override]) # raise Exception("gnit") - - if self.config[video_override]: - for arg in self.config[video_override].split(" "): + # FIXME: REMOVE video_override options? + if self.options[video_override]: + for arg in self.options[video_override].split(" "): arg = arg.strip() if arg: - self.add_arg(arg) + self.emulator.args.append(arg) else: - self.add_arg(*video_args) + self.emulator.args.extend(video_args) def mame_offset_and_scale(self): ox, oy, sx, sy = 0.0, 0.0, 1.0, 1.0 - viewport = self.config["viewport"] + viewport = self.options["viewport"] if viewport: src, dst = viewport.split("=") src = src.strip() @@ -370,7 +507,7 @@ pass -class MAMEInputMapper(InputMapper): +class MameInputMapper(InputMapper): def axis(self, axis, positive): axis_str = ["X", "Y", "Z", "RX", "RY", "RZ"][axis] if axis == 0: diff -Nru fs-uae-arcade-2.9.6/fsgs/drivers/mednafendriver.py fs-uae-arcade-2.9.7/fsgs/drivers/mednafendriver.py --- fs-uae-arcade-2.9.6/fsgs/drivers/mednafendriver.py 2017-05-25 10:07:53.000000000 +0000 +++ fs-uae-arcade-2.9.7/fsgs/drivers/mednafendriver.py 2017-10-16 21:40:40.000000000 +0000 @@ -1,27 +1,101 @@ import hashlib +import json import os import struct from functools import lru_cache -from fsbc.system import windows -from fsgs.drivers.gamedriver import GameDriver +import shutil + +from fsbc.paths import Paths +from fsbc.system import System +from fsgs.Archive import Archive +from fsgs.drivers.gamedriver import GameDriver, Emulator from fsgs.input.enumeratehelper import EnumerateHelper from fsgs.input.mapper import InputMapper from fsgs.option import Option +from fsgs.saves import SaveHandler class MednafenDriver(GameDriver): - def __init__(self, fsgs): - super().__init__(fsgs) - self.emulator.name = "mednafen-fs" + def __init__(self, fsgc): + super().__init__(fsgc) + self.emulator = Emulator("mednafen-fs") + self.save_handler = SaveHandler(self.fsgc, options=self.options) + # self._game_files_added = False def prepare(self): + if os.path.exists(os.path.join(self.home.path, ".mednafen")): + shutil.rmtree(os.path.join(self.home.path, ".mednafen")) + self.save_handler.prepare() # self.temp_home = self.create_temp_dir("mednafen-home") with open(self.mednafen_cfg_path(), "w", encoding="UTF-8") as f: self.mednafen_configure(f) + # if not self._game_files_added: + game_file = self.get_game_file() + if game_file is not None: + self.emulator.args.append(game_file) def finish(self): - pass + self.save_handler.finish() + + def set_mednafen_aspect(self, h, v): + if self.stretching() == self.NO_STRETCHING: + h, v = self.game_video_size() + self.emulator.env["FSGS_ASPECT"] = "{}/{}".format(h, v) + + # FIXME: REPLACE BAD IMPLEMENTATION OF prepare cd images + + @staticmethod + def expand_default_path(src, default_dir): + if "://" in src: + return src, None + src = Paths.expand_path(src, default_dir) + archive = Archive(src) + return src, archive + + def prepare_mednafen_cd_images(self): + # self._game_files_added = True + + temp_dir = self.temp_dir("media").path + game_file = None + # cdrom_drive_0 = self.config.get("cdrom_drive_0", "") + # if cdrom_drive_0.startswith("game:"): + if True: + # scheme, dummy, game_uuid, name = cdrom_drive_0.split("/") + # file_list = self.get_file_list_for_game_uuid(game_uuid) + file_list = json.loads(self.options[Option.FILE_LIST]) + for file_item in file_list: + src = self.fsgc.file.find_by_sha1(file_item["sha1"]) + + src, archive = self.expand_default_path(src, None) + dst_name = file_item["name"] + # current_task.set_progress(dst_name) + + dst = os.path.join(temp_dir, dst_name) + self.fsgc.file.copy_game_file(src, dst) + + # cue_sheets = self.get_cue_sheets_for_game_uuid(game_uuid) + cue_sheets = json.loads(self.options[Option.CUE_SHEETS]) + for i, cue_sheet in enumerate(cue_sheets): + # FIXME: Try to get this to work with the PyCharm type checker + path = os.path.join(temp_dir, cue_sheet["name"]) + if i == 0: + game_file = path + # noinspection PyTypeChecker + with open(path, "wb") as f: + # noinspection PyTypeChecker + f.write(cue_sheet["data"].encode("UTF-8")) + self.emulator.args.append(game_file) + + def prepare_mednafen_bios(self, known_file, name): + bios_path = os.path.join(self.home.path, ".mednafen", name) + if not os.path.exists(os.path.dirname(bios_path)): + os.makedirs(os.path.dirname(bios_path)) + src = self.fsgc.file.find_by_sha1(known_file.sha1) + if not src: + raise Exception("Could not find {} (SHA-1: {}".format( + known_file.name, known_file.sha1)) + self.fsgc.file.copy_game_file(src, bios_path) def get_supported_filters(self): supported = [ @@ -63,7 +137,7 @@ return None def mednafen_viewport(self): - viewport = self.config["viewport"] + viewport = self.options["viewport"] if viewport: src, dst = viewport.split("=") src = src.strip() @@ -79,6 +153,16 @@ self.configure_input(f) self.configure_video(f) + cheats_file_name = self.mednafen_system_prefix() + ".cht" + cheats_file_path = self.cheats_file(cheats_file_name) + if cheats_file_path: + self.emulator.args.extend(["-cheats", "0"]) + cheats_dir = self.temp_dir("cheats").path + shutil.copy( + cheats_file_path, os.path.join(cheats_dir, cheats_file_name)) + # self.emulator.args.extend(["-filesys.path_cheat", cheats_dir]) + self.emulator.args.extend(["-path_cheat", cheats_dir]) + # screen_w, screen_h = self.screen_size() # # dest_w, dest_h = screen_w, screen_h @@ -91,8 +175,8 @@ # else: # game_w, game_h = dst[2], dst[3] # - # self.args.extend(["-{}.xres".format(pfx), str(screen_w)]) - # self.args.extend(["-{}.yres".format(pfx), str(screen_h)]) + # self.emulator.args.extend(["-{}.xres".format(pfx), str(screen_w)]) + # self.emulator.args.extend(["-{}.yres".format(pfx), str(screen_h)]) # # integer_scaling = False # if self.use_fullscreen(): @@ -127,7 +211,7 @@ # # self.emulator.args.extend(["-{}.videoip".format(pfx), videoip]) - # if game_w and game_h: + # if game_w and game_h: # real_game_size = (game_w, game_h) # if self.use_stretching(): # x_scale = dest_w / game_w @@ -154,94 +238,125 @@ # else: # y_scale = x_scale # print("Fullscreen scale factors: %f %f", x_scale, y_scale) - # self.args.extend(["-%s.xscalefs" % pfx, str(x_scale)]) - # self.args.extend(["-%s.yscalefs" % pfx, str(y_scale)]) - # self.args.extend(["-%s.stretch" % pfx, "0"]) + # self.emulator.args.extend(["-%s.xscalefs" % pfx, str(x_scale)]) + # self.emulator.args.extend(["-%s.yscalefs" % pfx, str(y_scale)]) + # self.emulator.args.extend(["-%s.stretch" % pfx, "0"]) # else: # if self.use_stretching(): - # self.args.extend(["-%s.stretch" % pfx, "full"]) + # self.emulator.args.extend(["-%s.stretch" % pfx, "full"]) # else: - # self.args.extend(["-%s.stretch" % pfx, "aspect"]) + # self.emulator.args.extend(["-%s.stretch" % pfx, "aspect"]) # # if self.use_fullscreen(): # pass # else: - # self.args.extend(["-%s.xscale" % pfx, "2"]) - # self.args.extend(["-%s.yscale" % pfx, "2"]) + # self.emulator.args.extend(["-%s.xscale" % pfx, "2"]) + # self.emulator.args.extend(["-%s.yscale" % pfx, "2"]) - # self.args.extend(["-%s.scanlines" % pfx, "0"]) - # if gamew < 200: - # self.args.extend(["-%s.xscale" % pfx, "4"]) - # self.args.extend(["-%s.yscale" % pfx, "4"]) - # else: - # self.args.extend(["-%s.xscale" % pfx, "3"]) - # self.args.extend(["-%s.yscale" % pfx, "3"]) - # self.args.extend(["-%s.videoip" % pfx, "0"]) - # FIXME: - # self.options.smooth = False - # self.options.filter = '2x' + # self.emulator.args.extend(["-%s.scanlines" % pfx, "0"]) + # if gamew < 200: + # self.emulator.args.extend(["-%s.xscale" % pfx, "4"]) + # self.emulator.args.extend(["-%s.yscale" % pfx, "4"]) + # else: + # self.emulator.args.extend(["-%s.xscale" % pfx, "3"]) + # self.emulator.args.extend(["-%s.yscale" % pfx, "3"]) + # self.emulator.args.extend(["-%s.videoip" % pfx, "0"]) + # FIXME: + # self.options.smooth = False + # self.options.filter = '2x' # if self.use_doubling(): - # self.args.extend(["-%s.special" % pfx, "nn2x"]) + # self.emulator.args.extend(["-%s.special" % pfx, "nn2x"]) # - # self.args.extend(self.mednafen_extra_graphics_options()) + # self.emulator.args.extend(self.mednafen_extra_graphics_options()) # filter_data = self.configure_filter() # if filter_data["name"] == "ntsc": - # self.args.extend(["-{0}.ntscblitter".format(pfx), "1"]) - # self.args.extend(["-{0}.ntsc.preset".format(pfx), "composite"]) - # self.args.extend(["-{0}.ntsc.saturation".format(pfx), "0.5"]) + # self.emulator.args.extend(["-{0}.ntscblitter".format(pfx), "1"]) + # self.emulator.args.extend(["-{0}.ntsc.preset".format(pfx), "composite"]) + # self.emulator.args.extend(["-{0}.ntsc.saturation".format(pfx), "0.5"]) # else: - # self.args.extend( + # self.emulator.args.extend( # ["-{0}.special".format(pfx), filter_data["special"]]) - # self.args.extend(["-%s.special" % pfx, + # self.emulator.args.extend(["-%s.special" % pfx, # self.mednafen_special_filter()]) # if self.configure_vsync(): - # self.args.extend(["-glvsync", "1"]) + # self.emulator.args.extend(["-glvsync", "1"]) # else: - # self.args.extend(["-glvsync", "0"]) - - if self.config.get(Option.MEDNAFEN_AUDIO_DRIVER): - if self.config.get(Option.MEDNAFEN_AUDIO_DRIVER) == "sdl": - # Mednafen does not support PulseAudio directly, but using the - # sdl driver will "often" result in PulseAudio being used - # indirectly. - self.args.extend(["-sound.driver", "sdl"]) - # FIXME: Deprecated? - elif self.config.get("audio_driver", "") in ["sdl", "pulseaudio"]: - # Mednafen does not support PulseAudio directly, but using the - # sdl driver will "often" result in PulseAudio being used - # indirectly. - self.args.extend(["-sound.driver", "sdl"]) + # self.emulator.args.extend(["-glvsync", "0"]) - self.args.extend(["-video.driver", "opengl"]) + # # FIXME: Deprecated? + # elif self.config.get("audio_driver", "") in ["sdl", "pulseaudio"]: + # # Mednafen does not support PulseAudio directly, but using the + # # sdl driver will "often" result in PulseAudio being used + # # indirectly. + # self.emulator.args.extend(["-sound.driver", "sdl"]) print("\n" + "-" * 79 + "\n" + "CONFIGURE DIRS") state_dir = self.emulator_state_dir("mednafen") - self.args.extend(["-path_sav", state_dir]) - self.args.extend(["-path_state", state_dir]) - self.args.extend(["-filesys.fname_state", "%M%X"]) - self.args.extend(["-filesys.fname_sav", "%M%x"]) + + self.emulator.args.extend(["-path_sav", self.save_handler.save_dir()]) + self.emulator.args.extend([ + "-path_state", self.save_handler.emulator_state_dir("Mednafen")]) + + # self.emulator.args.extend(["-filesys.fname_state", "%M%X"]) + # self.emulator.args.extend(["-filesys.fname_sav", "%M%x"]) + + self.emulator.args.extend(["-filesys.fname_state", "%f.%X"]) + self.emulator.args.extend(["-filesys.fname_sav", "%f.%x"]) # docdir = pyapp.user.documents_dir() - self.doc_dir = self.create_temp_dir("mednafen-docs") - self.args.extend([ + self.doc_dir = self.temp_dir("mednafen-docs") + self.emulator.args.extend([ "-path_movie", self.doc_dir.path, - "-path_cheat", self.doc_dir.path, + # "-path_cheat", self.doc_dir.path, "-path_palette", self.home.path]) - self.args.extend(["-path_snap", self.screenshots_dir()]) - self.args.extend(["-filesys.fname_snap", - "{0}-%p.%x".format(self.screenshots_name())]) + self.emulator.args.extend(["-path_snap", self.screenshots_dir()]) + self.emulator.args.extend([ + "-filesys.fname_snap", "{0}-%p.%x".format(self.screenshots_name())]) - self.args.append(self.get_game_file()) self.mednafen_post_configure() - def configure_audio(self, f): - pfx = self.mednafen_system_prefix() + def configure_audio(self, _): + # pfx = self.mednafen_system_prefix() + audio_driver = self.options[Option.MEDNAFEN_AUDIO_DRIVER] + if audio_driver in ["", "auto"]: + if System.windows: + pass + elif System.macos: + pass + else: + audio_driver = "sdl" + if audio_driver == "sdl": + # Mednafen does not support PulseAudio directly, but using the + # sdl driver will "often" result in PulseAudio being used + # indirectly. + self.emulator.args.extend(["-sound.driver", "sdl"]) + elif audio_driver == "alsa": + self.emulator.args.extend( + ["-sound.device", "sexyal-literal-default"]) + else: + # Use Mednafen default selection + # self.emulator.args.extend(["-sound.device", "default"]) + pass + + default_buffer_size = 40 + buffer_size = default_buffer_size + if self.options[Option.MEDNAFEN_AUDIO_BUFFER]: + try: + buffer_size = int(self.options[Option.MEDNAFEN_AUDIO_BUFFER]) + except ValueError: + print("WARNING: Invalid Mednafen audio buffer size specified") + else: + if buffer_size < 0 or buffer_size > 1000: + print("WARNING: Mednafen audio buffer size out of range") + buffer_size = default_buffer_size + self.emulator.args.extend( + ["-sound.buffer_time", str(buffer_size)]) def configure_input(self, f): print("\n" + "-" * 79 + "\n" + "CONFIGURE PORTS") @@ -258,15 +373,18 @@ def configure_video(self, f): pfx = self.mednafen_system_prefix() + self.emulator.args.extend(["-video.driver", "opengl"]) screen_w, screen_h = self.screen_size() # screen_a = screen_w / screen_h - if self.border() == self.NO_BORDER: - border_h = 0 - border_w = 0 - else: - border_w = screen_h * 32 / 1080 - border_h = screen_h * 32 / 1080 + border_w, border_h = 0, 0 + + # if self.border() == self.NO_BORDER: + # pass + # else: + # border_w = screen_h * 32 / 1080 + # border_h = screen_h * 32 / 1080 + dest_w, dest_h = screen_w - border_w, screen_h - border_h if not self.use_fullscreen(): dest_w, dest_h = 960, 540 @@ -313,7 +431,7 @@ if scale_x == 1 and scale_y == 1: scale_x = 2 scale_y = 2 - # self.emulator.args.extend(["-{}.special".format(pfx), "nn2x"]) + # self.emulator.args.extend(["-{}.special".format(pfx), "nn2x"]) if self.smoothing() == self.SMOOTHING: videoip = "1" @@ -339,9 +457,9 @@ # Only enable v-sync if game refresh matches screen refresh. if self.configure_vsync(): - self.emulator.args.extend(["-glvsync", "1"]) + self.emulator.args.extend(["-video.glvsync", "1"]) else: - self.emulator.args.extend(["-glvsync", "0"]) + self.emulator.args.extend(["-video.glvsync", "0"]) # Specify fullscreen size and conditionally enable fullscreen mode. self.emulator.args.extend(["-{}.xres".format(pfx), str(screen_w)]) @@ -354,20 +472,50 @@ if self.effect() == self.CRT_EFFECT: self.emulator.args.extend(["-{}.shader".format(pfx), "goat"]) self.emulator.args.extend(["-{}.shader.goat.slen".format(pfx), "1"]) - self.emulator.args.extend(["-{}.tblur".format(pfx), "1"]) + # self.emulator.args.extend(["-{}.tblur".format(pfx), "1"]) special = "none" + video_scale = 2 + min_video_scale = 2 + elif self.effect() == self.DOUBLE_EFFECT: + special = "nn2x" + video_scale = 2 + min_video_scale = 1 elif self.effect() == self.HQ2X_EFFECT: special = "hq2x" + video_scale = 2 + min_video_scale = 2 elif self.effect() == self.SCALE2X_EFFECT: special = "scale2x" + video_scale = 2 + min_video_scale = 2 else: - special = "nn2x" + special = "none" + video_scale = 1 + min_video_scale = 1 + + if self.scaling() == self.MAX_SCALING: + window_w, window_h = 960, 540 + elif self.scaling() == self.INTEGER_SCALING: + window_w = game_w * min_video_scale + window_h = game_h * min_video_scale + s = min_video_scale + 1 + print(window_w, window_h) + while game_w * s <= 900 and game_h * s <= 700: + window_w = game_w * s + window_h = game_h * s + s += 1 + else: + window_w = game_w * video_scale + window_h = game_h * video_scale + self.emulator.env["FSGS_WINDOW_SIZE"] = "{},{}".format( + window_w, window_h) + self.emulator.args.extend(["-{}.special".format(pfx), special]) self.emulator.args.extend(self.mednafen_extra_graphics_options()) def set_mednafen_input_order(self): - if windows: - self.input_device_order = 'DINPUT8' + if System.windows: + self.input_device_order = "DINPUT8" self.input_mapping_multiple = False def mednafen_extra_graphics_options(self): @@ -443,8 +591,8 @@ class MednafenInputMapper(InputMapper): - def __init__(self, input, mapping): - InputMapper.__init__(self, input, mapping) + def __init__(self, port, mapping): + InputMapper.__init__(self, port, mapping) helper = EnumerateHelper() helper.init() seen_ids = set() diff -Nru fs-uae-arcade-2.9.6/fsgs/drivers/messdriver.py fs-uae-arcade-2.9.7/fsgs/drivers/messdriver.py --- fs-uae-arcade-2.9.6/fsgs/drivers/messdriver.py 2017-05-25 10:07:53.000000000 +0000 +++ fs-uae-arcade-2.9.7/fsgs/drivers/messdriver.py 2017-10-16 21:40:40.000000000 +0000 @@ -4,8 +4,7 @@ class MessDriver(MameDriver): def __init__(self, fsgs): super().__init__(fsgs) - # self.emulator.name = "multiemu-fs" - self.emulator.name = "mess-fs" + # self.emulator.name = "mess-fs" def is_pal(self): # return self.config.get("ntsc_mode") != "1" @@ -24,7 +23,11 @@ pass def mame_romset(self): - return self.mess_romset() + result = {} + romset_name, files = self.mess_romset() + for sha1, name in files.items(): + result[name] = sha1 + return romset_name, result def mess_romset(self): pass diff -Nru fs-uae-arcade-2.9.6/fsgs/drivers/retroarchdriver.py fs-uae-arcade-2.9.7/fsgs/drivers/retroarchdriver.py --- fs-uae-arcade-2.9.6/fsgs/drivers/retroarchdriver.py 1970-01-01 00:00:00.000000000 +0000 +++ fs-uae-arcade-2.9.7/fsgs/drivers/retroarchdriver.py 2017-10-16 21:40:40.000000000 +0000 @@ -0,0 +1,382 @@ +import os +import shutil +from typing import Optional + +from fsgs import Option +from fsgs.FSGSDirectories import FSGSDirectories +from fsgs.drivers.gamedriver import GameDriver, Emulator +from fsgs.input.mapper import InputMapper +from fsgs.plugins.plugin_manager import PluginManager +from fsgs.saves import SaveHandler + + +class RetroArchDriver(GameDriver): + def __init__(self, fsgc, libretro_core, retroarch_state_dir): + super().__init__(fsgc) + self.emulator = Emulator("retroarch-fs") + # self.emulator.allow_system_emulator = True + # self.libretro_core = "no_core_specified" + self.system_dir = self.temp_dir("system") + self.save_handler = SaveHandler(self.fsgc, options=self.options) + # self.retroarch_state_dir = None + + self.libretro_core = libretro_core + self.retroarch_state_dir = retroarch_state_dir + + def prepare(self): + self.erase_old_config() + + # if not os.path.exists(config_dir): + # os.makedirs(config_dir) + # config_file = os.path.join(config_dir, "retroarch.cfg") + + # FIXME: Do not use /etc/retroarch.cfg as template.. how to prevent? + + self.save_handler.prepare() + + config_file = self.temp_file("retroarch.cfg").path + with open(config_file, "w", encoding="UTF-8") as f: + self.write_retroarch_config(f) + self.write_retroarch_input_config(f) + self.write_retroarch_video_config(f) + + self.emulator.args.extend(["--appendconfig=" + config_file]) + if self.use_fullscreen(): + self.emulator.args.append("--fullscreen") + + libretro_core = self.find_libretro_core(self.libretro_core) + self.emulator.args.extend(["-L", libretro_core]) + + # Verbose logging + self.emulator.args.extend(["-v"]) + + def finish(self): + self.save_handler.finish() + + @staticmethod + def find_libretro_core(name): + # return "/usr/lib/x86_64-linux-gnu/libretro/{}.so".format(name) + return PluginManager.instance().find_library_path(name) + + @staticmethod + def find_retroarch_shader(name): + # FIXME: Better to find data file based on path/provides rather than + # hardcoding plugin name, but... + plugin = PluginManager.instance().plugin("RetroArch-FS") + return plugin.data_file_path("shaders/shaders_glsl/" + name + ".glslp") + + def display_aspect_ratio(self) -> Optional[float]: + return 4 / 3 + + def game_video_size(self): + # FIXME: Dummy values + print("[DRIVER] Warning: Using dummy game video size (320, 240)") + return 320, 240 + + def erase_old_config(self): + config_dir = os.path.join(self.home.path, ".config", "retroarch") + if os.path.exists(config_dir): + shutil.rmtree(config_dir) + + def open_retroarch_core_options(self): + config_dir = os.path.join(self.home.path, ".config", "retroarch") + if not os.path.exists(config_dir): + os.makedirs(config_dir) + config_file = os.path.join(config_dir, "retroarch-core-options.cfg") + return open(config_file, "w", encoding="UTF-8") + + def write_retroarch_config(self, f): + """ + joypad_autoconfig_dir = "~/.config/retroarch/autoconfig" + """ + f.write("screenshot_directory = \"{}\"\n".format( + FSGSDirectories.screenshots_output_dir())) + f.write("system_directory = \"{}\"\n".format(self.system_dir.path)) + # noinspection SpellCheckingInspection + + f.write("savefile_directory = \"{}\"\n".format( + self.save_handler.save_dir())) + f.write("savestate_directory = \"{}\"\n".format( + self.save_handler.emulator_state_dir(self.retroarch_state_dir))) + + # FIXME: Maybe enable autosave to save .srm while running the emulator + # and not only on shutdown? + # f.write("autosave_interval = 60\n") + + f.write("pause_nonactive = false\n") + f.write("video_font_enable = false\n") + f.write("rgui_show_start_screen = false\n") + f.write("all_users_control_menu = true\n") + f.write("video_gpu_screenshot = false\n") + + if self.g_sync(): + # Without timed frame limiting, there will be stuttering (probably) + # due to (some) audio driver sync not being stable enough to give + # a stable frame rate. + + # FIXME: Implement better G-SYNC method in RetroArch + f.write("fastforward_ratio = 1.000000\n") + + # FIXME: It's possible the above "fix" would be better for + # non-v-sync as well, if the audio sync is not stable. + + # f.write("audio_max_timing_skew = 0.0\n") + # f.write("audio_rate_control_delta = 0.0\n") + # f.write("video_refresh_rate = 144.0\n") + # f.write("audio_sync = false\n") + + default_buffer_size = 40 + buffer_size = default_buffer_size + if self.options[Option.RETROARCH_AUDIO_BUFFER]: + try: + buffer_size = int(self.options[Option.RETROARCH_AUDIO_BUFFER]) + except ValueError: + print("WARNING: Invalid RetroArch audio buffer size specified") + else: + if buffer_size < 0 or buffer_size > 1000: + print("WARNING: RetroArch audio buffer size out of range") + buffer_size = default_buffer_size + f.write("audio_latency = {}\n".format(buffer_size)) + + def write_retroarch_input_config(self, f): + f.write("input_driver = \"sdl2\"\n") + f.write("input_enable_hotkey = \"alt\"\n") + f.write("input_exit_emulator = \"q\"\n") + f.write("input_toggle_fast_forward = \"w\"\n") + f.write("input_screenshot = \"s\"\n") + # f.write("input_toggle_fullscreen = \"enter\"\n") + f.write("input_toggle_fullscreen = \"f\"\n") + f.write("input_audio_mute = \"m\"\n") + f.write("input_menu_toggle = \"f12\"\n") + f.write("input_pause_toggle = \"p\"\n") + + for i, port in enumerate(self.ports): + if port.device is None: + continue + input_mapping = self.retroarch_input_mapping(i) + # FIXME: EXCLUDE DUPLICATE ITEMS IN INPUT MAPPING??? + mapper = RetroArchInputMapper(port, input_mapping) + + f.write("input_player1_joypad_index = \"0\"\n") + f.write("input_player1_analog_dpad_mode = \"0\"\n") + if port.device.type == "joystick": + pass + else: + pass + + for key, value in mapper.items(): + # print("---->", key, value) + postfix, value = value + f.write("{}{} = \"{}\"\n".format(key, postfix, value)) + postfixes = ["", "_btn", "_axis"] + postfixes.remove(postfix) + for postfix in postfixes: + f.write("{}{} = \"{}\"\n".format(key, postfix, "nul")) + + def write_retroarch_video_config(self, f): + # f.write("video_driver = \"gl\"\n") + # FIXME + if self.fullscreen_window_mode(): + f.write("video_windowed_fullscreen = true\n") + else: + f.write("video_windowed_fullscreen = false\n") + + # FIXME: 1 or 0? + f.write("video_max_swapchain_images = 1\n") + + if self.effect() == self.CRT_EFFECT: + video_shader = "crt/crt-aperture" + video_scale = 2 + elif self.effect() == self.DOUBLE_EFFECT: + if self.smoothing() == self.NO_SMOOTHING: + video_shader = "" + else: + video_shader = "retro/sharp-bilinear-2x-prescale" + video_scale = 2 + elif self.effect() == self.HQ2X_EFFECT: + video_shader = "hqx/hq2x" + video_scale = 2 + elif self.effect() == self.SCALE2X_EFFECT: + video_shader = "scalenx/scale2x" + video_scale = 2 + else: + video_shader = "" + video_scale = 1 + + if self.smoothing() == self.NO_SMOOTHING or video_shader: + f.write("video_smooth = false\n") + + if video_shader: + print("[DRIVER] Video shader:", video_shader) + video_shader_path = self.find_retroarch_shader(video_shader) + print("[DRIVER] Video shader path:", video_shader_path) + if video_shader_path: + f.write("video_shader = \"{}\"\n".format(video_shader_path)) + f.write("video_shader_enable = true\n") + + # FIXME: video_monitor_index = 0 + # FIXME: video_disable_composition = true + if self.use_vsync(): + f.write("video_vsync = true\n") + else: + f.write("video_vsync = false\n") + + # aspect_ratio_index = 22 + # custom_viewport_width = 0 + # custom_viewport_height = 0 + # custom_viewport_x = 0 + # custom_viewport_y = 0 + + if self.stretching() == self.STRETCH_ASPECT: + aspect_ratio = self.display_aspect_ratio() + if aspect_ratio is not None: + f.write("aspect_ratio_index = 19\n") + f.write("video_aspect_ratio = {:f}\n".format(aspect_ratio)) + # f.write("video_force_aspect = \"true\"\n") + # f.write("video_aspect_ratio_auto = \"false\"\n") + elif self.stretching() == self.STRETCH_FILL_SCREEN: + screen_w, screen_h = self.screen_size() + display_aspect = screen_w / screen_h + f.write("aspect_ratio_index = 19\n") + f.write("video_aspect_ratio = {:f}\n".format(display_aspect)) + else: + f.write("aspect_ratio_index = 20\n") + + if self.scaling() == self.NO_SCALING: + # FIXME: Window size rounding issues? E.g. 897x672 for 4:3 + # NES 3x display. Maybe set window size manually instead + f.write("video_scale = {}\n".format(video_scale)) + + # f.write("input_osk_toggle = \"nul\"\n") + overlay_path = self.create_retroarch_layout() + if overlay_path: + f.write("input_overlay = \"{}\"\n".format(overlay_path)) + f.write("input_overlay_opacity = \"1.000000\"\n") + + def retroarch_input_mapping(self, port): + return {} + + def display_rect_fullscreen(self): + # FIXME: Check square pixels option! + + screen_w, screen_h = self.screen_size() + screen_aspect = screen_w / screen_h + + if self.stretching() == self.STRETCH_ASPECT: + display_aspect = self.display_aspect_ratio() + elif self.stretching() == self.STRETCH_FILL_SCREEN: + screen_w, screen_h = self.screen_size() + display_aspect = screen_w / screen_h + else: + game_w, game_h = self.game_video_size() + display_aspect = game_w / game_h + + # FIXME: round to nearest multiple of two? + if screen_aspect >= display_aspect: + game_h = screen_h + game_w = round(game_h * display_aspect) + else: + game_w = screen_w + game_h = round(game_w / display_aspect) + game_x = round((screen_w - game_w) / 2) + game_y = round((screen_h - game_h) / 2) + return game_x, game_y, game_w, game_h + + def create_retroarch_layout(self): + if self.stretching() == self.STRETCH_FILL_SCREEN or not self.bezel(): + return + if not self.use_fullscreen(): + return + + # FIXME: file cmp? + paths = self.prepare_emulator_skin() + print(paths) + + # FIXME: SUPPORT frame = 0 ( bezel = 0) option + + # FIXME: With no bezel, we should still use a black bezel to + # hide screen stretching + + screen_width, screen_height = self.screen_size() + # dst_x = 0 + dst_y = 0 + # dst_w = 0 + # dst_w = 160 + dst_h = screen_height + + # Bezel size is normalized against 1080 (height) + scale = screen_height / 1080 + # Bezel width: 160 + dst_w = round(160 * scale) + + game_x, game_y, game_w, game_h = self.display_rect_fullscreen() + + from fsui.qt import Qt, QImage, QPainter, QRect, QSize + image = QImage( + QSize(screen_width, screen_height), QImage.Format_RGBA8888) + image.fill(Qt.transparent) + # painter = image.paintEngine() + painter = QPainter(image) + + dst_x = game_x - dst_w + left = QImage(paths["left"]) + painter.drawImage(QRect(dst_x, dst_y, dst_w, dst_h), left) + + dst_x = game_x + game_w + right = QImage(paths["right"]) + painter.drawImage(QRect(dst_x, dst_y, dst_w, dst_h), right) + painter.end() + + overlay_png_file = self.temp_file("overlay.png").path + image.save(overlay_png_file) + + # noinspection SpellCheckingInspection + overlay_config = ( + """overlays = 1 +overlay0_overlay = {overlay} +overlay0_full_screen = true +overlay0_rect = "0.0,0.0,1.0,1.0" +overlay0_descs = 0 +""".format(overlay=overlay_png_file)) +# overlay_config = ( +# """overlays = 2 +# overlay0_overlay = {left} +# overlay0_full_screen = true +# overlay0_rect = "0.0,0.0,0.12,1.0" +# overlay0_descs = 0 +# overlay1_overlay = {right} +# overlay1_full_screen = true +# overlay1_rect = "0.8,0.0,0.2,1.0" +# overlay1_descs = 0 +# +# """.format(left=paths["left"], right=paths["right"])) + overlay_config_file = self.temp_file("overlay.cfg") + with open(overlay_config_file.path, "w") as f: + f.write(overlay_config) + return overlay_config_file.path + + def window_size(self): + return 0, 0 + + +class RetroArchInputMapper(InputMapper): + def __init__(self, port, mapping): + super().__init__(port, mapping, multiple=False) + + def axis(self, axis, positive): + dir_str = "+" if positive else "-" + return "_axis", dir_str + str(axis) + + def hat(self, hat, direction): + return "_btn", "h{}{}".format(hat, direction) + + def button(self, button): + return "_btn", button + + def key(self, key): + # FIXME: Correct RetroArch key names + name = key.sdl_name[5:].lower() + if name == "return": + # FIXME: HACK + name = "enter" + return "", name diff -Nru fs-uae-arcade-2.9.6/fsgs/filedatabase.py fs-uae-arcade-2.9.7/fsgs/filedatabase.py --- fs-uae-arcade-2.9.6/fsgs/filedatabase.py 1970-01-01 00:00:00.000000000 +0000 +++ fs-uae-arcade-2.9.7/fsgs/filedatabase.py 2017-10-16 21:40:40.000000000 +0000 @@ -0,0 +1,277 @@ +import os +import sqlite3 +import time +from binascii import unhexlify + +from fsgs.BaseDatabase import BaseDatabase +from fsgs.FSGSDirectories import FSGSDirectories + +SENTINEL = "fae7671d-e232-4b71-b179-b3cd45995f92" +VERSION = 5 +RESET_VERSION = 5 + + +class File(dict): + def __nonzero__(self): + # noinspection PyTypeChecker + return bool(self["path"]) + + def __bool__(self): + # noinspection PyTypeChecker + return bool(self["path"]) + + +class FileDatabase(BaseDatabase): + def __init__(self, sentinel): + BaseDatabase.__init__(self, sentinel) + self.last_file_insert = None + self.last_file_delete = None + + # This (class) map contains information about checksum -> uri locations + # discovered during runtime. This map is not saved. + static_files = {} + + @classmethod + def add_static_file(cls, path, size, sha1): + file = File() + file["sha1"] = sha1 + file["path"] = path + file["size"] = size + file["mtime"] = None + cls.static_files[sha1] = file + + @classmethod + def get_path(cls): + return os.path.join(FSGSDirectories.databases_dir(), "Files.sqlite") + + def get_version(self): + return VERSION + + def get_reset_version(self): + return RESET_VERSION + + @classmethod + def instance(cls, new=False): + if new or not hasattr(cls.thread_local, "file_database"): + cls.thread_local.file_database = cls(cls.SENTINEL) + return cls.thread_local.file_database + + @classmethod + def get_instance(cls): + if not hasattr(cls.thread_local, "file_database"): + cls.thread_local.file_database = cls(cls.SENTINEL) + return cls.thread_local.file_database + + def get_file_ids(self): + cursor = self.internal_cursor() + cursor.execute("SELECT id FROM file WHERE parent IS NULL") + return set([row[0] for row in cursor.fetchall()]) + + def get_file_hierarchy_ids(self, path): + path = self.encode_path(path) + if path.endswith("/"): + path = path[:-1] + a_path = path + "\u002f" # Forward slash + b_path = path + "\u0030" # Forward slash + 1 + cursor = self.internal_cursor() + cursor.execute( + "SELECT id FROM file WHERE path >= ? AND path < ?", + (a_path, b_path)) + return set([row[0] for row in cursor.fetchall()]) + + def encode_path(self, path): + # This only works if both path and FSGSDirectories.base_dir (etc) have + # been normalized with get_real_case. + path = path.replace("\\", "/") + base_dir = FSGSDirectories.get_base_dir() + if path.startswith(base_dir): + path = path[len(base_dir):] + if path.startswith("/"): + path = path[1:] + path = "$/" + path + return path + + def decode_path(self, path): + if not path or path[0] != "$": + return path + base_dir = FSGSDirectories.get_base_dir() + "/" + if path.startswith("$/"): + path = base_dir + path[2:] + return path + + def find_local_roms(self): + if not self.connection: + self.init() + a = "$/Kickstarts/" + b = "$/Kickstarts" + "\u0030" # one more than forward slash + query = "SELECT id, path FROM file WHERE path >= ? AND path < ?" + cursor = self.internal_cursor() + cursor.execute(query, (a, b)) + result = {} + for row in cursor.fetchall(): + result[self.decode_path(row[1])] = row[0] + return result + + def delete_file(self, id=None, path=None): + cursor = self.internal_cursor() + delete_ids = [] + if id is not None: + delete_ids.append(id) + if path is not None: + path = self.encode_path(path) + cursor.execute("SELECT id FROM file WHERE path = ?", (path,)) + for row in cursor: + delete_ids.append(row[0]) + for id in delete_ids: + cursor.execute( + "DELETE FROM file WHERE id = ? OR parent = ?", (id, id)) + self.last_file_delete = int(time.time()) + + def check_sha1(self, sha1): + if sha1 in self.static_files: + # FIXME: Is the count necessary? ref query below, or do we + # only need a True/False result? If so, change query to + # check for existence only, and change return value to boolean. + return True + cursor = self.internal_cursor() + cursor.execute( + "SELECT count(*) FROM file WHERE sha1 = ?", ( + sqlite3.Binary(unhexlify(sha1)),)) + return cursor.fetchone()[0] + + def find_file(self, name="", sha1="", path=""): + cursor = self.internal_cursor() + if sha1: + # First we try to find the file from the temporary static + # file map, in case we've been told about files from plugins or + # archives. + try: + return self.static_files[sha1] + except KeyError: + pass + sha1 = unhexlify(sha1.encode("ASCII")) + cursor.execute( + "SELECT id, path, sha1, mtime, size, parent " + "FROM file WHERE sha1 = ? LIMIT 1", + (sqlite3.Binary(sha1),)) + elif name: + # noinspection SpellCheckingInspection + cursor.execute( + "SELECT id, path, sha1, mtime, size, parent " + "FROM file WHERE name = ? COLLATE NOCASE LIMIT 1", + (name.lower(),)) + else: + path = self.encode_path(path) + cursor.execute( + "SELECT id, path, sha1, mtime, size, parent " + "FROM file WHERE path = ? LIMIT 1", (path,)) + row = cursor.fetchone() + result = File() + if row: + if row[5]: + # parent + cursor.execute( + "SELECT path FROM file WHERE id = ?", (row[5],)) + path = cursor.fetchone()[0] + row[1] + else: + path = row[1] + path = self.decode_path(path) + result["id"] = row[0] + result["path"] = path + result["sha1"] = row[2] + result["mtime"] = row[3] + result["size"] = row[4] + else: + result["id"] = None + result["path"] = None + result["sha1"] = None + result["mtime"] = None + result["size"] = None + return result + + def find_files(self, ext=None): + cursor = self.internal_cursor() + query = "SELECT path FROM file WHERE 1 = 1" + args = [] + if ext is not None: + # This is used (for now) to look up .fs-uae files, so.. + # we don't want files from archives (sqlite like is case + # insensitive by default). + query += " AND parent is NULL AND path LIKE ?" + args.append("%" + ext) + cursor.execute(query, args) + results = [] + for row in cursor: + data = { + "path": self.decode_path(row[0]), + } + results.append(data) + return results + + def add_file(self, path="", sha1=None, mtime=0, size=0, parent=None): + self.init() + path = self.encode_path(path) + cursor = self.internal_cursor() + print("[FILES] Add file", repr(path), repr(sha1), repr(mtime), + repr(size), repr(parent)) + if sha1: + sha1 = unhexlify(sha1) + cursor.execute( + "INSERT INTO file (path, sha1, mtime, size, parent) " + "VALUES (?, ?, ?, ?, ?)", + (path, sqlite3.Binary(sha1), mtime, size, parent)) + self.last_file_insert = int(time.time()) + return cursor.lastrowid + + def get_last_event_stamps(self): + cursor = self.internal_cursor() + cursor.execute( + "SELECT last_file_insert, last_file_delete FROM metadata") + row = cursor.fetchone() + result = { + "last_file_insert": row[0], + "last_file_delete": row[0], + } + return result + + def update_last_event_stamps(self): + if self.last_file_insert: + cursor = self.internal_cursor() + cursor.execute( + "UPDATE metadata set last_file_insert = ?", + (self.last_file_insert,)) + self.last_file_insert = None + if self.last_file_delete: + cursor = self.internal_cursor() + cursor.execute( + "UPDATE metadata set last_file_delete = ?", + (self.last_file_delete,)) + self.last_file_delete = None + + def clear(self): + if not self.connection: + self.init() + cursor = self.internal_cursor() + cursor.execute("DELETE FROM file") + + def update_database_to_version_5(self): + cursor = self.internal_cursor() + try: + cursor.execute("SELECT count(*) FROM file") + except sqlite3.OperationalError: + cursor.execute("""CREATE TABLE file ( + id INTEGER PRIMARY KEY, + sha1 BLOB, + path TEXT, + size INTEGER, + mtime INTEGER, + parent INTEGER + )""") + cursor.execute("CREATE INDEX file_sha1 ON file(sha1)") + cursor.execute("CREATE INDEX file_path ON file(path)") + cursor.execute("CREATE INDEX file_parent ON file(parent)") + + cursor.execute( + "ALTER TABLE metadata ADD COLUMN last_file_insert INTEGER") + cursor.execute( + "ALTER TABLE metadata ADD COLUMN last_file_delete INTEGER") diff -Nru fs-uae-arcade-2.9.6/fsgs/FileDatabase.py fs-uae-arcade-2.9.7/fsgs/FileDatabase.py --- fs-uae-arcade-2.9.6/fsgs/FileDatabase.py 2017-05-25 10:07:53.000000000 +0000 +++ fs-uae-arcade-2.9.7/fsgs/FileDatabase.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,283 +0,0 @@ -import os -import sqlite3 -import time -from binascii import unhexlify - -from fsgs.BaseDatabase import BaseDatabase -from fsgs.FSGSDirectories import FSGSDirectories - -SENTINEL = "fae7671d-e232-4b71-b179-b3cd45995f92" -VERSION = 4 -RESET_VERSION = 4 - - -class File(dict): - def __nonzero__(self): - # noinspection PyTypeChecker - return bool(self["path"]) - - def __bool__(self): - # noinspection PyTypeChecker - return bool(self["path"]) - - -class FileDatabase(BaseDatabase): - def __init__(self, sentinel): - BaseDatabase.__init__(self, sentinel) - self.last_file_insert = None - self.last_file_delete = None - - # This (class) map contains information about checksum -> uri locations - # discovered during runtime. This map is not saved. - static_files = {} - - @classmethod - def add_static_file(cls, path, size, sha1): - file = File() - file["sha1"] = sha1 - file["path"] = path - file["size"] = size - file["mtime"] = None - cls.static_files[sha1] = file - - @classmethod - def get_path(cls): - return os.path.join(FSGSDirectories.databases_dir(), "Files.sqlite") - - def get_version(self): - return VERSION - - def get_reset_version(self): - return RESET_VERSION - - @classmethod - def instance(cls, new=False): - if new or not hasattr(cls.thread_local, "file_database"): - cls.thread_local.file_database = cls(cls.SENTINEL) - return cls.thread_local.file_database - - @classmethod - def get_instance(cls): - if not hasattr(cls.thread_local, "file_database"): - cls.thread_local.file_database = cls(cls.SENTINEL) - return cls.thread_local.file_database - - def get_file_ids(self): - cursor = self.internal_cursor() - cursor.execute("SELECT id FROM file WHERE parent IS NULL") - return set([row[0] for row in cursor.fetchall()]) - - def get_file_hierarchy_ids(self, path): - path = self.encode_path(path) - if path.endswith("/"): - path = path[:-1] - a_path = path + "\u002f" # Forward slash - b_path = path + "\u0030" # Forward slash + 1 - cursor = self.internal_cursor() - cursor.execute( - "SELECT id FROM file WHERE path >= ? AND path < ?", - (a_path, b_path)) - return set([row[0] for row in cursor.fetchall()]) - - def encode_path(self, path): - # This only works if both path and FSGSDirectories.base_dir (etc) have - # been normalized with get_real_case. - path = path.replace("\\", "/") - base_dir = FSGSDirectories.get_base_dir() - if path.startswith(base_dir): - path = path[len(base_dir):] - if path.startswith("/"): - path = path[1:] - path = "$/" + path - return path - - def decode_path(self, path): - if not path or path[0] != "$": - return path - base_dir = FSGSDirectories.get_base_dir() + "/" - if path.startswith("$/"): - path = base_dir + path[2:] - return path - - def find_local_roms(self): - if not self.connection: - self.init() - a = "$/Kickstarts/" - b = "$/Kickstarts" + "\u0030" # one more than forward slash - query = "SELECT id, path FROM file WHERE path >= ? AND path < ?" - cursor = self.internal_cursor() - cursor.execute(query, (a, b)) - result = {} - for row in cursor.fetchall(): - result[self.decode_path(row[1])] = row[0] - return result - - def delete_file(self, id=None, path=None): - cursor = self.internal_cursor() - delete_ids = [] - if id is not None: - delete_ids.append(id) - if path is not None: - path = self.encode_path(path) - cursor.execute("SELECT id FROM file WHERE path = ?", (path,)) - for row in cursor: - delete_ids.append(row[0]) - for id in delete_ids: - cursor.execute( - "DELETE FROM file WHERE id = ? OR parent = ?", (id, id)) - self.last_file_delete = int(time.time()) - - def check_sha1(self, sha1): - if sha1 in self.static_files: - # FIXME: Is the count necessary? ref query below, or do we - # only need a True/False result? If so, change query to - # check for existence only, and change return value to boolean. - return True - cursor = self.internal_cursor() - cursor.execute( - "SELECT count(*) FROM file WHERE sha1 = ?", ( - sqlite3.Binary(unhexlify(sha1)),)) - return cursor.fetchone()[0] - - def find_file(self, name="", sha1="", path=""): - cursor = self.internal_cursor() - if sha1: - # First we try to find the file from the temporary static - # file map, in case we've been told about files from plugins or - # archives. - try: - return self.static_files[sha1] - except KeyError: - pass - sha1 = unhexlify(sha1.encode("ASCII")) - cursor.execute( - "SELECT id, path, sha1, mtime, size, parent " - "FROM file WHERE sha1 = ? LIMIT 1", - (sqlite3.Binary(sha1),)) - elif name: - # noinspection SpellCheckingInspection - cursor.execute( - "SELECT id, path, sha1, mtime, size, parent " - "FROM file WHERE name = ? COLLATE NOCASE LIMIT 1", - (name.lower(),)) - else: - path = self.encode_path(path) - cursor.execute( - "SELECT id, path, sha1, mtime, size, parent " - "FROM file WHERE path = ? LIMIT 1", (path,)) - row = cursor.fetchone() - result = File() - if row: - if row[5]: - # parent - cursor.execute( - "SELECT path FROM file WHERE id = ?", (row[5],)) - path = cursor.fetchone()[0] + row[1] - else: - path = row[1] - path = self.decode_path(path) - result["id"] = row[0] - result["path"] = path - result["sha1"] = row[2] - result["mtime"] = row[3] - result["size"] = row[4] - else: - result["id"] = None - result["path"] = None - result["sha1"] = None - result["mtime"] = None - result["size"] = None - return result - - def find_files(self, ext=None): - cursor = self.internal_cursor() - query = "SELECT path FROM file WHERE 1 = 1" - args = [] - if ext is not None: - # this is used (for now) to look up .fs-uae files, so.. - # we don't want files from archives - # sqlite like is case insensitive by default - query += " AND parent is NULL AND path LIKE ?" - args.append("%" + ext) - cursor.execute(query, args) - results = [] - for row in cursor: - data = { - "path": self.decode_path(row[0]), - # "name": row[1] - } - results.append(data) - return results - - def add_file(self, path="", sha1=None, mtime=0, size=0, parent=None): - self.init() - # if not name: - # name = os.path.basename(path) - path = self.encode_path(path) - # parent_path, name = path.rsplit("/", 1) - - cursor = self.internal_cursor() - # parent = self.ensure_dir(dir) - print("add_file", repr(path), repr(sha1), repr(mtime), repr(size), - repr(parent)) - if sha1: - sha1 = unhexlify(sha1) - cursor.execute( - "INSERT INTO file (path, sha1, mtime, size, parent) " - "VALUES (?, ?, ?, ?, ?)", - (path, sqlite3.Binary(sha1), mtime, size, parent)) - self.last_file_insert = int(time.time()) - return cursor.lastrowid - - def get_last_event_stamps(self): - cursor = self.internal_cursor() - cursor.execute( - "SELECT last_file_insert, last_file_delete FROM metadata") - row = cursor.fetchone() - result = { - "last_file_insert": row[0], - "last_file_delete": row[0], - } - return result - - def update_last_event_stamps(self): - if self.last_file_insert: - cursor = self.internal_cursor() - cursor.execute( - "UPDATE metadata set last_file_insert = ?", - (self.last_file_insert,)) - self.last_file_insert = None - if self.last_file_delete: - cursor = self.internal_cursor() - cursor.execute( - "UPDATE metadata set last_file_delete = ?", - (self.last_file_delete,)) - self.last_file_delete = None - - def clear(self): - if not self.connection: - self.init() - cursor = self.internal_cursor() - cursor.execute("DELETE FROM file") - - def update_database_to_version_4(self): - cursor = self.internal_cursor() - try: - cursor.execute("SELECT count(*) FROM file") - except sqlite3.OperationalError: - cursor.execute("""CREATE TABLE file ( - id INTEGER PRIMARY KEY, - sha1 BLOB, - path TEXT, - size INTEGER, - mtime INTEGER, - parent INTEGER - )""") - cursor.execute("CREATE INDEX file_sha1 ON file(sha1)") - cursor.execute("CREATE INDEX file_path ON file(path)") - cursor.execute("CREATE INDEX file_parent ON file(parent)") - - cursor.execute( - "ALTER TABLE metadata ADD COLUMN last_file_insert INTEGER") - cursor.execute( - "ALTER TABLE metadata ADD COLUMN last_file_delete INTEGER") diff -Nru fs-uae-arcade-2.9.6/fsgs/FSGameSystemContext.py fs-uae-arcade-2.9.7/fsgs/FSGameSystemContext.py --- fs-uae-arcade-2.9.6/fsgs/FSGameSystemContext.py 2017-05-25 10:07:53.000000000 +0000 +++ fs-uae-arcade-2.9.7/fsgs/FSGameSystemContext.py 2017-10-16 21:40:40.000000000 +0000 @@ -10,7 +10,7 @@ from fsgs.Archive import Archive from fsgs.BaseContext import BaseContext from fsgs.Database import Database -from fsgs.FileDatabase import FileDatabase +from fsgs.filedatabase import FileDatabase from fsgs.GameDatabase import GameDatabase, IncompleteGameException from fsgs.LockerDatabase import LockerDatabase from fsgs.download import Downloader @@ -90,6 +90,7 @@ return Archive(uri).open(uri) def open(self, uri, prefer_path=False): + print("[FILES] Open", uri) while isinstance(uri, str): uri = self.convert_uri(uri, prefer_path=prefer_path) if prefer_path and isinstance(uri, File): @@ -261,8 +262,8 @@ @property def settings(self): if self._settings is None: - from fsbc.application import settings - self._settings = settings + from fsbc.settings import Settings + self._settings = Settings.instance() return self._settings @property @@ -337,8 +338,10 @@ # return TemporaryFile(suffix) def load_game_by_uuid(self, game_uuid): + if self.load_game_variant(game_uuid): + return True variant_uuid = self.find_preferred_game_variant(game_uuid) - self.load_game_variant(variant_uuid) + return self.load_game_variant(variant_uuid) # noinspection PyMethodMayBeStatic def find_preferred_game_variant(self, game_uuid): @@ -442,7 +445,7 @@ from fsgs.input.enumeratehelper import EnumerateHelper device_helper = EnumerateHelper() - device_helper.default_port_selection(runner.ports) + device_helper.default_port_selection(runner.ports, runner.options) runner.prepare() process = runner.run() diff -Nru fs-uae-arcade-2.9.6/fsgs/FSGSDirectories.py fs-uae-arcade-2.9.7/fsgs/FSGSDirectories.py --- fs-uae-arcade-2.9.6/fsgs/FSGSDirectories.py 2017-05-25 10:07:53.000000000 +0000 +++ fs-uae-arcade-2.9.7/fsgs/FSGSDirectories.py 2017-10-16 21:40:40.000000000 +0000 @@ -1,6 +1,8 @@ +import logging import os import functools +import warnings from configparser import ConfigParser import fsboot @@ -15,14 +17,15 @@ class FSGSDirectories(object): @classmethod def initialize(cls): - print("[DEPRECATED] FSGSDirectories.initialize") + warnings.warn(DeprecationWarning) + logging.debug("[DEPRECATED] FSGSDirectories.initialize") cls._initialize() @classmethod def _initialize(cls): if hasattr(cls, "_initialized") and cls._initialized: return - print("FSGSDirectories._initialize") + logging.debug("FSGSDirectories._initialize") cls.get_base_dir() cls._initialized = True diff -Nru fs-uae-arcade-2.9.6/fsgs/GameDatabase.py fs-uae-arcade-2.9.7/fsgs/GameDatabase.py --- fs-uae-arcade-2.9.6/fsgs/GameDatabase.py 2017-05-25 10:07:53.000000000 +0000 +++ fs-uae-arcade-2.9.7/fsgs/GameDatabase.py 2017-10-16 21:40:40.000000000 +0000 @@ -131,6 +131,8 @@ data = data.decode("UTF-8") doc = json.loads(data) + doc["variant_uuid"] = game_uuid + # This is a hack so we can retrieve the published-status of the # child entry. The original key should have been named _published, # probably, to avoid it being inherited. diff -Nru fs-uae-arcade-2.9.6/fsgs/__init__.py fs-uae-arcade-2.9.7/fsgs/__init__.py --- fs-uae-arcade-2.9.6/fsgs/__init__.py 2017-05-25 10:07:53.000000000 +0000 +++ fs-uae-arcade-2.9.7/fsgs/__init__.py 2017-10-16 21:40:40.000000000 +0000 @@ -15,5 +15,27 @@ # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +from fsgs.option import Option + product = "FS-UAE" -openretro = False \ No newline at end of file +openretro = False + +OPENRETRO_DEFAULT_DATABASES = [ + Option.ARCADE_DATABASE, + Option.A7800_DATABASE, + Option.ATARI_DATABASE, + Option.C64_DATABASE, + Option.CPC_DATABASE, + Option.DOS_DATABASE, + Option.GB_DATABASE, + Option.GBA_DATABASE, + Option.GBC_DATABASE, + Option.NEOGEO_DATABASE, + Option.NES_DATABASE, + Option.PSX_DATABASE, + Option.SMD_DATABASE, + Option.SNES_DATABASE, + Option.TG16_DATABASE, + Option.TGCD_DATABASE, + Option.ZXS_DATABASE, +] diff -Nru fs-uae-arcade-2.9.6/fsgs/input/devicemanager.py fs-uae-arcade-2.9.7/fsgs/input/devicemanager.py --- fs-uae-arcade-2.9.6/fsgs/input/devicemanager.py 1970-01-01 00:00:00.000000000 +0000 +++ fs-uae-arcade-2.9.7/fsgs/input/devicemanager.py 2017-10-16 21:40:40.000000000 +0000 @@ -0,0 +1,84 @@ +import sys + +from arcade.notification import Notification +from .device import Device +from .enumeratehelper import EnumerateHelper + + +class DeviceManager(object): + + __instance = None + + @classmethod + def instance(cls): + if cls.__instance is None: + helper = EnumerateHelper() + cls.__instance = cls(helper) + return cls.__instance + + def __init__(self, helper): + self.helper = helper + self.devices = [] + if "--add-dummy-joystick" in sys.argv: + self.add_joystick_device({ + "axes": 2, + "hats": 0, + "balls": 0, + "buttons": 1, + "name": "Dummy Joystick", + "id": "Dummy Joystick", + }) + + def get_devices(self): + print("DeviceManager.get_devices") + if self.helper is not None: + self.helper.init() + return self.helper.devices + print("DeviceManager.get_devices ->", self.devices) + return self.devices + + def add_device_from_event(self, event): + if event["type"] == "joy-device-added": + return self.add_joystick_device(event) + elif event["type"] == "mouse-device-added": + return self.add_mouse_device(event) + elif event["type"] == "keyboard-device-added": + return self.add_keyboard_device(event) + else: + assert False + + def add_joystick_device(self, event): + device = Device() + device.axes = event["axes"] + device.balls = event["balls"] + device.hats = event["hats"] + device.buttons = event["buttons"] + device.name = event["name"] + device.id = event["id"] + device.type = Device.TYPE_JOYSTICK + self.devices.append(device) + print("[INPUT] Joystick device added:", device.name) + return device + + def add_keyboard_device(self, event): + device = Device() + device.name = event["name"] + device.id = event["id"] + device.type = Device.TYPE_KEYBOARD + self.devices.append(device) + print("[INPUT] Keyboard device added:", device.name) + return device + + def add_mouse_device(self, event): + device = Device() + device.name = event["name"] + device.id = event["id"] + device.type = Device.TYPE_MOUSE + self.devices.append(device) + print("[INPUT] Mouse device added:", device.name) + return device + + def remove_all_devices(self): + for device in self.devices: + print("[INPUT] Removing device", device.name) + self.devices.clear() diff -Nru fs-uae-arcade-2.9.6/fsgs/input/device.py fs-uae-arcade-2.9.7/fsgs/input/device.py --- fs-uae-arcade-2.9.6/fsgs/input/device.py 2017-05-25 10:07:53.000000000 +0000 +++ fs-uae-arcade-2.9.7/fsgs/input/device.py 2017-10-16 21:40:40.000000000 +0000 @@ -19,6 +19,7 @@ self.hats = 0 self.axes = 0 self.buttons = 0 + # self.event_id = "" def __lt__(self, other): if self.index < other.index: @@ -62,15 +63,21 @@ host_platform) return config_name - def configure(self, system): - name = self.name.rsplit("#", 1)[0] + def configure(self, system, multiple=True): + # name = self.name.rsplit("#", 1)[0] + name = self.name from fsgs.input.inputdevice import InputDevice try: # device id must end with #something (really a device number, # but can be anything + if "#" in name: + name_with_hash = name + else: + name_with_hash = name + " #DUMMY" device = InputDevice( - system, name + " #DUMMY", [], version=2, axes=self.axes, - hats=self.hats, buttons=self.buttons, balls=self.balls) + system, name_with_hash, [], version=2, axes=self.axes, + hats=self.hats, buttons=self.buttons, balls=self.balls, + multiple=multiple) config = device.get_config() except Exception as e: print("error initializing device {0} for {1}".format( diff -Nru fs-uae-arcade-2.9.6/fsgs/input/enumeratehelper.py fs-uae-arcade-2.9.7/fsgs/input/enumeratehelper.py --- fs-uae-arcade-2.9.6/fsgs/input/enumeratehelper.py 2017-05-25 10:07:53.000000000 +0000 +++ fs-uae-arcade-2.9.7/fsgs/input/enumeratehelper.py 2017-10-16 21:40:40.000000000 +0000 @@ -3,6 +3,7 @@ import traceback from fsgs.amiga.fsuaedevicehelper import FSUAEDeviceHelper +from launcher.devicemanager import DeviceManager from .device import Device @@ -32,17 +33,17 @@ self.initialized = True def init_fsuae(self): - print("EnumerateHelper: finding connected joysticks") + print("[INPUT] EnumerateHelper: Finding connected joysticks") try: p = FSUAEDeviceHelper.start_with_args( ["list"], stdout=subprocess.PIPE) joysticks = p.stdout.read() p.wait() except Exception: - print("exception while listing joysticks and devices") + print("[INPUT] Exception while listing joysticks and devices") traceback.print_exc() return - print(repr(joysticks)) + print("[INPUT]", repr(joysticks)) # If the character conversion fails, replace will ensure that # as much as possible succeeds. The joystick in question will # not be pre-selectable in the launcher, but the other ones will @@ -94,18 +95,34 @@ device.index = i for i, device in enumerate(self.keyboard_devices): device.index = i + self.joystick_like_devices.extend(self.joystick_devices) self.joystick_like_devices.extend(self.keyboard_devices) - def default_port_selection(self, ports): - print("devices:") + def default_port_selection(self, ports, options): + print("[INPUT] Default port selection") self.init() # for device in self.devices: # print(" #", device.id) # device.configure("megadrive") + if len(ports) > 0: + if ports[0].type_option: + # Supports new-style port selection + port_devices = DeviceManager.get_non_amiga_devices_for_ports( + options) + for i, port in enumerate(ports): + for device in self.devices: + if port_devices[i + 1].id == device.id: + port.device = device + print("[INPUT]", port.name, "<-", device.id) + break + else: + print("[INPUT]", port.name, "<- [None]") + return + joystick_like_devices = self.joystick_like_devices[:] - print("ports:") + print("[INPUT] Old Selection: Ports:") for port in ports: for i, device in enumerate(joystick_like_devices): # device.configure() @@ -113,4 +130,4 @@ joystick_like_devices.pop(i) port.device = device break - print(" /", port.name, port.device) + print("[INPUT] Old Selection:", port.name, port.device) diff -Nru fs-uae-arcade-2.9.6/fsgs/input/eventhelper.py fs-uae-arcade-2.9.7/fsgs/input/eventhelper.py --- fs-uae-arcade-2.9.6/fsgs/input/eventhelper.py 2017-05-25 10:07:53.000000000 +0000 +++ fs-uae-arcade-2.9.7/fsgs/input/eventhelper.py 2017-10-16 21:40:40.000000000 +0000 @@ -21,6 +21,7 @@ self.process = None self.events = Queue() + self.stopping = False def init(self): if self.initialized: @@ -28,6 +29,17 @@ self.init_device_helper() self.initialized = True + def deinit(self): + self.stopping = True + + def is_add_event(self, event): + return event["type"] in ["joy-device-added", "mouse-device-added", + "keyboard-device-added"] + + def is_remove_event(self, event): + return event["type"] in ["joy-device-removed", "mouse-device-removed", + "keyboard-device-removed"] + def get_next_event(self): try: event = self.events.get(False) @@ -47,7 +59,8 @@ self.process = None def _run(self): - while True: + print("[INPUT] EventHelper thread started") + while not self.stopping: line = self.process.stdout.readline() line = line.decode("UTF-8", "replace") if not line: @@ -62,6 +75,9 @@ continue # print(event) self.events.put(event) + print("[INPUT] EventHelper thread stopping") + self.process.kill() + atexit.unregister(self.kill_device_helper_on_exit) def init_device_helper(self): print("EventHelper.init_device_helper") @@ -73,9 +89,9 @@ traceback.print_exc() return - def kill_device_helper_on_exit(): - self.process.kill() - - atexit.register(kill_device_helper_on_exit) - + atexit.register(self.kill_device_helper_on_exit) self.start() + + def kill_device_helper_on_exit(self): + print("[INPUT] Killing device helper process (on exit)") + self.process.kill() diff -Nru fs-uae-arcade-2.9.6/fsgs/input/eventlistener.py fs-uae-arcade-2.9.7/fsgs/input/eventlistener.py --- fs-uae-arcade-2.9.6/fsgs/input/eventlistener.py 2017-05-25 10:07:53.000000000 +0000 +++ fs-uae-arcade-2.9.7/fsgs/input/eventlistener.py 2017-10-16 21:40:40.000000000 +0000 @@ -1,34 +1,105 @@ -from .eventhelper import EventHelper -from .manager import DeviceManager +import time +from arcade.notification import Notification +from fsgs.input.device import Device +from fsgs.input.eventhelper import EventHelper +from fsgs.input.devicemanager import DeviceManager -class EventListener: +CHECK_DELAY = 0.5 + +class EventListener: def __init__(self): self.helper = EventHelper() self.helper.init() self.device_names = {} - self.manager = DeviceManager(None) + self.device_manager = DeviceManager(None) + self.remove_count = 0 + self.restart_after_add_count = 0 + self.reinit_time = 0.0 + self.check_time = 0.0 + self.check_list = None def get_next_event(self): + # print(self.reinit_time - self.check_time) + if self.reinit_time > self.check_time: + delay = CHECK_DELAY + if self.check_time == 0.0: + # Force longer delay on startup to suppress false insert + # notifications on the initial set of devices. + delay = 2.5 + if time.time() - self.reinit_time > delay: + self.check_for_device_changes() + event = self.helper.get_next_event() if event is None: return None - if event["type"] in ["joy-device-added", "mouse-device-added", - "keyboard-device-added"]: + if self.helper.is_remove_event(event): + print("[INPUT] Remove event:", event) + # Restarting event helper so all devices are re-enumerated + self.remove_count += 1 + self.reinit() + + if self.helper.is_add_event(event): + if self.restart_after_add_count < self.remove_count: + # New device added after a device has been removed. Restarting + # device helper so device enumeration is consistent. + # Enumeration must be the same as emulators will do by + # themselves. + self.restart_after_add_count += 1 + self.reinit() + return + count = self.device_names.get(event["name"], 0) + 1 self.device_names[event["name"]] = count if count > 1: event["id"] = "{0} #{1}".format(event["name"], count) else: event["id"] = event["name"] - - if event["type"] == "joy-device-added": - self.manager.add_joystick_device(event) - elif event["type"] == "mouse-device-added": - self.manager.add_mouse_device(event) - elif event["type"] == "keyboard-device-added": - self.manager.add_keyboard_device(event) + # self.event_id = event["type"] + "-" + event["device"] + self.device_manager.add_device_from_event(event) + # self.notification((device.type, device.id)) + # self.check_for_device_changes() + self.reinit_time = time.time() return event + + def reinit(self): + self.device_manager.remove_all_devices() + self.device_names = {} + self.helper = EventHelper() + self.helper.init() + self.reinit_time = time.time() + + def check_for_device_changes(self): + print("[INPUT] Checking for device change notifications") + print(time.time(), self.reinit_time, self.check_time) + new_list = [] + for device in self.device_manager.get_devices(): + new_list.append((device.type, device.id)) + old_list = self.check_list + if old_list is not None: + print("[INPUT] Old:", old_list) + print("[INPUT] New:", new_list) + old_set = set(old_list) + new_set = set(new_list) + for item in old_list: + if item not in new_set: + self.notification(item, "Removed") + for item in new_list: + if item not in old_set: + self.notification(item, "Added") + self.check_list = new_list + self.check_time = time.time() + + def notification(self, item, action): + if item[0] == Device.TYPE_JOYSTICK: + text = "Joystick {}:\n{}".format(action, item[1]) + elif item[0] == Device.TYPE_KEYBOARD: + text = "Keyboard {}:\n{}".format(action, item[1]) + elif item[0] == Device.TYPE_MOUSE: + text = "Mouse {}:\n{}".format(action, item[1]) + else: + text = "Device {}:\n{}".format(action, item[1]) + Notification(text) diff -Nru fs-uae-arcade-2.9.6/fsgs/input/inputdevice.py fs-uae-arcade-2.9.7/fsgs/input/inputdevice.py --- fs-uae-arcade-2.9.6/fsgs/input/inputdevice.py 2017-05-25 10:07:53.000000000 +0000 +++ fs-uae-arcade-2.9.7/fsgs/input/inputdevice.py 2017-10-16 21:40:40.000000000 +0000 @@ -39,7 +39,7 @@ MissingPlatformSupportException = MissingPlatformSupportException def __init__(self, platform, name, sclist, sdl_name="", sdl_joystick_id=-1, - version=1, buttons=0, axes=0, hats=0, balls=0): + version=1, buttons=0, axes=0, hats=0, balls=0, multiple=True): """ sclist -- system controller list """ @@ -89,7 +89,7 @@ "No input device support for platform") elif version == 2: old_name = self.name - self.configure_version_2() + self.configure_version_2(multiple=multiple) if self.name != old_name: # print("****** name is now", self.name) print(self.id, "=>", repr(self.name)) @@ -281,8 +281,8 @@ # if cp.has_option('gamepad', value): # config[key] = cp.get('gamepad', value) - def configure_version_2(self): - self.config = self.configure(self.platform) + def configure_version_2(self, multiple=True): + self.config = self.configure(self.platform, multiple=multiple) def configure(self, platform, multiple=True): print("CONFIGURE", platform, self.name, self.sdl_name) @@ -311,6 +311,12 @@ config = {} try: + self.read_config(self.name, config, platform, multiple) + except Exception: + pass + else: + return config + try: self.read_config(self.sdl_name, config, platform, multiple) except Exception: pass diff -Nru fs-uae-arcade-2.9.6/fsgs/input/manager.py fs-uae-arcade-2.9.7/fsgs/input/manager.py --- fs-uae-arcade-2.9.6/fsgs/input/manager.py 2017-05-25 10:07:53.000000000 +0000 +++ fs-uae-arcade-2.9.7/fsgs/input/manager.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,61 +0,0 @@ -import sys -from .device import Device -from .enumeratehelper import EnumerateHelper - - -class DeviceManager(object): - - __instance = None - - @classmethod - def instance(cls): - if cls.__instance is None: - helper = EnumerateHelper() - cls.__instance = cls(helper) - return cls.__instance - - def __init__(self, helper): - self.helper = helper - self.devices = [] - if "--add-dummy-joystick" in sys.argv: - self.add_joystick_device({ - "axes": 2, - "hats": 0, - "balls": 0, - "buttons": 1, - "name": "Dummy Joystick", - "id": "Dummy Joystick", - }) - - def get_devices(self): - print("DeviceManager.get_devices") - if self.helper is not None: - self.helper.init() - return self.helper.devices - print("DeviceManager.get_devices ->", self.devices) - return self.devices - - def add_joystick_device(self, event): - device = Device() - device.axes = event["axes"] - device.balls = event["balls"] - device.hats = event["hats"] - device.buttons = event["buttons"] - device.name = event["name"] - device.id = event["id"] - device.type = Device.TYPE_JOYSTICK - self.devices.append(device) - - def add_keyboard_device(self, event): - device = Device() - device.name = event["name"] - device.id = event["id"] - device.type = Device.TYPE_KEYBOARD - self.devices.append(device) - - def add_mouse_device(self, event): - device = Device() - device.name = event["name"] - device.id = event["id"] - device.type = Device.TYPE_MOUSE - self.devices.append(device) diff -Nru fs-uae-arcade-2.9.6/fsgs/input/mapper.py fs-uae-arcade-2.9.7/fsgs/input/mapper.py --- fs-uae-arcade-2.9.6/fsgs/input/mapper.py 2017-05-25 10:07:53.000000000 +0000 +++ fs-uae-arcade-2.9.7/fsgs/input/mapper.py 2017-10-16 21:40:40.000000000 +0000 @@ -1,12 +1,17 @@ from fsbc.system import macosx +from fsgs.drivers.gamedriver import Port +from fsgs.input.device import Device class InputMapper(object): - - def __init__(self, input, mapping): - self.input = input - self.device = input.device + def __init__(self, port, mapping, multiple=True): + self.port = port # type: Port + self.device = port.device # type: Device self.mapping = mapping + self.multiple = multiple + print("[INPUT] Port", port.number, "Device:", + self.device.id if self.device is not None else "(none)") + print("[INPUT] Port", port.number, "mapping:", mapping) def items(self): # input = self.input @@ -14,7 +19,8 @@ # return if not self.device: return - config = self.device.configure(self.input.mapping_name) + config = self.device.configure( + self.port.mapping_name, multiple=self.multiple) # print(config) print("mapping:", self.mapping) for native_button, game_button in config.items(): @@ -89,7 +95,6 @@ class Key(object): - def __init__(self, name): self.name = name @@ -111,7 +116,6 @@ class Keyboard(object): - @staticmethod def key(name): if isinstance(name, int): diff -Nru fs-uae-arcade-2.9.6/fsgs/knownfiles.py fs-uae-arcade-2.9.7/fsgs/knownfiles.py --- fs-uae-arcade-2.9.6/fsgs/knownfiles.py 2017-05-25 10:07:53.000000000 +0000 +++ fs-uae-arcade-2.9.7/fsgs/knownfiles.py 2017-10-16 21:40:40.000000000 +0000 @@ -1,9 +1,25 @@ +import os from collections import namedtuple from fsgs.platforms import PLATFORM_AMIGA + +class KnownFilePath: + + def __init__(self, path=None): + self.__path = path + + @property + def path(self): + return self.__path + + def exists(self): + return os.path.exists(self.path) + + KnownFile = namedtuple("KnownFile", ["sha1", "platform", "name"]) KnownFileMod = namedtuple("KnownFileMod", ["sha1", "mod"]) + ACTION_REPLAY_MK_II_2_14_ROM = KnownFile( "255d6df63a4eab0a838eb1a16a267b0959dff634", PLATFORM_AMIGA, "Action Replay Mk II v2.14.rom") diff -Nru fs-uae-arcade-2.9.6/fsgs/network.py fs-uae-arcade-2.9.7/fsgs/network.py --- fs-uae-arcade-2.9.6/fsgs/network.py 2017-05-25 10:07:53.000000000 +0000 +++ fs-uae-arcade-2.9.7/fsgs/network.py 2017-10-16 21:40:40.000000000 +0000 @@ -4,6 +4,7 @@ from urllib.request import build_opener, HTTPBasicAuthHandler from fsbc.application import app +from fsbc.settings import Settings from fsgs.FSGSDirectories import FSGSDirectories @@ -27,7 +28,7 @@ def openretro_server(): - server = app.settings["database_server"] + server = Settings.instance()["database_server"] if not server: server = default_openretro_server_from_file() if not server: diff -Nru fs-uae-arcade-2.9.6/fsgs/ogd/client.py fs-uae-arcade-2.9.7/fsgs/ogd/client.py --- fs-uae-arcade-2.9.6/fsgs/ogd/client.py 2017-05-25 10:07:53.000000000 +0000 +++ fs-uae-arcade-2.9.7/fsgs/ogd/client.py 2017-10-16 21:40:40.000000000 +0000 @@ -11,6 +11,7 @@ from uuid import uuid4 from fsbc.application import app +from fsbc.settings import Settings from fsbc.task import Task from fsgs.network import openretro_http_connection, openretro_url_prefix, \ opener_for_url_prefix @@ -70,7 +71,7 @@ def is_logged_in(): # non-empty ogd_auth means we are logged in (probably, the # authorization can in theory have been invalidated on the server - return bool(app.settings["database_auth"]) + return bool(Settings.instance()["database_auth"]) def login_task(self, username, password): return LoginTask(self, username, password) @@ -106,7 +107,7 @@ @staticmethod def credentials(): - auth_token = app.settings["database_auth"] + auth_token = Settings.instance()["database_auth"] return "auth_token", auth_token def post(self, path, params=None, data=None, auth=True): @@ -210,19 +211,19 @@ def run(self): self.progressed("Logging into oagd.net...") - if not app.settings["device_id"]: - app.settings["device_id"] = str(uuid4()) + if not Settings.instance()["device_id"]: + Settings.instance()["device_id"] = str(uuid4()) try: result = self.client.auth( - self.username, self.password, app.settings["device_id"], + self.username, self.password, Settings.instance()["device_id"], get_device_name()) except UnauthorizedError: raise Task.Failure("Wrong e-mail address or password") - app.settings["database_username"] = result["username"] - app.settings["database_email"] = result["email"] - app.settings["database_auth"] = result["auth_token"] - app.settings["database_password"] = "" + Settings.instance()["database_username"] = result["username"] + Settings.instance()["database_email"] = result["email"] + Settings.instance()["database_auth"] = result["auth_token"] + Settings.instance()["database_password"] = "" class LogoutTask(Task): @@ -233,11 +234,11 @@ def run(self): self.progressed("Logging out from oagd.net...") - if not app.settings["device_id"]: - app.settings["device_id"] = str(uuid4()) + if not Settings.instance()["device_id"]: + Settings.instance()["device_id"] = str(uuid4()) self.client.deauth(self.auth_token) - app.settings["database_username"] = "" - # app.settings["database_email"] = "" - app.settings["database_auth"] = "" - app.settings["database_password"] = "" + Settings.instance()["database_username"] = "" + # Settings.instance()["database_email"] = "" + Settings.instance()["database_auth"] = "" + Settings.instance()["database_password"] = "" diff -Nru fs-uae-arcade-2.9.6/fsgs/ogd/locker.py fs-uae-arcade-2.9.7/fsgs/ogd/locker.py --- fs-uae-arcade-2.9.6/fsgs/ogd/locker.py 2017-05-25 10:07:53.000000000 +0000 +++ fs-uae-arcade-2.9.7/fsgs/ogd/locker.py 2017-10-16 21:40:40.000000000 +0000 @@ -2,7 +2,7 @@ from functools import lru_cache import fsbc.settings -from fsgs.FileDatabase import FileDatabase +from fsgs.filedatabase import FileDatabase from fsgs.LockerDatabase import LockerDatabase from fsgs.download import Downloader from fsgs.network import opener_for_url_prefix, openretro_url_prefix diff -Nru fs-uae-arcade-2.9.6/fsgs/option.py fs-uae-arcade-2.9.7/fsgs/option.py --- fs-uae-arcade-2.9.6/fsgs/option.py 2017-05-25 10:07:53.000000000 +0000 +++ fs-uae-arcade-2.9.7/fsgs/option.py 2017-10-16 21:40:40.000000000 +0000 @@ -23,6 +23,8 @@ ARCADE_FULLSCREEN = "arcade_fullscreen" ARCADE_INITIAL_FAVORITES = "arcade_initial_favorites" ARCADE_MAXIMIZED = "arcade_maximized" + ARCADE_SEARCH = "arcade_search" + ARCADE_SHUTDOWN = "arcade_shutdown" ARCADE_THEME = "arcade_theme" ASSUME_REFRESH_RATE = "assume_refresh_rate" ATARI_DATABASE = "atari_database" @@ -35,6 +37,7 @@ AUTO_QUIT = "auto_quit" AUTOMATIC_INPUT_GRAB = "automatic_input_grab" BASE_DIR = "base_dir" + BEZEL = "bezel" BLIZZARD_SCSI_KIT = "blizzard_scsi_kit" BORDER = "border" BSDSOCKET_LIBRARY = "bsdsocket_library" @@ -70,6 +73,7 @@ CDROM_IMAGE_9 = "cdrom_image_9" CDROMS_DIR = "cdroms_dir" CDTV_DATABASE = "cdtv_database" + CHEATS = "cheats" CHIP_MEMORY = "chip_memory" CLIPBOARD_SHARING = "clipboard_sharing" COMMAND = "command" @@ -82,6 +86,7 @@ CPUBOARD_FLASH_EXT_FILE = "cpuboard_flash_ext_file" CPUBOARD_FLASH_FILE = "cpuboard_flash_file" CROP = "crop" + CUE_SHEETS = "cue_sheets" CURSOR = "cursor" DATABASE_AUTH = "database_auth" DATABASE_EMAIL = "database_email" @@ -94,6 +99,7 @@ DATABASE_SHOW_UNPUBLISHED = "database_show_unpublished" DATABASE_USERNAME = "database_username" DETERMINISTIC = "deterministic" + DEVELOPER_MODE = "developer_mode" DEVICE_ID = "device_id" DISABLE_ASPECT_CORRECTION = "disable_aspect_correction" DONGLE_TYPE = "dongle_type" @@ -153,8 +159,9 @@ FLOPPY_IMAGE_9 = "floppy_image_9" FLOPPY_OVERLAYS_DIR = "floppy_overlays_dir" FMV_ROM = "fmv_rom" + FORCE_ASPECT = "force_aspect" FPU = "fpu" - FRAME = "frame" + FRAME_TIME = "frame_time" FREEZER_CARTRIDGE = "freezer_cartridge" FSAA = "fsaa" FULL_KEYBOARD = "full_keyboard" @@ -162,10 +169,12 @@ FULLSCREEN_HEIGHT = "fullscreen_height" FULLSCREEN_MODE = "fullscreen_mode" FULLSCREEN_WIDTH = "fullscreen_width" + G_SYNC = "g_sync" GAME_LIST_UUID = "game_list_uuid" GAME_NAME = "game_name" GB_DATABASE = "gb_database" GBA_DATABASE = "gba_database" + GBA_PORT_1_TYPE = "gba_port_1_type" GBC_DATABASE = "gbc_database" GRAB_INPUT = "grab_input" GRAPHICS_CARD = "graphics_card" @@ -256,6 +265,8 @@ LOGS_DIR = "logs_dir" LOW_LATENCY_VSYNC = "low_latency_vsync" LOW_RESOLUTION = "low_resolution" + MAME_ARTWORK = "mame_artwork" + MEDNAFEN_AUDIO_BUFFER = "mednafen_audio_buffer" MEDNAFEN_AUDIO_DRIVER = "mednafen_audio_driver" MIDDLE_CLICK_UNGRAB = "middle_click_ungrab" MIN_FIRST_LINE_NTSC = "min_first_line_ntsc" @@ -266,25 +277,41 @@ MOUSE_INTEGRATION = "mouse_integration" MOUSE_SPEED = "mouse_speed" MSX_DATABASE = "msx_database" + N64_DATABASE = "n64_database" + NEOGEO_DATABASE = "neogeo_database" + NEOGEO_MODEL = "neogeo_model" + NEOGEO_PORT_1_TYPE = "neogeo_port_1_type" + NEOGEO_PORT_2_TYPE = "neogeo_port_2_type" NES_DATABASE = "nes_database" NES_DRIVER = "nes_driver" + NES_EMULATOR = "nes_emulator" + NES_INES_HEADER = "nes_ines_header" + NES_MODEL = "nes_model" + NES_PORT_1_TYPE = "nes_port_1_type" + NES_PORT_2_TYPE = "nes_port_2_type" + NES_PORT_3_TYPE = "nes_port_3_type" + NES_PORT_4_TYPE = "nes_port_4_type" NETPLAY_FEATURE = "netplay_feature" NETPLAY_PASSWORD = "netplay_password" NETPLAY_PORT = "netplay_port" NETPLAY_SERVER = "netplay_server" NETPLAY_TAG = "netplay_tag" NETWORK_CARD = "network_card" + NGC_DATABASE = "ngc_database" NOTIFICATION_DURATION = "notification_duration" NTSC_MODE = "ntsc_mode" + ORIENTATION = "orientation" PLATFORM = "platform" PLATFORMS_FEATURE = "platforms_feature" PRIMARY_JOYSTICK = "primary_joystick" PSX_DATABASE = "psx_database" QUICK_SETTINGS = "quick_settings" + QUICK_SETTINGS_OPTIONS = "quick_settings_options" RAW_INPUT = "raw_input" REFRESH_RATE = "refresh_rate" RELATIVE_PATHS = "relative_paths" RELATIVE_TEMP_FEATURE = "relative_temp_feature" + RETROARCH_AUDIO_BUFFER = "retroarch_audio_buffer" RTG_SCANLINES = "rtg_scanlines" RTG_VIEWPORT = "rtg_viewport" SAVE_DISK = "save_disk" @@ -314,6 +341,8 @@ SMS_DRIVER = "sms_driver" SNAPSHOT_FILE = "snapshot_file" SNES_DATABASE = "snes_database" + SNES_PORT_1_TYPE = "snes_port_1_type" + SNES_PORT_2_TYPE = "snes_port_2_type" SOUND_CARD = "sound_card" SRAM_TYPE = "sram_type" STATE_DIR = "state_dir" @@ -327,6 +356,7 @@ TEXTURE_FILTER = "texture_filter" TEXTURE_FORMAT = "texture_format" TG16_DATABASE = "tg16_database" + TGCD_DATABASE = "tgcd_database" THEME = "theme" THEME_CUSTOM_0 = "theme_custom_0" THEME_CUSTOM_0_X = "theme_custom_0_x" diff -Nru fs-uae-arcade-2.9.6/fsgs/platform.py fs-uae-arcade-2.9.7/fsgs/platform.py --- fs-uae-arcade-2.9.6/fsgs/platform.py 2017-05-25 10:07:53.000000000 +0000 +++ fs-uae-arcade-2.9.7/fsgs/platform.py 2017-10-16 21:40:40.000000000 +0000 @@ -2,12 +2,13 @@ import os from functools import lru_cache +from fsgs.drivers.gamedriver import GameDriver from fsgs.option import Option from fsgs.platforms.c64 import C64_MODEL_C64C from fsgs.platforms.c64.vicec64driver import ViceC64Driver -class PlatformHandler(object): +class PlatformHandler: def __init__(self, loader_class=None, runner_class=None): self.loader_class = loader_class self.runner_class = runner_class @@ -40,17 +41,66 @@ raise Exception("loader class is None for " + repr(self)) return self.loader_class(fsgs) - def get_runner(self, fsgs): + def get_runner(self, fsgs) -> GameDriver: if self.runner_class is None: raise Exception("runner class is None for " + repr(self)) return self.runner_class(fsgs) + def driver(self, fsgc): + return self.get_runner(fsgc) + + def loader(self, fsgc): + return self.get_loader(fsgc) + + +class Platform(PlatformHandler): + AMIGA = "amiga" + ARCADE = "arcade" + A2600 = "a2600" + A5200 = "a5200" + A7800 = "a7800" + ATARI = "atari" + C64 = "c64" + CD32 = "cd32" + CDTV = "cdtv" + CPC = "cpc" + DOS = "dos" + GB = "gb" + GBA = "gba" + GBC = "gbc" + GAME_GEAR = "game-gear" + LYNX = "lynx" + MSX = "msx" + N64 = "n64" + NEOGEO = "neogeo" + NES = "nes" + NGC = "ngc" + PSX = "psx" + SNES = "snes" + SMD = "smd" + SMS = "sms" + TG16 = "tg16" + TGCD = "tgcd" + ZXS = "zxs" + + def get_loader(self, fsgc): + return self.loader(fsgc) + + def get_runner(self, fsgc): + return self.driver(fsgc) + + def driver(self, fsgc): + raise NotImplementedError() + + def loader(self, fsgc): + raise NotImplementedError() + from fsgs.platforms.amiga import AmigaPlatformHandler from fsgs.platforms.amstrad_cpc import AmstradCPCPlatformHandler from fsgs.platforms.arcade.arcadeplatform import ArcadePlatformHandler from fsgs.platforms.atari_2600 import Atari2600PlatformHandler -from fsgs.platforms.atari_5200 import Atari5200PlatformHandler +from fsgs.platforms.atari5200 import Atari5200PlatformHandler from fsgs.platforms.atari_7800 import Atari7800PlatformHandler from fsgs.platforms.atari.atariplatform import AtariSTPlatformHandler from fsgs.platforms.cd32 import CD32PlatformHandler @@ -63,12 +113,16 @@ from fsgs.platforms.loader import SimpleLoader from fsgs.platforms.lynx import LynxPlatformHandler from fsgs.platforms.master_system import MasterSystemPlatformHandler -from fsgs.platforms.mega_drive import MegaDrivePlatformHandler +from fsgs.platforms.megadrive import MegaDrivePlatform from fsgs.platforms.msx import MsxPlatformHandler -from fsgs.platforms.nintendo import NintendoPlatformHandler +from fsgs.platforms.nintendo64 import Nintendo64Platform +from fsgs.platforms.neogeo import NeoGeoPlatform +from fsgs.platforms.ngc.ngcplatform import NGCPlatformHandler +from fsgs.platforms.nintendo import NintendoPlatform from fsgs.platforms.psx.psxplatform import PlayStationPlatformHandler -from fsgs.platforms.super_nintendo import SuperNintendoPlatformHandler -from fsgs.platforms.turbografx_16 import TurboGrafx16PlatformHandler +from fsgs.platforms.supernintendo import SuperNintendoPlatformHandler +from fsgs.platforms.turbografx16 import TurboGrafx16Platform +from fsgs.platforms.turbografxcd import TurboGrafxCDPlatform from fsgs.platforms.zxs import SpectrumPlatformHandler @@ -110,32 +164,6 @@ super().__init__(C64Loader, ViceC64Driver) -class Platform: - AMIGA = "amiga" - ARCADE = "arcade" - A2600 = "a2600" - A5200 = "a5200" - A7800 = "a7800" - ATARI = "atari" - C64 = "c64" - CD32 = "cd32" - CDTV = "cdtv" - CPC = "cpc" - DOS = "dos" - GB = "gb" - GBA = "gba" - GBC = "gbc" - GAME_GEAR = "game-gear" - LYNX = "lynx" - MSX = "msx" - NES = "nes" - PSX = "psx" - SNES = "snes" - SMD = "smd" - SMS = "sms" - TG16 = "tg16" - ZXS = "zxs" - platforms = { Platform.AMIGA: AmigaPlatformHandler, Platform.ARCADE: ArcadePlatformHandler, @@ -154,12 +182,16 @@ Platform.GAME_GEAR: GameGearPlatformHandler, Platform.LYNX: LynxPlatformHandler, Platform.MSX: MsxPlatformHandler, - Platform.NES: NintendoPlatformHandler, + Platform.N64: Nintendo64Platform, + Platform.NEOGEO: NeoGeoPlatform, + Platform.NES: NintendoPlatform, + Platform.NGC: NGCPlatformHandler, Platform.SNES: SuperNintendoPlatformHandler, Platform.PSX: PlayStationPlatformHandler, - Platform.SMD: MegaDrivePlatformHandler, + Platform.SMD: MegaDrivePlatform, Platform.SMS: MasterSystemPlatformHandler, - Platform.TG16: TurboGrafx16PlatformHandler, + Platform.TG16: TurboGrafx16Platform, + Platform.TGCD: TurboGrafxCDPlatform, Platform.ZXS: SpectrumPlatformHandler, } PLATFORM_IDS = platforms.keys() @@ -186,13 +218,17 @@ return Platform.GBC elif platform_id in ["nintendo", "famicom"]: return Platform.NES + elif platform_id in ["nintendo64"]: + return Platform.N64 + elif platform_id in ["nintendogamecube", "gamecube", "gc"]: + return Platform.NGC elif platform_id in ["supernintendo", "supernes", "superfamicom"]: return Platform.SNES elif platform_id in ["zxspectrum"]: return Platform.ZXS - elif platform_id in ["mastersystem"]: + elif platform_id in ["mastersystem", "segamastersystem"]: return Platform.SMS - elif platform_id in ["megadrive"]: + elif platform_id in ["megadrive", "segamegadrive", "genesis"]: return Platform.SMD elif platform_id in ["atari2600"]: return Platform.A2600 diff -Nru fs-uae-arcade-2.9.6/fsgs/platforms/a5200/messa5200driver.py fs-uae-arcade-2.9.7/fsgs/platforms/a5200/messa5200driver.py --- fs-uae-arcade-2.9.6/fsgs/platforms/a5200/messa5200driver.py 2017-05-25 10:07:53.000000000 +0000 +++ fs-uae-arcade-2.9.7/fsgs/platforms/a5200/messa5200driver.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,61 +0,0 @@ -from fsgs.drivers.messdriver import MessDriver - - -class MessA5200Driver(MessDriver): - CONTROLLER = { - "type": "controller", - "description": "Controller", - "mapping_name": "atari5200", - } - - PORTS = [ - { - "description": "1st Controller", - "types": [CONTROLLER] - }, { - "description": "2nd Controller", - "types": [CONTROLLER] - }, - ] - - def get_game_refresh_rate(self): - # no PAL version of Atari 5200 - return 59.94 - - def mess_configure(self): - self.mess_configure_cartridge() - - def mess_input_mapping(self, port): - return { - "START": 'type="P#_START"', - "PAUSE": 'tag="keypad_2" type="KEYPAD" mask="1" defvalue="0"', - "RESET": 'tag="keypad_1" type="KEYPAD" mask="1" defvalue="0"', - "RIGHT": ('type="P#_AD_STICK_X"', 'increment'), - "LEFT": ('type="P#_AD_STICK_X"', 'decrement'), - "UP": ('type="P#_AD_STICK_Y"', 'decrement'), - "DOWN": ('type="P#_AD_STICK_Y"', 'increment'), - "1": "P#_BUTTON1", - "2": "P#_BUTTON2", - "PAD#": 'tag="keypad_0" type="KEYPAD" mask="2" defvalue="0"', - "PAD0": 'tag="keypad_0" type="KEYPAD" mask="4" defvalue="0"', - "PAD*": 'tag="keypad_0" type="KEYPAD" mask="8" defvalue="0"', - "PAD9": 'tag="keypad_1" type="KEYPAD" mask="2" defvalue="0"', - "PAD8": 'tag="keypad_1" type="KEYPAD" mask="4" defvalue="0"', - "PAD7": 'tag="keypad_1" type="KEYPAD" mask="8" defvalue="0"', - "PAD6": 'tag="keypad_2" type="KEYPAD" mask="2" defvalue="0"', - "PAD5": 'tag="keypad_2" type="KEYPAD" mask="4" defvalue="0"', - "PAD4": 'tag="keypad_2" type="KEYPAD" mask="8" defvalue="0"', - "PAD3": 'tag="keypad_3" type="KEYPAD" mask="2" defvalue="0"', - "PAD2": 'tag="keypad_3" type="KEYPAD" mask="4" defvalue="0"', - "PAD1": 'tag="keypad_3" type="KEYPAD" mask="8" defvalue="0"', - } - - def mess_romset(self): - return "a5200", A5200_ROMS - - -# noinspection SpellCheckingInspection -A5200_ROMS = { - "6ad7a1e8c9fad486fbec9498cb48bf5bc3adc530": "5200.rom", - "1d2a3f00109d75d2d79fecb565775eb95b7d04d5": "5200a.rom", -} diff -Nru fs-uae-arcade-2.9.6/fsgs/platforms/a7800/messa7800driver.py fs-uae-arcade-2.9.7/fsgs/platforms/a7800/messa7800driver.py --- fs-uae-arcade-2.9.6/fsgs/platforms/a7800/messa7800driver.py 2017-05-25 10:07:53.000000000 +0000 +++ fs-uae-arcade-2.9.7/fsgs/platforms/a7800/messa7800driver.py 2017-10-16 21:40:40.000000000 +0000 @@ -18,6 +18,11 @@ }, ] + def prepare(self): + super().prepare() + # self.emulator.args.extend( + # ["-lightgun", "-lightgun_device", "mouse"]) + def mess_configure(self): self.mess_configure_cartridge() diff -Nru fs-uae-arcade-2.9.6/fsgs/platforms/amiga.py fs-uae-arcade-2.9.7/fsgs/platforms/amiga.py --- fs-uae-arcade-2.9.6/fsgs/platforms/amiga.py 2017-05-25 10:07:53.000000000 +0000 +++ fs-uae-arcade-2.9.7/fsgs/platforms/amiga.py 2017-10-16 21:40:40.000000000 +0000 @@ -1,3 +1,4 @@ +from fsgs.drivers.gamedriver import GameDriver from fsgs.platform import PlatformHandler from fsgs.amiga.valueconfigloader import ValueConfigLoader from fsgs.amiga.fsuaeamigadriver import FSUAEAmigaDriver @@ -14,5 +15,5 @@ loader.uuid = fsgs.game.variant.uuid return loader - def get_runner(self, fsgs): + def get_runner(self, fsgs) -> GameDriver: return FSUAEAmigaDriver(fsgs) diff -Nru fs-uae-arcade-2.9.6/fsgs/platforms/arcade/arcadeplatform.py fs-uae-arcade-2.9.7/fsgs/platforms/arcade/arcadeplatform.py --- fs-uae-arcade-2.9.6/fsgs/platforms/arcade/arcadeplatform.py 2017-05-25 10:07:53.000000000 +0000 +++ fs-uae-arcade-2.9.7/fsgs/platforms/arcade/arcadeplatform.py 2017-10-16 21:40:40.000000000 +0000 @@ -29,4 +29,6 @@ def load_extra(self, values): if "refresh_rate" in values: self.config["refresh_rate"] = values["refresh_rate"] + if "orientation" in values: + self.config["orientation"] = values["orientation"] self.config["mame_rom_set"] = values["mame_rom_set"] diff -Nru fs-uae-arcade-2.9.6/fsgs/platforms/arcade/mamearcadedriver.py fs-uae-arcade-2.9.7/fsgs/platforms/arcade/mamearcadedriver.py --- fs-uae-arcade-2.9.6/fsgs/platforms/arcade/mamearcadedriver.py 2017-05-25 10:07:53.000000000 +0000 +++ fs-uae-arcade-2.9.7/fsgs/platforms/arcade/mamearcadedriver.py 2017-10-16 21:40:40.000000000 +0000 @@ -1,5 +1,6 @@ import json +from fsgs import Option from fsgs.drivers.mamedriver import MameDriver @@ -12,25 +13,25 @@ PORTS = [ { - "description": "1st Controller", + "description": "Player 1", "types": [CONTROLLER] }, { - "description": "2nd Controller", + "description": "Player 2", "types": [CONTROLLER] }, { - "description": "3rd Controller", + "description": "Player 3", "types": [CONTROLLER] }, { - "description": "4th Controller", + "description": "Player 4", "types": [CONTROLLER] }, ] def mame_romset(self): - name = self.config["mame_rom_set"] + name = self.options["mame_rom_set"] files = {} - for entry in json.loads(self.config["file_list"]): - files[entry["sha1"]] = entry["name"] + for entry in json.loads(self.options["file_list"]): + files[entry["name"]] = entry["sha1"] return name, files def mame_input_mapping(self, _): @@ -51,3 +52,10 @@ b += 1 mapping[str(b)] = "P#_BUTTON" + button return mapping + + def prepare(self): + super().prepare() + if self.options[Option.MAME_ARTWORK] == "1": + pass + else: + self.create_mame_layout() diff -Nru fs-uae-arcade-2.9.6/fsgs/platforms/atari_5200.py fs-uae-arcade-2.9.7/fsgs/platforms/atari_5200.py --- fs-uae-arcade-2.9.6/fsgs/platforms/atari_5200.py 2017-05-25 10:07:53.000000000 +0000 +++ fs-uae-arcade-2.9.7/fsgs/platforms/atari_5200.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,20 +0,0 @@ -from fsgs.platform import PlatformHandler -from fsgs.platforms.a5200.messa5200driver import MessA5200Driver -from fsgs.platforms.loader import SimpleLoader - - -class Atari5200PlatformHandler(PlatformHandler): - PLATFORM_NAME = "Atari 5200" - - def __init__(self): - PlatformHandler.__init__(self) - - def get_loader(self, fsgs): - return Atari5200Loader(fsgs) - - def get_runner(self, fsgs): - return MessA5200Driver(fsgs) - - -class Atari5200Loader(SimpleLoader): - pass diff -Nru fs-uae-arcade-2.9.6/fsgs/platforms/atari5200.py fs-uae-arcade-2.9.7/fsgs/platforms/atari5200.py --- fs-uae-arcade-2.9.6/fsgs/platforms/atari5200.py 1970-01-01 00:00:00.000000000 +0000 +++ fs-uae-arcade-2.9.7/fsgs/platforms/atari5200.py 2017-10-16 21:40:40.000000000 +0000 @@ -0,0 +1,92 @@ +import filecmp +import os + +import shutil + +from fsgs.drivers.messdriver import MessDriver +from fsgs.knownfiles import KnownFile +from fsgs.platform import Platform +from fsgs.platforms.loader import SimpleLoader +from fsgs.plugins.plugin_manager import PluginManager + +# noinspection SpellCheckingInspection +A5200_ROM = KnownFile( + "6ad7a1e8c9fad486fbec9498cb48bf5bc3adc530", "A5200", "5200.rom") +# noinspection SpellCheckingInspection +A5200_ROM_ALT = KnownFile( + "1d2a3f00109d75d2d79fecb565775eb95b7d04d5", "A5200", "5200a.rom") +A5200_CONTROLLER = { + "type": "controller", + "description": "Controller", + "mapping_name": "atari5200", +} + + +class Atari5200PlatformHandler(Platform): + PLATFORM_ID = "A5200" + PLATFORM_NAME = "Atari 5200" + + def driver(self, fsgs): + return Atari5200MessDriver(fsgs) + + def loader(self, fsgs): + return Atari5200Loader(fsgs) + + +class Atari5200Loader(SimpleLoader): + pass + + +class Atari5200MessDriver(MessDriver): + PORTS = [ + { + "description": "Input Port 1", + "types": [A5200_CONTROLLER] + }, { + "description": "Input Port 2", + "types": [A5200_CONTROLLER] + }, + ] + + def prepare(self): + super().prepare() + self.install_mame_hash_file("a5200.hsi") + self.install_mame_hash_file("a5200.xml") + + def get_game_refresh_rate(self): + # There were no PAL version of Atari 5200 + return 59.94 + + def mess_configure(self): + self.mess_configure_cartridge() + + def mess_input_mapping(self, port): + return { + "START": 'type="P#_START"', + "PAUSE": 'tag="keypad_2" type="KEYPAD" mask="1" defvalue="0"', + "RESET": 'tag="keypad_1" type="KEYPAD" mask="1" defvalue="0"', + "RIGHT": ('type="P#_AD_STICK_X"', 'increment'), + "LEFT": ('type="P#_AD_STICK_X"', 'decrement'), + "UP": ('type="P#_AD_STICK_Y"', 'decrement'), + "DOWN": ('type="P#_AD_STICK_Y"', 'increment'), + "1": "P#_BUTTON1", + "2": "P#_BUTTON2", + "PAD#": 'tag="keypad_0" type="KEYPAD" mask="2" defvalue="0"', + "PAD0": 'tag="keypad_0" type="KEYPAD" mask="4" defvalue="0"', + "PAD*": 'tag="keypad_0" type="KEYPAD" mask="8" defvalue="0"', + "PAD9": 'tag="keypad_1" type="KEYPAD" mask="2" defvalue="0"', + "PAD8": 'tag="keypad_1" type="KEYPAD" mask="4" defvalue="0"', + "PAD7": 'tag="keypad_1" type="KEYPAD" mask="8" defvalue="0"', + "PAD6": 'tag="keypad_2" type="KEYPAD" mask="2" defvalue="0"', + "PAD5": 'tag="keypad_2" type="KEYPAD" mask="4" defvalue="0"', + "PAD4": 'tag="keypad_2" type="KEYPAD" mask="8" defvalue="0"', + "PAD3": 'tag="keypad_3" type="KEYPAD" mask="2" defvalue="0"', + "PAD2": 'tag="keypad_3" type="KEYPAD" mask="4" defvalue="0"', + "PAD1": 'tag="keypad_3" type="KEYPAD" mask="8" defvalue="0"', + } + + def mess_romset(self): + return "a5200", { + A5200_ROM.sha1: "5200.rom", + A5200_ROM_ALT.sha1: "5200a.rom", + } diff -Nru fs-uae-arcade-2.9.6/fsgs/platforms/c64/vicec64driver.py fs-uae-arcade-2.9.7/fsgs/platforms/c64/vicec64driver.py --- fs-uae-arcade-2.9.6/fsgs/platforms/c64/vicec64driver.py 2017-05-25 10:07:53.000000000 +0000 +++ fs-uae-arcade-2.9.7/fsgs/platforms/c64/vicec64driver.py 2017-10-16 21:40:40.000000000 +0000 @@ -114,13 +114,13 @@ if self.helper.model() == C64_MODEL_C64: self.set_model_name("Commodore C64") - self.args.extend(["-model", "c64"]) + self.emulator.args.extend(["-model", "c64"]) elif self.helper.model() == C64_MODEL_C64C_1541_II: self.set_model_name("Commodore C64C 1541-II") - self.args.extend(["-model", "c64c"]) + self.emulator.args.extend(["-model", "c64c"]) else: self.set_model_name("Commodore C64C") - self.args.extend(["-model", "c64c"]) + self.emulator.args.extend(["-model", "c64c"]) media_keys = ["floppy_drive_0", "tape_drive_0"] for i in range(20): diff -Nru fs-uae-arcade-2.9.6/fsgs/platforms/gba/mednafengbadriver.py fs-uae-arcade-2.9.7/fsgs/platforms/gba/mednafengbadriver.py --- fs-uae-arcade-2.9.6/fsgs/platforms/gba/mednafengbadriver.py 2017-05-25 10:07:53.000000000 +0000 +++ fs-uae-arcade-2.9.7/fsgs/platforms/gba/mednafengbadriver.py 2017-10-16 21:40:40.000000000 +0000 @@ -3,23 +3,27 @@ from fsgs.drivers.mednafendriver import MednafenDriver +# FIXME: Use KnownFile # [BIOS] Game Boy Advance (World).gba GBA_WORLD_BIOS_SHA1 = "300c20df6731a33952ded8c436f7f186d25d3492" +GBA_CONTROLLER = { + "type": "gamepad", + "description": "Built-in Controller", + "mapping_name": "gameboyadvance", +} +GBA_PORTS = [ + { + "description": "Built-in", + "types": [GBA_CONTROLLER], + "type_option": "gba_port_1_type", + "device_option": "gba_port_1", + }, +] + class MednafenGbaDriver(MednafenDriver): - CONTROLLER = { - "type": "gamepad", - "description": "Built-in Controller", - "mapping_name": "gameboyadvance", - } - - PORTS = [ - { - "description": "Controller", - "types": [CONTROLLER] - }, - ] + PORTS = GBA_PORTS def __init__(self, fsgs): super().__init__(fsgs) diff -Nru fs-uae-arcade-2.9.6/fsgs/platforms/__init__.py fs-uae-arcade-2.9.7/fsgs/platforms/__init__.py --- fs-uae-arcade-2.9.6/fsgs/platforms/__init__.py 2017-05-25 10:07:53.000000000 +0000 +++ fs-uae-arcade-2.9.7/fsgs/platforms/__init__.py 2017-10-16 21:40:40.000000000 +0000 @@ -33,6 +33,7 @@ # PLATFORM_LYNX = "lynx" PLATFORM_SMS = "sms" PLATFORM_SMD = "smd" +PLATFORM_N64 = "n64" PLATFORM_NES = "nes" PLATFORM_SNES = "snes" PLATFORM_TG16 = "tg16" diff -Nru fs-uae-arcade-2.9.6/fsgs/platforms/loader.py fs-uae-arcade-2.9.7/fsgs/platforms/loader.py --- fs-uae-arcade-2.9.6/fsgs/platforms/loader.py 2017-05-25 10:07:53.000000000 +0000 +++ fs-uae-arcade-2.9.7/fsgs/platforms/loader.py 2017-10-16 21:40:40.000000000 +0000 @@ -24,14 +24,15 @@ self.config["cue_sheets"] = values["cue_sheets"] - def load_extra(self, values): pass def load_basic(self, values): self.config["command"] = values["command"] self.config["game_name"] = values["game_name"] + self.config["game_uuid"] = values["game_uuid"] self.config["variant_name"] = values["variant_name"] + self.config["variant_uuid"] = values["variant_uuid"] self.config["model"] = values["model"] self.config["platform"] = values["platform"] self.config["protection"] = values["protection"] @@ -78,3 +79,17 @@ self.load_files(values) return self.get_config() + + +class PlatformLoader(SimpleLoader): + pass + + +class CartridgePlatformLoader(PlatformLoader): + pass + + +class CDPlatformLoader(PlatformLoader): + def load_files(self, values): + self.config["cue_sheets"] = values["cue_sheets"] + self.config["file_list"] = values["file_list"] diff -Nru fs-uae-arcade-2.9.6/fsgs/platforms/mega_drive.py fs-uae-arcade-2.9.7/fsgs/platforms/mega_drive.py --- fs-uae-arcade-2.9.6/fsgs/platforms/mega_drive.py 2017-05-25 10:07:53.000000000 +0000 +++ fs-uae-arcade-2.9.7/fsgs/platforms/mega_drive.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,38 +0,0 @@ -from fsbc import settings -from fsgs.drivers.mess.messsmddriver import MessSmdDriver -from fsgs.option import Option -from fsgs.platform import PlatformHandler -from fsgs.platforms.loader import SimpleLoader -from fsgs.platforms.smd.mednafensmddriver import MednafenSmdDriver - - -class MegaDrivePlatformHandler(PlatformHandler): - PLATFORM_NAME = "Mega Drive" - - def __init__(self): - PlatformHandler.__init__(self) - - def get_loader(self, fsgs): - return MegaDriveLoader(fsgs) - - def get_runner(self, fsgs): - if settings.get(Option.SMD_DRIVER) == "mess": - return MessSmdDriver(fsgs) - else: - return MednafenSmdDriver(fsgs) - - -class MegaDriveLoader(SimpleLoader): - def load_extra(self, values): - self.config[Option.SMD_MODEL] = values["model"] - if not self.config[Option.SMD_MODEL]: - variant = values["variant_name"].lower() - if "world" in variant or "usa" in variant: - model = "ntsc-u" - elif "europe" in variant or "australia" in variant: - model = "pal" - elif "japan" in variant: - model = "ntsc-j" - else: - model = "auto" - self.config[Option.SMD_MODEL] = model diff -Nru fs-uae-arcade-2.9.6/fsgs/platforms/megadrive.py fs-uae-arcade-2.9.7/fsgs/platforms/megadrive.py --- fs-uae-arcade-2.9.6/fsgs/platforms/megadrive.py 1970-01-01 00:00:00.000000000 +0000 +++ fs-uae-arcade-2.9.7/fsgs/platforms/megadrive.py 2017-10-16 21:40:40.000000000 +0000 @@ -0,0 +1,158 @@ +from fsbc import settings +from fsgs.drivers.mednafendriver import MednafenDriver +from fsgs.drivers.mess.messsmddriver import MessSmdDriver +from fsgs.option import Option +from fsgs.platform import Platform +from fsgs.platforms.loader import SimpleLoader + +SMD_MODEL_NTSC_U = "ntsc" +SMD_MODEL_NTSC_J = "ntsc-j" +SMD_MODEL_PAL = "pal" +# FIXME: REMOVE MODEL AUTO? It makes it difficult to predict video size +# without inspecting ROM file. +SMD_MODEL_AUTO = "auto" +SMD_CONTROLLER = { + "type": "gamepad", + "description": "Gamepad", + "mapping_name": "megadrive", +} +SMD_PORTS = [ + { + "description": "Port 1", + "types": [SMD_CONTROLLER] + }, { + "description": "Port 2", + "types": [SMD_CONTROLLER] + }, +] + + +class MegaDrivePlatform(Platform): + PLATFORM_NAME = "Mega Drive" + + def driver(self, fsgc): + if settings.get(Option.SMD_DRIVER) == "mess": + return MessSmdDriver(fsgc) + else: + return MegaDriveMednafenDriver(fsgc) + + def loader(self, fsgc): + return MegaDriveLoader(fsgc) + + +class MegaDriveLoader(SimpleLoader): + def load_extra(self, values): + self.config[Option.SMD_MODEL] = values["smd_model"] + # self.config[Option.SMD_PORT_1_TYPE] = values["smd_port_1_type"] + # self.config[Option.SMD_PORT_2_TYPE] = values["smd_port_2_type"] + # self.config[Option.SMD_PORT_3_TYPE] = values["smd_port_3_type"] + # self.config[Option.SMD_PORT_4_TYPE] = values["smd_port_4_type"] + + # FIXME: Remove later when database contains smd_model + if not self.config[Option.SMD_MODEL]: + variant = values["variant_name"].lower() + if "world" in variant or "usa" in variant: + model = SMD_MODEL_NTSC_U + elif "europe" in variant or "australia" in variant: + model = SMD_MODEL_PAL + elif "japan" in variant: + model = SMD_MODEL_NTSC_J + else: + # FIXME: Remove? + model = SMD_MODEL_AUTO + self.config[Option.SMD_MODEL] = model + + +class MegaDriveMednafenDriver(MednafenDriver): + PORTS = SMD_PORTS + + def __init__(self, fsgs): + super().__init__(fsgs) + self.helper = MegaDriveHelper(self.options) + + def prepare(self): + self.init_mega_drive_model() + self.init_mednafen_crop_from_viewport() + self.set_mednafen_aspect(4, 3) + # We do aspect calculation separately. Must not be done twice. + self.emulator.args.extend(["-md.correct_aspect", "0"]) + # The arguments must be added before the cartridge, which is why + # we call super().prepare() at the end here. + super().prepare() + + def init_mega_drive_model(self): + if self.helper.model() == SMD_MODEL_NTSC_U: + self.set_model_name("Genesis") + region = "overseas_ntsc" + elif self.helper.model() == SMD_MODEL_PAL: + self.set_model_name("Mega Drive PAL") + region = "overseas_pal" + elif self.helper.model() == SMD_MODEL_NTSC_J: + self.set_model_name("Mega Drive NTSC-J") + region = "domestic_ntsc" + else: + # FIXME: This model might disappear + self.set_model_name("Mega Drive / Genesis") + region = "game" + self.emulator.args.extend(["-md.region", region]) + + def init_mednafen_crop_from_viewport(self): + viewport = self.options[Option.VIEWPORT] + if viewport: + if viewport.startswith("0 0 320 240 ="): + viewport_src, viewport = self.mednafen_viewport() + self.emulator.env["FSGS_CROP"] = "{},{},{},{}".format( + viewport[0], viewport[1], viewport[2], viewport[3]) + + def mednafen_system_prefix(self): + return "md" + + def mednafen_input_mapping(self, port): + n = port + 1 + return { + "A": "md.input.port{}.gamepad.a".format(n), + "B": "md.input.port{}.gamepad.b".format(n), + "C": "md.input.port{}.gamepad.c".format(n), + "X": "md.input.port{}.gamepad.x".format(n), + "Y": "md.input.port{}.gamepad.y".format(n), + "Z": "md.input.port{}.gamepad.z".format(n), + "UP": "md.input.port{}.gamepad.up".format(n), + "DOWN": "md.input.port{}.gamepad.down".format(n), + "LEFT": "md.input.port{}.gamepad.left".format(n), + "RIGHT": "md.input.port{}.gamepad.right".format(n), + "MODE": "md.input.port{}.gamepad.mode".format(n), + "START": "md.input.port{}.gamepad.start".format(n), + } + + # FIXME: add PAUSE button to universal gamepad config + + # def game_video_par(self): + # # These may not be entirely correct... + # if self.is_pal(): + # return (4 / 3) / (320 / 240) + # else: + # return (4 / 3) / (320 / 224) + + def game_video_size(self): + viewport_src, viewport = self.mednafen_viewport() + if viewport is not None: + return viewport[2], viewport[3] + if self.helper.model() == SMD_MODEL_PAL: + return 320, 240 + else: + return 320, 224 + + +class MegaDriveHelper: + def __init__(self, options): + self.options = options + + def model(self): + if self.options[Option.SMD_MODEL] == SMD_MODEL_NTSC_U: + return SMD_MODEL_NTSC_U + if self.options[Option.SMD_MODEL] == SMD_MODEL_NTSC_J: + return SMD_MODEL_NTSC_J + if self.options[Option.SMD_MODEL] == SMD_MODEL_PAL: + return SMD_MODEL_PAL + # FIXME: REMOVE? + return SMD_MODEL_AUTO diff -Nru fs-uae-arcade-2.9.6/fsgs/platforms/neogeo.py fs-uae-arcade-2.9.7/fsgs/platforms/neogeo.py --- fs-uae-arcade-2.9.6/fsgs/platforms/neogeo.py 1970-01-01 00:00:00.000000000 +0000 +++ fs-uae-arcade-2.9.7/fsgs/platforms/neogeo.py 2017-10-16 21:40:40.000000000 +0000 @@ -0,0 +1,162 @@ +import json + +from fsgs.drivers.mamedriver import MameDriver +from fsgs.option import Option +from fsgs.platform import Platform +from fsgs.platforms.arcade.arcadeplatform import ArcadeLoader + +NEOGEO_PLATFORM_ID = "neogeo" +NEOGEO_PLATFORM_NAME = "Neo-Geo" +NEOGEO_MODEL_MVS = "mvs" +NEOGEO_MODEL_MVS_US = "mvs/us" +NEOGEO_MODEL_MVS_JP = "mvs/jp" +NEOGEO_MODEL_AES = "aes" +NEOGEO_MODEL_AES_JP = "aes/jp" +# noinspection SpellCheckingInspection +NEOGEO_ASIA_BIOS = { + "neo-epo.bin": "1b3b22092f30c4d1b2c15f04d1670eb1e9fbea07" +} +# noinspection SpellCheckingInspection +NEOGEO_JAPAN_BIOS = { + "neo-po.bin": "4e4a440cae46f3889d20234aebd7f8d5f522e22c" +} +NEOGEO_CONTROLLER = { + "type": "gamepad", + "description": "Gamepad", + "mapping_name": "neogeo", +} + + +class NeoGeoPlatform(Platform): + PLATFORM_ID = NEOGEO_PLATFORM_ID + PLATFORM_NAME = NEOGEO_PLATFORM_NAME + + def __init__(self): + super().__init__() + + def driver(self, fsgc): + return NeoGeoDriver(fsgc) + + def loader(self, fsgc): + return NeoGeoLoader(fsgc) + + +class NeoGeoLoader(ArcadeLoader): + pass + + +class NeoGeoDriver(MameDriver): + PORTS = [ + { + "description": "Input Port 1", + "types": [NEOGEO_CONTROLLER] + }, { + "description": "Input Port 2", + "types": [NEOGEO_CONTROLLER] + } + ] + + def __init__(self, fsgc): + super().__init__(fsgc) + self.helper = NeoGeoHelper(self.options) + + def mame_input_mapping(self, _): + if self.helper.aes(): + return { + "START": "P#_START", + "SELECT": "P#_SELECT", + "UP": "P#_JOYSTICK_UP", + "DOWN": "P#_JOYSTICK_DOWN", + "LEFT": "P#_JOYSTICK_LEFT", + "RIGHT": "P#_JOYSTICK_RIGHT", + "A": "P#_BUTTON1", + "B": "P#_BUTTON2", + "C": "P#_BUTTON3", + "D": "P#_BUTTON4", + } + else: + return { + "START": "START#", + "SELECT": "COIN#", + "UP": "P#_JOYSTICK_UP", + "DOWN": "P#_JOYSTICK_DOWN", + "LEFT": "P#_JOYSTICK_LEFT", + "RIGHT": "P#_JOYSTICK_RIGHT", + "A": "P#_BUTTON1", + "B": "P#_BUTTON2", + "C": "P#_BUTTON3", + "D": "P#_BUTTON4", + } + + def mame_romset(self): + romset = {} + for entry in json.loads(self.options["file_list"]): + romset[entry["name"]] = entry["sha1"] + + if self.helper.model() == NEOGEO_MODEL_MVS: + romset.update({ + "sp-s2.sp1": "4f5ed7105b7128794654ce82b51723e16e389543", + }) + elif self.helper.model() == NEOGEO_MODEL_MVS_JP: + # noinspection SpellCheckingInspection + romset.update({ + "vs-bios.rom": "ecf01eda815909f1facec62abf3594eaa8d11075", + }) + elif self.helper.model() == NEOGEO_MODEL_MVS_US: + romset.update({ + "sp-u2.sp1": "5c6bba07d2ec8ac95776aa3511109f5e1e2e92eb", + }) + elif self.helper.model() == NEOGEO_MODEL_AES: + romset.update(NEOGEO_ASIA_BIOS) + elif self.helper.model() == NEOGEO_MODEL_AES_JP: + romset.update(NEOGEO_JAPAN_BIOS) + + if self.helper.aes(): + machine = "aes" + # noinspection SpellCheckingInspection + romset.update({ + "000-lo.lo": "5992277debadeb64d1c1c64b0a92d9293eaf7e4a", + }) + else: + machine = "neogeo" + # noinspection SpellCheckingInspection + romset.update({ + "000-lo.lo": "5992277debadeb64d1c1c64b0a92d9293eaf7e4a", + "sfix.sfix": "fd4a618cdcdbf849374f0a50dd8efe9dbab706c3", + "sm1.sm1": "42f9d7ddd6c0931fd64226a60dc73602b2819dcf", + }) + + return machine, romset + + def prepare(self): + super().prepare() + self.create_mame_layout() + + + self.install_mame_hash_file("neogeo.xml") + self.emulator.args.append(self.options["mame_rom_set"]) + if self.helper.model() == NEOGEO_MODEL_AES_JP: + self.emulator.args.extend(["-bios", "japan"]) + elif self.helper.model() == NEOGEO_MODEL_MVS_JP: + self.emulator.args.extend(["-bios", "japan"]) + elif self.helper.model() == NEOGEO_MODEL_MVS_US: + self.emulator.args.extend(["-bios", "us"]) + + +class NeoGeoHelper: + def __init__(self, options): + self.options = options + + def model(self): + if self.options[Option.NEOGEO_MODEL] == NEOGEO_MODEL_AES: + return NEOGEO_MODEL_AES + if self.options[Option.NEOGEO_MODEL] == NEOGEO_MODEL_AES_JP: + return NEOGEO_MODEL_AES_JP + if self.options[Option.NEOGEO_MODEL] == NEOGEO_MODEL_MVS_US: + return NEOGEO_MODEL_MVS_US + if self.options[Option.NEOGEO_MODEL] == NEOGEO_MODEL_MVS_JP: + return NEOGEO_MODEL_MVS_JP + return NEOGEO_MODEL_MVS + + def aes(self): + return self.model() in [NEOGEO_MODEL_AES, NEOGEO_MODEL_AES_JP] diff -Nru fs-uae-arcade-2.9.6/fsgs/platforms/nes/mednafennesdriver.py fs-uae-arcade-2.9.7/fsgs/platforms/nes/mednafennesdriver.py --- fs-uae-arcade-2.9.6/fsgs/platforms/nes/mednafennesdriver.py 2017-05-25 10:07:53.000000000 +0000 +++ fs-uae-arcade-2.9.7/fsgs/platforms/nes/mednafennesdriver.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,107 +0,0 @@ -from binascii import unhexlify - -from fsgs.drivers.mednafendriver import MednafenDriver - - -class MednafenNesDriver(MednafenDriver): - - CONTROLLER = { - "type": "gamepad", - "description": "Gamepad", - "mapping_name": "nintendo", - } - - PORTS = [ - { - "description": "1st Controller", - "types": [CONTROLLER] - }, { - "description": "2nd Controller", - "types": [CONTROLLER] - }, - ] - - def __init__(self, fsgs): - super().__init__(fsgs) - - def force_aspect_ratio(self): - return 4.0 / 3.0 - - def mednafen_extra_graphics_options(self): - options = [] - if self.is_pal(): - options.extend(["-nes.pal", "1"]) - else: - options.extend(["-nes.pal", "0"]) - if self.nes_clip_sides(): - options.extend(["-nes.clipsides", "1"]) - return options - - def mednafen_input_mapping(self, port): - if port == 0: - return { - "A": "nes.input.port1.gamepad.a", - "B": "nes.input.port1.gamepad.b", - "UP": "nes.input.port1.gamepad.up", - "DOWN": "nes.input.port1.gamepad.down", - "LEFT": "nes.input.port1.gamepad.left", - "RIGHT": "nes.input.port1.gamepad.right", - "SELECT": "nes.input.port1.gamepad.select", - "START": "nes.input.port1.gamepad.start", - } - elif port == 1: - return { - "A": "nes.input.port2.gamepad.a", - "B": "nes.input.port2.gamepad.b", - "UP": "nes.input.port2.gamepad.up", - "DOWN": "nes.input.port2.gamepad.down", - "LEFT": "nes.input.port2.gamepad.left", - "RIGHT": "nes.input.port2.gamepad.right", - "SELECT": "nes.input.port2.gamepad.select", - "START": "nes.input.port2.gamepad.start", - } - - def mednafen_rom_extensions(self): - return [".nes"] - - def mednafen_system_prefix(self): - return "nes" - - def game_video_size(self): - # FIXME - if self.is_pal(): - size = (256, 240) - else: - size = (256, 224) - if self.nes_clip_sides(): - size = (size[0] - 16, size[1]) - return size - - def nes_clip_sides(self): - # FIXME: Sane default? -Or enable this in a per-game config - # instead? SMB3 looks better with this - return True - - def get_game_file(self, config_key="cartridge_slot"): - path = super().get_game_file() - data1 = b"" - with open(path, "rb") as f: - - # Start iNES Hack - data = f.read(16) - if len(data) == 16 and data.startswith(b"NES\x1a"): - print("[DRIVER] Stripping iNES header") - else: - # No iNES header, include data - data1 = data - # End iNES Hack - - data2 = f.read() - with open(path, "wb") as f: - ines_header = self.config.get("ines_header", "") - if ines_header: - assert len(ines_header) == 32 - f.write(unhexlify(ines_header)) - f.write(data1) - f.write(data2) - return path diff -Nru fs-uae-arcade-2.9.6/fsgs/platforms/ngc/ngcdriver.py fs-uae-arcade-2.9.7/fsgs/platforms/ngc/ngcdriver.py --- fs-uae-arcade-2.9.6/fsgs/platforms/ngc/ngcdriver.py 1970-01-01 00:00:00.000000000 +0000 +++ fs-uae-arcade-2.9.7/fsgs/platforms/ngc/ngcdriver.py 2017-10-16 21:40:40.000000000 +0000 @@ -0,0 +1,102 @@ +import os + +from fsgs.drivers.dolphindriver import DolphinDriver, DolphinInputMapper +from fsgs.platforms.ngc.ngcplatform import NGC_CONTROLLER + + +class NGCDriver(DolphinDriver): + PORTS = [ + { + "description": "Controller 1", + "types": [NGC_CONTROLLER] + }, { + "description": "Controller 2", + "types": [NGC_CONTROLLER] + }, { + "description": "Controller 3", + "types": [NGC_CONTROLLER] + }, { + "description": "Controller 4", + "types": [NGC_CONTROLLER] + }, + ] + + def __init__(self, fsgs): + super().__init__(fsgs) + self.helper = NGCHelper(self.options) + + def dolphin_configure_core(self, f): + # find devices now (need to know how many devices to + # specify in dolphin.ini) + # devices = self.context.input.get_devices(1, 4) + # configure dolphin.ini + device_count = 0 + for i, port in enumerate(self.ports): + if port.device: + device_count += 1 + for i in range(device_count): + f.write("SIDevice{0} = 150994944\n".format(i)) + + # FIXME: + memcard_region = "USA" + + f.write("MemcardA = {dir}/MemoryCardA.{region}.raw\n".format( + dir=self.get_state_dir(), region=memcard_region)) + f.write("MemcardB = {dir}/MemoryCardB.{region}.raw\n".format( + dir=self.get_state_dir(), region=memcard_region)) + + def dolphin_configure_input(self): + # devices = self.context.input.get_devices(1, 4) + input_mapping = { + "A": "Buttons/A", + "B": "Buttons/B", + "X": "Buttons/X", + "Y": "Buttons/Y", + "Z": "Buttons/Z", + "START": "Buttons/Start", + "STICK_UP": "Main Stick/Up", + "STICK_DOWN": "Main Stick/Down", + "STICK_LEFT": "Main Stick/Left", + "STICK_RIGHT": "Main Stick/Right", + "C_UP": "C-Stick/Up", # or z? + "C_DOWN": "C-Stick/Down", + "C_LEFT": "C-Stick/Left", + "C_RIGHT": "C-Stick/Right", + "L": "Triggers/L", + "R": "Triggers/R", + "DPAD_UP": "D-Pad/Up", + "DPAD_DOWN": "D-Pad/Down", + "DPAD_LEFT": "D-Pad/Left", + "DPAD_RIGHT": "D-Pad/Right", + "L_ANALOG": "Triggers/L-Analog", + "R_ANALOG": "Triggers/R-Analog", + # FIXME: ADD MODIFIER? + } + temp_dir = self.temp_dir("dolphin") + input_config_file = os.path.join( + temp_dir.path, "user", "Config", "GCPadNew.ini") + f = open(input_config_file, "w") + for i, port in enumerate(self.ports): + if not port.device: + continue + f.write("[GCPad{num}]\n".format(num=i + 1)) + if port.device.is_keyboard(): + f.write("Device = DInput/0/Keyboard Mouse\n") + else: + type_index = int(port.device.id.rsplit("#", 1)[1]) - 1 + f.write("Device = SDL/{index}/{name}\n".format( + index=type_index, name=port.device.sdl_name)) + mapper = DolphinInputMapper(port, input_mapping) + for key, value in mapper.items(): + if isinstance(key, tuple): + for k in key: + f.write("{key} = {value}\n".format( + key=k, value=value)) + else: + f.write("{key} = {value}\n".format( + key=key, value=value)) + + +class NGCHelper: + def __init__(self, options): + self.options = options diff -Nru fs-uae-arcade-2.9.6/fsgs/platforms/ngc/ngcplatform.py fs-uae-arcade-2.9.7/fsgs/platforms/ngc/ngcplatform.py --- fs-uae-arcade-2.9.6/fsgs/platforms/ngc/ngcplatform.py 1970-01-01 00:00:00.000000000 +0000 +++ fs-uae-arcade-2.9.7/fsgs/platforms/ngc/ngcplatform.py 2017-10-16 21:40:40.000000000 +0000 @@ -0,0 +1,28 @@ +from fsgs.platform import PlatformHandler +from fsgs.platforms.loader import SimpleLoader + +NGC_PLATFORM_NAME = "GameCube" +NGC_CONTROLLER = { + "type": "gamepad", + "description": "Gamepad", + "mapping_name": "ngc", +} + + +class NGCPlatformHandler(PlatformHandler): + # FIXME: Move to init instead + PLATFORM_NAME = NGC_PLATFORM_NAME + + def __init__(self): + PlatformHandler.__init__(self) + + def get_loader(self, fsgs): + return NGCLoader(fsgs) + + def get_runner(self, fsgs): + from fsgs.platforms.ngc.ngcdriver import NGCDriver + return NGCDriver(fsgs) + + +class NGCLoader(SimpleLoader): + pass \ No newline at end of file diff -Nru fs-uae-arcade-2.9.6/fsgs/platforms/nintendo64.py fs-uae-arcade-2.9.7/fsgs/platforms/nintendo64.py --- fs-uae-arcade-2.9.6/fsgs/platforms/nintendo64.py 1970-01-01 00:00:00.000000000 +0000 +++ fs-uae-arcade-2.9.7/fsgs/platforms/nintendo64.py 2017-10-16 21:40:40.000000000 +0000 @@ -0,0 +1,353 @@ +import os + +from fsgs.drivers.retroarchdriver import RetroArchDriver +from fsgs.platform import Platform +from fsgs.platforms.loader import SimpleLoader +from fsgs.drivers.gamedriver import GameDriver, Emulator +from fsgs.input.mapper import InputMapper + +N64_PLATFORM_ID = "n64" +N64_PLATFORM_NAME = "Nintendo 64" +N64_CONTROLLER = { + "type": "gamepad", + "description": "Gamepad", + "mapping_name": "nintendo64", +} +N64_PORTS = [ + { + "description": "Input Port 1", + "types": [N64_CONTROLLER] + }, { + "description": "Input Port 2", + "types": [N64_CONTROLLER] + }, { + "description": "Input Port 3", + "types": [N64_CONTROLLER] + }, { + "description": "Input Port 4", + "types": [N64_CONTROLLER] + }, +] + + +class Nintendo64Platform(Platform): + # FIXME: Move to init instead + PLATFORM_NAME = N64_PLATFORM_NAME + + def driver(self, fsgs): + return Nintendo64RetroArchDriver(fsgs) + + def loader(self, fsgs): + return Nintendo64Loader(fsgs) + + +class Nintendo64Loader(SimpleLoader): + pass + + +class Nintendo64MupenDriver(GameDriver): + PORTS = [ + { + "description": "Input Port 1", + "types": [N64_CONTROLLER] + }, { + "description": "Input Port 2", + "types": [N64_CONTROLLER] + }, { + "description": "Input Port 3", + "types": [N64_CONTROLLER] + }, { + "description": "Input Port 4", + "types": [N64_CONTROLLER] + }, + ] + + def __init__(self, fsgs): + super().__init__(fsgs) + self.emulator = Emulator("mupen64plus") + self.emulator.allow_system_emulator = True + self.helper = Nintendo64Helper(self.options) + + # def force_aspect_ratio(self): + # return 4.0 / 3.0 + + # def game_video_size(self): + # # FIXME + # if self.is_pal(): + # size = (256, 240) + # else: + # size = (256, 224) + # return size + + # FIXME: Scan/index byteswapped .n64/.z64 files? (i.e. #/original + # and #!/byte-swapped. + + def prepare(self): + temp_dir = self.temp_dir("mupen64plus") + self.emulator.args.extend(["--configdir", temp_dir.path]) + self.emulator.args.extend(["--datadir", temp_dir.path]) + config_file = os.path.join(temp_dir.path, "mupen64plus.cfg") + with open(config_file, "w") as f: + self.write_config(f) + input_config_file = os.path.join(temp_dir.path, "InputAutoCfg.ini") + with open(input_config_file, "wb") as f: + pass + rom_path = self.get_game_file() + self.emulator.args.extend([rom_path]) + + def finish(self): + pass + + # def run(self): + # plugin = pyapp.plug.get_plugin("no.fengestad.emulator.mupen64plus") + # if fs.windows: + # temp_dir = self.context.temp.dir("mupen64plus") + # GameCenterUtil.copy_folder_tree(plugin.get_bin_dir(), temp_dir) + # args = self.args[:] + # args.insert(0, os.path.join(temp_dir, + # "mupen64plus-ui-console.exe")) + # return subprocess.Popen(args, env=self.env, cwd=temp_dir, + # close_fds=True) + # return plugin.mupen64plus(self.args, env=self.env, + # cwd=plugin.get_bin_dir(), close_fds=True) + + def write_config(self, f): + f.write("[Core]\n") + f.write("OnScreenDisplay = False\n") + # FIXME: Save state path does not seem to work + # -stuff are saved to default dir in AppData/Roaming/Mupen64Plus/save + # instead... + # FIXME: Might work now with updated mupen64plus + f.write("SaveStatePath = '{path}'\n".format( + path=self.get_state_dir() + os.sep)) + self.configure_audio(f) + self.configure_input(f) + self.configure_video(f) + + def configure_audio(self, f): + pass + + def configure_input(self, f): + input_mapping = { + "DPAD_RIGHT": "DPad R", + "DPAD_LEFT": "DPad L", + "DPAD_DOWN": "DPad D", + "DPAD_UP": "DPad U", + "START": "Start", + "Z": "Z Trig", + "B": "B Button", + "A": "A Button", + "C_RIGHT": "C Button R", + "C_LEFT": "C Button L", + "C_DOWN": "C Button D", + "C_UP": "C Button U", + "R": "R Trig", + "L": "L Trig", + "MEMPAK": "Mempak switch", + "RUMBLEPAK": "Rumblepak switch", + "STICK_LEFT": ("X Axis", 0), + "STICK_RIGHT": ("X Axis", 1), + "STICK_UP": ("Y Axis", 0), + "STICK_DOWN": ("Y Axis", 1), + } + for i, port in enumerate(self.ports): + if port.device is None: + continue + mapper = Mupen64PlusInputMapper(port, input_mapping) + config = {} + for key, value in mapper.items(): + print("---->", key, value) + if isinstance(key, tuple): + key, index = key + else: + index = 0 + config.setdefault(key, {})[index] = value + f.write("\n[Input-SDL-Control{0}]\n\n".format(i + 1)) + f.write("version = 2\n") + # Specifies whether this controller is 'plugged in' to the + # simulated N64. + f.write("plugged = True\n") + # Specifies which type of expansion pak is in the controller: + # 1=None, 2=Mem pak, 5=Rumble pak. + f.write("plugin = 2\n") + # If True, then mouse buttons may be used with this controller. + f.write("mouse = False\n") + # Controller configuration mode: + # 0=Fully Manual, 1=Auto with named SDL Device, 2=Fully automatic. + f.write("mode = 0\n") + if port.device.type == "joystick": + f.write("device = {0}\n".format(port.device.index)) + f.write("AnalogDeadZone = \"512,512\"\n") + f.write("AnalogPeak = \"32767,32767\"\n") + else: + # -2 means keyboard/mouse + f.write("device = -2\n") + + for key, value in config.items(): + type = value[0][0] + values = [x[1][1] for x in sorted(list(value.items()))] + values_str = ",".join(values) + f.write("{key} = \"{type}({values})\"\n".format( + key=key, type=type, values=values_str)) + print("{key} = \"{type}({values})\"\n".format( + key=key, type=type, values=values_str)) + + def configure_video(self, f): + f.write("[Video-General]\n") + if self.use_fullscreen(): + f.write("Fullscreen = True\n") + f.write("ScreenWidth = {0}\n".format(self.screen_size()[0])) + f.write("ScreenHeight = {0}\n".format(self.screen_size()[1])) + else: + f.write("Fullscreen = False\n") + # f.write("ScreenWidth = 320\n") + # f.write("ScreenHeight = 240\n") + + f.write("\n[UI-Console]\n\n") + f.write("Version = 1\n") + video_plugin = "glide64mk2" + # video_plugin = "glide64" + video_plugin = "rice" + f.write("VideoPlugin = \"mupen64plus-video-{}\"\n".format(video_plugin)) + + if self.configure_vsync(): + # cannot find config for vsync in rice video plugin, + # but should work for linux/nvidia due to env. variable + # being set + pass + + if video_plugin == "rice": + self.configure_video_rice(f) + elif video_plugin == "glide64": + self.configure_video_glide64(f) + elif video_plugin == "glide64mk2": + self.configure_video_glide64mk2(f) + + def configure_video_rice(self, f): + f.write("\n[Video-Rice]\n\n") + f.write("AccurateTextureMapping = True\n") + f.write("ForceAlphaBlender = True\n") + # f.write("InN64Resolution = True\n") + # f.write("RenderToTexture = 4\n") + f.write("ScreenUpdateSetting = 2\n") + # f.write("TextureFilteringMethod = 0\n") + # f.write("Mipmapping = 1\n") + # f.write("MultiSampling = 2\n") + # f.write("AccurateTextureMapping = False\n") + # f.write("WinFrameMode = True\n") + # f.write("InN64Resolution = False\n") + # f.write("ForceTextureFilter = 0\n") + # f.write("TextureFilteringMethod = 1\n") + # f.write("TextureFilteringMethod = 0\n") + # f.write("FogMethod = 1\n") + # f.write("EnableVertexShader = True\n") + # f.write("NormalAlphaBlender = True\n") + # f.write("ShowFPS = True\n") + # f.write("EnableMipmaping = False\n") + + # self.args.extend(["--set", "Video-Rice[InN64resolution]=True"]) + # self.args.extend(["--set", "Video-Rice[ForceTextureFilter]=0"]) + + def configure_video_glide64(self, f): + f.write("\n[Video-Glide64]\n\n") + + def configure_video_glide64mk2(self, f): + f.write("\n[Video-Glide64mk2]\n\n") + # f.write("wrpResolution = 320x200\n") + # f.write("filtering = 0\n") + + +class Mupen64PlusInputMapper(InputMapper): + def axis(self, axis, positive): + dir_str = "+" if positive else "-" + return "axis", "{0}{1}".format(axis, dir_str) + + def hat(self, hat, direction): + dir_str = { + "left": "Left", + "right": "Right", + "up": "Up", + "down": "Down", + }[direction] + return "hat", "{0} {1}".format(hat, dir_str) + + def button(self, button): + return "button", str(button) + + def key(self, key): + return "key", str(key.sdl_code) + + +class Nintendo64RetroDriver(GameDriver): + PORTS = N64_PORTS + + def __init__(self, fsgs): + super().__init__(fsgs) + self.emulator = Emulator("retroarch") + self.emulator.allow_system_emulator = True + self.helper = Nintendo64Helper(self.options) + + def prepare(self): + temp_dir = self.temp_dir("mupen64plus") + self.emulator.args.extend(["--configdir", temp_dir.path]) + self.emulator.args.extend(["--datadir", temp_dir.path]) + config_file = os.path.join(temp_dir.path, "mupen64plus.cfg") + with open(config_file, "w") as f: + self.write_config(f) + input_config_file = os.path.join(temp_dir.path, "InputAutoCfg.ini") + with open(input_config_file, "wb") as f: + pass + rom_path = self.get_game_file() + self.emulator.args.extend([rom_path]) + + def finish(self): + pass + + +class Nintendo64RetroArchDriver(RetroArchDriver): + PORTS = N64_PORTS + + def __init__(self, fsgc): + super().__init__(fsgc, "mupen64plus_libretro", "RetroArch/Mupen64Plus") + self.helper = Nintendo64Helper(self.options) + + def prepare(self): + super().prepare() + rom_path = self.get_game_file() + # self.helper.fix_ines_rom(rom_path) + self.emulator.args.extend([rom_path]) + + # Workaround for Intel / MESA on Linux (FIXME: Should perhaps check if + # Intel / MESA driver is in use first... + # https://github.com/gonetz/GLideN64/issues/454 + self.emulator.env["MESA_GL_VERSION_OVERRIDE"] = "3.3COMPAT" + self.emulator.env["MESA_GLSL_VERSION_OVERRIDE"] = "420" + + def retroarch_input_mapping(self, port): + input_mapping = { + "A": "input_player{n}_b", + "B": "input_player{n}_y", + "DPAD_UP": "input_player{n}_up", + "DPAD_DOWN": "input_player{n}_down", + "DPAD_LEFT": "input_player{n}_left", + "DPAD_RIGHT": "input_player{n}_right", + "STICK_UP": "input_player{n}_l_y_minus", + "STICK_DOWN": "input_player{n}_l_y_plus", + "STICK_LEFT": "input_player{n}_l_x_minus", + "STICK_RIGHT": "input_player{n}_l_x_plus", + "C_UP": "input_player{n}_r_y_plus", + "C_DOWN": "input_player{n}_r_y_minus", + "C_LEFT": "input_player{n}_r_x_minus", + "C_RIGHT": "input_player{n}_r_x_plus", + "RUMBLEPAK": "input_player{n}_select", # ??? + "START": "input_player{n}_start", + "L": "input_player{n}_l", + "R": "input_player{n}_r", + "Z": "input_player{n}_l2", + } + return {k: v.format(n=port + 1) for k, v in input_mapping.items()} + + +class Nintendo64Helper: + def __init__(self, options): + self.options = options diff -Nru fs-uae-arcade-2.9.6/fsgs/platforms/nintendo.py fs-uae-arcade-2.9.7/fsgs/platforms/nintendo.py --- fs-uae-arcade-2.9.6/fsgs/platforms/nintendo.py 2017-05-25 10:07:53.000000000 +0000 +++ fs-uae-arcade-2.9.7/fsgs/platforms/nintendo.py 2017-10-16 21:40:40.000000000 +0000 @@ -1,27 +1,404 @@ +import hashlib +import os +from binascii import unhexlify + +import shutil + from fsbc import settings +from fsgs.drivers.gamedriver import GameDriver, Emulator +from fsgs.drivers.mednafendriver import MednafenDriver from fsgs.drivers.mess.messnesdriver import MessNesDriver +from fsgs.drivers.retroarchdriver import RetroArchDriver from fsgs.option import Option -from fsgs.platform import PlatformHandler +from fsgs.platform import Platform from fsgs.platforms.loader import SimpleLoader -from fsgs.platforms.nes.mednafennesdriver import MednafenNesDriver +NES_PLATFORM_ID = "nes" +NES_PLATFORM_NAME = "Nintendo" -class NintendoPlatformHandler(PlatformHandler): - PLATFORM_NAME = "Nintendo" +NES_MODEL_NTSC = "ntsc" +NES_MODEL_PAL = "pal" +NES_MODEL_FAMICOM = "ntsc-j" - def __init__(self): - PlatformHandler.__init__(self) +NES_CONTROLLER_TYPE = "gamepad" +NES_CONTROLLER = { + "type": NES_CONTROLLER_TYPE, + "description": "NES Gamepad", + "mapping_name": "nintendo", +} +NES_ZAPPER_CONTROLLER_TYPE = "zapper" +NES_ZAPPER_CONTROLLER = { + "type": NES_ZAPPER_CONTROLLER_TYPE, + "description": "NES Zapper", + "mapping_name": "", +} +NES_ARKANOID_CONTROLLER_TYPE = "arkanoid" +NES_ARKANOID_CONTROLLER = { + "type": NES_ARKANOID_CONTROLLER_TYPE, + "description": "Arkanoid Paddle", + "mapping_name": "", +} +NO_CONTROLLER_TYPE = "none" +NO_CONTROLLER = { + "type": NO_CONTROLLER_TYPE, + "description": "None", + "mapping_name": "", +} - def get_loader(self, fsgs): - return NintendoLoader(fsgs) +NES_PORTS = [ + { + "description": "Port 1", + "types": [NES_CONTROLLER, NES_ZAPPER_CONTROLLER, + NES_ARKANOID_CONTROLLER, NO_CONTROLLER], + "type_option": "nes_port_1_type", + "device_option": "nes_port_1", + }, { + "description": "Port 2", + "types": [NES_CONTROLLER, NES_ZAPPER_CONTROLLER, + NES_ARKANOID_CONTROLLER, NO_CONTROLLER], + "type_option": "nes_port_2_type", + "device_option": "nes_port_2", + }, { + "description": "Port 3", + "types": [NES_CONTROLLER, NO_CONTROLLER], + "type_option": "nes_port_3_type", + "device_option": "nes_port_3", + }, { + "description": "Port 4", + "types": [NES_CONTROLLER, NO_CONTROLLER], + "type_option": "nes_port_4_type", + "device_option": "nes_port_4", + }, +] - def get_runner(self, fsgs): - if settings.get(Option.NES_DRIVER) == "mess": - return MessNesDriver(fsgs) - else: - return MednafenNesDriver(fsgs) + +class NintendoPlatform(Platform): + PLATFORM_NAME = NES_PLATFORM_NAME + + def driver(self, fsgc): + # if settings.get(Option.NES_DRIVER) == "mess": + # return MessNesDriver(fsgc) + # else: + # return NintendoMednafenDriver(fsgc) + driver = settings.get(Option.NES_EMULATOR) + + # FIXME: nestopia-libretro? retroarch-nestopia? + if driver in ["retroarch-nestopia", "libretro-nestopia"]: + return NintendoRetroArchDriver(fsgc) + elif driver in ["mame", "mess"]: + return MessNesDriver(fsgc) + elif driver == "higan": + return NintendoHiganDriver(fsgc) + return NintendoMednafenDriver(fsgc) + + def loader(self, fsgc): + return NintendoLoader(fsgc) class NintendoLoader(SimpleLoader): def load_extra(self, values): - self.config["ines_header"] = values["ines_header"] + self.config[Option.NES_MODEL] = values["nes_model"] + self.config[Option.NES_INES_HEADER] = values["ines_header"] + self.config[Option.NES_PORT_1_TYPE] = values["nes_port_1_type"] + self.config[Option.NES_PORT_2_TYPE] = values["nes_port_2_type"] + self.config[Option.NES_PORT_3_TYPE] = values["nes_port_3_type"] + self.config[Option.NES_PORT_4_TYPE] = values["nes_port_4_type"] + + # FIXME: Temporary + if self.config[Option.NES_MODEL] == "famicom": + self.config[Option.NES_MODEL] = NES_MODEL_FAMICOM + + +class NintendoMednafenDriver(MednafenDriver): + PORTS = NES_PORTS + + def __init__(self, fsgs): + super().__init__(fsgs) + self.helper = NintendoHelper(self.options) + # port_2 = self.options[Option.NES_PORT_2_TYPE] + # if port_2 == NES_ARKANOID_CONTROLLER_TYPE: + # print("[DRIVER] NES Port 2 type:", port_2) + # for i, t in enumerate(self.ports[1].types): + # if t["type"] == NES_ARKANOID_CONTROLLER_TYPE: + # self.ports[1].index = i + # break + port_3 = self.options[Option.NES_PORT_3_TYPE] + port_4 = self.options[Option.NES_PORT_4_TYPE] + if not port_3 and not port_4: + # Remove the last two input ports - not in use. + self.ports[2:4] = [] + + def prepare(self): + print("[DRIVER] Mednafen NES driver preparing...") + super().prepare() + pfx = self.mednafen_system_prefix() + + self.emulator.args.extend( + ["-{}.input.port1".format(pfx), self.ports[0].type]) + self.emulator.args.extend( + ["-{}.input.port2".format(pfx), self.ports[1].type]) + + if self.helper.nes_model() == NES_MODEL_PAL: + self.set_model_name("Nintendo (PAL)") + self.emulator.args.extend(["-{}.pal".format(pfx), "1"]) + else: + if self.helper.nes_model() == NES_MODEL_FAMICOM: + self.set_model_name("Famicom") + else: + self.set_model_name("Nintendo (NTSC)") + self.emulator.args.extend(["-{}.pal".format(pfx), "0"]) + self.emulator.args.extend(["-{}.fnscan".format(pfx), "0"]) + + self.emulator.env["FSGS_ASPECT"] = "4/3" + + viewport = self.options[Option.VIEWPORT] + if viewport == "0 0 256 240 = 0 0 256 240": + self.emulator.env["FSGS_CROP"] = "0,0,256,240" + # elif viewport == "0 0 256 240 = 0 8 256 224": + # self.emulator.env["FSGS_CROP"] = "0,8,256,224" + elif viewport == "0 0 256 240 = 8 0 240 240": + self.emulator.env["FSGS_CROP"] = "8,0,240,240" + elif viewport == "0 0 256 240 = 8 8 240 224": + self.emulator.env["FSGS_CROP"] = "8,8,240,224" + else: + self.emulator.env["FSGS_CROP"] = "0,8,256,224" + + self.emulator.args.append(self.helper.prepare_rom(self)) + + def finish(self): + print("[DRIVER] Mednafen NES Driver finishing...") + super().finish() + + # def mednafen_extra_graphics_options(self): + # options = [] + # if self.nes_clip_sides(): + # options.extend(["-nes.clipsides", "1"]) + # return options + + def mednafen_input_mapping(self, port): + n = port + 1 + return { + "A": "nes.input.port{}.gamepad.a".format(n), + "B": "nes.input.port{}.gamepad.b".format(n), + "UP": "nes.input.port{}.gamepad.up".format(n), + "DOWN": "nes.input.port{}.gamepad.down".format(n), + "LEFT": "nes.input.port{}.gamepad.left".format(n), + "RIGHT": "nes.input.port{}.gamepad.right".format(n), + "SELECT": "nes.input.port{}.gamepad.select".format(n), + "START": "nes.input.port{}.gamepad.start".format(n), + } + + def mednafen_rom_extensions(self): + return [".nes"] + + def mednafen_system_prefix(self): + return "nes" + + def game_video_par(self): + size = self.game_video_size() + return (4 / 3) / (size[0] / size[1]) + + def game_video_size(self): + # FIXME + if self.is_pal(): + size = (256, 240) + else: + size = (256, 224) + # if self.nes_clip_sides(): + # size = (size[0] - 16, size[1]) + return size + + # def nes_clip_sides(self): + # # FIXME: Sane default? -Or enable this in a per-game config + # # instead? SMB3 looks better with this + # # return True + # return False + + def get_game_file(self, config_key="cartridge_slot"): + # path = super().get_game_file() + # + # # FIXME: Replace and do in one go without going via super + # input_stream = self.fsgc.file.open(path) + # _, ext = os.path.splitext(path) + # return self.prepare_rom_with_stream(self, input_stream, ext) + # return self.helper.prepare_rom(self) + return None + + +class NintendoRetroArchDriver(RetroArchDriver): + PORTS = NES_PORTS + + def __init__(self, fsgc): + super().__init__(fsgc, "nestopia_libretro", "RetroArch/Nestopia") + self.helper = NintendoHelper(self.options) + + def prepare(self): + super().prepare() + with self.open_retroarch_core_options() as f: + if self.helper.nes_model() == NES_MODEL_PAL: + self.set_model_name("Nintendo (PAL)") + f.write("nestopia_favored_system = pal\n") + elif self.helper.nes_model() == NES_MODEL_FAMICOM: + self.set_model_name("Famicom") + f.write("nestopia_favored_system = famicom\n") + else: + self.set_model_name("Nintendo (NTSC)") + f.write("nestopia_favored_system = ntsc\n") + + viewport = self.options[Option.VIEWPORT] + if viewport == "0 0 256 240 = 0 0 256 240": + overscan_h, overscan_v = "disabled", "disabled" + # elif viewport == "0 0 256 240 = 0 8 256 224": + # return 256, 224 + elif viewport == "0 0 256 240 = 8 0 240 240": + overscan_h, overscan_v = "enabled", "disabled" + elif viewport == "0 0 256 240 = 8 8 240 224": + overscan_h, overscan_v = "enabled", "enabled" + else: + overscan_h, overscan_v = "disabled", "enabled" + f.write("nestopia_overscan_h = {}\n".format(overscan_h)) + f.write("nestopia_overscan_v = {}\n".format(overscan_v)) + self.emulator.args.append(self.helper.prepare_rom(self)) + + def game_video_size(self): + # FIXME: Account for horizontal overscan + # FIXME: Account for vertical overscan + + viewport = self.options[Option.VIEWPORT] + if viewport == "0 0 256 240 = 0 0 256 240": + return 256, 240 + # elif viewport == "0 0 256 240 = 0 8 256 224": + # return 256, 224 + elif viewport == "0 0 256 240 = 8 0 240 240": + return 240, 240 + elif viewport == "0 0 256 240 = 8 8 240 224": + return 240, 224 + else: + return 256, 224 + + def retroarch_input_mapping(self, port): + n = port + 1 + return { + "A": "input_player{}_a".format(n), + "B": "input_player{}_b".format(n), + "UP": "input_player{}_up".format(n), + "DOWN": "input_player{}_down".format(n), + "LEFT": "input_player{}_left".format(n), + "RIGHT": "input_player{}_right".format(n), + "SELECT": "input_player{}_select".format(n), + "START": "input_player{}_start".format(n), + } + + # def window_size(self): + # return 256, 224 + + +class NintendoHiganDriver(GameDriver): + PORTS = NES_PORTS + + def __init__(self, fsgs): + super().__init__(fsgs) + self.emulator = Emulator("higan") + self.emulator.allow_system_emulator = True + self.helper = NintendoHelper(self.options) + + def prepare(self): + if self.use_fullscreen(): + self.emulator.args.append("--fullscreen") + + # model = self.helper.nes_model() + + rom_path = self.get_game_file() + self.helper.fix_ines_rom(rom_path) + self.emulator.args.extend([rom_path]) + + +class NintendoHelper: + def __init__(self, options): + self.options = options + + def fix_ines_rom(self, path): + data1 = b"" + with open(path, "rb") as f: + # Start iNES Hack ------------------------------------------------- + data = f.read(16) + if len(data) == 16 and data.startswith(b"NES\x1a"): + print("[DRIVER] Stripping iNES header") + else: + # No iNES header, include data + data1 = data + # End iNES Hack --------------------------------------------------- + data2 = f.read() + with open(path, "wb") as f: + ines_header = self.options[Option.NES_INES_HEADER] + if ines_header: + assert len(ines_header) == 32 + f.write(unhexlify(ines_header)) + f.write(data1) + f.write(data2) + return path + + def prepare_rom(self, driver): + file_uri = self.options[Option.CARTRIDGE_SLOT] + input_stream = driver.fsgc.file.open(file_uri) + _, ext = os.path.splitext(file_uri) + return self.prepare_rom_with_stream(driver, input_stream, ext) + + def prepare_rom_with_stream(self, driver, input_stream, ext): + # This should not be necessary for files found via the file database + # and the online game database, but could be necessary for manually + # loaded files. + data = input_stream.read(16) + if len(data) == 16 and data.startswith(b"NES\x1a"): + print("[DRIVER] Stripping iNES header") + data = None + else: + # No iNES header, include data + pass + + sha1_obj = hashlib.sha1() + + path = driver.temp_file("rom" + ext).path + with open(path, "wb") as f: + ines_header = self.options[Option.NES_INES_HEADER] + if ines_header: + assert len(ines_header) == 32 + f.write(unhexlify(ines_header)) + sha1_obj.update(unhexlify(ines_header)) + if data is not None: + f.write(data) + sha1_obj.update(data) + while True: + data = input_stream.read(65536) + if not data: + break + f.write(data) + sha1_obj.update(data) + + new_path = os.path.join( + os.path.dirname(path), sha1_obj.hexdigest()[:8].upper() + ext) + os.rename(path, new_path) + return new_path + + # FIXME: Replace code and write ROM in one go with iNES fix... + # rom_path = self.get_game_file() + # self.fix_ines_rom(rom_path) + # with open(rom_path, "rb") as f: + # new_rom_name = hashlib.sha1(f.read()).hexdigest()[:8] + # new_rom_path = os.path.join(os.path.dirname(rom_path), new_rom_name) + # shutil.copy(rom_path, new_rom_path) + # rom_path = new_rom_path + + def nes_model(self): + model = self.options[Option.NES_MODEL] + if model in ["", NES_MODEL_NTSC]: + return NES_MODEL_NTSC + elif model == NES_MODEL_PAL: + return NES_MODEL_PAL + elif model == NES_MODEL_FAMICOM: + return NES_MODEL_FAMICOM + print("[NES] Warning: Invalid model:", model) + return NES_MODEL_NTSC + + def pal(self): + return self.nes_model() == NES_MODEL_PAL diff -Nru fs-uae-arcade-2.9.6/fsgs/platforms/psx/mednafenpsxdriver.py fs-uae-arcade-2.9.7/fsgs/platforms/psx/mednafenpsxdriver.py --- fs-uae-arcade-2.9.6/fsgs/platforms/psx/mednafenpsxdriver.py 2017-05-25 10:07:53.000000000 +0000 +++ fs-uae-arcade-2.9.7/fsgs/platforms/psx/mednafenpsxdriver.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,118 +0,0 @@ -import json -import os - -from fsbc.paths import Paths -from fsgs.Archive import Archive -from fsgs.drivers.mednafendriver import MednafenDriver -from fsgs.platforms.psx import PSX_CONTROLLER, PSX_SCPH5501_BIN_SHA1 - - -class MednafenPsxDriver(MednafenDriver): - PORTS = [ - { - "description": "1st Controller", - "types": [PSX_CONTROLLER] - }, { - "description": "2nd Controller", - "types": [PSX_CONTROLLER] - }, - ] - - def __init__(self, fsgs): - super().__init__(fsgs) - - @staticmethod - def expand_default_path(src, default_dir): - if "://" in src: - return src, None - src = Paths.expand_path(src, default_dir) - archive = Archive(src) - return src, archive - - def get_game_file(self, config_key=None): - - # FIXME: Move somewhere else - bios_path = os.path.join(self.home.path, ".mednafen", "scph5501.bin") - if not os.path.exists(os.path.dirname(bios_path)): - os.makedirs(os.path.dirname(bios_path)) - # FIXME: This is for US region - src = self.fsgs.file.find_by_sha1(PSX_SCPH5501_BIN_SHA1) - self.fsgs.file.copy_game_file(src, bios_path) - - temp_dir = self.temp_dir("media").path - game_file = None - # cdrom_drive_0 = self.config.get("cdrom_drive_0", "") - # if cdrom_drive_0.startswith("game:"): - if True: - # scheme, dummy, game_uuid, name = cdrom_drive_0.split("/") - # file_list = self.get_file_list_for_game_uuid(game_uuid) - file_list = json.loads(self.config["file_list"]) - for file_item in file_list: - src = self.fsgs.file.find_by_sha1(file_item["sha1"]) - - src, archive = self.expand_default_path(src, None) - dst_name = file_item["name"] - # current_task.set_progress(dst_name) - - dst = os.path.join(temp_dir, dst_name) - self.fsgs.file.copy_game_file(src, dst) - - # cue_sheets = self.get_cue_sheets_for_game_uuid(game_uuid) - cue_sheets = json.loads(self.config["cue_sheets"]) - for i, cue_sheet in enumerate(cue_sheets): - # FIXME: Try to get this to work with the PyCharm type checker - path = os.path.join(temp_dir, cue_sheet["name"]) - if i == 0: - game_file = path - # noinspection PyTypeChecker - with open(path, "wb") as f: - # noinspection PyTypeChecker - f.write(cue_sheet["data"].encode("UTF-8")) - return game_file - - def mednafen_input_mapping(self, port): - if port == 0: - return { - "CIRCLE": "psx.input.port1.gamepad.circle", - "CROSS": "psx.input.port1.gamepad.cross", - "TRIANGLE": "psx.input.port1.gamepad.triangle", - "SQUARE": "psx.input.port1.gamepad.square", - "L1": "psx.input.port1.gamepad.l1", - "L2": "psx.input.port1.gamepad.l2", - "R1": "psx.input.port1.gamepad.r1", - "R2": "psx.input.port1.gamepad.r2", - "UP": "psx.input.port1.gamepad.up", - "DOWN": "psx.input.port1.gamepad.down", - "LEFT": "psx.input.port1.gamepad.left", - "RIGHT": "psx.input.port1.gamepad.right", - "SELECT": "psx.input.port1.gamepad.select", - "START": "psx.input.port1.gamepad.start", - } - elif port == 1: - return { - "CIRCLE": "psx.input.port2.gamepad.circle", - "CROSS": "psx.input.port2.gamepad.cross", - "TRIANGLE": "psx.input.port2.gamepad.triangle", - "SQUARE": "psx.input.port2.gamepad.square", - "L1": "psx.input.port2.gamepad.l1", - "L2": "psx.input.port2.gamepad.l2", - "R1": "psx.input.port2.gamepad.r1", - "R2": "psx.input.port2.gamepad.r2", - "UP": "psx.input.port2.gamepad.up", - "DOWN": "psx.input.port2.gamepad.down", - "LEFT": "psx.input.port2.gamepad.left", - "RIGHT": "psx.input.port2.gamepad.right", - "SELECT": "psx.input.port2.gamepad.select", - "START": "psx.input.port2.gamepad.start", - } - - def mednafen_system_prefix(self): - return "psx" - - # def mednafen_video_size(self): - # # FIXME - # if self.is_pal(): - # size = (320, 240) - # else: - # size = (320, 224) - # return size diff -Nru fs-uae-arcade-2.9.6/fsgs/platforms/psx/psxmednafendriver.py fs-uae-arcade-2.9.7/fsgs/platforms/psx/psxmednafendriver.py --- fs-uae-arcade-2.9.6/fsgs/platforms/psx/psxmednafendriver.py 1970-01-01 00:00:00.000000000 +0000 +++ fs-uae-arcade-2.9.7/fsgs/platforms/psx/psxmednafendriver.py 2017-10-16 21:40:40.000000000 +0000 @@ -0,0 +1,73 @@ +from fsgs.drivers.mednafendriver import MednafenDriver +from fsgs.platforms.psx.psxplatform import PSX_CONTROLLER +from fsgs.platforms.psx.psxplatform import PSX_SCPH5501_BIN + + +class PlayStationMednafenDriver(MednafenDriver): + PORTS = [ + { + "description": "Input Port 1", + "types": [PSX_CONTROLLER] + }, { + "description": "Input Port 2", + "types": [PSX_CONTROLLER] + }, + ] + + def __init__(self, fsgs): + super().__init__(fsgs) + + def mednafen_input_mapping(self, port): + if port == 0: + return { + "CIRCLE": "psx.input.port1.gamepad.circle", + "CROSS": "psx.input.port1.gamepad.cross", + "TRIANGLE": "psx.input.port1.gamepad.triangle", + "SQUARE": "psx.input.port1.gamepad.square", + "L1": "psx.input.port1.gamepad.l1", + "L2": "psx.input.port1.gamepad.l2", + "R1": "psx.input.port1.gamepad.r1", + "R2": "psx.input.port1.gamepad.r2", + "UP": "psx.input.port1.gamepad.up", + "DOWN": "psx.input.port1.gamepad.down", + "LEFT": "psx.input.port1.gamepad.left", + "RIGHT": "psx.input.port1.gamepad.right", + "SELECT": "psx.input.port1.gamepad.select", + "START": "psx.input.port1.gamepad.start", + } + elif port == 1: + return { + "CIRCLE": "psx.input.port2.gamepad.circle", + "CROSS": "psx.input.port2.gamepad.cross", + "TRIANGLE": "psx.input.port2.gamepad.triangle", + "SQUARE": "psx.input.port2.gamepad.square", + "L1": "psx.input.port2.gamepad.l1", + "L2": "psx.input.port2.gamepad.l2", + "R1": "psx.input.port2.gamepad.r1", + "R2": "psx.input.port2.gamepad.r2", + "UP": "psx.input.port2.gamepad.up", + "DOWN": "psx.input.port2.gamepad.down", + "LEFT": "psx.input.port2.gamepad.left", + "RIGHT": "psx.input.port2.gamepad.right", + "SELECT": "psx.input.port2.gamepad.select", + "START": "psx.input.port2.gamepad.start", + } + + def mednafen_system_prefix(self): + return "psx" + + def game_video_size(self): + # FIXME + if self.is_pal(): + size = (320, 240) + else: + size = (320, 224) + return size + + def prepare(self): + super().prepare() + self.prepare_mednafen_bios(PSX_SCPH5501_BIN, "scph5501.bin") + self.prepare_mednafen_cd_images() + + def get_game_file(self, config_key="cartridge_slot"): + return None diff -Nru fs-uae-arcade-2.9.6/fsgs/platforms/psx/psxplatform.py fs-uae-arcade-2.9.7/fsgs/platforms/psx/psxplatform.py --- fs-uae-arcade-2.9.6/fsgs/platforms/psx/psxplatform.py 2017-05-25 10:07:53.000000000 +0000 +++ fs-uae-arcade-2.9.7/fsgs/platforms/psx/psxplatform.py 2017-10-16 21:40:40.000000000 +0000 @@ -1,6 +1,8 @@ +from fsgs.knownfiles import KnownFile from fsgs.platform import PlatformHandler from fsgs.platforms.loader import SimpleLoader +PSX_PLATFORM_ID = "PSX" PSX_PLATFORM_NAME = "PlayStation" # noinspection SpellCheckingInspection PSX_SCPH5500_BIN_SHA1 = "b05def971d8ec59f346f2d9ac21fb742e3eb6917" @@ -8,16 +10,26 @@ PSX_SCPH5501_BIN_SHA1 = "0555c6fae8906f3f09baf5988f00e55f88e9f30b" # noinspection SpellCheckingInspection PSX_SCPH5502_BIN_SHA1 = "f6bc2d1f5eb6593de7d089c425ac681d6fffd3f0" -PSX_ROMS_FOR_REGION = { - "JA": ["scph5500.bin", PSX_SCPH5500_BIN_SHA1], - "US": ["scph5501.bin", PSX_SCPH5501_BIN_SHA1], - "EU": ["scph5502.bin", PSX_SCPH5502_BIN_SHA1], -} PSX_CONTROLLER = { "type": "gamepad", "description": "Gamepad", "mapping_name": "playstation", } +PSX_SCPH5500_BIN = KnownFile( + "b05def971d8ec59f346f2d9ac21fb742e3eb6917", PSX_PLATFORM_ID, + "scph5500.bin") +PSX_SCPH5501_BIN = KnownFile( + "0555c6fae8906f3f09baf5988f00e55f88e9f30b", PSX_PLATFORM_ID, + "scph5501.bin") +# noinspection SpellCheckingInspection +PSX_SCPH5502_BIN = KnownFile( + "f6bc2d1f5eb6593de7d089c425ac681d6fffd3f0", PSX_PLATFORM_ID, + "scph5502.bin") +PSX_ROMS_FOR_REGION = { + "JA": ["scph5500.bin", PSX_SCPH5500_BIN.sha1], + "US": ["scph5501.bin", PSX_SCPH5501_BIN.sha1], + "EU": ["scph5502.bin", PSX_SCPH5502_BIN.sha1], +} class PlayStationPlatformHandler(PlatformHandler): @@ -31,8 +43,8 @@ return PlayStationLoader(fsgs) def get_runner(self, fsgs): - from fsgs.platforms.psx.mednafenpsxdriver import MednafenPsxDriver - return MednafenPsxDriver(fsgs) + from fsgs.platforms.psx.psxmednafendriver import PlayStationMednafenDriver + return PlayStationMednafenDriver(fsgs) class PlayStationLoader(SimpleLoader): diff -Nru fs-uae-arcade-2.9.6/fsgs/platforms/smd/mednafensmddriver.py fs-uae-arcade-2.9.7/fsgs/platforms/smd/mednafensmddriver.py --- fs-uae-arcade-2.9.6/fsgs/platforms/smd/mednafensmddriver.py 2017-05-25 10:07:53.000000000 +0000 +++ fs-uae-arcade-2.9.7/fsgs/platforms/smd/mednafensmddriver.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,111 +0,0 @@ -from fsgs.drivers.mednafendriver import MednafenDriver -from fsgs.option import Option - -SMD_MODEL_NTSC_U = "ntsc-u" -SMD_MODEL_NTSC_J = "ntsc-j" -SMD_MODEL_PAL = "pal" -SMD_MODEL_AUTO = "auto" - - -class MednafenSmdDriver(MednafenDriver): - CONTROLLER = { - "type": "gamepad", - "description": "Gamepad", - "mapping_name": "megadrive", - } - - PORTS = [ - { - "description": "1st Controller", - "types": [CONTROLLER] - }, { - "description": "2nd Controller", - "types": [CONTROLLER] - }, - ] - - def __init__(self, fsgs): - super().__init__(fsgs) - self.helper = MegaDriveHelper(self.options) - - def prepare(self): - if self.helper.model() == SMD_MODEL_NTSC_U: - region = "overseas_ntsc" - elif self.helper.model() == SMD_MODEL_PAL: - region = "overseas_pal" - elif self.helper.model() == SMD_MODEL_NTSC_J: - region = "domestic_ntsc" - else: - region = "game" - self.emulator.args.extend(["-md.region", region]) - # We do aspect calculation separately. Must not be done twice. - self.emulator.args.extend(["-md.correct_aspect", "0"]) - # The arguments must be added before the cartridge, which is why - # we call super().prepare() at the end here. - super().prepare() - - def mednafen_system_prefix(self): - return "md" - - def mednafen_input_mapping(self, port): - if port == 0: - return { - "A": "md.input.port1.gamepad.a", - "B": "md.input.port1.gamepad.b", - "C": "md.input.port1.gamepad.c", - "X": "md.input.port1.gamepad.x", - "Y": "md.input.port1.gamepad.y", - "Z": "md.input.port1.gamepad.z", - "UP": "md.input.port1.gamepad.up", - "DOWN": "md.input.port1.gamepad.down", - "LEFT": "md.input.port1.gamepad.left", - "RIGHT": "md.input.port1.gamepad.right", - "MODE": "md.input.port1.gamepad.mode", - "START": "md.input.port1.gamepad.start", - } - elif port == 1: - return { - "A": "md.input.port2.gamepad.a", - "B": "md.input.port2.gamepad.b", - "C": "md.input.port2.gamepad.c", - "X": "md.input.port2.gamepad.x", - "Y": "md.input.port2.gamepad.y", - "Z": "md.input.port2.gamepad.z", - "UP": "md.input.port2.gamepad.up", - "DOWN": "md.input.port2.gamepad.down", - "LEFT": "md.input.port2.gamepad.left", - "RIGHT": "md.input.port2.gamepad.right", - "MODE": "md.input.port2.gamepad.mode", - "START": "md.input.port2.gamepad.start", - } - - # FIXME: add PAUSE button to universal gamepad config - - def game_video_par(self): - # These may not be entirely correct... - if self.is_pal(): - return (4 / 3) / (320 / 240) - else: - return (4 / 3) / (320 / 224) - - def game_video_size(self): - # FIXME: is_pal should probably be influenced by model now. - if self.is_pal(): - size = (320, 240) - else: - size = (320, 224) - return size - - -class MegaDriveHelper: - def __init__(self, options): - self.options = options - - def model(self): - if self.options[Option.SMD_MODEL] == "ntsc-u": - return SMD_MODEL_NTSC_U - if self.options[Option.SMD_MODEL] == "ntsc-j": - return SMD_MODEL_NTSC_J - if self.options[Option.SMD_MODEL] == "pal": - return SMD_MODEL_PAL - return SMD_MODEL_AUTO diff -Nru fs-uae-arcade-2.9.6/fsgs/platforms/sms/mednafensmsdriver.py fs-uae-arcade-2.9.7/fsgs/platforms/sms/mednafensmsdriver.py --- fs-uae-arcade-2.9.6/fsgs/platforms/sms/mednafensmsdriver.py 2017-05-25 10:07:53.000000000 +0000 +++ fs-uae-arcade-2.9.7/fsgs/platforms/sms/mednafensmsdriver.py 2017-10-16 21:40:40.000000000 +0000 @@ -53,11 +53,8 @@ # FIXME: add PAUSE button to universal gamepad config def game_video_par(self): - # These may not be entirely correct... - if self.is_pal(): - return (4 / 3) / (256 / 240) - else: - return (4 / 3) / (256 / 224) + size = self.game_video_size() + return (4 / 3) / (size[0] / size[1]) def game_video_size(self): if self.is_pal(): diff -Nru fs-uae-arcade-2.9.6/fsgs/platforms/snes/mednafensnesdriver.py fs-uae-arcade-2.9.7/fsgs/platforms/snes/mednafensnesdriver.py --- fs-uae-arcade-2.9.6/fsgs/platforms/snes/mednafensnesdriver.py 2017-05-25 10:07:53.000000000 +0000 +++ fs-uae-arcade-2.9.7/fsgs/platforms/snes/mednafensnesdriver.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,79 +0,0 @@ -from fsgs.drivers.mednafendriver import MednafenDriver - - -class MednafenSnesDriver(MednafenDriver): - - CONTROLLER = { - "type": "gamepad", - "description": "Gamepad", - "mapping_name": "supernintendo", - } - - PORTS = [ - { - "description": "1st Controller", - "types": [CONTROLLER] - }, { - "description": "2nd Controller", - "types": [CONTROLLER] - }, - ] - - def __init__(self, fsgs): - super().__init__(fsgs) - - # def mednafen_aspect_ratio(self): - # return 4.0 / 3.0 - - def mednafen_input_mapping(self, port): - if port == 0: - return { - "A": "snes.input.port1.gamepad.a", - "B": "snes.input.port1.gamepad.b", - "X": "snes.input.port1.gamepad.x", - "Y": "snes.input.port1.gamepad.y", - "L": "snes.input.port1.gamepad.l", - "R": "snes.input.port1.gamepad.r", - "UP": "snes.input.port1.gamepad.up", - "DOWN": "snes.input.port1.gamepad.down", - "LEFT": "snes.input.port1.gamepad.left", - "RIGHT": "snes.input.port1.gamepad.right", - "SELECT": "snes.input.port1.gamepad.select", - "START": "snes.input.port1.gamepad.start", - } - elif port == 1: - return { - "A": "snes.input.port2.gamepad.a", - "B": "snes.input.port2.gamepad.b", - "X": "snes.input.port2.gamepad.x", - "Y": "snes.input.port2.gamepad.y", - "L": "snes.input.port2.gamepad.l", - "R": "snes.input.port2.gamepad.r", - "UP": "snes.input.port2.gamepad.up", - "DOWN": "snes.input.port2.gamepad.down", - "LEFT": "snes.input.port2.gamepad.left", - "RIGHT": "snes.input.port2.gamepad.right", - "SELECT": "snes.input.port2.gamepad.select", - "START": "snes.input.port2.gamepad.start", - } - - def mednafen_system_prefix(self): - return "snes" - - def mednafen_extra_graphics_options(self): - # We do aspect calculation already. Must not do twice. - return ["-snes.correct_aspect", "0"] - - def game_video_par(self): - # These may not be entirely correct... - if self.is_pal(): - return (4 / 3) / (256 / 239) - else: - return (4 / 3) / (256 / 224) - - def game_video_size(self): - if self.is_pal(): - size = (256, 239) - else: - size = (256, 224) - return size diff -Nru fs-uae-arcade-2.9.6/fsgs/platforms/super_nintendo.py fs-uae-arcade-2.9.7/fsgs/platforms/super_nintendo.py --- fs-uae-arcade-2.9.6/fsgs/platforms/super_nintendo.py 2017-05-25 10:07:53.000000000 +0000 +++ fs-uae-arcade-2.9.7/fsgs/platforms/super_nintendo.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,20 +0,0 @@ -from fsgs.platform import PlatformHandler -from fsgs.platforms.loader import SimpleLoader -from fsgs.platforms.snes.mednafensnesdriver import MednafenSnesDriver - - -class SuperNintendoPlatformHandler(PlatformHandler): - PLATFORM_NAME = "Super Nintendo" - - def __init__(self): - PlatformHandler.__init__(self) - - def get_loader(self, fsgs): - return SuperNintendoLoader(fsgs) - - def get_runner(self, fsgs): - return MednafenSnesDriver(fsgs) - - -class SuperNintendoLoader(SimpleLoader): - pass diff -Nru fs-uae-arcade-2.9.6/fsgs/platforms/supernintendo.py fs-uae-arcade-2.9.7/fsgs/platforms/supernintendo.py --- fs-uae-arcade-2.9.6/fsgs/platforms/supernintendo.py 1970-01-01 00:00:00.000000000 +0000 +++ fs-uae-arcade-2.9.7/fsgs/platforms/supernintendo.py 2017-10-16 21:40:40.000000000 +0000 @@ -0,0 +1,130 @@ +import hashlib +import os + +from fsgs import Option +from fsgs.drivers.mednafendriver import MednafenDriver +from fsgs.platform import PlatformHandler +from fsgs.platforms.loader import SimpleLoader + + +class SuperNintendoPlatformHandler(PlatformHandler): + PLATFORM_NAME = "Super Nintendo" + + def __init__(self): + PlatformHandler.__init__(self) + + def get_loader(self, fsgs): + return SuperNintendoLoader(fsgs) + + def get_runner(self, fsgs): + return SuperNintendoMednafenDriver(fsgs) + + +class SuperNintendoLoader(SimpleLoader): + pass + + +SNES_CONTROLLER = { + "type": "gamepad", + "description": "Gamepad", + "mapping_name": "supernintendo", +} +SNES_PORTS = [ + { + "description": "Port 1", + "types": [SNES_CONTROLLER], + "type_option": "snes_port_1_type", + "device_option": "snes_port_1", + }, { + "description": "Port 2", + "types": [SNES_CONTROLLER], + "type_option": "snes_port_2_type", + "device_option": "snes_port_2", + }, +] + + +class SuperNintendoMednafenDriver(MednafenDriver): + PORTS = SNES_PORTS + + def __init__(self, fsgc): + super().__init__(fsgc) + self.helper = SuperNintendoHelper(self.options) + + def prepare(self): + print("[DRIVER] Mednafen SNES driver preparing...") + super().prepare() + self.set_mednafen_aspect(4, 3) + # We do aspect calculation separately. Must not be done twice. + self.emulator.args.extend(["-snes.correct_aspect", "0"]) + # FIXME: Input ports configuration + # FIXME: SNES model + self.emulator.args.append(self.helper.prepare_rom(self)) + + # def mednafen_aspect_ratio(self): + # return 4.0 / 3.0 + + def mednafen_input_mapping(self, port): + n = port + 1 + return { + "A": "snes.input.port{}.gamepad.a".format(n), + "B": "snes.input.port{}.gamepad.b".format(n), + "X": "snes.input.port{}.gamepad.x".format(n), + "Y": "snes.input.port{}.gamepad.y".format(n), + "L": "snes.input.port{}.gamepad.l".format(n), + "R": "snes.input.port{}.gamepad.r".format(n), + "UP": "snes.input.port{}.gamepad.up".format(n), + "DOWN": "snes.input.port{}.gamepad.down".format(n), + "LEFT": "snes.input.port{}.gamepad.left".format(n), + "RIGHT": "snes.input.port{}.gamepad.right".format(n), + "SELECT": "snes.input.port{}.gamepad.select".format(n), + "START": "snes.input.port{}.gamepad.start".format(n), + } + + def mednafen_system_prefix(self): + return "snes" + + def game_video_par(self): + # These may not be entirely correct... + # if self.is_pal(): + # return (4 / 3) / (256 / 239) + # else: + # return (4 / 3) / (256 / 224) + size = self.game_video_size() + return (4 / 3) / (size[0] / size[1]) + + def game_video_size(self): + if self.is_pal(): + size = (256, 239) + else: + size = (256, 224) + return size + + def get_game_file(self, config_key="cartridge_slot"): + return None + + +class SuperNintendoHelper: + def __init__(self, options): + self.options = options + + def prepare_rom(self, driver): + file_uri = self.options[Option.CARTRIDGE_SLOT] + input_stream = driver.fsgc.file.open(file_uri) + _, ext = os.path.splitext(file_uri) + return self.prepare_rom_with_stream(driver, input_stream, ext) + + def prepare_rom_with_stream(self, driver, input_stream, ext): + sha1_obj = hashlib.sha1() + path = driver.temp_file("rom" + ext).path + with open(path, "wb") as f: + while True: + data = input_stream.read(65536) + if not data: + break + f.write(data) + sha1_obj.update(data) + new_path = os.path.join( + os.path.dirname(path), sha1_obj.hexdigest()[:8].upper() + ext) + os.rename(path, new_path) + return new_path diff -Nru fs-uae-arcade-2.9.6/fsgs/platforms/tg16/mednafentg16driver.py fs-uae-arcade-2.9.7/fsgs/platforms/tg16/mednafentg16driver.py --- fs-uae-arcade-2.9.6/fsgs/platforms/tg16/mednafentg16driver.py 2017-05-25 10:07:53.000000000 +0000 +++ fs-uae-arcade-2.9.7/fsgs/platforms/tg16/mednafentg16driver.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,93 +0,0 @@ -from fsgs.drivers.mednafendriver import MednafenDriver - -""" -Some information from https://en.wikipedia.org/wiki/TurboGrafx-16: - -All PC Engine hardware outputs video in NTSC format, including the European -TurboGrafx; it generates a PAL-compatible video signal by using a chroma -encoder chip not found in any other system in the series. - -X (Horizontal) Resolution: variable, maximum of 565 (programmable to 282, -377 or 565 pixels, or as 5.3693175 MHz, 7.15909 MHz, and 10.738635 MHz pixel -dot clock). Taking into consideration overscan limitations of CRT televisions -at the time, the horizontal resolutions were realistically limited to -something a bit less than what the system was actually capable of. -Consequently, most game developers limited their games to either 256, 352, -or 512 pixels in display width for each of the three modes.[23] - -Y (Vertical) Resolution: variable, maximum of 242 (programmable in increments -of 1 scanline). It is possible to achieve an interlaced "mode" with a maximum -vertical resolution of 484 scanlines by alternating between the two different -vertical resolution modes used by the system. However, it is unknown, at this -time, if this interlaced resolution is compliant with (and hence displayed -correctly on) NTSC televisions. - -The majority of TurboGrafx-16 games use 256×239 -""" - - -class MednafenTg16Driver(MednafenDriver): - CONTROLLER = { - "type": "gamepad", - "description": "Gamepad", - "mapping_name": "turbografx16", - } - - PORTS = [ - { - "description": "1st Controller", - "types": [CONTROLLER] - }, { - "description": "2nd Controller", - "types": [CONTROLLER] - }, - ] - - def __init__(self, fsgs): - super().__init__(fsgs) - - def mednafen_input_mapping(self, port): - if port == 0: - return { - "1": "pce.input.port1.gamepad.i", - "2": "pce.input.port1.gamepad.ii", - "UP": "pce.input.port1.gamepad.up", - "DOWN": "pce.input.port1.gamepad.down", - "LEFT": "pce.input.port1.gamepad.left", - "RIGHT": "pce.input.port1.gamepad.right", - "SELECT": "pce.input.port1.gamepad.select", - "RUN": "pce.input.port1.gamepad.run", - } - elif port == 1: - return { - "1": "pce.input.port2.gamepad.i", - "2": "pce.input.port2.gamepad.ii", - "UP": "pce.input.port2.gamepad.up", - "DOWN": "pce.input.port2.gamepad.down", - "LEFT": "pce.input.port2.gamepad.left", - "RIGHT": "pce.input.port2.gamepad.right", - "SELECT": "pce.input.port2.gamepad.select", - "RUN": "pce.input.port2.gamepad.run", - } - - def mednafen_system_prefix(self): - return "pce" - - def game_video_par(self): - # These calculations are just approximations, and not verified. - return (4 / 3) / (288 / 232) - # if self.is_pal(): - # return (4 / 3) / (256 / 240) - # else: - # return (4 / 3) / (256 / 240) - - def game_video_size(self): - # if self.is_pal(): - # size = (256, 240) - # else: - # size = (256, 240) - size = (288, 232) - return size - - def get_game_refresh_rate(self): - return 59.94 diff -Nru fs-uae-arcade-2.9.6/fsgs/platforms/turbografx_16.py fs-uae-arcade-2.9.7/fsgs/platforms/turbografx_16.py --- fs-uae-arcade-2.9.6/fsgs/platforms/turbografx_16.py 2017-05-25 10:07:53.000000000 +0000 +++ fs-uae-arcade-2.9.7/fsgs/platforms/turbografx_16.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,20 +0,0 @@ -from fsgs.platform import PlatformHandler -from fsgs.platforms.loader import SimpleLoader -from fsgs.platforms.tg16.mednafentg16driver import MednafenTg16Driver - - -class TurboGrafx16PlatformHandler(PlatformHandler): - PLATFORM_NAME = "TurboGrafx-16" - - def __init__(self): - PlatformHandler.__init__(self) - - def get_loader(self, fsgs): - return TurboGrafx16Loader(fsgs) - - def get_runner(self, fsgs): - return MednafenTg16Driver(fsgs) - - -class TurboGrafx16Loader(SimpleLoader): - pass diff -Nru fs-uae-arcade-2.9.6/fsgs/platforms/turbografx16.py fs-uae-arcade-2.9.7/fsgs/platforms/turbografx16.py --- fs-uae-arcade-2.9.6/fsgs/platforms/turbografx16.py 1970-01-01 00:00:00.000000000 +0000 +++ fs-uae-arcade-2.9.7/fsgs/platforms/turbografx16.py 2017-10-16 21:40:40.000000000 +0000 @@ -0,0 +1,109 @@ +from fsgs.drivers.mednafendriver import MednafenDriver +from fsgs.platform import Platform +from fsgs.platforms.loader import CartridgePlatformLoader + +""" +Some information from https://en.wikipedia.org/wiki/TurboGrafx-16: + +All PC Engine hardware outputs video in NTSC format, including the European +TurboGrafx; it generates a PAL-compatible video signal by using a chroma +encoder chip not found in any other system in the series. + +X (Horizontal) Resolution: variable, maximum of 565 (programmable to 282, +377 or 565 pixels, or as 5.3693175 MHz, 7.15909 MHz, and 10.738635 MHz pixel +dot clock). Taking into consideration overscan limitations of CRT televisions +at the time, the horizontal resolutions were realistically limited to +something a bit less than what the system was actually capable of. +Consequently, most game developers limited their games to either 256, 352, +or 512 pixels in display width for each of the three modes.[23] + +Y (Vertical) Resolution: variable, maximum of 242 (programmable in increments +of 1 scanline). It is possible to achieve an interlaced "mode" with a maximum +vertical resolution of 484 scanlines by alternating between the two different +vertical resolution modes used by the system. However, it is unknown, at this +time, if this interlaced resolution is compliant with (and hence displayed +correctly on) NTSC televisions. + +The majority of TurboGrafx-16 games use 256×239 +""" + +TG16_CONTROLLER = { + "type": "gamepad", + "description": "Gamepad", + "mapping_name": "turbografx16", +} + + +class TurboGrafx16Platform(Platform): + PLATFORM_ID = "tg16" + PLATFORM_NAME = "TurboGrafx-16" + + def __init__(self): + super().__init__() + + def driver(self, fsgc): + return TurboGrafx16MednafenDriver(fsgc) + + def loader(self, fsgc): + return TurboGrafx16Loader(fsgc) + + +class TurboGrafx16Loader(CartridgePlatformLoader): + pass + + +class TurboGrafx16MednafenDriver(MednafenDriver): + PORTS = [ + { + "description": "Input Port 1", + "types": [TG16_CONTROLLER] + }, { + "description": "Input Port 2", + "types": [TG16_CONTROLLER] + }, + ] + + def __init__(self, fsgc): + super().__init__(fsgc) + + def game_video_par(self): + size = self.game_video_size() + return (4 / 3) / (size[0] / size[1]) + + def game_video_size(self): + # if self.is_pal(): + # size = (256, 240) + # else: + # size = (256, 240) + size = (288, 232) + return size + + def get_game_refresh_rate(self): + return 59.94 + + def mednafen_input_mapping(self, port): + if port == 0: + return { + "1": "pce.input.port1.gamepad.i", + "2": "pce.input.port1.gamepad.ii", + "UP": "pce.input.port1.gamepad.up", + "DOWN": "pce.input.port1.gamepad.down", + "LEFT": "pce.input.port1.gamepad.left", + "RIGHT": "pce.input.port1.gamepad.right", + "SELECT": "pce.input.port1.gamepad.select", + "RUN": "pce.input.port1.gamepad.run", + } + elif port == 1: + return { + "1": "pce.input.port2.gamepad.i", + "2": "pce.input.port2.gamepad.ii", + "UP": "pce.input.port2.gamepad.up", + "DOWN": "pce.input.port2.gamepad.down", + "LEFT": "pce.input.port2.gamepad.left", + "RIGHT": "pce.input.port2.gamepad.right", + "SELECT": "pce.input.port2.gamepad.select", + "RUN": "pce.input.port2.gamepad.run", + } + + def mednafen_system_prefix(self): + return "pce" diff -Nru fs-uae-arcade-2.9.6/fsgs/platforms/turbografxcd.py fs-uae-arcade-2.9.7/fsgs/platforms/turbografxcd.py --- fs-uae-arcade-2.9.6/fsgs/platforms/turbografxcd.py 1970-01-01 00:00:00.000000000 +0000 +++ fs-uae-arcade-2.9.7/fsgs/platforms/turbografxcd.py 2017-10-16 21:40:40.000000000 +0000 @@ -0,0 +1,69 @@ +from fsgs.knownfiles import KnownFile +from fsgs.platform import Platform +from fsgs.platforms.loader import CDPlatformLoader +from fsgs.platforms.turbografx16 import TurboGrafx16MednafenDriver + +TGCD_PLATFORM_ID = "tgcd" +TGCD_PLATFORM_NAME = "TurboGrafx-CD" +TGCD_CONTROLLER = { + "type": "gamepad", + "description": "Gamepad", + "mapping_name": "turbografx16", +} +SYSCARD3_PCE = KnownFile( + "1b4c260326d905bc718812dad0f68089977f427b", TGCD_PLATFORM_ID, + "syscard3.pce") + + +class TurboGrafxCDPlatform(Platform): + PLATFORM_ID = TGCD_PLATFORM_ID + PLATFORM_NAME = TGCD_PLATFORM_NAME + + def __init__(self): + super().__init__() + + def driver(self, fsgc): + return TurboGrafxCDMednafenDriver(fsgc) + + def loader(self, fsgc): + return TurboGrafxCDLoader(fsgc) + + +class TurboGrafxCDLoader(CDPlatformLoader): + pass + + +class TurboGrafxCDMednafenDriver(TurboGrafx16MednafenDriver): + PORTS = [ + { + "description": "Input Port 1", + "types": [TGCD_CONTROLLER] + }, { + "description": "Input Port 2", + "types": [TGCD_CONTROLLER] + }, + ] + + def __init__(self, fsgc): + super().__init__(fsgc) + + # def game_video_par(self): + # return (4 / 3) / (288 / 232) + # + # def game_video_size(self): + # return 288 * 2, 232 * 2 + + def mednafen_system_prefix(self): + return "pce" + + def prepare(self): + # self.emulator.args.extend( + # ["-pce.enable", "0", "-pce_fast.enable", "0"]) + # self.emulator.args.extend(["-force_module", "pce_fast"]) + # self.emulator.args.extend(["-force_module", "pce"]) + super().prepare() + self.prepare_mednafen_bios(SYSCARD3_PCE, "syscard3.pce") + self.prepare_mednafen_cd_images() + + def get_game_file(self, config_key="cartridge_slot"): + return None diff -Nru fs-uae-arcade-2.9.6/fsgs/platforms/zxs/messspectrumdriver.py fs-uae-arcade-2.9.7/fsgs/platforms/zxs/messspectrumdriver.py --- fs-uae-arcade-2.9.6/fsgs/platforms/zxs/messspectrumdriver.py 2017-05-25 10:07:53.000000000 +0000 +++ fs-uae-arcade-2.9.7/fsgs/platforms/zxs/messspectrumdriver.py 2017-10-16 21:40:40.000000000 +0000 @@ -1,7 +1,5 @@ from fsgs.drivers.messdriver import MessDriver -from fsgs.knownfiles import KnownFile from fsgs.option import Option -from fsgs.platforms import PLATFORM_ZXS from fsgs.spectrum import ZXS_48_ROM, ZXS_128_0_ROM, ZXS_128_1_ROM, \ ZXS_PLUS3_0_ROM, ZXS_PLUS3_1_ROM, ZXS_PLUS3_2_ROM, ZXS_PLUS3_3_ROM diff -Nru fs-uae-arcade-2.9.6/fsgs/plugins/plugin_manager.py fs-uae-arcade-2.9.7/fsgs/plugins/plugin_manager.py --- fs-uae-arcade-2.9.6/fsgs/plugins/plugin_manager.py 2017-05-25 10:07:53.000000000 +0000 +++ fs-uae-arcade-2.9.7/fsgs/plugins/plugin_manager.py 2017-10-16 21:40:40.000000000 +0000 @@ -9,21 +9,31 @@ import fsboot from fsbc.util import Version -from fsbc.system import windows, linux, macosx +from fsbc.system import System, windows, linux from fsgs.FSGSDirectories import FSGSDirectories +X86_MACHINES = ["x86", "i386", "i486", "i586", "i686"] +X86_64_MACHINES = ["x86_64", "x86-64", "amd64"] +X86_ANY_MACHINES = X86_MACHINES + X86_64_MACHINES + logger = logging.getLogger("PLUGINS") known_plugin_versions = { - "Arcade-FS": "0.168fs0", + # "Arcade-FS": "0.168fs0", "CAPSImg": "5.1fs3", - "DOSBox-FS": "0.74.4006fs2", + "Cheats": "1.0.0", + "DOSBox-FS": "0.74.4006fs7", "Fuse-FS": "1.3.3fs5", "Hatari-FS": "2.0.0fs1", - "Mednafen-FS": "0.9.42fs1", - "Mess-FS": "0.168fs0", + "MAME-FS": "0.189fs1", + # "Mess-FS": "0.168fs0", + # "MultiEmu-FS": "0.189fs1", + "Mednafen-FS": "0.9.47fs6", + "Mupen64Plus-LR": "2.5fs0git", + "Nestopia-LR": "1.49fs0wip", "QEMU-UAE": "3.8.2qemu2.2.0", "Regina-FS": "3.9.1fs0", + "RetroArch-FS": "1.6.7fs1", "UADE-FS": "2.13fs1", "Vice-FS": "3.0fs1", } @@ -50,6 +60,9 @@ def provides(self): return self._provides + def data_file_path(self, name): + return os.path.join(self.path, "Data", name) + class Plugin(BasePlugin): def __init__(self, path, name, version, cp): @@ -73,7 +86,7 @@ if pretty: return "Windows" return "windows" - elif macosx: + elif System.macos: if pretty: return "macOS" return "macos" @@ -93,8 +106,7 @@ @staticmethod def arch_name(pretty=False): - if platform.machine().lower() in ["x86_64", "x86-64", "amd64", - "i386", "i486", "i586", "i686"]: + if platform.machine().lower() in X86_ANY_MACHINES: if platform.architecture()[0] == "64bit": return "x86-64" else: @@ -113,24 +125,36 @@ def __init__(self, path, arch_path): name = os.path.basename(path) version_path = os.path.join(arch_path, "Version.txt") - with open(version_path, "r", encoding="UTF-8") as f: - version = f.read().strip() + if os.path.exists(version_path): + with open(version_path, "r", encoding="UTF-8") as f: + version = f.read().strip() + else: + # version_path = os.path.join(path, "Data", "Version.txt") + version_path = os.path.join(path, "Version.txt") + with open(version_path, "r", encoding="UTF-8") as f: + version = f.read().strip() super().__init__(path, name, version) self._arch_path = arch_path def load_provides(self): logger.debug("Loading provides for %s", repr(self.path)) + if os.path.exists(self._arch_path): + self.load_arch_provides() + + def load_arch_provides(self): for item in os.listdir(self._arch_path): path = os.path.join(self._arch_path, item) if windows: if item.endswith(".exe"): self.add_provide("executable:" + item[:-4].lower(), path) + elif path.endswith(".dll"): + self.add_provide("library:" + item.lower()[:-4], path) else: if path.endswith(".so"): self.add_provide("library:" + item.lower()[:-3], path) elif os.access(path, os.X_OK): self.add_provide("executable:" + item.lower(), path) - if macosx: + if System.macos: if path.endswith(".app"): app_path = os.path.join( path, "Contents", "MacOS") @@ -145,6 +169,10 @@ path = self.provides()["executable:" + name] return PluginExecutable(self, path) + def library_path(self, name): + path = self.provides()["library:" + name] + return path + def __str__(self): return " min_width: @@ -327,6 +330,9 @@ min_height = 0 last_margin = 0 for child in self.children: + if hasattr(child.element, "explicitly_hidden"): + if child.element.explicitly_hidden(): + continue min_height += child.element.get_min_height() min_height += max(last_margin, child.margin_top) last_margin = child.margin_bottom diff -Nru fs-uae-arcade-2.9.6/fsui/qt/widget.py fs-uae-arcade-2.9.7/fsui/qt/widget.py --- fs-uae-arcade-2.9.6/fsui/qt/widget.py 2017-05-25 10:07:53.000000000 +0000 +++ fs-uae-arcade-2.9.7/fsui/qt/widget.py 2017-10-16 21:40:40.000000000 +0000 @@ -19,6 +19,7 @@ # noinspection PyProtectedMember self._window = parent._window self._widget = None + self._explicitly_hidden = False def widget(self): return self._widget @@ -46,11 +47,15 @@ print("Widget.on_destroy", self) self.destroyed.emit() + def explicitly_hidden(self): + return self._explicitly_hidden + def set_visible(self, show=True): if show: self.widget().show() else: self.widget().hide() + self._explicitly_hidden = not show def show(self): self.set_visible(True) diff -Nru fs-uae-arcade-2.9.6/launcher/apps/fs_game_runner.py fs-uae-arcade-2.9.7/launcher/apps/fs_game_runner.py --- fs-uae-arcade-2.9.6/launcher/apps/fs_game_runner.py 2017-05-25 10:07:54.000000000 +0000 +++ fs-uae-arcade-2.9.7/launcher/apps/fs_game_runner.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,23 +0,0 @@ -import sys - -from fsbc.settings import Settings -from fsgs.context import fsgs - - -def app_main(): - args = [] - for arg in sys.argv[1:]: - if not arg.startswith("--"): - args.append(arg) - if len(args) == 0: - print("Usage: fs-game-runner [options] ") - return - - game_or_variant_uuid = args[-1] - print(game_or_variant_uuid) - - fsgs.load_game_by_uuid(game_or_variant_uuid) - fsgs.config.add_from_argv() - print("settings:fullscreen", Settings.instance()["fullscreen"]) - print("config:fullscreen", fsgs.config.get("fullscreen")) - fsgs.run_game() diff -Nru fs-uae-arcade-2.9.6/launcher/apps/fsgs.py fs-uae-arcade-2.9.7/launcher/apps/fsgs.py --- fs-uae-arcade-2.9.6/launcher/apps/fsgs.py 1970-01-01 00:00:00.000000000 +0000 +++ fs-uae-arcade-2.9.7/launcher/apps/fsgs.py 2017-10-16 21:40:41.000000000 +0000 @@ -0,0 +1,49 @@ +import sys + +from fsbc.settings import Settings +from fsgs.context import default_context + + +def app_main(): + args = [] + for arg in sys.argv[1:]: + if not arg.startswith("--"): + args.append(arg) + if len(args) == 0: + print("Usage: fsgc [options] ") + return + + game_or_variant_uuid = args[-1] + print(game_or_variant_uuid) + + fsgc = default_context() + fsgc.load_game_by_uuid(game_or_variant_uuid) + fsgc.config.add_from_argv() + + print("settings:fullscreen", Settings.instance()["fullscreen"]) + print("config:fullscreen", fsgc.config.get("fullscreen")) + + # sys.exit(1) + # fsgc.run_game() + + from fsgs.platform import Platform + platform = Platform.create(fsgc.game.platform.id) + driver = platform.driver(fsgc) + + from fsgs.input.enumeratehelper import EnumerateHelper + device_helper = EnumerateHelper() + device_helper.default_port_selection(driver.ports, driver.options) + + print("") + for port in driver.ports: + print("Port", port.index) + print(" ", port.type, [x["type"] for x in port.types]) + print(" ", port.device) + print("") + + # sys.exit(1) + + driver.prepare() + process = driver.run() + process.wait() + driver.finish() diff -Nru fs-uae-arcade-2.9.6/launcher/apps/fs_uae_arcade.py fs-uae-arcade-2.9.7/launcher/apps/fs_uae_arcade.py --- fs-uae-arcade-2.9.6/launcher/apps/fs_uae_arcade.py 2017-05-25 10:07:54.000000000 +0000 +++ fs-uae-arcade-2.9.7/launcher/apps/fs_uae_arcade.py 2017-10-16 21:40:41.000000000 +0000 @@ -1,10 +1,17 @@ +import platform import sys +from launcher.version import VERSION + def app_main(): if "--help" in sys.argv: print(help_text) return + print("FS-UAE Arcade {0}".format(VERSION)) + print(sys.argv) + print(platform.uname()) + for i, arg in enumerate(sys.argv): if arg.startswith("--monitor="): sys.argv[i] = "--settings:monitor=" + arg[10:] @@ -26,14 +33,15 @@ help_text = """\ -FS-UAE Arcade Help" +FS-UAE Arcade {version} Options: --fullscreen - --window + --window[=maximize] --monitor=left|right|middle-left|middle-right (Requires fullscreen) - --platform= - --favorites + --platform= Open with chosen platform pre-selected + --favorites Open with favorites pre-selected + --disable-search Disable search function in Arcade UI TODO: Add more documentation -""" +""".format(version=VERSION) diff -Nru fs-uae-arcade-2.9.6/launcher/apps/fs_uae_launcher.py fs-uae-arcade-2.9.7/launcher/apps/fs_uae_launcher.py --- fs-uae-arcade-2.9.6/launcher/apps/fs_uae_launcher.py 2017-05-25 10:07:54.000000000 +0000 +++ fs-uae-arcade-2.9.7/launcher/apps/fs_uae_launcher.py 2017-10-16 21:40:41.000000000 +0000 @@ -1,3 +1,4 @@ +import platform import sys import traceback @@ -9,6 +10,8 @@ print(help_text) return print("FS-UAE Launcher {0}".format(VERSION)) + print(sys.argv) + print(platform.uname()) for i, arg in enumerate(sys.argv[:]): if arg == "--fullscreen" or arg == "--fullscreen=1": @@ -33,6 +36,7 @@ "An error occurred starting FS-UAE Launcher:\n\n" + repr(e) + "\n\nFS-UAE Launcher cannot start " "because of this.", "FS-UAE Launcher") + fsui.show_error(traceback.format_exc()) else: app.run() app.save_settings() @@ -46,7 +50,7 @@ help_text = """\ -FS-UAE Launcher Help" +FS-UAE Launcher {version} Options: --new-config[=] @@ -55,8 +59,13 @@ --fullscreen --no-fullscreen --no-gui Do not show launch progress dialog - --no-auto-detect-game Do not try to auto-detect game from archive - + --no-auto-detect-game Do not auto-detect game from archive TODO: Add more documentation -""" + +Or you can execute fs-uae-launcher - where command is one of: + * arcade Launch FS-UAE Arcade + * fs-uae-netplay-server Run netplay server + * list-dirs List directories used by the Launcher + * list-plugins List installed plugins +""".format(version=VERSION) diff -Nru fs-uae-arcade-2.9.6/launcher/apps/__init__.py fs-uae-arcade-2.9.7/launcher/apps/__init__.py --- fs-uae-arcade-2.9.6/launcher/apps/__init__.py 2017-05-25 10:07:54.000000000 +0000 +++ fs-uae-arcade-2.9.7/launcher/apps/__init__.py 2017-10-16 21:40:41.000000000 +0000 @@ -6,60 +6,83 @@ # Workaround to make import typing work without having it on the default # python path (would confuse mypy). +from fsgs import OPENRETRO_DEFAULT_DATABASES, openretro from launcher.option import Option sys.modules["typing"] = fstd.typing -def main(): - app = "fs-uae-launcher" +def find_app(app): + if app in ["launcher", "fs-uae-launcher"]: + from launcher.apps.fs_uae_launcher import app_main + elif app in ["arcade", "fs-uae-arcade"]: + from launcher.apps.fs_uae_arcade import app_main + elif app in ["workspace"]: + from launcher.apps.workspace import app_main + elif app == "fs-uae-netplay-server": + from launcher.apps.fs_uae_netplay_server import app_main + elif app == "fs-game-center": + from launcher.apps.fs_game_center import app_main + elif app in ["dump-game-database", "game-database-dumper"]: + from fsgs.gamedb.game_database_dumper import game_database_dumper_main + app_main = game_database_dumper_main + elif app in ["fsgs", "fs-game-runner"]: + from launcher.apps.fsgs import app_main + elif app == "list-plugins": + from launcher.apps.listplugins import app_main + elif app == "list-dirs": + from launcher.apps.listdirs import app_main + elif app in ["dosbox", "dosbox-fs"]: + from launcher.apps.dosbox_fs import app_main + elif app in ["mame", "mame-fs"]: + from launcher.apps.mame_fs import app_main + elif app in ["mednafen", "mednafen-fs"]: + from launcher.apps.mednafen_fs import app_main + elif app in ["libretro-nestopia"]: + from launcher.apps.libretro_nestopia import app_main + + elif app in ["uade", "uade-fs"]: + from launcher.apps.uade_fs import app_main + else: + return None + return app_main + + +def main(): + app_name = "" # Check deprecated/legacy app options. if "--server" in sys.argv: sys.argv.remove("--server") - app = "fs-uae-netplay-server" + app_name = "fs-uae-netplay-server" if "--arcade" in sys.argv: sys.argv.remove("--arcade") - app = "fs-uae-arcade" + app_name = "fs-uae-arcade" if "--fs-uae-arcade" in sys.argv: sys.argv.remove("--fs-uae-arcade") - app = "fs-uae-arcade" + app_name = "fs-uae-arcade" if sys.argv[0].endswith("fs-game-center"): - app = "fs-game-center" + app_name = "fs-game-center" if len(sys.argv) > 1: if sys.argv[1] == "xdftool": - app = "xdftool" + app_name = "xdftool" del sys.argv[1] if "--xdftool" in sys.argv: sys.argv.remove("--xdftool") - app = "xdftool" + app_name = "xdftool" + # Check new app option. + for arg in sys.argv: + if arg.startswith("--app="): + app_name = arg[6:] + sys.argv.remove(arg) import fsgs if "--openretro" in sys.argv: sys.argv.remove("--openretro") fsgs.product = "OpenRetro" fsgs.openretro = True - # Must also remember to update game_scanner.py - Option.get(Option.ARCADE_DATABASE)["default"] = "1" - Option.get(Option.ATARI_DATABASE)["default"] = "1" - Option.get(Option.C64_DATABASE)["default"] = "1" - Option.get(Option.CPC_DATABASE)["default"] = "1" - Option.get(Option.DOS_DATABASE)["default"] = "1" - Option.get(Option.GB_DATABASE)["default"] = "1" - Option.get(Option.GBA_DATABASE)["default"] = "1" - Option.get(Option.GBC_DATABASE)["default"] = "1" - Option.get(Option.NES_DATABASE)["default"] = "1" - Option.get(Option.PSX_DATABASE)["default"] = "1" - Option.get(Option.SMD_DATABASE)["default"] = "1" - Option.get(Option.SNES_DATABASE)["default"] = "1" - Option.get(Option.TG16_DATABASE)["default"] = "1" - Option.get(Option.ZXS_DATABASE)["default"] = "1" - - # Check new app option. - for arg in sys.argv: - if arg.startswith("--app="): - app = arg[6:] - sys.argv.remove(arg) + for option_name in OPENRETRO_DEFAULT_DATABASES: + Option.get(option_name)["default"] = "1" # Check for (fake) version override for arg in sys.argv: @@ -69,44 +92,36 @@ if key == "fake_version": launcher.version.VERSION = value - import socket - socket.setdefaulttimeout(30.0) - - if app == "xdftool": + if app_name == "xdftool": sys.argv[0] = "xdftool" - import amitools.tools.xdftool + import amitools.tols.xdftool sys.exit(amitools.tools.xdftool.main()) + app_main = None + if app_main is None: + if app_name: + app_main = find_app(app_name) + elif len(sys.argv) > 1: + app_main = find_app(sys.argv[1]) + if app_main is not None: + # Remove app name from sys.argv + del sys.argv[1] + + if app_main is None and not app_name: + app_name = "fs-uae-launcher" + app_main = find_app(app_name) + # if openretro: + # if app_name == "fs-uae-launcher": + # app_name = "openretro-launcher" + # elif app_name == "fs-uae-arcade": + # app_name = "openretro-arcade" + + import socket + socket.setdefaulttimeout(30.0) from fsbc.init import initialize_application - initialize_application(app, version=launcher.version.VERSION) + initialize_application(app_name, version=launcher.version.VERSION) - if app in ["launcher", "fs-uae-launcher"]: - from launcher.apps.fs_uae_launcher import app_main - elif app in ["arcade", "fs-uae-arcade"]: - from launcher.apps.fs_uae_arcade import app_main - elif app in ["workspace"]: - from launcher.apps.workspace import app_main - elif app == "fs-uae-netplay-server": - from launcher.apps.fs_uae_netplay_server import app_main - elif app == "fs-game-center": - from launcher.apps.fs_game_center import app_main - elif app == "game-database-dumper": - from fsgs.gamedb.game_database_dumper import game_database_dumper_main - app_main = game_database_dumper_main - elif app == "fs-game-runner": - from launcher.apps.fs_game_runner import app_main - elif app == "dosbox-fs": - from launcher.apps.dosbox_fs import app_main - elif app == "mame-fs": - from launcher.apps.mame_fs import app_main - elif app == "mednafen-fs": - from launcher.apps.mednafen_fs import app_main - elif app == "uade-fs": - from launcher.apps.uade_fs import app_main - elif app == "list-plugins": - from launcher.apps.list_plugins import app_main - elif app == "list-dirs": - from launcher.apps.list_dirs import app_main - else: - raise Exception("Unknown app specified") + if app_main is None: + print("No valid app specified", file=sys.stderr) + sys.exit(1) app_main() diff -Nru fs-uae-arcade-2.9.6/launcher/apps/libretro_nestopia.py fs-uae-arcade-2.9.7/launcher/apps/libretro_nestopia.py --- fs-uae-arcade-2.9.6/launcher/apps/libretro_nestopia.py 1970-01-01 00:00:00.000000000 +0000 +++ fs-uae-arcade-2.9.7/launcher/apps/libretro_nestopia.py 2017-10-16 21:40:41.000000000 +0000 @@ -0,0 +1,21 @@ +import sys +from fsgs.plugins.plugin_manager import PluginManager +""" +RetroArch launcher script used for testing. +""" + + +# FIXME: Use from RetroArchDriver +def find_libretro_core(name): + return "/usr/lib/x86_64-linux-gnu/libretro/{}.so".format(name) + + +def app_main(): + executable = PluginManager.instance().find_executable("retroarch") + + args = sys.argv[1:] + libretro_core = find_libretro_core("nestopia_libretro") + args.extend(["-L", libretro_core]) + + process = executable.popen(args) + process.wait() diff -Nru fs-uae-arcade-2.9.6/launcher/apps/list_dirs.py fs-uae-arcade-2.9.7/launcher/apps/list_dirs.py --- fs-uae-arcade-2.9.6/launcher/apps/list_dirs.py 2017-05-25 10:07:54.000000000 +0000 +++ fs-uae-arcade-2.9.7/launcher/apps/list_dirs.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,35 +0,0 @@ -from fsbc.settings import Settings -from fsgs.FSGSDirectories import FSGSDirectories -""" -Debug script used to dump information about detected plugins -""" - - -def app_main(): - FSGSDirectories.initialize() - Settings.instance().load() - print("") - print("Directories:") - print("* base_dir =", FSGSDirectories.get_base_dir()) - print("* cache_dir =", FSGSDirectories.get_cache_dir()) - print("* cdroms_dir =", FSGSDirectories.get_cdroms_dir()) - print("* configurations_dir =", FSGSDirectories.get_configurations_dir()) - print("* controllers_dir =", FSGSDirectories.get_controllers_dir()) - print("* covers_dir =", FSGSDirectories.get_covers_dir()) - print("* data_dir =", FSGSDirectories.get_data_dir()) - print("* downloads_dir =", FSGSDirectories.downloads_dir()) - print("* floppies_dir =", FSGSDirectories.get_floppies_dir()) - print("* hard_drives_dir =", FSGSDirectories.get_hard_drives_dir()) - # print("* images_dir =", FSGSDirectories.get_images_dir()) - print("* kickstarts_dir =", FSGSDirectories.get_kickstarts_dir()) - print("* launcher_dir =", FSGSDirectories.get_launcher_dir()) - print("* logs_dir =", FSGSDirectories.get_logs_dir()) - print("* plugins_dir =", FSGSDirectories.get_plugins_dir()) - # print("* portable_dir =", FSGSDirectories.portable_dir()) - print("* save_states_dir =", FSGSDirectories.get_save_states_dir()) - print("* screenshots_dir =", FSGSDirectories.get_screenshots_dir()) - print("* screenshots_output_dir =", - FSGSDirectories.screenshots_output_dir()) - print("* themes_dir =", FSGSDirectories.get_themes_dir()) - print("* titles_dir =", FSGSDirectories.get_titles_dir()) - print("") diff -Nru fs-uae-arcade-2.9.6/launcher/apps/listdirs.py fs-uae-arcade-2.9.7/launcher/apps/listdirs.py --- fs-uae-arcade-2.9.6/launcher/apps/listdirs.py 1970-01-01 00:00:00.000000000 +0000 +++ fs-uae-arcade-2.9.7/launcher/apps/listdirs.py 2017-10-16 21:40:41.000000000 +0000 @@ -0,0 +1,33 @@ +from fsbc.settings import Settings +from fsgs.FSGSDirectories import FSGSDirectories +""" +Debug script used to dump information about detected plugins +""" + + +def app_main(): + FSGSDirectories.initialize() + Settings.instance().verbose = False + Settings.instance().load() + print("base_dir", FSGSDirectories.get_base_dir()) + print("cache_dir", FSGSDirectories.get_cache_dir()) + print("cdroms_dir", FSGSDirectories.get_cdroms_dir()) + print("configurations_dir", FSGSDirectories.get_configurations_dir()) + print("controllers_dir", FSGSDirectories.get_controllers_dir()) + print("covers_dir", FSGSDirectories.get_covers_dir()) + print("data_dir", FSGSDirectories.get_data_dir()) + print("downloads_dir", FSGSDirectories.downloads_dir()) + print("floppies_dir", FSGSDirectories.get_floppies_dir()) + print("hard_drives_dir", FSGSDirectories.get_hard_drives_dir()) + # print("images_dir", FSGSDirectories.get_images_dir()) + print("kickstarts_dir", FSGSDirectories.get_kickstarts_dir()) + print("launcher_dir", FSGSDirectories.get_launcher_dir()) + print("logs_dir", FSGSDirectories.get_logs_dir()) + print("plugins_dir", FSGSDirectories.get_plugins_dir()) + # print("portable_dir", FSGSDirectories.portable_dir()) + print("save_states_dir", FSGSDirectories.get_save_states_dir()) + print("screenshots_dir", FSGSDirectories.get_screenshots_dir()) + print("screenshots_output_dir =", + FSGSDirectories.screenshots_output_dir()) + print("themes_dir", FSGSDirectories.get_themes_dir()) + print("titles_dir", FSGSDirectories.get_titles_dir()) diff -Nru fs-uae-arcade-2.9.6/launcher/apps/list_plugins.py fs-uae-arcade-2.9.7/launcher/apps/list_plugins.py --- fs-uae-arcade-2.9.6/launcher/apps/list_plugins.py 2017-05-25 10:07:54.000000000 +0000 +++ fs-uae-arcade-2.9.7/launcher/apps/list_plugins.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,25 +0,0 @@ -from fsgs.plugins.plugin_manager import PluginManager -""" -Debug script used to dump information about detected plugins -""" - - -def app_main(): - plugin_manager = PluginManager.instance() - plugins = plugin_manager.plugins() - provides = plugin_manager.provides() - - print("") - print("Plugins:") - for plugin in plugins: - print("* {} ({})".format(plugin.name, type(plugin).__name__)) - print(" ", plugin.path) - - print("") - print("Provides:") - for name in sorted(provides): - plugin = provides[name] - print("* {} ({})".format(name, plugin.name)) - # print("", plugin.provides()[name]) - - print("") diff -Nru fs-uae-arcade-2.9.6/launcher/apps/listplugins.py fs-uae-arcade-2.9.7/launcher/apps/listplugins.py --- fs-uae-arcade-2.9.6/launcher/apps/listplugins.py 1970-01-01 00:00:00.000000000 +0000 +++ fs-uae-arcade-2.9.7/launcher/apps/listplugins.py 2017-10-16 21:40:41.000000000 +0000 @@ -0,0 +1,23 @@ +from fsgs.plugins.plugin_manager import PluginManager +""" +Debug script used to dump information about detected plugins +""" + + +def app_main(): + plugin_manager = PluginManager.instance() + plugins = plugin_manager.plugins() + provides = plugin_manager.provides() + + for plugin in plugins: + print("# {} ({})".format(plugin.name, type(plugin).__name__)) + print(plugin.path) + print("") + + print("# Provides:") + for name in sorted(provides): + plugin = provides[name] + print("# {} ({})".format(name, plugin.name)) + # print("", plugin.provides()[name]) + + print("") diff -Nru fs-uae-arcade-2.9.6/launcher/configuration_scanner.py fs-uae-arcade-2.9.7/launcher/configuration_scanner.py --- fs-uae-arcade-2.9.6/launcher/configuration_scanner.py 2017-05-25 10:07:54.000000000 +0000 +++ fs-uae-arcade-2.9.7/launcher/configuration_scanner.py 2017-10-16 21:40:41.000000000 +0000 @@ -1,6 +1,6 @@ import os -from fsgs.FileDatabase import FileDatabase +from fsgs.filedatabase import FileDatabase from fsgs.util.gamenameutil import GameNameUtil from .i18n import gettext diff -Nru fs-uae-arcade-2.9.6/launcher/device_manager.py fs-uae-arcade-2.9.7/launcher/device_manager.py --- fs-uae-arcade-2.9.6/launcher/device_manager.py 2017-05-25 10:07:54.000000000 +0000 +++ fs-uae-arcade-2.9.7/launcher/device_manager.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,275 +0,0 @@ -import re -import subprocess -import sys -import traceback - -from fsgs.amiga.fsuaedevicehelper import FSUAEDeviceHelper -from .i18n import gettext -from .launcher_settings import LauncherSettings -from .launcher_signal import LauncherSignal - - -def create_cmp_id(id): - return id.lower().replace(" ", "") - - -class Device: - def __init__(self, id, name, type): - self.id = id - self.name = name - self.type = type - self.port = None - self.cmp_id = create_cmp_id(id) - - -class DeviceManager: - initialized = False - devices = [] - device_ids = [] - device_names = [] - device_types = [] - device_name_count = {} - - joystick_data = {} - - @classmethod - def joystick_buttons(cls, device): - return cls.joystick_data[device][0] - - @classmethod - def joystick_hats(cls, device): - return cls.joystick_data[device][1] - - @classmethod - def joystick_axes(cls, device): - return cls.joystick_data[device][2] - - @classmethod - def joystick_balls(cls, device): - return cls.joystick_data[device][3] - - @classmethod - def joystick_guid(cls, device): - return cls.joystick_data[device][4] - - @classmethod - def init(cls): - if cls.initialized: - return - # init can be called more than once (by setting initialized to - # false, used by refresh function, so we need to clear the lists... - cls.devices = [] - cls.device_ids = [] - cls.device_names = [] - cls.device_types = [] - cls.device_name_count = {} - - cls.devices.append(Device("none", gettext("No Device"), "none")) - # cls.devices.append(Device("mouse", _("Mouse"), "mouse")) - cls.init_fsuae() - for id, name, type in zip(cls.device_ids, cls.device_names, - cls.device_types): - cls.devices.append(Device(id, name, type)) - # cls.devices.append( - # Device("keyboard", _("Cursor Keys and Right Ctrl/Alt"), - # "joystick")) - cls.initialized = True - - @classmethod - def refresh(cls): - cls.initialized = False - cls.init() - LauncherSignal.broadcast("device_list_updated") - - @classmethod - def init_fsuae(cls): - print("DeviceManager: finding connected joysticks") - try: - p = FSUAEDeviceHelper.start_with_args( - ["--list"], stdout=subprocess.PIPE) - joysticks = p.stdout.read() - p.wait() - except Exception: - print("exception while listing joysticks and devices") - traceback.print_exc() - return - print(repr(joysticks)) - # If the character conversion fails, replace will ensure that - # as much as possible succeeds. The joystick in question will - # not be pre-selectable in the launcher, but the other ones will - # work at least. - joysticks = joysticks.decode("UTF-8", "replace") - joysticks = [x.strip() for x in joysticks.split("\n") if x.strip()] - last_joystick = "" - for line in joysticks: - if line.startswith("#"): - continue - if line.startswith("Buttons:"): - parts = line.split(" ") - buttons = int(parts[1]) - hats = int(parts[3]) - axes = int(parts[5]) - balls = int(parts[7]) - guid = parts[9] - cls.joystick_data[last_joystick] = \ - buttons, hats, axes, balls, guid - - continue - device_type, name = line.split(" ", 1) - # if name.lower() in ["keyboard", "mouse"]: - # # these are automatically added - # continue - name_count = cls.device_name_count.get((device_type, name), 0) + 1 - cls.device_name_count[(device_type, name)] = name_count - if name_count > 1: - name = name + " #" + str(name_count) - cls.device_ids.append(name) - name = re.sub("[ ]+", " ", name) - cls.device_names.append(name) - if device_type == "J:": - device_type = "joystick" - last_joystick = name - elif device_type == "M:": - device_type = "mouse" - elif device_type == "K:": - # works as an emulated joystick... - # device_type = "joystick" - device_type = "keyboard" - cls.device_types.append(device_type) - - if "--add-dummy-joystick" in sys.argv: - cls.device_ids.append("Dummy Joystick") - cls.device_names.append("Dummy Joystick") - cls.device_types.append("joystick") - cls.joystick_data["Dummy Joystick"] = \ - 1, 0, 2, 0, "c6c1bc29b0124fe6890757bb09ef006f" - - @classmethod - def get_joystick_names(cls): - cls.init() - return cls.device_names[:] - - @classmethod - def get_device_type(cls, device): - cls.init() - for i in range(len(cls.device_names)): - if cls.device_names[i] == device: - return cls.device_types[i] - return "" - - @classmethod - def is_joystick(cls, device): - return cls.get_device_type(device) == "joystick" - - @classmethod - def get_joystick_ids(cls): - cls.init() - return cls.device_ids[:] - - @classmethod - def get_preferred_joysticks(cls): - prefs = [] - if LauncherSettings.get("primary_joystick"): - prefs.append( - create_cmp_id(LauncherSettings.get("primary_joystick"))) - if LauncherSettings.get("secondary_joystick"): - prefs.append( - create_cmp_id(LauncherSettings.get("secondary_joystick"))) - return prefs - - @classmethod - def get_preferred_gamepads(cls): - return cls.get_preferred_joysticks() - - @classmethod - def get_calculated_port_mode(cls, config, port): - value = config.get("joystick_port_{0}_mode".format(port)) - if not value: - if port == 0: - return "mouse" - elif port == 1: - if config.get("amiga_model").startswith("CD32"): - return "cd32 gamepad" - else: - return "joystick" - return "nothing" - return value - - @classmethod - def get_devices_for_ports(cls, config): - cls.init() - ports = [cls.devices[0] for _ in range(5)] - for device in cls.devices: - device.port = None - for p in range(4): - key = "joystick_port_{0}".format(p) - value = config.get(key) - for device in cls.devices: - if device.id == value: - device.port = p - break - - # print("-") - # for device in cls.devices: - # print(device.port, device.id) - # print("-") - - def auto_fill(port, type): - mode = config.get("joystick_port_{0}_mode".format(port)) - if not mode: - mode = cls.get_calculated_port_mode(config, port) - val = config.get("joystick_port_{0}".format(port)) - if val: - # specific device chosen - for dev in cls.devices: - if dev.id == val: - ports[port] = dev - break - return - if type == "mouse": - # print("a", mode) - if mode != "mouse": - return - # print("b") - for dev in cls.devices: - # print("c") - if dev.type == "mouse" and dev.port is None: - # print("d") - ports[port] = dev - dev.port = port - return - elif type == "joystick": - if mode == "cd32 gamepad": - prefs = cls.get_preferred_joysticks() - elif mode == "joystick": - prefs = cls.get_preferred_gamepads() - else: - return - # try to find an available preferred device first - for pref in prefs: - for dev in cls.devices: - if dev.cmp_id == pref and dev.port is None: - ports[port] = dev - dev.port = port - return - # find first suitable device - for dev in cls.devices: - if dev.type == "joystick" and dev.port is None: - ports[port] = dev - dev.port = port - return - for dev in cls.devices: - if dev.type == "keyboard" and dev.port is None: - ports[port] = dev - dev.port = port - return - - for p in [0, 1, 2, 3]: - auto_fill(p, "mouse") - for p in [1, 0, 3, 2]: - auto_fill(p, "joystick") - return ports - - @classmethod - def get_device_for_port(cls, config, port): - return cls.get_devices_for_ports(config)[port] diff -Nru fs-uae-arcade-2.9.6/launcher/devicemanager.py fs-uae-arcade-2.9.7/launcher/devicemanager.py --- fs-uae-arcade-2.9.6/launcher/devicemanager.py 1970-01-01 00:00:00.000000000 +0000 +++ fs-uae-arcade-2.9.7/launcher/devicemanager.py 2017-10-16 21:40:41.000000000 +0000 @@ -0,0 +1,402 @@ +import re +import subprocess +import sys +import traceback + +from fsgs.amiga.fsuaedevicehelper import FSUAEDeviceHelper +from launcher.i18n import gettext +from launcher.launcher_settings import LauncherSettings +from launcher.launcher_signal import LauncherSignal +from launcher.option import Option + + +class Device: + def __init__(self, id, name, type): + self.id = id + self.name = name + self.type = type + self.port = None + self.cmp_id = Device.create_cmp_id(id) + + @staticmethod + def create_cmp_id(id_): + return id_.lower().replace(" ", "") + + def __repr__(self): + return " 1: + name = name + " #" + str(name_count) + cls.device_ids.append(name) + name = re.sub("[ ]+", " ", name) + cls.device_names.append(name) + if device_type == "J:": + device_type = "joystick" + last_joystick = name + elif device_type == "M:": + device_type = "mouse" + elif device_type == "K:": + # works as an emulated joystick... + # device_type = "joystick" + device_type = "keyboard" + cls.device_types.append(device_type) + + if "--add-dummy-joystick" in sys.argv: + cls.device_ids.append("Dummy Joystick") + cls.device_names.append("Dummy Joystick") + cls.device_types.append("joystick") + cls.joystick_data["Dummy Joystick"] = \ + 1, 0, 2, 0, "c6c1bc29b0124fe6890757bb09ef006f" + + @classmethod + def get_joystick_names(cls): + cls.init() + return cls.device_names[:] + + @classmethod + def get_device_type(cls, device): + cls.init() + for i in range(len(cls.device_names)): + if cls.device_names[i] == device: + return cls.device_types[i] + return "" + + @classmethod + def is_joystick(cls, device): + return cls.get_device_type(device) == "joystick" + + @classmethod + def get_joystick_ids(cls): + cls.init() + return cls.device_ids[:] + + @classmethod + def get_preferred_joysticks(cls): + prefs = [] + if LauncherSettings.get("primary_joystick"): + prefs.append( + Device.create_cmp_id(LauncherSettings.get("primary_joystick"))) + if LauncherSettings.get("secondary_joystick"): + prefs.append( + Device.create_cmp_id( + LauncherSettings.get("secondary_joystick"))) + return prefs + + @classmethod + def get_preferred_gamepads(cls): + return cls.get_preferred_joysticks() + + @classmethod + def get_calculated_port_mode(cls, config, port): + value = config.get("joystick_port_{0}_mode".format(port)) + if not value: + if port == 0: + return "mouse" + elif port == 1: + if config.get("amiga_model").startswith("CD32"): + return "cd32 gamepad" + else: + return "joystick" + return "nothing" + return value + + @classmethod + def get_devices_for_ports(cls, config): + cls.init() + ports = [cls.devices[0] for _ in range(5)] + for device in cls.devices: + device.port = None + for p in range(4): + key = "joystick_port_{0}".format(p) + value = config.get(key) + for device in cls.devices: + if device.id == value: + device.port = p + break + + # print("-") + # for device in cls.devices: + # print(device.port, device.id) + # print("-") + + def auto_fill(port, type): + mode = config.get("joystick_port_{0}_mode".format(port)) + if not mode: + mode = cls.get_calculated_port_mode(config, port) + val = config.get("joystick_port_{0}".format(port)) + if val: + # specific device chosen + for dev in cls.devices: + if dev.id == val: + ports[port] = dev + break + return + if type == "mouse": + # print("a", mode) + if mode != "mouse": + return + # print("b") + for dev in cls.devices: + # print("c") + if dev.type == "mouse" and dev.port is None: + # print("d") + ports[port] = dev + dev.port = port + return + elif type == "joystick": + if mode == "cd32 gamepad": + prefs = cls.get_preferred_gamepads() + elif mode == "joystick": + prefs = cls.get_preferred_joysticks() + else: + return + # try to find an available preferred device first + for pref in prefs: + for dev in cls.devices: + if dev.cmp_id == pref and dev.port is None: + ports[port] = dev + dev.port = port + return + # find first suitable device + for dev in cls.devices: + if dev.type == "joystick" and dev.port is None: + ports[port] = dev + dev.port = port + return + for dev in cls.devices: + if dev.type == "keyboard" and dev.port is None: + ports[port] = dev + dev.port = port + return + + for p in [0, 1, 2, 3]: + auto_fill(p, "mouse") + for p in [1, 0, 3, 2]: + auto_fill(p, "joystick") + return ports + + @classmethod + def get_device_for_port(cls, config, port): + return cls.get_devices_for_ports(config)[port] + + @classmethod + def get_non_amiga_devices_for_ports(cls, config): + platform = config.get("platform") + cls.init() + # Launcher has 5 input devices in total (4 + 1 virtual) + # ports = [cls.devices[0] for _ in range(5)] + # + 1 dummy device for index 0 + ports = [cls.devices[0] for _ in range(6)] + + for device in cls.devices: + device.port = None + for port_ in range(1, 4 + 1): + key = "{}_port_{}".format(platform, port_) + value = config.get(key) + print(key, value) + for device in cls.devices: + if device.id == value: + device.port = port_ + break + + print("before auto-fill") + print(ports) + + # print("-") + # for device in cls.devices: + # print(device.port, device.id) + # print("-") + + def auto_fill(port, type): + mode = config.get("{}_port_{}_type".format(platform, port)) + if not mode: + # FIXME: DEFAULT + # mode = cls.get_calculated_port_mode(config, port) + # mode = "gamepad" + try: + option = Option.get("{}_port_{}_type".format( + platform, port)) + mode = option["default"] + except KeyError: + # FIXME: How to handle? + mode = "" + + print("mode for", port, "is", mode) + if cls.is_mouse_device(mode): + mode = "mouse" + + val = config.get("{}_port_{}".format(platform, port)) + if val: + # specific device chosen + for dev in cls.devices: + if dev.id == val: + ports[port] = dev + break + return + + if type == "mouse": + # print("a", mode) + if mode != "mouse": + return + # print("b") + for dev in cls.devices: + # print("c") + if dev.type == "mouse" and dev.port is None: + # print("d") + ports[port] = dev + dev.port = port + return + elif type == "joystick": + if mode == "none": + return + elif mode == "mouse": + return + # elif mode == "gamepad": + # prefs = cls.get_preferred_gamepads() + # elif mode == "joystick": + # prefs = cls.get_preferred_joysticks() + # else: + # return + + prefs = cls.get_preferred_gamepads() + + # try to find an available preferred device first + for pref in prefs: + for dev in cls.devices: + if dev.cmp_id == pref and dev.port is None: + ports[port] = dev + dev.port = port + return + # find first suitable device + for dev in cls.devices: + if dev.type == "joystick" and dev.port is None: + ports[port] = dev + dev.port = port + return + for dev in cls.devices: + if dev.type == "keyboard" and dev.port is None: + ports[port] = dev + dev.port = port + return + + for p in [1, 2, 3, 4]: + auto_fill(p, "mouse") + for p in [1, 2, 3, 4]: + auto_fill(p, "joystick") + print("auto-fill", p, "=", ports[p]) + return ports + + @classmethod + def get_non_amiga_device_for_port(cls, config, port): + ports = cls.get_non_amiga_devices_for_ports(config) + print(ports, port) + return ports[port] + + @classmethod + def is_mouse_device(cls, type_): + # FIXME: Should be put in platform-specific code, here for now. + return type_ in [ + "mouse", # Amiga, ... + "zapper", # NES + "arkanoid", # NES + ] diff -Nru fs-uae-arcade-2.9.6/launcher/file_scanner.py fs-uae-arcade-2.9.7/launcher/file_scanner.py --- fs-uae-arcade-2.9.6/launcher/file_scanner.py 2017-05-25 10:07:54.000000000 +0000 +++ fs-uae-arcade-2.9.7/launcher/file_scanner.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,330 +0,0 @@ -import hashlib -import os -import traceback - -from fsbc.paths import Paths -from fsgs.Archive import Archive -from fsgs.FileDatabase import FileDatabase -from fsgs.amiga.rommanager import ROMManager -from .i18n import gettext - - -class FileScanner(object): - def __init__(self, paths, purge_other_dirs, - on_status=None, on_rom_found=None, stop_check=None): - self.paths = paths - - self.purge_other_dirs = purge_other_dirs - self.database_file_ids = [] - - self.on_status = on_status - self.on_rom_found = on_rom_found - self._stop_check = stop_check - self.scan_count = 0 - self.files_added = 0 - self.bytes_added = 0 - - self.extensions = set() - self.extensions.add(".rom") - self.extensions.add(".adf") - self.extensions.add(".adz") - self.extensions.add(".ipf") - self.extensions.add(".dms") - self.extensions.add(".bin") - self.extensions.add(".cue") - self.extensions.add(".iso") - self.extensions.add(".wav") - self.extensions.add(".zip") - self.extensions.add(".lha") - self.extensions.add(".rp9") - self.extensions.add(".fs-uae") - self.extensions.add(".7z") - - # if OGDClient.get_server() != "oagd.net": - if True: - self.extensions.add(".bin") - - # Amstrad CPC - self.extensions.add(".dsk") - - # Atari 2600 - self.extensions.add(".a26") - - # Atari 5200 - self.extensions.add(".a52") - - # Atari 7800 - self.extensions.add(".a78") - - # Atari ST - self.extensions.add(".st") # disk images - self.extensions.add(".img") # bios images - self.extensions.add(".bin") # bios images - - # Commodore 64 - - # Game Boy - self.extensions.add(".gb") - - # Game Boy Advance - self.extensions.add(".gba") - - # Game Boy Color - self.extensions.add(".gbc") - - # Master System - self.extensions.add(".sms") - - # Mega Drive - self.extensions.add(".md") - self.extensions.add(".bin") - self.extensions.add(".gen") - # FIXME: Is .smd a common extension? - self.extensions.add(".smd") - - # Nintendo - self.extensions.add(".nes") - - # Super Nintendo - self.extensions.add(".sfc") - - # ZX Spectrum - self.extensions.add(".dsk") - self.extensions.add(".tap") - self.extensions.add(".tzx") - self.extensions.add(".z80") - - def stop_check(self): - if self._stop_check: - return self._stop_check() - - def set_status(self, title, status): - if self.on_status: - self.on_status((title, status)) - - def get_scan_dirs(self): - return self.paths - - def purge_file_ids(self, file_ids): - self.set_status(gettext("Scanning files"), - gettext("Purging old entries...")) - database = FileDatabase.get_instance() - for file_id in file_ids: - database.delete_file(id=file_id) - - def scan(self): - self.set_status(gettext("Scanning files"), - gettext("Scanning files")) - database = FileDatabase.get_instance() - # database.clear() - scan_dirs = self.get_scan_dirs() - - if self.purge_other_dirs: - all_database_file_ids = database.get_file_ids() - else: - all_database_file_ids = None - - for dir_ in scan_dirs: - if not os.path.exists(dir_): - print("[SCANNER] Directory does not exist:", dir_) - continue - # this is important to make sure the database is portable across - # operating systems - dir_ = Paths.get_real_case(dir_) - - self.database_file_ids = database.get_file_hierarchy_ids(dir_) - if self.purge_other_dirs: - all_database_file_ids.difference_update( - self.database_file_ids) - - self.scan_dir(database, dir_) - - # print("Remaining files:", self.database_file_ids) - self.purge_file_ids(self.database_file_ids) - - self.set_status(gettext("Scanning files"), - gettext("Committing data...")) - # update last_file_insert and last_file_delete - database.update_last_event_stamps() - print("FileScanner.scan - committing data") - database.commit() - - if self.purge_other_dirs: - self.purge_file_ids(all_database_file_ids) - - if self.stop_check(): - database.rollback() - else: - self.set_status(gettext("Scanning files"), - gettext("Committing data...")) - print("FileScanner.scan - committing data") - database.commit() - - def scan_dir(self, database, dir_, all_files=False): - if not os.path.exists(dir_): - return - dir_content = os.listdir(dir_) - if not all_files: - for name in dir_content: - if not check_valid_name(name): - continue - l_name = name.lower() - if l_name.endswith(".slave") or l_name.endswith(".slav"): - all_files = True - break - - for name in dir_content: - if not check_valid_name(name): - continue - if self.stop_check(): - return - if name in [".git", "Cache", "Save States"]: - continue - - path = os.path.join(dir_, name) - if os.path.isdir(path): - self.scan_dir(database, path, all_files=all_files) - continue - if not all_files: - dummy, ext = os.path.splitext(path) - ext = ext.lower() - if ext not in self.extensions: - continue - try: - self.scan_file(database, path) - except Exception: - traceback.print_exc() - - def scan_file(self, database, path): - name = os.path.basename(path) - # path = os.path.normcase(os.path.normpath(path)) - - self.scan_count += 1 - self.set_status( - gettext("Scanning files ({count} scanned)").format( - count=self.scan_count), name) - - try: - st = os.stat(path) - except: - print("error stat-ing file", repr(path)) - return - size = st.st_size - mtime = int(st.st_mtime) - - result = database.find_file(path=path) - # print(result) - if result["path"]: - if size == result["size"] and mtime == result["mtime"]: - - self.database_file_ids.remove(result["id"]) - # self.database_file_ids.difference_update( - # database.get_file_hierarchy_ids(path + "#")) - - # self.database_file_ids.difference_update( - # database.get_child_ids(id=result["id"])) - return - - archive = Archive(path) - # if archive.is_archive(): - - file_id = self.scan_archive_stream( - database, archive, path, name, size, mtime) - - for p in archive.list_files(): - if p.endswith("/"): - # don't index archive directory entries - continue - # print(p) - if self.stop_check(): - return - # n = p.replace("\\", "/").replace("|", "/").split("/")[-1] - n = os.path.basename(p) - self.scan_count += 1 - self.scan_archive_stream( - database, archive, p, n, size, mtime, parent=file_id) - if self.stop_check(): - return - - def scan_archive_stream( - self, database, archive, path, name, size, mtime, parent=None): - self.set_status( - gettext("Scanning files ({count} scanned)").format( - count=self.scan_count), name) - base_name, ext = os.path.splitext(name) - ext = ext.lower() - - f = archive.open(path) - s = hashlib.sha1() - - # Start iNES Hack - if ext == ".nes": - # FIXME: NES header hack. Would be better to add a proper notion of - # file filters to the file database. - # FIXME: This will confuse some functionaly, such as the - # Locker uploader or other tools expecting on-disk data to match - # the database checksum (this also applies to the Cloanto ROM - # hack). Should be done properly. - data = f.read(16) - if len(data) == 16 and data.startswith(b"NES\x1a"): - print("Stripping iNES header for", path) - else: - # No iNES header, include data in checksum - s.update(data) - # End iNES Hack - - while True: - if self.stop_check(): - return - data = f.read(65536) - if not data: - break - s.update(data) - sha1 = s.hexdigest() - - if ext == ".rom": - try: - sha1_dec = ROMManager.decrypt_archive_rom(archive, path)["sha1"] - except Exception: - import traceback - traceback.print_exc() - sha1_dec = None - if sha1_dec: - if sha1_dec != sha1: - print("found encrypted rom {0} => {1}".format( - sha1, sha1_dec)) - # sha1 is now the decrypted sha1, not the actual sha1 of the - # file itself, a bit ugly, since md5 and crc32 are still - # encrypted hashes, but it works well with the kickstart - # lookup mechanism - sha1 = sha1_dec - else: - # If the ROM was encrypted and could not be decrypted, we - # don't add it to the database. This way, the file will be - # correctly added on later scans if rom.key is added to the - # directory. - return None - - if parent: - path = "#/" + path.rsplit("#/", 1)[1] - file_id = database.add_file( - path=path, sha1=sha1, mtime=mtime, size=size, parent=parent) - - self.files_added += 1 - self.bytes_added += size - - if ext == '.rom': - if self.on_rom_found: - self.on_rom_found(path, sha1) - return file_id - - -def check_valid_name(name): - # check that the file is actually unicode object (indirectly). listdir - # will return str objects for file names on Linux with invalid encoding. - # FIXME: not needed for Python 3 - try: - str(name) - except UnicodeDecodeError: - return False - return True diff -Nru fs-uae-arcade-2.9.6/launcher/filescanner.py fs-uae-arcade-2.9.7/launcher/filescanner.py --- fs-uae-arcade-2.9.6/launcher/filescanner.py 1970-01-01 00:00:00.000000000 +0000 +++ fs-uae-arcade-2.9.7/launcher/filescanner.py 2017-10-16 21:40:41.000000000 +0000 @@ -0,0 +1,366 @@ +import hashlib +import os +import traceback + +from fsbc.paths import Paths +from fsgs.Archive import Archive +from fsgs.filedatabase import FileDatabase +from fsgs.amiga.rommanager import ROMManager +from .i18n import gettext + + +class FileScanner(object): + def __init__(self, paths, purge_other_dirs, + on_status=None, on_rom_found=None, stop_check=None): + self.paths = paths + + self.purge_other_dirs = purge_other_dirs + self.database_file_ids = [] + + self.on_status = on_status + self.on_rom_found = on_rom_found + self._stop_check = stop_check + self.scan_count = 0 + self.files_added = 0 + self.bytes_added = 0 + + self.extensions = set() + self.extensions.add(".rom") + self.extensions.add(".adf") + self.extensions.add(".adz") + self.extensions.add(".ipf") + self.extensions.add(".dms") + self.extensions.add(".bin") + self.extensions.add(".cue") + self.extensions.add(".iso") + self.extensions.add(".wav") + self.extensions.add(".zip") + self.extensions.add(".lha") + self.extensions.add(".rp9") + self.extensions.add(".fs-uae") + self.extensions.add(".7z") + + # if OGDClient.get_server() != "oagd.net": + if True: + self.extensions.add(".bin") + + # Amstrad CPC + self.extensions.add(".dsk") + + # Arcade + self.extensions.add(".chd") + + # Atari 2600 + self.extensions.add(".a26") + + # Atari 5200 + self.extensions.add(".a52") + + # Atari 7800 + self.extensions.add(".a78") + + # Atari ST + self.extensions.add(".st") # disk images + self.extensions.add(".img") # bios images + self.extensions.add(".bin") # bios images + + # Commodore 64 + + # DOS / GOG + # self.extensions.add(".mp3") + # self.extensions.add(".ogg") + + # Game Boy + self.extensions.add(".gb") + + # Game Boy Advance + self.extensions.add(".gba") + + # Game Boy Color + self.extensions.add(".gbc") + + # Master System + self.extensions.add(".sms") + + # Mega Drive + self.extensions.add(".md") + self.extensions.add(".bin") + self.extensions.add(".gen") + # FIXME: Is .smd a common extension? + self.extensions.add(".smd") + + # Nintendo + self.extensions.add(".nes") + + # Nintendo 64 + self.extensions.add(".n64") + self.extensions.add(".v64") + self.extensions.add(".z64") + + # Super Nintendo + self.extensions.add(".sfc") + + # TurboGrafx-CD system ROM + self.extensions.add(".pce") + + # ZX Spectrum + self.extensions.add(".dsk") + self.extensions.add(".tap") + self.extensions.add(".tzx") + self.extensions.add(".z80") + + def stop_check(self): + if self._stop_check: + return self._stop_check() + + def set_status(self, title, status): + if self.on_status: + self.on_status((title, status)) + + def get_scan_dirs(self): + return self.paths + + def purge_file_ids(self, file_ids): + self.set_status(gettext("Scanning files"), + gettext("Purging old entries...")) + database = FileDatabase.get_instance() + for file_id in file_ids: + database.delete_file(id=file_id) + + def scan(self): + self.set_status(gettext("Scanning files"), + gettext("Scanning files")) + file_database = FileDatabase.get_instance() + # database.clear() + scan_dirs = self.get_scan_dirs() + + if self.purge_other_dirs: + all_database_file_ids = file_database.get_file_ids() + else: + all_database_file_ids = None + + for dir_ in scan_dirs: + if not os.path.exists(dir_): + print("[FILES] Scanner: Directory does not exist:", dir_) + continue + # this is important to make sure the database is portable across + # operating systems + dir_ = Paths.get_real_case(dir_) + + self.database_file_ids = file_database.get_file_hierarchy_ids(dir_) + if self.purge_other_dirs: + all_database_file_ids.difference_update( + self.database_file_ids) + + self.scan_dir(file_database, dir_) + + print("Remaining files:", self.database_file_ids) + self.purge_file_ids(self.database_file_ids) + + self.set_status(gettext("Scanning files"), + gettext("Committing data...")) + # update last_file_insert and last_file_delete + file_database.update_last_event_stamps() + print("[FILES] FileScanner.scan - committing data") + file_database.commit() + + if self.purge_other_dirs: + self.purge_file_ids(all_database_file_ids) + + if self.stop_check(): + file_database.rollback() + else: + self.set_status(gettext("Scanning files"), + gettext("Committing data...")) + print("[FILES] FileScanner.scan - committing data") + file_database.commit() + + def scan_dir(self, file_database, dir_, all_files=False): + if not os.path.exists(dir_): + return + dir_content = os.listdir(dir_) + if not all_files: + for name in dir_content: + if not check_valid_name(name): + continue + l_name = name.lower() + if l_name.endswith(".slave") or l_name.endswith(".slav"): + all_files = True + break + + for name in dir_content: + if not check_valid_name(name): + continue + if self.stop_check(): + return + if name in [".git", "Cache", "Save States"]: + continue + + path = os.path.join(dir_, name) + if os.path.isdir(path): + self.scan_dir(file_database, path, all_files=all_files) + continue + if not all_files: + dummy, ext = os.path.splitext(path) + ext = ext.lower() + if ext not in self.extensions: + continue + try: + self.scan_file(file_database, path) + except Exception: + traceback.print_exc() + + def scan_file(self, file_database, path): + name = os.path.basename(path) + # path = os.path.normcase(os.path.normpath(path)) + + self.scan_count += 1 + self.set_status( + gettext("Scanning files ({count} scanned)").format( + count=self.scan_count), name) + + try: + st = os.stat(path) + except: + print("[FILES] WARNING: Error stat-ing file", repr(path)) + return + size = st.st_size + mtime = int(st.st_mtime) + + result = file_database.find_file(path=path) + if result["path"]: + if size == result["size"] and mtime == result["mtime"]: + # We've already got this file indexed. + self.database_file_ids.remove(result["id"]) + return + + archive = Archive(path) + file_id = self.scan_archive_stream( + file_database, archive, path, name, size, mtime) + for p in archive.list_files(): + if p.endswith("/"): + # don't index archive directory entries + continue + # print(p) + if self.stop_check(): + return + # n = p.replace("\\", "/").replace("|", "/").split("/")[-1] + n = os.path.basename(p) + self.scan_count += 1 + self.scan_archive_stream( + file_database, archive, p, n, size, mtime, parent=file_id) + if self.stop_check(): + return + + def scan_archive_stream( + self, database, archive, path, name, size, mtime, parent=None): + self.set_status( + gettext("Scanning files ({count} scanned)").format( + count=self.scan_count), name) + + f = archive.open(path) + raw_sha1_obj = hashlib.sha1() + filter_sha1_obj = hashlib.sha1() + filter_name = "" + filter_size = 0 + + base_name, ext = os.path.splitext(name) + ext = ext.lower() + if ext == ".nes": + # FIXME: NES header hack. Would be better to add a proper notion of + # file filters to the file database. + # FIXME: This will confuse some functionality, such as the + # Locker uploader or other tools expecting on-disk data to match + # the database checksum (this also applies to the Cloanto ROM + # hack). Should be done properly. + data = f.read(16) + if len(data) == 16 and data.startswith(b"NES\x1a"): + print("Stripping iNES header for", path) + filter_name = "Skip(16)" + raw_sha1_obj.update(data) + elif ext in [".v64", ".n64"]: + filter_name = "ByteSwapWords" + + while True: + if self.stop_check(): + return + data = f.read(65536) + if not data: + break + raw_sha1_obj.update(data) + if filter_name == "Skip(16)": + filter_sha1_obj.update(data) + filter_size += len(data) + elif filter_name == "ByteSwapWords": + for i in range(0, len(data), 2): + filter_sha1_obj.update(data[i+1:i+2]) + filter_sha1_obj.update(data[i:i+1]) + filter_size += 2 + # FIXME: Handle it when we get an odd number of bytes.. + assert len(data) % 2 == 0 + sha1 = raw_sha1_obj.hexdigest() + filter_sha1 = filter_sha1_obj.hexdigest() + + if ext == ".rom": + try: + filter_data = ROMManager.decrypt_archive_rom(archive, path) + filter_sha1 = filter_data["sha1"] + filter_size = len(filter_data["data"]) + except Exception: + import traceback + traceback.print_exc() + filter_sha1 = None + if filter_sha1: + if filter_sha1 != sha1: + print("[Files] Found encrypted rom {0} => {1}".format( + sha1, filter_sha1)) + # sha1 is now the decrypted sha1, not the actual sha1 of the + # file itself, a bit ugly, since md5 and crc32 are still + # encrypted hashes, but it works well with the kickstart + # lookup mechanism + # FIXME: Enable use of filter mechanism for Cloanto ROMs + sha1 = filter_sha1 + # filter_name = "Cloanto" + else: + # If the ROM was encrypted and could not be decrypted, we + # don't add it to the database. This way, the file will be + # correctly added on later scans if rom.key is added to the + # directory. + return None + + if parent: + path = "#/" + path.rsplit("#/", 1)[1] + file_id = database.add_file( + path=path, sha1=sha1, mtime=mtime, size=size, parent=parent) + self.files_added += 1 + self.bytes_added += size + + if filter_name: + if parent: + # We want to add to the previous archive path + pass + else: + # Reset path + path = "" + path += "#?Filter=" + filter_name + # If not already in an archive (has a real file parent), set + # parent to the real file + database.add_file( + path=path, sha1=filter_sha1, + mtime=mtime, size=filter_size, parent=(parent or file_id)) + + if ext == ".rom": + if self.on_rom_found: + self.on_rom_found(path, sha1) + return file_id + + +def check_valid_name(name): + # check that the file is actually unicode object (indirectly). listdir + # will return str objects for file names on Linux with invalid encoding. + # FIXME: not needed for Python 3 + try: + str(name) + except UnicodeDecodeError: + return False + return True diff -Nru fs-uae-arcade-2.9.6/launcher/game_scanner.py fs-uae-arcade-2.9.7/launcher/game_scanner.py --- fs-uae-arcade-2.9.6/launcher/game_scanner.py 2017-05-25 10:07:54.000000000 +0000 +++ fs-uae-arcade-2.9.7/launcher/game_scanner.py 2017-10-16 21:40:41.000000000 +0000 @@ -5,9 +5,9 @@ from functools import lru_cache from fsbc import settings -from fsgs import openretro +from fsgs import openretro, OPENRETRO_DEFAULT_DATABASES from fsgs.FSGSDirectories import FSGSDirectories -from fsgs.FileDatabase import FileDatabase +from fsgs.filedatabase import FileDatabase from fsgs.GameDatabase import IncompleteGameException from fsgs.GameDatabaseClient import GameDatabaseClient from fsgs.context import fsgs @@ -15,6 +15,7 @@ from fsgs.ogd.locker import LockerSynchronizer from fsgs.option import Option from fsgs.util.gamenameutil import GameNameUtil +from launcher.launcher_config import LauncherConfig from .i18n import gettext from .launcher_settings import LauncherSettings @@ -41,33 +42,17 @@ self.on_status((title, status)) def _check_platform(self, platform_option): + if LauncherSettings.get(platform_option) == "1": + return True if platform_option in [Option.AMIGA_DATABASE, Option.CD32_DATABASE, Option.CDTV_DATABASE]: if LauncherSettings.get(platform_option) != "0": return True return False - if openretro: - if platform_option in [ - Option.ARCADE_DATABASE, - Option.ATARI_DATABASE, - Option.C64_DATABASE, - Option.CPC_DATABASE, - Option.DOS_DATABASE, - Option.GB_DATABASE, - Option.GBA_DATABASE, - Option.GBC_DATABASE, - Option.NES_DATABASE, - Option.PSX_DATABASE, - Option.SMD_DATABASE, - Option.SNES_DATABASE, - Option.TG16_DATABASE, - Option.ZXS_DATABASE]: - if LauncherSettings.get(platform_option) != "0": - return True + # Must also remember to update apps/__init__.py + if openretro and platform_option in OPENRETRO_DEFAULT_DATABASES: + if LauncherSettings.get(platform_option) == "0": return False - if settings.get(Option.PLATFORMS_FEATURE) != "1": - return False - if LauncherSettings.get(platform_option) == "1": return True return False @@ -102,8 +87,14 @@ yield "GBC", self.fsgs.game_database("GBC") if self._check_platform(Option.MSX_DATABASE): yield "MSX", self.fsgs.game_database("MSX") + if self._check_platform(Option.N64_DATABASE): + yield "N64", self.fsgs.game_database("N64") + if self._check_platform(Option.NEOGEO_DATABASE): + yield "NEOGEO", self.fsgs.game_database("NEOGEO") if self._check_platform(Option.NES_DATABASE): yield "NES", self.fsgs.game_database("NES") + if self._check_platform(Option.NGC_DATABASE): + yield "NGC", self.fsgs.game_database("NGC") if self._check_platform(Option.PSX_DATABASE): yield "PSX", self.fsgs.game_database("PSX") if self._check_platform(Option.SMD_DATABASE): @@ -114,6 +105,8 @@ yield "SNES", self.fsgs.game_database("SNES") if self._check_platform(Option.TG16_DATABASE): yield "TG16", self.fsgs.game_database("TG16") + if self._check_platform(Option.TGCD_DATABASE): + yield "TGCD", self.fsgs.game_database("TGCD") if self._check_platform(Option.ZXS_DATABASE): yield "ZXS", self.fsgs.game_database("ZXS") if custom: @@ -515,6 +508,16 @@ cached_file_stamps["last_file_insert"]) self.deleted_files = (self.file_stamps["last_file_delete"] != cached_file_stamps["last_file_delete"]) + # print(LauncherSettings.get(Option.DATABASE_LOCKER), + # cached_file_stamps["database_locker"]) + # assert 0 + if LauncherSettings.get(Option.DATABASE_LOCKER) != \ + cached_file_stamps["database_locker"]: + # Assume that files could be deleted or removed... + if LauncherSettings.get(Option.DATABASE_LOCKER) == "0": + self.deleted_files = True + else: + self.added_files = True def game_seen(self, seen_game_uuid): # after the loop has run its course, games to be removed @@ -559,7 +562,10 @@ "game_variant WHERE game_uuid = game.uuid) WHERE uuid IS NOT " "NULL AND (have IS NULL OR have != (SELECT coalesce(max(" "have), 0) FROM game_variant WHERE game_uuid = game.uuid))") + # FIXME: Remove this line? FileDatabase.get_instance().get_last_event_stamps() + self.file_stamps["database_locker"] = \ + LauncherSettings.get(Option.DATABASE_LOCKER) self.database.update_last_file_event_stamps(self.file_stamps) diff -Nru fs-uae-arcade-2.9.6/launcher/launcherapp.py fs-uae-arcade-2.9.7/launcher/launcherapp.py --- fs-uae-arcade-2.9.6/launcher/launcherapp.py 2017-05-25 10:07:54.000000000 +0000 +++ fs-uae-arcade-2.9.7/launcher/launcherapp.py 2017-10-16 21:40:41.000000000 +0000 @@ -2,6 +2,7 @@ import os import sys import traceback +import warnings from collections import defaultdict from configparser import ConfigParser @@ -12,7 +13,7 @@ from fsbc.util import unused, is_uuid from fsgs.Archive import Archive from fsgs.FSGSDirectories import FSGSDirectories -from fsgs.FileDatabase import FileDatabase +from fsgs.filedatabase import FileDatabase from fsgs.amiga import whdload from fsgs.amiga.amiga import Amiga from fsgs.application import ApplicationMixin @@ -422,12 +423,12 @@ name = LauncherSettings.get("config_name") uuid = LauncherConfig.get("x_game_uuid") - from fsgs.SaveStateHandler import SaveStateHandler - save_state_handler = SaveStateHandler(fsgs, name, platform, uuid) + from fsgs.saves import SaveHandler + save_state_handler = SaveHandler(fsgs, name, platform, uuid) from fsgs.amiga.launchhandler import LaunchHandler - launch_handler = LaunchHandler(fsgs, name, prepared_config, - save_state_handler) + launch_handler = LaunchHandler( + fsgs, name, prepared_config, save_state_handler) from .ui.launcherwindow import LauncherWindow task = AmigaLaunchTask(launch_handler) @@ -485,7 +486,7 @@ if not config["joystick_port_3_mode"]: config["joystick_port_3_mode"] = "none" - from .device_manager import DeviceManager + from .devicemanager import DeviceManager devices = DeviceManager.get_devices_for_ports(config) for port in range(4): key = "joystick_port_{0}".format(port) @@ -564,20 +565,26 @@ class RunnerTask(Task): - def __init__(self, runner): + def __init__(self, driver): Task.__init__(self, "Runner Task") - self.runner = runner + self.driver = driver + + @property + def runner(self): + warnings.warn("Deprecated", DeprecationWarning) + return self.driver def __del__(self): print("RunnerTask.__del__") def run(self): device_helper = EnumerateHelper() - device_helper.default_port_selection(self.runner.ports) + device_helper.default_port_selection( + self.driver.ports, self.driver.options) - self.runner.prepare() - self.runner.install() + self.driver.prepare() + self.driver.install() self.set_progress("__run__") - self.runner.run() - self.runner.wait() - self.runner.finish() + self.driver.run() + self.driver.wait() + self.driver.finish() diff -Nru fs-uae-arcade-2.9.6/launcher/launcher_config.py fs-uae-arcade-2.9.7/launcher/launcher_config.py --- fs-uae-arcade-2.9.6/launcher/launcher_config.py 2017-05-25 10:07:54.000000000 +0000 +++ fs-uae-arcade-2.9.7/launcher/launcher_config.py 2017-10-16 21:40:41.000000000 +0000 @@ -94,6 +94,7 @@ ("homepage_url", ""), ("longplay_url", ""), ("variant_uuid", ""), + ("game_uuid", ""), ("download_file", ""), ("download_page", ""), @@ -135,9 +136,15 @@ (Option.ATARI_MODEL, "", "checksum", "sync"), (Option.C64_MODEL, "", "checksum", "sync"), (Option.CARTRIDGE_SLOT, "", "checksum", "sync"), + (Option.FILE_LIST, "", "custom", "sync"), (Option.FLOPPY_DRIVE_VOLUME_EMPTY, "", "sync"), (Option.GAME_NAME, ""), + ("mame_rom_set", "", "custom", "checksum", "sync"), + (Option.NEOGEO_MODEL, "", "checksum", "sync"), + (Option.NES_INES_HEADER, "", "checksum", "sync"), + (Option.NES_MODEL, "", "checksum", "sync"), (Option.SMD_MODEL, "", "checksum", "sync"), + ("refresh_rate", "", "custom", "checksum", "sync"), (Option.TAPE_DRIVE_0, "", "checksum", "sync"), (Option.VARIANT_NAME, ""), (Option.ZXS_MODEL, "", "checksum", "sync"), @@ -393,7 +400,7 @@ print("---", config["joystick_port_0"]) print("---", config["joystick_port_1"]) - from .device_manager import DeviceManager + from .devicemanager import DeviceManager available = DeviceManager.get_joystick_names() available.extend(["none", "mouse", "keyboard"]) available_lower = [x.lower() for x in available] diff -Nru fs-uae-arcade-2.9.6/launcher/launcher_settings.py fs-uae-arcade-2.9.7/launcher/launcher_settings.py --- fs-uae-arcade-2.9.6/launcher/launcher_settings.py 2017-05-25 10:07:54.000000000 +0000 +++ fs-uae-arcade-2.9.7/launcher/launcher_settings.py 2017-10-16 21:40:41.000000000 +0000 @@ -78,6 +78,7 @@ "middle_click_ungrab": "", "monitor": "", "mouse_speed": "", + Option.NEOGEO_DATABASE: "", Option.NES_DATABASE: "", "netplay_feature": "1", "netplay_tag": "", @@ -92,11 +93,15 @@ # "scanlines": "", "search_path": "", "secondary_joystick": "", + Option.SMD_DATABASE: "", + Option.SMS_DATABASE: "", Option.SNES_DATABASE: "", "stereo_separation": "", "swap_ctrl_keys": "", "texture_filter": "", "texture_format": "", + Option.TG16_DATABASE: "", + Option.TGCD_DATABASE: "", "__variant_rating": "", "video_format": "", "video_sync": "", diff -Nru fs-uae-arcade-2.9.6/launcher/option.py fs-uae-arcade-2.9.7/launcher/option.py --- fs-uae-arcade-2.9.6/launcher/option.py 2017-05-25 10:07:54.000000000 +0000 +++ fs-uae-arcade-2.9.7/launcher/option.py 2017-10-16 21:40:41.000000000 +0000 @@ -89,6 +89,16 @@ "description": N_("Arcade starts with favorites filter"), "type": "Boolean", }, + Option.ARCADE_SEARCH: { + "default": "", + "description": N_("Enable/disable search function in Arcade"), + "type": "boolean", + }, + Option.ARCADE_SHUTDOWN: { + "default": "", + "description": N_("Arcade Shutdown Command"), + "type": "", + }, Option.ARCADE_THEME: { "default": "blue", "description": N_("Arcade theme"), @@ -154,6 +164,15 @@ "description": N_("Grab Input on Click"), "type": "boolean", }, + Option.BEZEL: { + "default": "1", + "description": N_("Bezel"), + "type": "Choice", + "values": [ + ("1", N_("Show Bezel")), + ("0", N_("Hide Bezel")), + ] + }, Option.BLIZZARD_SCSI_KIT: { "default": "0", "description": N_("Blizzard SCSI Kit"), @@ -242,6 +261,11 @@ "description": N_("Enable/disable use of the CDTV game database"), "type": "Boolean", }, + Option.CHEATS: { + "default": "0", + "description": N_("Cheats"), + "type": "Boolean", + }, Option.CHIP_MEMORY: { "default": "", "description": N_("Chip RAM"), @@ -317,9 +341,9 @@ "type": "boolean", }, Option.DATABASE_LOCKER: { - "default": "", - "description": N_("Enable/disable use of OAGD.net locker"), - "type": "boolean", + "default": "1", + "description": N_("OpenRetro.org Locker"), + "type": "Boolean", }, Option.DATABASE_PASSWORD: { "default": "", @@ -357,6 +381,11 @@ "description": N_("Game database user name"), "type": "string", }, + Option.DEVELOPER_MODE: { + "default": "", + "description": N_("Developer Mode"), + "type": "", + }, Option.DEVICE_ID: { "default": "", "description": N_("Device ID used with OAGD.net authentication"), @@ -399,11 +428,12 @@ "type": "Choice", }, Option.EFFECT: { - "default": "0", + "default": "2x", "description": N_("Effect"), "type": "Choice", "values": [ ("0", N_("No Effect")), + ("2x", N_("Pixel Doubling")), ("hq2x", "HQ2X"), ("scale2x", "Scale2X"), ("crt", N_("CRT Emulation")), @@ -474,14 +504,15 @@ "min": 0, "max": 100, }, - Option.FRAME: { - "default": "auto", - "description": N_("Frame"), + Option.FORCE_ASPECT: { + "default": "", + "description": N_("Force Aspect"), + "type": "Double", + }, + Option.FRAME_TIME: { + "default": "0", + "description": N_("Frame time (ms)"), "type": "", - "values": [ - ("0", N_("None")), - ("auto", N_("Auto")), - ] }, Option.FREEZER_CARTRIDGE: { "default": "0", @@ -525,6 +556,16 @@ ("window", N_("Fullscreen Window")), ] }, + Option.G_SYNC: { + "default": "ignore", + "description": N_("G-Sync"), + "type": "Choice", + "values": [ + ("ignore", "Ignore"), + ("1", "On"), + ("0", "Off (Linux)"), + ] + }, Option.GB_DATABASE: { "default": "0", "description": N_("Enable/disable use of the Game Boy database"), @@ -536,6 +577,14 @@ "Enable/disable use of the Game Boy Advance database"), "type": "Boolean", }, + Option.GBA_PORT_1_TYPE: { + "default": "builtin", + "description": N_("GBA Port 1"), + "type": "Choice", + "values": [ + ("builtin", N_("Built-in")), + ] + }, Option.GBC_DATABASE: { "default": "0", "description": N_("Enable/disable use of the Game Boy Color database"), @@ -599,6 +648,16 @@ "description": N_("Automatic mouse/joystick mode for mouse port"), "type": "boolean", }, + Option.JOYSTICK_PORT_0_MODE: { + "default": "mouse", + "description": N_("Joystick Port 0"), + "type": "", + }, + Option.JOYSTICK_PORT_1_MODE: { + "default": "joystick", + "description": N_("Joystick Port 1"), + "type": "", + }, Option.KEEP_ASPECT: { "default": "0", "description": N_("Keep aspect ratio when scaling (do not stretch)"), @@ -732,12 +791,27 @@ "description": N_("Low latency video sync"), "type": "boolean", }, + Option.MAME_ARTWORK: { + "default": "0", + "description": N_("Use MAME Artwork"), + "type": "Boolean", + }, + Option.MEDNAFEN_AUDIO_BUFFER: { + "default": "40", + "description": N_("Mednafen Audio Buffer"), + "type": "Integer", + "min": 0, + "max": 1000, + }, Option.MEDNAFEN_AUDIO_DRIVER: { - "default": "", + "default": "auto", "description": N_("Mednafen Audio Driver"), "type": "Choice", "values": [ - ("sdl", "sdl"), + ("auto", "Auto"), + ("sdl", "SDL"), + ("alsa", "ALSA"), + ("mednafen", "Mednafen Default"), ] }, Option.MIDDLE_CLICK_UNGRAB: { @@ -793,6 +867,46 @@ "description": N_("Enable/disable use of the MSX game database"), "type": "Boolean", }, + Option.N64_DATABASE: { + "default": "0", + "description": N_("Enable/disable use of the Nintendo 64 database"), + "type": "Boolean", + }, + Option.NEOGEO_DATABASE: { + "default": "0", + "description": N_("Enable/disable use of the Neo-Geo game database"), + "type": "Boolean", + }, + Option.NEOGEO_MODEL: { + "default": "mvs", + "description": N_("Neo-Geo Model"), + "type": "Choice", + "values": [ + ("mvs", "MVS Europe"), + ("mvs/jp", "MVS Japan"), + ("mvs/us", "MVS US"), + ("aes", "AES"), + ("aes/jp", "AES Japan"), + ] + }, + Option.NEOGEO_PORT_1_TYPE: { + "default": "joystick", + "description": N_("Neo-Geo Port 1"), + "type": "Choice", + "values": [ + ("none", N_("None")), + ("joystick", N_("Joystick")), + ] + }, + Option.NEOGEO_PORT_2_TYPE: { + "default": "joystick", + "description": N_("Neo-Geo Port 2"), + "type": "Choice", + "values": [ + ("none", N_("None")), + ("joystick", N_("Joystick")), + ] + }, Option.NES_DATABASE: { "default": "0", "description": N_("Enable/disable use of the Nintendo (NES) database"), @@ -803,10 +917,73 @@ "description": N_("NES Game Driver"), "type": "Choice", "values": [ + ("mednafen-fs", "Mednafen-FS"), + ("higan", "Higan"), + ("libretro-nestopia", "Nestopia (RetroArch)"), ("mednafen", "mednafen"), ("mess", "mess"), ] }, + Option.NES_EMULATOR: { + "default": "mednafen", + "description": N_("Nintendo Emulator"), + "type": "Choice", + "values": [ + ("mednafen", "Mednafen"), + ("higan", "Higan"), + ("retroarch-nestopia", "RetroArch Nestopia"), + ] + }, + Option.NES_MODEL: { + "default": "ntsc", + "description": N_("NES Model"), + "type": "Choice", + "values": [ + ("ntsc", "NES NTSC"), + ("pal", "NES PAL"), + ("ntsc-j", "Famicom"), + ] + }, + Option.NES_PORT_1_TYPE: { + "default": "gamepad", + "description": N_("NES Port 1"), + "type": "Choice", + "values": [ + ("none", N_("None")), + ("gamepad", N_("Gamepad")), + ("zapper", "Zapper"), + ("arkanoid", "Arkanoid"), + ] + }, + Option.NES_PORT_2_TYPE: { + "default": "gamepad", + "description": N_("NES Port 2"), + "type": "Choice", + "values": [ + ("none", N_("None")), + ("gamepad", N_("Gamepad")), + ("zapper", "Zapper"), + ("arkanoid", "Arkanoid"), + ] + }, + Option.NES_PORT_3_TYPE: { + "default": "none", + "description": N_("NES Port 3"), + "type": "Choice", + "values": [ + ("none", N_("None")), + ("gamepad", N_("Gamepad")), + ] + }, + Option.NES_PORT_4_TYPE: { + "default": "none", + "description": N_("NES Port 4"), + "type": "Choice", + "values": [ + ("none", N_("None")), + ("gamepad", N_("Gamepad")), + ] + }, Option.NETPLAY_FEATURE: { "default": "0", "description": N_( @@ -827,6 +1004,11 @@ ("a2065", "A2065"), ] }, + Option.NGC_DATABASE: { + "default": "0", + "description": N_("Enable/disable use of the GameCube database"), + "type": "Boolean", + }, Option.PLATFORM: { "default": "amiga", "description": N_("Platform"), @@ -846,12 +1028,16 @@ ("gb", "Game Boy"), ("gba", "Game Boy Advance"), ("gbc", "Game Boy Color"), + ("ngc", "GameCube"), + ("neogeo", "Neo-Geo"), ("nes", "Nintendo"), + ("n64", "Nintendo 64"), ("sms", "Master System"), ("smd", "Mega Drive"), ("psx", "PlayStation"), ("snes", "Super Nintendo"), ("tg16", "TurboGrafx-16"), + ("tgcd", "TurboGrafx-CD"), ("zxs", "ZX Spectrum"), ] }, @@ -865,6 +1051,11 @@ "description": N_("Enable/disable use of the PlayStation database"), "type": "Boolean", }, + Option.QUICK_SETTINGS_OPTIONS: { + "default": "", + "description": N_("Quick Settings to Display"), + "type": "String", + }, Option.RAW_INPUT: { "default": "1", "description": N_("Use keyboard raw input (Windows)"), @@ -880,6 +1071,13 @@ "description": N_("Relative Temporary Directories"), "type": "Boolean", }, + Option.RETROARCH_AUDIO_BUFFER: { + "default": "40", + "description": N_("RetroArch Audio Buffer"), + "type": "Integer", + "min": 0, + "max": 1000, + }, Option.RTG_SCANLINES: { "default": "0", "description": N_("Render scan lines in RTG graphics mode"), @@ -939,9 +1137,9 @@ "type": "Choice", "values": [ ("auto", "Auto-Select Region"), - ("ntsc-u", "NTSC-U (Genesis)"), - ("ntsc-j", "NTSC-J"), - ("pal", "PAL"), + ("ntsc", "Genesis"), + ("pal", "Mega Drive PAL"), + ("ntsc-j", "Mega Drive NTSC-J"), ] }, Option.SMOOTHING: { @@ -974,6 +1172,24 @@ "description": N_("Enable/disable use of the Super Nintendo database"), "type": "Boolean", }, + Option.SNES_PORT_1_TYPE: { + "default": "gamepad", + "description": N_("SNES Port 1"), + "type": "Choice", + "values": [ + ("none", N_("None")), + ("gamepad", N_("Gamepad")), + ] + }, + Option.SNES_PORT_2_TYPE: { + "default": "gamepad", + "description": N_("SNES Port 2"), + "type": "Choice", + "values": [ + ("none", N_("None")), + ("gamepad", N_("Gamepad")), + ] + }, Option.SOUND_CARD: { "default": "0", "description": N_("Sound Card"), @@ -1044,6 +1260,12 @@ "Enable/disable use of the TurboGrafx-16 game database"), "type": "Boolean", }, + Option.TGCD_DATABASE: { + "default": "0", + "description": N_( + "Enable/disable use of the TurboGrafx-CD game database"), + "type": "Boolean", + }, Option.TURBO_LOAD: { "default": "1", "description": N_("Turbo Load"), diff -Nru fs-uae-arcade-2.9.6/launcher/panels/additionalconfigpanel.py fs-uae-arcade-2.9.7/launcher/panels/additionalconfigpanel.py --- fs-uae-arcade-2.9.6/launcher/panels/additionalconfigpanel.py 2017-05-25 10:07:54.000000000 +0000 +++ fs-uae-arcade-2.9.7/launcher/panels/additionalconfigpanel.py 2017-10-16 21:40:41.000000000 +0000 @@ -2,6 +2,7 @@ from launcher.i18n import gettext from launcher.option import Option from launcher.ui.IconButton import IconButton +from launcher.ui.behaviors.platformbehavior import AmigaShowBehavior from launcher.ui.config.configpanel import ConfigPanel from launcher.ui.config.configdialog import ConfigDialog @@ -17,12 +18,19 @@ hori_layout.add_spacer(0, expand=True) hori_layout.add(CustomConfigButton(self), margin_right=10) self.layout.add_spacer(0) - self.add_amiga_option(Option.CPU) - self.add_amiga_option(Option.JIT_COMPILER) - self.add_amiga_option(Option.FLOPPY_DRIVE_SPEED) - self.add_amiga_option(Option.FLOPPY_DRIVE_VOLUME_EMPTY) - self.add_amiga_option(Option.FREEZER_CARTRIDGE) - self.add_amiga_option(Option.DONGLE_TYPE) + + amiga_panel = fsui.Panel(self) + # AmigaShowBehavior(amiga_panel) + amiga_panel.layout = fsui.VerticalLayout() + self.layout.add(amiga_panel, fill=True) + + self.add_amiga_option(Option.CPU, parent=amiga_panel) + self.add_amiga_option(Option.JIT_COMPILER, parent=amiga_panel) + self.add_amiga_option(Option.FLOPPY_DRIVE_SPEED, parent=amiga_panel) + self.add_amiga_option( + Option.FLOPPY_DRIVE_VOLUME_EMPTY, parent=amiga_panel) + self.add_amiga_option(Option.FREEZER_CARTRIDGE, parent=amiga_panel) + self.add_amiga_option(Option.DONGLE_TYPE, parent=amiga_panel) class CustomConfigButton(IconButton): diff -Nru fs-uae-arcade-2.9.6/launcher/panels/mainpanel.py fs-uae-arcade-2.9.7/launcher/panels/mainpanel.py --- fs-uae-arcade-2.9.6/launcher/panels/mainpanel.py 2017-05-25 10:07:54.000000000 +0000 +++ fs-uae-arcade-2.9.7/launcher/panels/mainpanel.py 2017-10-16 21:40:41.000000000 +0000 @@ -13,7 +13,7 @@ self.model_group = ModelGroup(self) self.layout.add(self.model_group, fill=True) self.layout.add_spacer(Skin.EXTRA_GROUP_MARGIN) - self.removable_media_group = RemovableMediaGroup(self, 2) + self.removable_media_group = RemovableMediaGroup(self, 2, main=True) self.layout.add(self.removable_media_group, fill=True) self.layout.add_spacer(10) self.input_group = InputGroup( diff -Nru fs-uae-arcade-2.9.6/launcher/panels/quicksettingspanel.py fs-uae-arcade-2.9.7/launcher/panels/quicksettingspanel.py --- fs-uae-arcade-2.9.6/launcher/panels/quicksettingspanel.py 2017-05-25 10:07:54.000000000 +0000 +++ fs-uae-arcade-2.9.7/launcher/panels/quicksettingspanel.py 2017-10-16 21:40:41.000000000 +0000 @@ -1,28 +1,41 @@ +import traceback + import fsui from fsgs.option import Option from fsgs.platform import Platform from launcher.i18n import gettext +from launcher.launcher_config import LauncherConfig +from launcher.option import options from launcher.settings.fullscreenmodebutton import FullscreenModeButton from launcher.settings.monitorbutton import MonitorButton from launcher.settings.option_ui import OptionUI +from launcher.settings.platformsettingsdialog import PlatformSettingsDialog from launcher.settings.settings_dialog import SettingsDialog from launcher.settings.videosynccheckbox import VideoSyncCheckBox from launcher.ui.IconButton import IconButton from launcher.ui.behaviors.configbehavior import ConfigBehavior from launcher.ui.behaviors.platformbehavior import PlatformShowBehavior, \ AMIGA_PLATFORMS +from launcher.ui.behaviors.settingsbehavior import SettingsBehavior -MEDNAFEN = [Platform.SNES, Platform.GB, Platform.GBA, Platform.GBC, - Platform.SMD, Platform.SMS, Platform.TG16] +MEDNAFEN = [ + Platform.SNES, Platform.NES, Platform.GB, Platform.GBA, Platform.GBC, + Platform.PSX, Platform.SMD, Platform.SMS, Platform.TG16, Platform.TGCD] SCALING = [Platform.C64] + MEDNAFEN STRETCHING = [Platform.C64, Platform.DOS, Platform.ZXS] + MEDNAFEN EFFECTS = [Platform.C64, Platform.DOS, Platform.ZXS] + MEDNAFEN -BORDER = [Platform.C64, Platform.ZXS] + MEDNAFEN +BORDER = [Platform.C64, Platform.ZXS] +# BORDER += MEDNAFEN SMOOTHING = [] + MEDNAFEN CROPPING = [Platform.ZXS] SCALING += AMIGA_PLATFORMS STRETCHING += AMIGA_PLATFORMS +STRETCHING += [Platform.ARCADE, Platform.NEOGEO] + +BEZEL = AMIGA_PLATFORMS + MEDNAFEN + [ + Platform.ARCADE, Platform.NEOGEO, Platform.ZXS, Platform.DOS] +CHEATS = MEDNAFEN + [Platform.ARCADE, Platform.NEOGEO] class QuickSettingsPanel(fsui.Panel): @@ -41,29 +54,68 @@ hori_layout.add(settings_button, margin_right=10) self.layout.add_spacer(0) + # button = fsui.Button(self, "Platform Settings") + # button.activated.connect(self.on_platform_settings_button) + # self.layout.add(button, margin=10, fill=True) + # self.add_option(Option.KEEP_ASPECT, text=gettext("Keep Aspect"), # platforms=AMIGA_PLATFORMS) self.add_option(Option.SCALE, text=None, platforms=SCALING) self.add_option(Option.STRETCH, text=None, platforms=STRETCHING) + self.add_option(Option.BEZEL, text=None, platforms=BEZEL) + self.add_option(Option.ZOOM, text=None, platforms=AMIGA_PLATFORMS) self.add_option(Option.BORDER, text=None, platforms=BORDER) - self.add_option(Option.SMOOTHING, text=None, platforms=SMOOTHING) + # self.add_option(Option.SMOOTHING, text=None, platforms=SMOOTHING) self.add_option(Option.EFFECT, text=None, platforms=EFFECTS) # self.add_option(Option.CROP, text=None, platforms=CROPPING) - # self.add_option(Option.ZXS_DRIVER, [Platform.ZXS]) - # self.add_option(Option.DOS_EMULATOR, [Platform.DOS]) + # if fsgc.settings[Option.DEVELOPER_MODE] == "1": + # pass + # # self.add_option(Option.ZXS_DRIVER, [Platform.ZXS]) + # # self.add_option(Option.DOS_EMULATOR, [Platform.DOS]) + # self.add_option(Option.NES_EMULATOR, [Platform.NES]) + self.add_option(Option.AUTO_LOAD, [Platform.DOS, Platform.ZXS]) self.add_option(Option.AUTO_QUIT, [Platform.DOS]) self.add_option(Option.TURBO_LOAD, [Platform.ZXS]) self.add_option(Option.C64_PALETTE, [Platform.C64]) + # self.add_option(Option.FRAME, text=None, platforms=BEZEL) + # self.add_option(Option.BEZEL, text=None, platforms=BEZEL) + + # self.add_option(Option.CHEATS, platforms=CHEATS) + + quick_settings = fsgc.settings[Option.QUICK_SETTINGS_OPTIONS] + for option in quick_settings.split(","): + option = option.strip().lower() + if "[" in option: + # For future use of e.g.: + # option1[platform1,platform2],option2[platform,...] + option, platforms = option.split("[", 1) + else: + platforms = [] + if option in options: + try: + self.add_option(option) + except Exception: + print("Error adding quick setting") + traceback.print_exc() + self.layout.add_spacer(expand=True) hori_layout = fsui.HorizontalLayout() hori_layout.add_spacer(expand=True) + self.platform_settings_button = fsui.Button(self, "Platform Settings") + self.platform_settings_button.activated.connect( + self.on_platform_settings_button) + hori_layout.add(self.platform_settings_button, margin=10) + self.layout.add(hori_layout, fill=True) + + hori_layout = fsui.HorizontalLayout() + hori_layout.add_spacer(expand=True) self.video_sync_checkbox = VideoSyncCheckBox(self) hori_layout.add(self.video_sync_checkbox, margin_right=10) self.layout.add(hori_layout, fill=True) @@ -74,6 +126,7 @@ self.layout.add_spacer(10) ConfigBehavior(self, [Option.PLATFORM]) + SettingsBehavior(self, [Option.G_SYNC]) def add_option(self, option, platforms=None, text=""): panel = fsui.Panel(self) @@ -86,12 +139,22 @@ if platforms: PlatformShowBehavior(panel, platforms) - def on_platform_config(self, _): + def on_platform_config(self, value): self.layout.update() + self.platform_settings_button.enable( + len(PlatformSettingsDialog.option_list_for_platform(value)) > 0) + + def on_g_sync_setting(self, value): + self.video_sync_checkbox.enable(value != "1") def on_settings_button(self): SettingsDialog.open(self.window) + def on_platform_settings_button(self): + platform = LauncherConfig.get("platform") + if platform: + PlatformSettingsDialog.open(self.window, platform) + def get_min_height(self): # Because we add a lot of controls, force min size to 0 to avoid # this control reporting too large min height at startup. Binary files /tmp/tmpYweXHu/uxFP7mr62z/fs-uae-arcade-2.9.6/launcher/res/16x16/fullscreen_fullscreen.png and /tmp/tmpYweXHu/yyd8WCnHx7/fs-uae-arcade-2.9.7/launcher/res/16x16/fullscreen_fullscreen.png differ Binary files /tmp/tmpYweXHu/uxFP7mr62z/fs-uae-arcade-2.9.6/launcher/res/16x16/fullscreen_window.png and /tmp/tmpYweXHu/yyd8WCnHx7/fs-uae-arcade-2.9.7/launcher/res/16x16/fullscreen_window.png differ Binary files /tmp/tmpYweXHu/uxFP7mr62z/fs-uae-arcade-2.9.6/launcher/res/16x16/gamecube.png and /tmp/tmpYweXHu/yyd8WCnHx7/fs-uae-arcade-2.9.7/launcher/res/16x16/gamecube.png differ Binary files /tmp/tmpYweXHu/uxFP7mr62z/fs-uae-arcade-2.9.6/launcher/res/16x16/ngc.png and /tmp/tmpYweXHu/yyd8WCnHx7/fs-uae-arcade-2.9.7/launcher/res/16x16/ngc.png differ diff -Nru fs-uae-arcade-2.9.6/launcher/scanner.py fs-uae-arcade-2.9.7/launcher/scanner.py --- fs-uae-arcade-2.9.6/launcher/scanner.py 2017-05-25 10:07:54.000000000 +0000 +++ fs-uae-arcade-2.9.7/launcher/scanner.py 2017-10-16 21:40:41.000000000 +0000 @@ -9,7 +9,7 @@ from fsgs.ogd.lists import ListsSynchronizer from fsgs.ogd.meta import MetaSynchronizer from launcher.configuration_scanner import ConfigurationScanner -from launcher.file_scanner import FileScanner +from launcher.filescanner import FileScanner from launcher.game_scanner import GameScanner from launcher.launcher_config import LauncherConfig from launcher.launcher_settings import LauncherSettings diff -Nru fs-uae-arcade-2.9.6/launcher/settings/gamedatabasesettingspage.py fs-uae-arcade-2.9.7/launcher/settings/gamedatabasesettingspage.py --- fs-uae-arcade-2.9.6/launcher/settings/gamedatabasesettingspage.py 2017-05-25 10:07:54.000000000 +0000 +++ fs-uae-arcade-2.9.7/launcher/settings/gamedatabasesettingspage.py 2017-10-16 21:40:41.000000000 +0000 @@ -1,8 +1,4 @@ -from fsgs import openretro -from launcher.settings.option_ui import OptionUI - import fsui -from fsbc import settings from launcher.i18n import gettext from launcher.option import Option from launcher.settings.settings_page import SettingsPage diff -Nru fs-uae-arcade-2.9.6/launcher/settings/gameplatformssettingspage.py fs-uae-arcade-2.9.7/launcher/settings/gameplatformssettingspage.py --- fs-uae-arcade-2.9.6/launcher/settings/gameplatformssettingspage.py 2017-05-25 10:07:54.000000000 +0000 +++ fs-uae-arcade-2.9.7/launcher/settings/gameplatformssettingspage.py 2017-10-16 21:40:41.000000000 +0000 @@ -1,11 +1,14 @@ from fsgs import openretro +from fsgs.platform import Platform from launcher.settings.option_ui import OptionUI import fsui from fsbc import settings from launcher.i18n import gettext from launcher.option import Option +from launcher.settings.platformsettingsdialog import PlatformSettingsDialog from launcher.settings.settings_page import SettingsPage +from launcher.ui.IconButton import IconButton class GamePlatformsSettingsPage(SettingsPage): @@ -28,20 +31,42 @@ "Additional plugins are needed."), 640) self.layout.add(label, margin_top=20, margin_bottom=20) - self.add_database_option(Option.CPC_DATABASE, "Amstrad CPC") - self.add_database_option(Option.ARCADE_DATABASE, "Arcade") - self.add_database_option(Option.ATARI_DATABASE, "Atari ST") - self.add_database_option(Option.C64_DATABASE, "Commodore 64") - self.add_database_option(Option.DOS_DATABASE, "DOS") - self.add_database_option(Option.GB_DATABASE, "Game Boy") - self.add_database_option(Option.GBA_DATABASE, "Game Boy Advance") - self.add_database_option(Option.GBC_DATABASE, "Game Boy Color") - self.add_database_option(Option.NES_DATABASE, "Nintendo") - self.add_database_option(Option.PSX_DATABASE, "PlayStation") - self.add_database_option(Option.SMD_DATABASE, "Mega Drive") - self.add_database_option(Option.SNES_DATABASE, "Super Nintendo") - self.add_database_option(Option.TG16_DATABASE, "TurboGrafx-16") - self.add_database_option(Option.ZXS_DATABASE, "ZX Spectrum") + self.add_database_option( + Platform.CPC, Option.CPC_DATABASE, "Amstrad CPC") + self.add_database_option( + Platform.ARCADE, Option.ARCADE_DATABASE, "Arcade") + self.add_database_option( + Platform.A7800, Option.A7800_DATABASE, "Atari 7800") + self.add_database_option( + Platform.ATARI, Option.ATARI_DATABASE, "Atari ST") + self.add_database_option( + Platform.C64, Option.C64_DATABASE, "Commodore 64") + self.add_database_option( + Platform.DOS, Option.DOS_DATABASE, "DOS") + self.add_database_option( + Platform.GB, Option.GB_DATABASE, "Game Boy") + self.add_database_option( + Platform.GBA, Option.GBA_DATABASE, "Game Boy Advance") + self.add_database_option( + Platform.GBC, Option.GBC_DATABASE, "Game Boy Color") + self.add_database_option( + Platform.SMS, Option.SMS_DATABASE, "Master System") + self.add_database_option( + Platform.SMD, Option.SMD_DATABASE, "Mega Drive") + self.add_database_option( + Platform.NEOGEO, Option.NEOGEO_DATABASE, "Neo-Geo") + self.add_database_option( + Platform.NES, Option.NES_DATABASE, "Nintendo") + self.add_database_option( + Platform.PSX, Option.PSX_DATABASE, "PlayStation") + self.add_database_option( + Platform.SNES, Option.SNES_DATABASE, "Super Nintendo") + self.add_database_option( + Platform.TG16, Option.TG16_DATABASE, "TurboGrafx-16") + self.add_database_option( + Platform.TGCD, Option.TGCD_DATABASE, "TurboGrafx-CD") + self.add_database_option( + Platform.ZXS, Option.ZXS_DATABASE, "ZX Spectrum") # label = fsui.MultiLineLabel( # self, gettext( @@ -52,7 +77,7 @@ # "games."), 640) # self.layout.add(label, margin_top=20) - def add_database_option(self, name, description=""): + def add_database_option(self, platform, name, description=""): self.options_on_page.add(name) group = OptionUI.create_group( self, name, description=description, help_button=False) @@ -62,8 +87,23 @@ self.layout.add( self.hori_layout, fill=True, margin_top=10, margin_bottom=10, margin_left=-10, margin_right=-10) + self.hori_layout.add(group, fill=True, expand=-1, margin=10, margin_top=0, margin_bottom=0) + self.hori_layout.add( + PlatformSettingsButton(self, platform), margin_right=10) + if self.hori_counter % 2 == 0: - self.hori_layout.add_spacer(10) + self.hori_layout.add_spacer(0) self.hori_counter += 1 + + +class PlatformSettingsButton(IconButton): + def __init__(self, parent, platform): + super().__init__(parent, "16x16/settings.png") + self.platform = platform + self.enable( + len(PlatformSettingsDialog.option_list_for_platform(platform)) > 0) + + def on_activate(self): + PlatformSettingsDialog.open(self.window, self.platform) diff -Nru fs-uae-arcade-2.9.6/launcher/settings/joystick_settings_page.py fs-uae-arcade-2.9.7/launcher/settings/joystick_settings_page.py --- fs-uae-arcade-2.9.6/launcher/settings/joystick_settings_page.py 2017-05-25 10:07:54.000000000 +0000 +++ fs-uae-arcade-2.9.7/launcher/settings/joystick_settings_page.py 2017-10-16 21:40:41.000000000 +0000 @@ -1,6 +1,6 @@ import fsui from fsgs.option import Option -from launcher.device_manager import DeviceManager +from launcher.devicemanager import DeviceManager from launcher.i18n import gettext from launcher.launcher_settings import LauncherSettings from launcher.settings.settings_page import SettingsPage diff -Nru fs-uae-arcade-2.9.6/launcher/settings/maintenance_settings_page.py fs-uae-arcade-2.9.7/launcher/settings/maintenance_settings_page.py --- fs-uae-arcade-2.9.6/launcher/settings/maintenance_settings_page.py 2017-05-25 10:07:54.000000000 +0000 +++ fs-uae-arcade-2.9.7/launcher/settings/maintenance_settings_page.py 2017-10-16 21:40:41.000000000 +0000 @@ -1,6 +1,6 @@ from fsbc.task import Task from fsgs.Database import Database -from fsgs.FileDatabase import FileDatabase +from fsgs.filedatabase import FileDatabase from fsgs.LockerDatabase import LockerDatabase from fsgs.context import fsgs from launcher.i18n import gettext diff -Nru fs-uae-arcade-2.9.6/launcher/settings/option_ui.py fs-uae-arcade-2.9.7/launcher/settings/option_ui.py --- fs-uae-arcade-2.9.6/launcher/settings/option_ui.py 2017-05-25 10:07:54.000000000 +0000 +++ fs-uae-arcade-2.9.7/launcher/settings/option_ui.py 2017-10-16 21:40:41.000000000 +0000 @@ -34,9 +34,11 @@ choice_values = [] if description: - default_tmpl = "Default - {0}" + default_tmpl = "{0} (*)" + # default_tmpl = "Default - {0}" else: - default_tmpl = "{0} (Default)" + default_tmpl = "{0} (*)" + # default_tmpl = "Default - {0}" if option["type"].lower() == "boolean": if option["default"] == "1": diff -Nru fs-uae-arcade-2.9.6/launcher/settings/platformsettingsdialog.py fs-uae-arcade-2.9.7/launcher/settings/platformsettingsdialog.py --- fs-uae-arcade-2.9.6/launcher/settings/platformsettingsdialog.py 1970-01-01 00:00:00.000000000 +0000 +++ fs-uae-arcade-2.9.7/launcher/settings/platformsettingsdialog.py 2017-10-16 21:40:41.000000000 +0000 @@ -0,0 +1,74 @@ +import fsui +from fsgs.platform import Platform +from launcher.option import Option +from launcher.settings.option_ui import OptionUI + + +class PlatformSettingsDialog(fsui.Window): + + @classmethod + def open(cls, parent, platform): + # return fsui.open_window_instance(cls, parent) + dialog = cls(parent, platform) + dialog.show() + return dialog + + def __init__(self, parent, platform): + title = "Platform Settings: " + platform.upper() + super().__init__(parent, title) + # self.layout = fsui.VerticalLayout() + buttons, layout = fsui.DialogButtons.create_with_layout(self) + # if self.window.theme.has_close_buttons: + # buttons.create_close_button() + + # self.layout.padding = 10 + + for option in self.option_list_for_platform(platform): + self.add_option(layout, option) + + self.set_size((600, 400)) + + def add_option(self, layout, option, platforms=None, text=""): + panel = fsui.Panel(self) + panel.layout = fsui.VerticalLayout() + panel.layout.add( + OptionUI.create_group( + panel, option, text, thin=False, help_button=True), + fill=True) + layout.add(panel, fill=True, margin_bottom=10) + + @staticmethod + def option_list_for_platform(platform): + options = [] + if platform == Platform.ARCADE: + # options.append(Option.ARCADE_DATABASE) + options.append(Option.MAME_ARTWORK) + options.extend(mame_options) + elif platform == Platform.NEOGEO: + # options.append(Option.NEOGEO_DATABASE) + options.extend(mame_options) + elif platform == Platform.NES: + # options.append(Option.NES_DATABASE) + options.append(Option.NES_EMULATOR) + options.extend(mednafen_options) + options.extend(retroarch_options) + elif platform == Platform.SMD: + # options.append(Option.SMD_DATABASE) + options.extend(mednafen_options) + elif platform == Platform.SNES: + # options.append(Option.SNES_DATABASE) + options.extend(mednafen_options) + return options + +mame_options = [ + +] + +mednafen_options = [ + Option.MEDNAFEN_AUDIO_DRIVER, + Option.MEDNAFEN_AUDIO_BUFFER, +] + +retroarch_options = [ + Option.RETROARCH_AUDIO_BUFFER, +] diff -Nru fs-uae-arcade-2.9.6/launcher/settings/settings_dialog.py fs-uae-arcade-2.9.7/launcher/settings/settings_dialog.py --- fs-uae-arcade-2.9.6/launcher/settings/settings_dialog.py 2017-05-25 10:07:54.000000000 +0000 +++ fs-uae-arcade-2.9.7/launcher/settings/settings_dialog.py 2017-10-16 21:40:41.000000000 +0000 @@ -134,6 +134,10 @@ defaults_button.activated.connect(self.__defaults_activated) self.button_layout.insert(0, defaults_button, fill=True) + defaults_label = fsui.Label( + self, gettext("Choices marked with (*) is the default setting")) + self.button_layout.insert(1, defaults_label, margin_left=20) + self.set_size((940, 560)) # self.center_on_parent() diff -Nru fs-uae-arcade-2.9.6/launcher/startup_scan.py fs-uae-arcade-2.9.7/launcher/startup_scan.py --- fs-uae-arcade-2.9.6/launcher/startup_scan.py 2017-05-25 10:07:54.000000000 +0000 +++ fs-uae-arcade-2.9.7/launcher/startup_scan.py 2017-10-16 21:40:41.000000000 +0000 @@ -4,7 +4,7 @@ from fsbc.paths import Paths from fsgs.Database import Database from fsgs.FSGSDirectories import FSGSDirectories -from fsgs.FileDatabase import FileDatabase +from fsgs.filedatabase import FileDatabase from fsgs.amiga.amiga import Amiga from fsgs.amiga.rommanager import ROMManager from fsgs.context import fsgs diff -Nru fs-uae-arcade-2.9.6/launcher/ui/config/configpanel.py fs-uae-arcade-2.9.7/launcher/ui/config/configpanel.py --- fs-uae-arcade-2.9.6/launcher/ui/config/configpanel.py 2017-05-25 10:07:54.000000000 +0000 +++ fs-uae-arcade-2.9.7/launcher/ui/config/configpanel.py 2017-10-16 21:40:41.000000000 +0000 @@ -15,7 +15,9 @@ self.layout.add(self.config_widget_factory.create( self, name), fill=fill, margin=10, margin_bottom=0) - def add_amiga_option(self, name, fill=True): - self.layout.add(self.config_widget_factory.create( - self, name, platforms=AMIGA_PLATFORMS), + def add_amiga_option(self, name, fill=True, parent=None): + if parent is None: + parent = self + parent.layout.add(self.config_widget_factory.create( + parent, name, platforms=AMIGA_PLATFORMS), fill=fill, margin=10, margin_bottom=0) diff -Nru fs-uae-arcade-2.9.6/launcher/ui/config/InputGroup.py fs-uae-arcade-2.9.7/launcher/ui/config/InputGroup.py --- fs-uae-arcade-2.9.6/launcher/ui/config/InputGroup.py 2017-05-25 10:07:54.000000000 +0000 +++ fs-uae-arcade-2.9.7/launcher/ui/config/InputGroup.py 2017-10-16 21:40:41.000000000 +0000 @@ -1,5 +1,5 @@ import fsui -from ...device_manager import DeviceManager +from ...devicemanager import DeviceManager from ...i18n import gettext from ..IconButton import IconButton from .InputSelector import InputSelector diff -Nru fs-uae-arcade-2.9.6/launcher/ui/config/InputSelector.py fs-uae-arcade-2.9.7/launcher/ui/config/InputSelector.py --- fs-uae-arcade-2.9.6/launcher/ui/config/InputSelector.py 2017-05-25 10:07:54.000000000 +0000 +++ fs-uae-arcade-2.9.7/launcher/ui/config/InputSelector.py 2017-10-16 21:40:41.000000000 +0000 @@ -1,13 +1,18 @@ import sys import fsui -from launcher.ui.behaviors.amigaenablebehavior import AmigaEnableBehavior -from ..HelpButton import HelpButton -from ..IconButton import IconButton -from ...device_manager import DeviceManager -from ...i18n import gettext -from ...launcher_config import LauncherConfig -from ...launcher_signal import LauncherSignal +from launcher.option import Option +from fsgs.context import fsgs +from launcher.devicemanager import DeviceManager +from launcher.i18n import gettext +from launcher.launcher_config import LauncherConfig +from launcher.launcher_signal import LauncherSignal +from launcher.ui.HelpButton import HelpButton +from launcher.ui.IconButton import IconButton +from launcher.ui.behaviors.platformbehavior import AMIGA_PLATFORMS, \ + AmigaShowBehavior + +MIN_TYPE_CHOICE_WIDTH = 160 class InputSelector(fsui.Group): @@ -21,6 +26,14 @@ fsui.Group.__init__(self, parent) self.layout = fsui.HorizontalLayout() + if port == 1: + non_amiga_port = 1 + elif port == 0: + non_amiga_port = 2 + else: + non_amiga_port = port + 1 + self.layout.add(InputPortTypeChoice(self, fsgs, non_amiga_port)) + self.joystick_mode_values = ["nothing", "mouse", "joystick", "cd32 gamepad"] self.joystick_mode_titles = [gettext("No Amiga Device"), @@ -29,7 +42,10 @@ gettext("CD32 Pad")] self.mode_choice = fsui.Choice(self, self.joystick_mode_titles) - AmigaEnableBehavior(self.mode_choice) + self.mode_choice.set_min_width(MIN_TYPE_CHOICE_WIDTH) + # AmigaEnableBehavior(self.mode_choice) + AmigaShowBehavior(self.mode_choice) + self.layout.add(self.mode_choice) self.layout.add_spacer(10) # else: @@ -46,17 +62,22 @@ # self.joystick_values_initialized = True self.device_choice = fsui.ComboBox(self, [""], read_only=True) - AmigaEnableBehavior(self.device_choice) + # AmigaEnableBehavior(self.device_choice) self.joystick_values = [] self.rebuild_device_list() self.device_choice.set_index(0) # print(self.device_choice.get_index()) + AmigaShowBehavior(self.device_choice) self.layout.add(self.device_choice, expand=True) + self.layout.add(InputPortDeviceChoice( + self, fsgs, non_amiga_port), expand=True) + if port < 4 and autofire_button: self.autofire_button = IconButton(self, "16x16/lightning_off.png") self.autofire_button.activated.connect(self.on_autofire_button) self.layout.add(self.autofire_button, margin_left=10) + AmigaShowBehavior(self.autofire_button) else: self.autofire_button = None @@ -126,12 +147,14 @@ index = self.device_choice.get_index() value = self.joystick_values[index] - for port in range(4): - if self.port == port: - continue - key = "joystick_port_{0}".format(port) - if LauncherConfig.get(key) == value: - LauncherConfig.set(key, "") + if value != "none": + # Reset to default device for other ports using the same device. + for port in range(4): + if self.port == port: + continue + key = "joystick_port_{0}".format(port) + if LauncherConfig.get(key) == value: + LauncherConfig.set(key, "") LauncherConfig.set(self.device_option_key, value) def on_autofire_button(self): @@ -141,6 +164,10 @@ LauncherConfig.set(self.autofire_mode_option_key, "1") def on_config(self, key, value): + if key == "platform": + self.layout.update() + return + if key == "amiga_model": value = LauncherConfig.get( "joystick_port_{0}_mode".format(self.port)) @@ -221,6 +248,218 @@ # print(self.device_choice.get_index()) +class InputPortTypeChoice(fsui.Choice): + def __init__(self, parent, fsgc, port): + self.fsgc = fsgc + self._choice_values = [] + self._choice_labels = [] + self.port = port + super().__init__(parent, self._choice_labels) + self._platform = "" + self._config_key = "" + self.fsgc.signal.connect("config", self.on_config) + self.on_config(Option.PLATFORM, self.fsgc.config.get(Option.PLATFORM)) + self.changed.connect(self.__changed) + self.set_min_width(MIN_TYPE_CHOICE_WIDTH) + + def on_destroy(self): + self.fsgc.signal.disconnect("config", self.on_config) + + def __changed(self): + self.fsgc.config.set( + self._config_key, self._choice_values[self.get_index()]) + + def on_config(self, key, value): + if key == Option.PLATFORM: + self._platform = value + self._config_key = "{}_port_{}_type".format(value, self.port) + self.update_options() + self.update_index(self.fsgc.config.get(self._config_key)) + self.update_enabled() + elif key == self._config_key: + self.update_index(value) + + def update_enabled(self): + self.set_visible(self._platform not in AMIGA_PLATFORMS) + self.set_enabled(self._choice_labels != ["N/A"]) + + def update_index(self, value): + try: + index = self._choice_values.index(value) + except ValueError: + index = 0 + with self.changed.inhibit: + self.set_index(index) + + def update_options(self): + try: + option = Option.get(self._config_key) + except KeyError: + self._choice_values = ["0"] + self._choice_labels = ["N/A"] + else: + choices = option["values"] + self._choice_values = [x[0] for x in choices] + self._choice_labels = [x[1] for x in choices] + + if "default" in option: + for x in choices: + if x[0] == option["default"]: + default_label = x[1] + " (*)" + break + else: + default_label = "??? (*)" + self._choice_values.insert(0, "") + self._choice_labels.insert(0, default_label) + with self.changed.inhibit: + self.clear() + for label in self._choice_labels: + self.add_item(label) + + +class InputPortDeviceChoice(fsui.ComboBox): + def __init__(self, parent, fsgc, port): + super().__init__(parent, [""], read_only=True) + self.fsgc = fsgc + self.port = port + self._platform = "" + self._config_key = "" + self.device_option_key = "" + + # AmigaEnableBehavior(self.device_choice) + self.device_values = [] + self.rebuild_device_list() + + self.fsgc.signal.connect("config", self.on_config) + # Must check platform before device option key + self.on_config(Option.PLATFORM, self.fsgc.config.get(Option.PLATFORM)) + self.on_config( + self.device_option_key, LauncherConfig.get(self.device_option_key)) + # self.changed.connect(self.__changed) + self.set_index(0) + + LauncherConfig.add_listener(self) + LauncherSignal.add_listener("settings_updated", self) + LauncherSignal.add_listener("device_list_updated", self) + + def rebuild_device_list(self): + self.device_values = ["", "none"] + devices = ["", gettext("No Host Device")] + for i, name in enumerate(DeviceManager.get_joystick_names()): + devices.append(fix_device_name(name)) + self.device_values.append(DeviceManager.device_ids[i]) + self.set_items(devices) + + def on_destroy(self): + print("on_destroy") + LauncherConfig.remove_listener(self) + LauncherSignal.remove_listener("settings_updated", self) + LauncherSignal.remove_listener("device_list_updated", self) + + def on_changed(self): + index = self.get_index() + value = self.device_values[index] + if value != "none": + # Reset to default device for other ports using the same device. + for port in range(1, 4 + 1): + if self.port == port: + continue + key = "{}_port_{}".format(self._platform, port) + if LauncherConfig.get(key) == value: + LauncherConfig.set(key, "") + LauncherConfig.set(self.device_option_key, value) + + def update_enabled(self): + self.set_visible(self._platform not in AMIGA_PLATFORMS) + # self.set_enabled(self._choice_labels != ["N/A"]) + + def on_config(self, key, value): + if key == "platform": + self._platform = value + self.update_enabled() + self.device_option_key = "{}_port_{}".format( + self._platform, self.port) + # Disable the control if the type option does not exist + try: + Option.get("{}_port_{}_type".format(self._platform, self.port)) + except KeyError: + self.disable() + else: + self.enable() + + return + # if key == self.mode_option_key or key == "amiga_model": + # value = DeviceManager.get_calculated_port_mode( + # LauncherConfig, self.port) + # for i, config in enumerate(self.joystick_mode_values): + # if config == value: + # if self.mode_choice is not None: + # self.mode_choice.set_index(i) + # if self.port >= 4: + # self.enable(i != 0) + # break + # else: + # print("FIXME: could not set mode") + # elif key == self.type_option_key: + elif key == self.device_option_key: + value_lower = value.lower() + for i, name in enumerate(self.device_values): + if value_lower == name.lower(): + self.set_index(i) + break + + # This is intended to catch all config changes for all ports (both + # mode and device) to update the defaults + + # FIXME: + # if key.startswith("joystick_port_") or key == "amiga_model": + # self.update_default_device() + + if key.startswith("{}_port_".format(self._platform)): + self.update_default_device() + + def on_device_list_updated_signal(self): + # print(self.get_index()) + had_default = (self.get_index() == 0) + self.rebuild_device_list() + self.update_default_device(had_default=had_default) + + def on_settings_updated_signal(self): + self.update_default_device() + + def update_default_device(self, had_default=None): + config = {"platform": self._platform} + for port in range(1, 4 + 1): + key = "{}_port_{}".format(self._platform, port) + if self.port == port: + config[key] = "" + else: + config[key] = LauncherConfig.get(key) + key = "{}_port_{}_type".format(self._platform, port) + config[key] = LauncherConfig.get(key) + # config[key] = DeviceManager.get_calculated_port_mode( + # LauncherConfig, port) + + device = DeviceManager.get_non_amiga_device_for_port( + config, self.port) + + default_description = gettext("{} (*)").format( + fix_device_name(device.name)) + # print("default_description = ", default_description) + + if had_default is None: + had_default = (self.get_index() == 0) + # print("had default", had_default, self.get_index()) + self.set_item_text(0, default_description) + # print("had_default", had_default) + if had_default: + # print("set text for", self.port, default_description) + # self.set_index(1) + self.set_text(default_description) + self.set_index(0) + # print(self.get_index()) + + def fix_device_name(name): system = gettext("System") if sys.platform == "win32": diff -Nru fs-uae-arcade-2.9.6/launcher/ui/config/modelgroup.py fs-uae-arcade-2.9.7/launcher/ui/config/modelgroup.py --- fs-uae-arcade-2.9.6/launcher/ui/config/modelgroup.py 2017-05-25 10:07:54.000000000 +0000 +++ fs-uae-arcade-2.9.7/launcher/ui/config/modelgroup.py 2017-10-16 21:40:41.000000000 +0000 @@ -1,6 +1,7 @@ import fsui from fsbc import settings from fsbc.util import unused +from fsgs import openretro from fsgs.amiga.amiga import Amiga from fsgs.context import fsgs from fsgs.platform import PLATFORM_IDS @@ -46,38 +47,40 @@ gettext("Medium"), gettext("Low")]) # AmigaEnableBehavior(self.accuracy_choice) - self.ntsc_checkbox = ConfigCheckBox(self, "NTSC", Option.NTSC_MODE) - AmigaEnableBehavior(self.ntsc_checkbox) + + AmigaShowBehavior(self.accuracy_label) + AmigaShowBehavior(self.accuracy_choice) + AmigaShowBehavior(self.ntsc_checkbox) # if fs_uae_launcher.ui.get_screen_size()[1] > 768: # self.layout.add(heading_label, margin=10) # self.layout.add_spacer(0) - hori_layout = fsui.HorizontalLayout() - self.layout.add(hori_layout, fill=True) + self.model_title_layout = fsui.HorizontalLayout() + self.layout.add(self.model_title_layout, fill=True) - if settings.get(Option.PLATFORMS_FEATURE) == "1": + if openretro or settings.get(Option.PLATFORMS_FEATURE) == "1": heading_label = fsui.HeadingLabel(self, gettext("Platform & Model")) - hori_layout.add(heading_label, margin=10) + self.model_title_layout.add(heading_label, margin=10) # platform_group = ConfigWidgetFactory( # check=False, label=False).create(self, Option.PLATFORM) - # hori_layout.add(platform_group, margin_left=20) + # self.model_title_layout.add(platform_group, margin_left=20) # Adding label to get the vertical spacing correct. # heading_label = fsui.HeadingLabel(self, "") - # hori_layout.add(heading_label, margin=10) + # self.model_title_layout.add(heading_label, margin=10) else: heading_label = fsui.HeadingLabel(self, gettext("Amiga Model")) - hori_layout.add(heading_label, margin=10) + self.model_title_layout.add(heading_label, margin=10) - hori_layout.add_spacer(0, expand=True) - hori_layout.add(self.ntsc_checkbox, expand=False, + self.model_title_layout.add_spacer(0, expand=True) + self.model_title_layout.add(self.ntsc_checkbox, expand=False, margin_left=10, margin_right=10) - hori_layout.add_spacer(20) + self.model_title_layout.add_spacer(20) - hori_layout.add(self.accuracy_label, margin_right=10) - hori_layout.add(self.accuracy_choice, margin_right=10) - hori_layout.add(CustomConfigButton(self), margin_right=10) + self.model_title_layout.add(self.accuracy_label, margin_right=10) + self.model_title_layout.add(self.accuracy_choice, margin_right=10) + self.model_title_layout.add(CustomConfigButton(self), margin_right=10) self.model_layout = fsui.HorizontalLayout() @@ -89,7 +92,7 @@ self.model_layout.get_min_width = dummy_min_width self.layout.add(self.model_layout, fill=True) - if settings.get(Option.PLATFORMS_FEATURE) == "1": + if openretro or settings.get(Option.PLATFORMS_FEATURE) == "1": platform_group = ConfigWidgetFactory( check=False, label=False).create(self, Option.PLATFORM) self.model_layout.add(platform_group, margin=10) @@ -112,6 +115,7 @@ def on_platform_config(self, _): # Update layout after widgets have been shown/hidden. + self.model_title_layout.update() self.model_layout.update() def on_model_changed(self): diff -Nru fs-uae-arcade-2.9.6/launcher/ui/ConfigurationsBrowser.py fs-uae-arcade-2.9.7/launcher/ui/ConfigurationsBrowser.py --- fs-uae-arcade-2.9.6/launcher/ui/ConfigurationsBrowser.py 2017-05-25 10:07:54.000000000 +0000 +++ fs-uae-arcade-2.9.7/launcher/ui/ConfigurationsBrowser.py 2017-10-16 21:40:41.000000000 +0000 @@ -1,3 +1,4 @@ +from fsgs import openretro from fsgs.Database import Database from fsgs.platform import PlatformHandler from fsgs.util.gamenameutil import GameNameUtil @@ -88,7 +89,7 @@ else: sep = " \u00b7 " name = name.replace("\n", " \u00b7 ") - if platform == "Amiga": + if platform == "Amiga" and not openretro: platform = "" elif platform: platform = sep + PlatformHandler.get_platform_name(platform) diff -Nru fs-uae-arcade-2.9.6/launcher/ui/download.py fs-uae-arcade-2.9.7/launcher/ui/download.py --- fs-uae-arcade-2.9.6/launcher/ui/download.py 2017-05-25 10:07:54.000000000 +0000 +++ fs-uae-arcade-2.9.7/launcher/ui/download.py 2017-10-16 21:40:41.000000000 +0000 @@ -1,4 +1,4 @@ -from launcher.file_scanner import FileScanner +from launcher.filescanner import FileScanner from fsgs.download import Downloader from fsgs.FSGSDirectories import FSGSDirectories import fsui diff -Nru fs-uae-arcade-2.9.6/launcher/ui/imports/ImportTask.py fs-uae-arcade-2.9.7/launcher/ui/imports/ImportTask.py --- fs-uae-arcade-2.9.6/launcher/ui/imports/ImportTask.py 2017-05-25 10:07:54.000000000 +0000 +++ fs-uae-arcade-2.9.7/launcher/ui/imports/ImportTask.py 2017-10-16 21:40:41.000000000 +0000 @@ -8,7 +8,7 @@ from fsgs.amiga.rommanager import ROMManager from fsgs.FSGSDirectories import FSGSDirectories from ...launcher_signal import LauncherSignal -from fsgs.FileDatabase import FileDatabase +from fsgs.filedatabase import FileDatabase class ImportTask(threading.Thread): diff -Nru fs-uae-arcade-2.9.6/launcher/ui/newbutton.py fs-uae-arcade-2.9.7/launcher/ui/newbutton.py --- fs-uae-arcade-2.9.6/launcher/ui/newbutton.py 2017-05-25 10:07:54.000000000 +0000 +++ fs-uae-arcade-2.9.7/launcher/ui/newbutton.py 2017-10-16 21:40:41.000000000 +0000 @@ -1,4 +1,5 @@ from fsbc import settings +from fsgs import openretro from fsgs.option import Option from launcher.i18n import gettext from launcher.launcher_config import LauncherConfig @@ -16,7 +17,7 @@ @staticmethod def new_config(): - if settings.get(Option.PLATFORMS_FEATURE): + if openretro or settings.get(Option.PLATFORMS_FEATURE): platform_id = LauncherConfig.get(Option.PLATFORM) else: platform_id = None diff -Nru fs-uae-arcade-2.9.6/launcher/ui/removablemediagroup.py fs-uae-arcade-2.9.7/launcher/ui/removablemediagroup.py --- fs-uae-arcade-2.9.6/launcher/ui/removablemediagroup.py 2017-05-25 10:07:54.000000000 +0000 +++ fs-uae-arcade-2.9.7/launcher/ui/removablemediagroup.py 2017-10-16 21:40:41.000000000 +0000 @@ -11,7 +11,7 @@ class RemovableMediaGroup(FloppiesGroup): - def __init__(self, parent, drives): + def __init__(self, parent, drives, main=False): FloppiesGroup.__init__( self, parent, drives, removable_media=True) self.layout3 = fsui.HorizontalLayout() @@ -20,9 +20,16 @@ self.cd_mode = False self.__platform = "" self.__amiga_model = "" + self._main = main self._c64_model = "" self._zxs_model = "" + + self._ines_header_widget = INesHeaderWidget(self) + self._ines_header_widget.hide() + self.layout.add(self._ines_header_widget, fill=True) + self.update_media_type() + ConfigBehavior( self, [Option.PLATFORM, Option.AMIGA_MODEL, Option.C64_MODEL, Option.ZXS_MODEL]) @@ -67,6 +74,17 @@ else: self.set_mode(self.CARTRIDGE_MODE) + if self._main: + if self.__platform == Platform.NES: + # if self.selectors[1].is_visible(): + self.selectors[1].hide() + self._ines_header_widget.show() + else: + # if not self.selectors[1].is_visible(): + self.selectors[1].show() + self._ines_header_widget.hide() + self.layout.update() + def set_mode(self, mode): if self.mode == mode: return @@ -83,3 +101,32 @@ self.set_mode(self.CD_MODE) else: self.set_mode(self.FLOPPY_MODE) + + +class INesHeaderWidget(fsui.Panel): + + def __init__(self, parent): + fsui.Panel.__init__(self, parent) + self.layout = fsui.VerticalLayout() + hori_layout = fsui.HorizontalLayout() + self.layout.add(hori_layout, fill=True, margin=10) + + self.text_field = fsui.TextField(self, "") + self.text_field.on_changed = self.on_text_changed + self.text_field.disable() + hori_layout.add(self.text_field, expand=True) + + # self.help_button = HelpButton( + # self, "https://fs-uae.net/docs/options/nes-ines-header") + # hori_layout.add(self.help_button, margin_left=10) + + ConfigBehavior(self, [Option.NES_INES_HEADER]) + + def on_nes_ines_header_config(self, value): + if value != self.text_field.get_text(): + value = "iNES Header: " + value + self.text_field.set_text(value) + + def on_text_changed(self): + # LauncherConfig.set("nes_ines_header", self.text_field.get_text()) + pass diff -Nru fs-uae-arcade-2.9.6/launcher/ui/savebutton.py fs-uae-arcade-2.9.7/launcher/ui/savebutton.py --- fs-uae-arcade-2.9.6/launcher/ui/savebutton.py 2017-05-25 10:07:54.000000000 +0000 +++ fs-uae-arcade-2.9.7/launcher/ui/savebutton.py 2017-10-16 21:40:41.000000000 +0000 @@ -10,7 +10,7 @@ from launcher.ui.IconButton import IconButton from fsgs.Database import Database from fsgs.FSGSDirectories import FSGSDirectories -from fsgs.FileDatabase import FileDatabase +from fsgs.filedatabase import FileDatabase from fsgs.context import fsgs diff -Nru fs-uae-arcade-2.9.6/launcher/ui/statusbar/WarningsElement.py fs-uae-arcade-2.9.7/launcher/ui/statusbar/WarningsElement.py --- fs-uae-arcade-2.9.6/launcher/ui/statusbar/WarningsElement.py 2017-05-25 10:07:54.000000000 +0000 +++ fs-uae-arcade-2.9.7/launcher/ui/statusbar/WarningsElement.py 2017-10-16 21:40:41.000000000 +0000 @@ -5,7 +5,7 @@ from fsgs.context import fsgs from fsgs.plugins.plugin_manager import PluginManager from fsui import Image -from launcher.device_manager import DeviceManager +from launcher.devicemanager import DeviceManager from launcher.i18n import gettext from launcher.launcher_config import LauncherConfig from launcher.launcher_settings import LauncherSettings @@ -282,36 +282,36 @@ def add_option_warnings(self): if len(self.settings_config_keys): if len(self.settings_config_keys) == 1: - text = gettext("Config in Settings: {name}".format( + text = gettext("Config in settings: {name}".format( name=list(self.settings_config_keys)[0])) else: - text = gettext("Config Options in Settings") + text = gettext("Config options in settings") self.warnings.append((ERROR_LEVEL, text, "on_advanced_settings")) if len(self.custom_uae_config): if len(self.custom_uae_config) == 1: - text = gettext("Custom Option: {name}".format( + text = gettext("Custom option: {name}".format( name=list(self.custom_uae_config)[0])) else: text = gettext("Custom uae_ Options") self.warnings.append((WARNING_LEVEL, text, "on_uae_config")) if len(self.custom_config): if len(self.custom_config) == 1: - text = gettext("Custom Option: {name}".format( + text = gettext("Custom option: {name}".format( name=list(self.custom_config)[0])) else: - text = gettext("Custom Options") + text = gettext("Custom options") self.warnings.append((NOTICE_LEVEL, text, "on_uae_config")) def add_game_warnings(self): if is_warning(self.x_missing_files): if self.download_file: - text = gettext("Auto-Download") + text = gettext("Auto-download") self.warnings.append((NOTICE_LEVEL, text, "")) elif self.download_page: - text = gettext("Download Game") + text = gettext("Download game") self.warnings.append((WARNING_LEVEL, text, "on_download_page")) else: - text = gettext("Missing Game Files") + text = gettext("Missing game files") self.warnings.append((ERROR_LEVEL, text, "")) for name in ["variant_notice", "game_notice"]: diff -Nru fs-uae-arcade-2.9.6/launcher/ui/VariantsBrowser.py fs-uae-arcade-2.9.7/launcher/ui/VariantsBrowser.py --- fs-uae-arcade-2.9.6/launcher/ui/VariantsBrowser.py 2017-05-25 10:07:54.000000000 +0000 +++ fs-uae-arcade-2.9.7/launcher/ui/VariantsBrowser.py 2017-10-16 21:40:41.000000000 +0000 @@ -316,6 +316,8 @@ LauncherConfig.load_values(values, uuid=variant_uuid) + # print("--->", LauncherConfig.get("variant_uuid")) + # variant_rating = 0 # if item["work_rating"] is not None: # variant_rating = item["work_rating"] - 2 @@ -323,7 +325,7 @@ # variant_rating = item["like_rating"] # Config.set("__variant_rating", str(variant_rating)) - LauncherConfig.set("variant_uuid", variant_uuid) + # LauncherConfig.set("variant_uuid", variant_uuid) LauncherConfig.set("__changed", "0") LauncherConfig.set("__database", database_name) diff -Nru fs-uae-arcade-2.9.6/launcher/version.py fs-uae-arcade-2.9.7/launcher/version.py --- fs-uae-arcade-2.9.6/launcher/version.py 2017-05-25 10:07:54.000000000 +0000 +++ fs-uae-arcade-2.9.7/launcher/version.py 2017-10-16 21:40:41.000000000 +0000 @@ -1 +1 @@ -VERSION = "2.9.6dev" +VERSION = "2.9.7dev2" diff -Nru fs-uae-arcade-2.9.6/Makefile fs-uae-arcade-2.9.7/Makefile --- fs-uae-arcade-2.9.6/Makefile 2017-05-25 10:07:54.000000000 +0000 +++ fs-uae-arcade-2.9.7/Makefile 2017-10-16 21:40:41.000000000 +0000 @@ -189,3 +189,8 @@ rm -f Makefile rm -f setup.py +bindist: distdir + cd $(dist_dir)/dist/linux && fs-sdk-linux-x86-64 make + mv $(dist_dir)/fs-uae-launcher_*linux* . + rm -Rf $(dist_dir) + diff -Nru fs-uae-arcade-2.9.6/setup.py fs-uae-arcade-2.9.7/setup.py --- fs-uae-arcade-2.9.6/setup.py 2017-05-25 10:07:53.000000000 +0000 +++ fs-uae-arcade-2.9.7/setup.py 2017-10-16 21:40:40.000000000 +0000 @@ -20,7 +20,7 @@ name = "fs-uae-arcade" py_name = "fs_uae_arcade" tar_name = "fs-uae-arcade" -version = "2.9.6dev" +version = "2.9.7dev2" author = "Frode Solheim" author_email = "frode@fs-uae.net" package_map = { @@ -119,12 +119,12 @@ if sys.argv[1] == "build_exe": if sys.platform == "win32": setup_kwargs["executables"] = [ - Executable(s, base="Win32GUI", icon="icon/" + s + ".ico") + Executable(s, base="Win32GUI", icon="icon/" + s + ".ico") for s in scripts] else: setup_kwargs["executables"] = [Executable(s) for s in scripts] - setup_kwargs["version"] = "2.9.6" + setup_kwargs["version"] = "2.9.7" build_exe_options = { "includes": [ # "ctypes", @@ -174,7 +174,7 @@ if sys.platform == "darwin": setup_kwargs["name"] = title - setup_kwargs["version"] = "2.9.6" + setup_kwargs["version"] = "2.9.7" else: setup_kwargs["scripts"] = scripts diff -Nru fs-uae-arcade-2.9.6/VERSION fs-uae-arcade-2.9.7/VERSION --- fs-uae-arcade-2.9.6/VERSION 2017-05-25 10:07:54.000000000 +0000 +++ fs-uae-arcade-2.9.7/VERSION 2017-10-16 21:40:40.000000000 +0000 @@ -1 +1 @@ -2.9.6dev +2.9.7dev2