Repository: lutris/lutris

Issue:
PCSX2 Runner: Add config path/file options (Feature request)
It would be nice to have a way to specify a config file as for example some games run better with a multi-threaded microVU than others. It would also enable to have different window sizes set for those seeking square pixels as some NTSC games run at 640x448 and others use 512x448. Same goes for PAL region games. :slightly_smiling_face: 

The command line is: `PCSX2 --cfg=<str>`

I'm absolutely fine if you put it on low priority as probably only few people use PCSX2 anyways. :wink: 


Discussion:
Anything that involves adding an option for a command line flag is super easy to implement. And having a way to set the whole config can be really useful. I'm going to mark this for next release.
this should be tested.
I heard the call :smile: 

Doesn't work unfortunately. When launching a game with a configure ini file the button on the panel simply changes to "Lauching" and that's it.

I should've put in a little more effort before.

Not sure why --cfg doesn't work correctly. Running PCSX2 from a terminal with config file given, it doesn't write anything to it after exiting PCSX2 (1.4.0). May be a bug in the emulator itself.

However passing a folder with --cfgpath works as intended and all ini files are written correctly. Full reconfiguration of the emulator is needed if no files are present. This also enables to confgure the plugins separately, which --cfg doesn't.

The runner also said "--conf" instead of "--cfg" in line 47. :wink: 

I forked it and changed it to use --cfgpath but I get this error:
```
arguments.append("--cfgpath=%s", self.runner_config["config_path"])
TypeError: append() takes exactly one argument (2 given)
```
That's a far as I get with my non-coding skills. :stuck_out_tongue_winking_eye: 

Here's a link to my fork for reference:
https://github.com/7HEPOW/lutris/blob/master/lutris/runners/pcsx2.py

I guess having the arguments fixed, should solve all of the issues.
dammit, I forgot to push a fix for that before the release
Preferably --cfgpath should be the thing instead of --cfg :smile: 
Please add support for both options (--cfgpath for sets of config files and --cfg which is better suited for game specific small config file) and allow the user to choose which one to use depending on their requirements.
Added both options in this PR: https://github.com/lutris/lutris/pull/2758
I am not sure if I should open a new ticket or not, but attempting to launch a game with either `--cfg` and `--cfgpath` options set will throw this error:

```
  File "/path/to/lutris/lutris/runners/pcsx2.py", line 64, in play
    arguments.append("--cfgpath=%s", self.runner_config["config_path"])
TypeError: append() takes exactly one argument (2 given)
```

This happens because `arguments` is a actually Python `list`, so the `append` method invoked is the `list`'s `append` method, not some magical function (which I initially thought) that accepts an arbitrary number of arguments that will substitute placeholders like `%s` with the actual values. The substitution must be performed manually, so instead of commas, they should have been `%` symbols like so:

```
# line 62
arguments.append("--cfg=%s" % self.runner_config["config_file"])

# line 64
arguments.append("--cfgpath=%s" % self.runner_config["config_path"])
```

Or using `str.format` style:

```
# line 62
arguments.append("--cfg={}".format(self.runner_config["config_file"]))

# line 64
arguments.append("--cfgpath={}".format(self.runner_config["config_path"]))
```

Anyway, I prefer the `str.format` style, and I can submit a PR for this if you wish. Just tell me which style you prefer.



Resolving patch:
diff --git a/lutris/runners/pcsx2.py b/lutris/runners/pcsx2.py
--- a/lutris/runners/pcsx2.py
+++ b/lutris/runners/pcsx2.py
@@ -62,9 +62,9 @@ def play(self):
         if self.runner_config.get("nogui"):
             arguments.append("--nogui")
         if self.runner_config.get("config_file"):
-            arguments.append("--cfg=%s", self.runner_config["config_file"])
+            arguments.append("--cfg={}".format(self.runner_config["config_file"]))
         if self.runner_config.get("config_path"):
-            arguments.append("--cfgpath=%s", self.runner_config["config_path"])
+            arguments.append("--cfgpath={}".format(self.runner_config["config_path"]))
 
         iso = self.game_config.get("main_file") or ""
         if not system.path_exists(iso):


