diff --git a/README.rst b/README.rst index 51c13e2..b69dff0 100644 --- a/README.rst +++ b/README.rst @@ -137,6 +137,7 @@ Currently, couple of wrapper modules are available: - cd32 - archive - savestate +- whdload plain ----- @@ -323,6 +324,10 @@ Options used: * ``wrapper_whdload_base`` (required) path to the whdload base system. Usually it's minimal system containing at least whdload executables in C, and config in S. Read on below for further details. +* ``wrapper_whdload_options`` (optional) this option will replace the line in + ``s:whdload-startup`` with specific ``whdload`` options for certain slave. + For reference look at WHDLoad documentation and/or on ``s:WHDLoad.prefs``. + Note, that ``Slave=`` option must not be used. * ``wrapper_archive`` (optional) path to the whdload archive, defaults to same name as configuration file with some detected archive extension. Note, that name is case sensitive @@ -336,19 +341,69 @@ internet). Base image ~~~~~~~~~~ -To make it work, first the minimal system archive need to be prepared. There -are few dependences to be included in such small system: +To make it work, first the absolute minimal image need to contain following +structure: +.. code:: + . + ├── C + │   ├── DIC + │   ├── Execute + │   ├── Patcher + │   ├── RawDIC + │   ├── SetPatch + │   ├── WHDLoad + │   └── WHDLoadCD32 + └── S + ├── startup-sequence + └── WHDLoad.prefs + +where the minimum dependences are: + +- ``Excecute`` from your copy of Workbench - `WHDLoad`_ 18.9 -- `uaequit`_ - `SetPatch`_ 43.6 -- ``Excecute``, ``Assign`` and whatever commands you'll be use in scripts from - your copy of Workbench + +and the ``S/startup-sequence`` should at east contain: + +.. code:: + + setpatch QUIET + + IF EXISTS S:whdload-startup + Execute S:whdload-startup + EndIF + +To leverage more pleasant UX, additionally those bits should be installed (or - +copied into base image filesystem): + +- ``Assign`` and whatever commands you'll be use in scripts from your copy of +- `uaequit`_ - this will allow to quit emulator, after quiting game + Workbench - `kgiconload`_ - tool for reading icon and executing *default tool* with optionally defined tool types as parameters (in this case: WHDLoad) - `SKick`_ optionally - for kickstart relocations. Also images of corresponding kickstart ROM images will be needed. + +and then ``s/startup-sequence`` might looks a follows: + +.. code:: + + Assign >NIL: ENV: RAM: + Assign >NIL: T: RAM: + + setpatch QUIET + + IF EXISTS S:whdload-startup + Execute S:whdload-startup + EndIF + + C:UAEquit + +Creating base image archive +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + Now, the tree for the minimal image could look like that: .. code:: @@ -369,7 +424,9 @@ Now, the tree for the minimal image could look like that: └── WHDLoad.prefs to use relocation tables you'll need to place ``Kickstarts`` drawer into Devs -drawer, so it'll looks like this: +drawer. Also keep in mind, that corresponding kickstart rom images need to be +placed there as well, otherwise it may or may not work. Structure looks like +this: .. code:: . @@ -421,7 +478,7 @@ tar with different compressions: ``tar Jcf /tmp/base.tar.xz .``, ``tar zcf /tmp/base.tgz .``, ``tar jcf /tmp/base.tar.bz2 .``. It should work with all mentioned at the beginning of this document archivers. -Starting point is in ``S/startup-sequence`` file, where eventually +Starting point is in ``S/startup-sequence`` file, where eventually ``S/whdload-startup`` is executed, which will be created by fs-uae-warpper before execution by fs-uae. diff --git a/fs_uae_wrapper/base.py b/fs_uae_wrapper/base.py index 2a08a7f..77c8011 100644 --- a/fs_uae_wrapper/base.py +++ b/fs_uae_wrapper/base.py @@ -82,7 +82,7 @@ class Base(object): """execute fs-uae""" curdir = os.path.abspath('.') os.chdir(self.dir) - utils.run_command(['fs-uae'] + self.fsuae_options.list()) + utils.run_command(['fs-uae', *self.fsuae_options.list()]) os.chdir(curdir) return True diff --git a/fs_uae_wrapper/file_archive.py b/fs_uae_wrapper/file_archive.py index 21ef680..e1639f0 100644 --- a/fs_uae_wrapper/file_archive.py +++ b/fs_uae_wrapper/file_archive.py @@ -11,8 +11,8 @@ from fs_uae_wrapper import path class Archive(object): """Base class for archive support""" - ADD = ['a'] - EXTRACT = ['x'] + ADD = ('a',) + EXTRACT = ('x',) ARCH = 'false' def __init__(self): @@ -27,8 +27,8 @@ class Archive(object): files = files if files else ['.'] logging.debug("Calling `%s %s %s %s'.", self._compress, " ".join(self.ADD), arch_name, " ".join(files)) - result = subprocess.call([self._compress] + self.ADD + [arch_name] - + files) + result = subprocess.call([self._compress, *self.ADD, arch_name, + *files]) if result != 0: logging.error("Unable to create archive `%s'.", arch_name) return False @@ -44,8 +44,7 @@ class Archive(object): logging.debug("Calling `%s %s %s'.", self._compress, " ".join(self.ADD), arch_name) - result = subprocess.call([self._decompress] + self.EXTRACT + - [arch_name]) + result = subprocess.call([self._decompress, *self.EXTRACT, arch_name]) if result != 0: logging.error("Unable to extract archive `%s'.", arch_name) return False @@ -53,16 +52,16 @@ class Archive(object): class TarArchive(Archive): - ADD = ['cf'] - EXTRACT = ['xf'] + ADD = ('cf',) + EXTRACT = ('xf',) ARCH = 'tar' def create(self, arch_name, files=None): files = files if files else sorted(os.listdir('.')) logging.debug("Calling `%s %s %s %s'.", self._compress, " ".join(self.ADD), arch_name, " ".join(files)) - result = subprocess.call([self._compress] + self.ADD + [arch_name] + - files) + result = subprocess.call([self._compress, *self.ADD, arch_name, + *files]) if result != 0: logging.error("Unable to create archive `%s'.", arch_name) return False @@ -70,15 +69,15 @@ class TarArchive(Archive): class TarGzipArchive(TarArchive): - ADD = ['zcf'] + ADD = ('zcf',) class TarBzip2Archive(TarArchive): - ADD = ['jcf'] + ADD = ('jcf',) class TarXzArchive(TarArchive): - ADD = ['Jcf'] + ADD = ('Jcf',) class LhaArchive(Archive): @@ -86,8 +85,8 @@ class LhaArchive(Archive): class ZipArchive(Archive): - ADD = ['a', '-tzip'] - ARCH = ['7z', 'zip'] + ADD = ('a', '-tzip') + ARCH = ('7z', 'zip') def __init__(self): super(ZipArchive, self).__init__() @@ -102,7 +101,7 @@ class SevenZArchive(Archive): class LzxArchive(Archive): - EXTRACT = ['-x'] + EXTRACT = ('-x',) ARCH = 'unlzx' @classmethod @@ -113,7 +112,7 @@ class LzxArchive(Archive): class RarArchive(Archive): - ARCH = ['rar', 'unrar'] + ARCH = ('rar', 'unrar') def create(self, arch_name, files=None): files = files if files else sorted(os.listdir('.')) @@ -124,8 +123,8 @@ class RarArchive(Archive): logging.debug("Calling `%s %s %s %s'.", self._compress, " ".join(self.ADD), arch_name, " ".join(files)) - result = subprocess.call([self._compress] + self.ADD + [arch_name] + - files) + result = subprocess.call([self._compress, *self.ADD, arch_name, + *files]) if result != 0: logging.error("Unable to create archive `%s'.", arch_name) return False @@ -134,7 +133,7 @@ class RarArchive(Archive): class Archivers(object): """Archivers class""" - archivers = [{'arch': TarArchive, 'name': 'tar', 'ext': ['tar']}, + archivers = ({'arch': TarArchive, 'name': 'tar', 'ext': ['tar']}, {'arch': TarGzipArchive, 'name': 'tgz', 'ext': ['tar.gz', 'tgz']}, {'arch': TarBzip2Archive, 'name': 'tar.bz2', @@ -144,7 +143,7 @@ class Archivers(object): {'arch': SevenZArchive, 'name': '7z', 'ext': ['7z']}, {'arch': ZipArchive, 'name': 'zip', 'ext': ['zip']}, {'arch': LhaArchive, 'name': 'lha', 'ext': ['lha', 'lzh']}, - {'arch': LzxArchive, 'name': 'lzx', 'ext': ['lzx']}] + {'arch': LzxArchive, 'name': 'lzx', 'ext': ['lzx']}) @classmethod def get(cls, extension): diff --git a/fs_uae_wrapper/plain.py b/fs_uae_wrapper/plain.py index 2310534..2a5974b 100644 --- a/fs_uae_wrapper/plain.py +++ b/fs_uae_wrapper/plain.py @@ -16,8 +16,8 @@ class Wrapper(base.Base): def _run_emulator(self): """execute fs-uae""" - utils.run_command(['fs-uae'] + [self.conf_file] + - self.fsuae_options.list()) + utils.run_command(['fs-uae', self.conf_file, + *self.fsuae_options.list()]) def clean(self): """Do the cleanup. Here - just do nothing""" diff --git a/fs_uae_wrapper/whdload.py b/fs_uae_wrapper/whdload.py index 8ebf3b6..59c3d73 100644 --- a/fs_uae_wrapper/whdload.py +++ b/fs_uae_wrapper/whdload.py @@ -80,12 +80,20 @@ class Wrapper(base.ArchiveBase): # find slave name slave_fname = None slave_path = None + case_insensitvie_map = {} + + # build case insensitive map of paths and find the slave file + for root, dirnames, fnames in os.walk('.'): + for dirname in dirnames: + full_path = os.path.normpath(os.path.join(root, dirname)) + case_insensitvie_map[full_path.lower()] = full_path - for root, dirs, fnames in os.walk('.'): for fname in fnames: - if fname.lower().endswith('.slave'): + full_path = os.path.normpath(os.path.join(root, fname)) + case_insensitvie_map[full_path.lower()] = full_path + if not slave_fname and fname.lower().endswith('.slave'): slave_path, slave_fname = os.path.normpath(root), fname - break + if slave_fname is None: logging.error("Cannot find .slave file in archive.") return False @@ -103,10 +111,26 @@ class Wrapper(base.ArchiveBase): "archive.", slave_fname) return False - # Write startup file - with open("S/whdload-startup", "w") as fobj: - fobj.write(f"cd {slave_path}\n") - fobj.write(f"C:kgiconload {icon_fname}\n") + # find proper way to handle slave + # 1. check if there are user provided params + contents = f"cd {slave_path}\n" + if self.fsuae_options.get('wrapper_whdload_options'): + contents = (f"{contents}" + f"C:whdload " + f"{self.fsuae_options['wrapper_whdload_options']} " + f"Slave={slave_fname}\n") + else: + # no params, find if kgiconload is available + if case_insensitvie_map.get('c/kgiconload'): + contents = f"{contents}C:kgiconload {icon_fname}\n" + else: + # if not, just add common defaults + contents = (f"{contents}C:whdload Preload " + f"Slave={slave_fname}\n") + + fname = os.path.join(case_insensitvie_map.get('s'), 'whdload-startup') + with open(fname, "w") as fobj: + fobj.write(contents) os.chdir(curdir) return True diff --git a/tests/test_whdload.py b/tests/test_whdload.py index 7afa437..9d2e007 100644 --- a/tests/test_whdload.py +++ b/tests/test_whdload.py @@ -176,15 +176,58 @@ class TestWHDLoad(TestCase): wrapper = whdload.Wrapper('Config.fs-uae', utils.CmdOption(), {}) self.assertFalse(wrapper._find_slave()) - @mock.patch('builtins.open') @mock.patch('os.listdir') @mock.patch('os.walk') @mock.patch('os.chdir') - def test_find_slave_success(self, chdir, walk, listdir, bopen): + def test_find_slave_success(self, chdir, walk, listdir): contents = ('foo', 'bar', 'baz.slave', 'baz.info') - walk.return_value = [(".", ('game'), ()), + _open = mock.mock_open() + walk.return_value = [(".", ('C', 'S', 'game'), ()), + ('./C', (), ('Assign', 'kgiconload')), + ('./S', (), ()), ('./game', (), contents)] listdir.return_value = contents wrapper = whdload.Wrapper('Config.fs-uae', utils.CmdOption(), {}) - self.assertTrue(wrapper._find_slave()) - bopen.assert_called_once() + with mock.patch('builtins.open', _open): + self.assertTrue(wrapper._find_slave()) + handle = _open() + handle.write.assert_called_once_with('cd game\n' + 'C:kgiconload baz.info\n') + + @mock.patch('os.listdir') + @mock.patch('os.walk') + @mock.patch('os.chdir') + def test_find_slave_minial(self, chdir, walk, listdir): + contents = ('foo', 'bar', 'baz.slave', 'baz.info') + _open = mock.mock_open() + walk.return_value = [(".", ('C', 'S', 'game'), ()), + ('./C', (), ('Assign', 'WHDLoad')), + ('./S', (), ()), + ('./game', (), contents)] + listdir.return_value = contents + wrapper = whdload.Wrapper('Config.fs-uae', utils.CmdOption(), {}) + with mock.patch('builtins.open', _open): + self.assertTrue(wrapper._find_slave()) + handle = _open() + handle.write.assert_called_once_with('cd game\nC:whdload Preload ' + 'Slave=baz.slave\n') + + @mock.patch('os.listdir') + @mock.patch('os.walk') + @mock.patch('os.chdir') + def test_find_custom_options(self, chdir, walk, listdir): + contents = ('foo', 'bar', 'baz.slave', 'baz.info') + _open = mock.mock_open() + walk.return_value = [(".", ('C', 'S', 'game'), ()), + ('./C', (), ('Assign', 'WHDLoad')), + ('./S', (), ()), + ('./game', (), contents)] + listdir.return_value = contents + wrapper = whdload.Wrapper('Config.fs-uae', utils.CmdOption(), {}) + whdl_opts = 'Preload SplashDelay=0 MMU PAL' + wrapper.fsuae_options['wrapper_whdload_options'] = whdl_opts + with mock.patch('builtins.open', _open): + self.assertTrue(wrapper._find_slave()) + handle = _open() + handle.write.assert_called_once_with(f'cd game\nC:whdload {whdl_opts} ' + 'Slave=baz.slave\n')