Add Vita3K runner support

A Unit Test script has also been added to validate the runner options.

resolves #5357
This commit is contained in:
ItsAllAboutTheCode 2024-03-16 10:04:21 -05:00 committed by Mathieu Comandon
parent 71765daf4f
commit ed0f86e6f6
3 changed files with 153 additions and 0 deletions

View file

@ -33,6 +33,7 @@ __all__ = [
# Sony
"pcsx2",
"rpcs3",
"vita3k",
# Sega
"osmose",
"reicast",

101
lutris/runners/vita3k.py Normal file
View file

@ -0,0 +1,101 @@
import os
from gettext import gettext as _
from lutris.exceptions import MissingGameExecutableError
from lutris.runners.runner import Runner
from lutris.util import system
from lutris.util.log import logger
class MissingVitaTitleIDError(MissingGameExecutableError):
"""Raise when the Title ID field has not be supplied to the Vita runner game options"""
def __init__(self, message=None, *args, **kwargs):
if not message:
message = _("The Vita App has no Title ID set")
super().__init__(message, *args, **kwargs)
class vita3k(Runner):
human_name = _("Vita3K")
platforms = [_("Sony PlayStation Vita")]
description = _("Sony PlayStation Vita emulator")
runnable_alone = True
runner_executable = "vita3k/Vita3K-x86_64.AppImage"
flatpak_id = None
download_url = "https://github.com/Vita3K/Vita3K/releases/download/continuous/Vita3K-x86_64.AppImage"
game_options = [
{
"option": "main_file",
"type": "string",
"label": _("Title ID of Installed Application"),
"argument": "-r",
"help": _("Title ID of installed application. Eg.\"PCSG00042\". User installed apps are located in ux0:/app/<title-id>."),
}
]
runner_options = [
{
"option": "fullscreen",
"type": "bool",
"label": _("Fullscreen"),
"default": True,
"argument": "-F",
"help": _("Start the emulator in fullscreen mode."),
},
{
"option": "config",
"type": "file",
"label": _("Config location"),
"argument": "-c",
"help": _("Get a configuration file from a given location. If a filename is given, it must end with \".yml\", otherwise it will be assumed to be a directory."),
},
{
"option": "load-config",
"label": _("Load configuration file"),
"type": "bool",
"argument": "-f",
"help": _("If trues, informs the emualtor to load the config file from the \"Config location\" option.")
},
]
# Vita3k uses an AppImage and doesn't require the Lutris runtime.
system_options_override = [{"option": "disable_runtime", "default": True}]
def play(self):
"""Run the game."""
arguments = self.get_command()
# adds arguments from the supplied option dictionary to the arguments list
def append_args(option_dict, config):
for option in option_dict:
if option["option"] not in config:
continue
if option["type"] == "bool":
if self.runner_config.get(option["option"]):
if 'argument' in option:
arguments.append(option["argument"])
elif option["type"] == "choice":
if self.runner_config.get(option["option"]) != "off":
if 'argument' in option:
arguments.append(option["argument"])
arguments.append(config.get(option["option"]))
elif option["type"] in ("string", "file"):
if 'argument' in option:
arguments.append(option["argument"])
arguments.append(config.get(option["option"]))
else:
raise RuntimeError("Unhandled type %s" % option["type"])
# Append the runner arguments first, and game arguments afterwards
append_args(self.runner_options, self.runner_config)
title_id = self.game_config.get("main_file") or ""
if not title_id:
raise MissingVitaTitleIDError(_("The Vita App has no Title ID set"))
append_args(self.game_options, self.game_config)
return {"command": arguments}
@property
def game_path(self):
return self.game_config.get(self.entry_point_option, '')

View file

@ -0,0 +1,51 @@
import unittest
from unittest.mock import MagicMock, patch
from lutris.runners.vita3k import MissingVitaTitleIDError
from lutris.runners.vita3k import vita3k
class TestVita3kRunner(unittest.TestCase):
def setUp(self):
self.runner = vita3k()
self.runner.get_executable = lambda: "vita3k"
self.runner.get_command = lambda: ["vita3k"]
@patch("os.path.isfile")
def test_empty_title_id(self, mock_isfile):
main_file = ""
mock_isfile.return_value = True
mock_config = MagicMock()
mock_config.game_config = {"main_file": main_file}
mock_config.runner_config = MagicMock()
self.runner.config = mock_config
with self.assertRaises(MissingVitaTitleIDError) as cm:
self.runner.play()
@patch("lutris.util.system.path_exists")
@patch("os.path.isfile")
def test_play_fullscreen(self, mock_path_exists, mock_isfile):
main_file = "PCSG00042"
mock_path_exists.return_value = True
mock_isfile.return_value = True
mock_config = MagicMock()
mock_config.game_config = {"main_file": main_file}
mock_config.runner_config = {"fullscreen": True}
self.runner.config = mock_config
expected = {"command": [self.runner.get_executable(), "-F", "-r", main_file]}
self.assertEqual(self.runner.play(), expected)
@patch("lutris.util.system.path_exists")
@patch("os.path.isfile")
def test_play(self, mock_path_exists, mock_isfile):
main_file = "/valid/path/to/iso"
mock_path_exists.return_value = True
mock_isfile.return_value = True
mock_config = MagicMock()
mock_config.game_config = {"main_file": main_file}
mock_config.runner_config = {
"fullscreen": False
}
self.runner.config = mock_config
expected = {"command": [self.runner.get_executable(), "-r", main_file]}
self.assertEqual(self.runner.play(), expected)