Test patch:
diff --git a/tests/runners/__init__.py b/tests/runners/__init__.py
new file mode 100644
diff --git a/tests/runners/test_pcsx2.py b/tests/runners/test_pcsx2.py
new file mode 100644
--- /dev/null
+++ b/tests/runners/test_pcsx2.py
@@ -0,0 +1,96 @@
+import unittest
+from unittest.mock import patch, MagicMock
+
+from lutris.runners.pcsx2 import pcsx2
+
+
+class TestPCSX2Runner(unittest.TestCase):
+
+    def setUp(self):
+        self.runner = pcsx2()
+
+    @patch('lutris.util.system.path_exists')
+    def test_play_iso_does_not_exist(self, mock_path_exists):
+        main_file = '/invalid/path/to/iso'
+        mock_path_exists.return_value = False
+        mock_config = MagicMock()
+        mock_config.game_config = {'main_file': main_file}
+        mock_config.runner_config = MagicMock()
+        self.runner.config = mock_config
+        expected = {'error': 'FILE_NOT_FOUND', 'file': main_file}
+        self.assertEqual(self.runner.play(), expected)
+
+    @patch('lutris.util.system.path_exists')
+    def test_play_fullscreen(self, mock_path_exists):
+        main_file = '/valid/path/to/iso'
+        mock_path_exists.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(), '--fullscreen', main_file]}
+        self.assertEqual(self.runner.play(), expected)
+
+    @patch('lutris.util.system.path_exists')
+    def test_play_full_boot(self, mock_path_exists):
+        main_file = '/valid/path/to/iso'
+        mock_path_exists.return_value = True
+        mock_config = MagicMock()
+        mock_config.game_config = {'main_file': main_file}
+        mock_config.runner_config = {'full_boot': True}
+        self.runner.config = mock_config
+        expected = {'command': [self.runner.get_executable(), '--fullboot', main_file]}
+        self.assertEqual(self.runner.play(), expected)
+
+    @patch('lutris.util.system.path_exists')
+    def test_play_nogui(self, mock_path_exists):
+        main_file = '/valid/path/to/iso'
+        mock_path_exists.return_value = True
+        mock_config = MagicMock()
+        mock_config.game_config = {'main_file': main_file}
+        mock_config.runner_config = {'nogui': True}
+        self.runner.config = mock_config
+        expected = {'command': [self.runner.get_executable(), '--nogui', main_file]}
+        self.assertEqual(self.runner.play(), expected)
+
+    @patch('lutris.util.system.path_exists')
+    def test_play_cfg_set(self, mock_path_exists):
+        main_file = '/valid/path/to/iso'
+        config_file = '/valid/path/to/cfg'
+        cfg_arg = '--cfg=' + config_file
+        mock_path_exists.return_value = True
+        mock_config = MagicMock()
+        mock_config.game_config = {'main_file': main_file}
+        mock_config.runner_config = {'config_file': config_file}
+        self.runner.config = mock_config
+        expected = {'command': [self.runner.get_executable(), cfg_arg, main_file]}
+        self.assertEqual(self.runner.play(), expected)
+
+    @patch('lutris.util.system.path_exists')
+    def test_play_cfgpath_set(self, mock_path_exists):
+        main_file = '/valid/path/to/iso'
+        config_path = '/valid/path/to/cfgpath'
+        cfgpath_arg = '--cfgpath=' + config_path
+        mock_path_exists.return_value = True
+        mock_config = MagicMock()
+        mock_config.game_config = {'main_file': main_file}
+        mock_config.runner_config = {'config_path': config_path}
+        self.runner.config = mock_config
+        expected = {'command': [self.runner.get_executable(), cfgpath_arg, main_file]}
+        self.assertEqual(self.runner.play(), expected)
+
+    @patch('lutris.util.system.path_exists')
+    def test_play(self, mock_path_exists):
+        main_file = '/valid/path/to/iso'
+        config_path = '/valid/path/to/cfgpath'
+        cfgpath_arg = '--cfgpath=' + config_path
+        mock_path_exists.return_value = True
+        mock_config = MagicMock()
+        mock_config.game_config = {'main_file': main_file}
+        mock_config.runner_config = {
+            'config_path': config_path, 'fullscreen': False, 'nogui': True,
+            'full_boot': True, 'config_file': '',
+        }
+        self.runner.config = mock_config
+        expected = {'command': [self.runner.get_executable(), '--fullboot', '--nogui', cfgpath_arg, main_file]}
+        self.assertEqual(self.runner.play(), expected)