Beispiel #1
0
def calculate_whdload_args(archive_path: str) -> str:
    """
    This function, as it is currently written, only works if there
    is an .info with the same name as the .slave file. In theory, they
    could be different since the .info file contains a slave=... tool type.
    """
    archive = Archive(archive_path)
    slave_args = {}
    lower_to_name = {}
    for path in archive.list_files():
        path_lower = path.lower()
        if path_lower.rsplit("#/", 1)[1] == "s/startup-sequence":
            logging.info("[WHDLOAD] Found Startup-Sequence, assuming "
                         "non-WHDLoad archive")
            return ""
        lower_to_name[path_lower] = path
    for path in lower_to_name.values():
        name = os.path.basename(path)
        name_lower = name.lower()
        if name_lower.endswith(".info"):
            try:
                args = read_whdload_args_from_info_stream(archive.open(path))
                args = strip_whdload_slave_prefix(args)
            except Exception as e:
                traceback.print_exc()
                logging.warning("[WHDLOAD] WARNING: Error reading args: %s",
                                repr(e))
            else:
                if args:
                    archive_name = path.rsplit("#/", 1)[1]
                    logging.debug("[WHDLOAD] {} => {}".format(
                        archive_name, " ".join(args)))
                    # EmeraldMines_v1.0_CD.lha contains \ instead of / ???
                    archive_name = archive_name.replace(
                        "EmeraldMinesCD%5c", "EmeraldMinesCD/")
                    slave_args[archive_name] = args
    if len(slave_args) == 0:
        return ""
    if len(slave_args) > 1:
        logging.debug("[WHDLOAD] Multiple WHDLoad icons found")
        # See if we have a hardcoded primary icon for this game
        for icon, args in slave_args.items():
            if icon.lower() in primary_icons:
                logging.debug("[WHDLOAD] Choosing %s as primary icon", icon)
                return fix_whdload_args(args)
        # Try to the main icon by comparing icon name to directory name
        for icon, args in slave_args.items():
            icon_lower = icon.lower()
            parts = icon_lower.split("/")
            if len(parts) == 2:
                if parts[0] + ".info" == parts[1]:
                    logging.debug("[WHDLOAD] Assuming %s as primary icon",
                                  icon)
                    return fix_whdload_args(args)
        # Giving up...
        print([x.lower() for x in slave_args.keys()])
        raise Exception("Multiple icons found, couldn't decide on one")
    return fix_whdload_args(slave_args.popitem()[1])
    def decrypt_archive_rom(cls, archive, path, file=None):
        print("decrypt_archive_rom", path)
        result = {}
        f = archive.open(path)
        data = f.read(len("AMIROMTYPE1"))
        out_data = b""
        sha1 = hashlib.sha1()
        if data != b"AMIROMTYPE1":
            # not encrypted, write raw data
            sha1.update(data)
            out_data += data
            data = f.read()
            sha1.update(data)
            out_data += data
            result["data"] = out_data
            result["sha1"] = sha1.hexdigest()
            cls.patch_rom(result)
            if file is not None:
                file.write(result["data"])
            return result

        key_path = archive.join(archive.dirname(path), "rom.key")
        key_archive = Archive(key_path)
        try:
            f2 = key_archive.open(key_path)
        except Exception:
            raise Exception("did not find rom.key to decrypt ROM with")
        print("using key file", key_path)
        # if not os.path.exists(key_file):
        #     raise Exception("did not find rom.key to decrypt ROM with")
        key_data = f2.read()
        f2.close()

        while True:
            data = f.read(len(key_data))
            if not data:
                break
            dec = []
            for i in range(len(data)):
                dec.append(data[i] ^ key_data[i])
            dec_data = bytes(dec)
            # if file is not None:
            #     file.write(dec_data)
            out_data += dec_data
            if sha1 is not None:
                sha1.update(dec_data)
        result["data"] = out_data
        result["sha1"] = sha1.hexdigest()
        cls.patch_rom(result)
        if file is not None:
            file.write(result["data"])
        return result
    def insert_multiple_floppies(self, insert_paths):
        paths = []
        for path in insert_paths:
            embedded_files = []
            if path.endswith(".zip"):
                archive = Archive(path)
                files = archive.list_files()
                for file in files:
                    name, ext = os.path.splitext(file)
                    # FIXME: get list of floppy extensions from a central
                    # place
                    if ext in [".adf", ".ipf"]:
                        embedded_files.append(file)
            if len(embedded_files) > 0:
                embedded_files.sort()
                print("found embedded floppy images:")
                print(embedded_files)
                for file in embedded_files:
                    paths.append(file)
            else:
                paths.append(path)

        default_dir = FSGSDirectories.get_floppies_dir()
        checksum_tool = ChecksumTool()
        for i, path in enumerate(paths):
            sha1 = checksum_tool.checksum(path)
            path = Paths.contract_path(path, default_dir)

            if i < 4:
                self.set_config([
                    ("floppy_drive_{0}".format(i), path),
                    ("x_floppy_drive_{0}_sha1".format(i), sha1),
                ])
            self.set_config([
                ("floppy_image_{0}".format(i), path),
                ("x_floppy_image_{0}_sha1".format(i), sha1),
            ])

        # blank the rest of the drives
        for i in range(len(paths), 4):
            self.set_config([
                ("floppy_drive_{0}".format(i), ""),
                ("x_floppy_drive_{0}_sha1".format(i), ""),
            ])
        # blank the rest of the image list
        for i in range(len(paths), 20):
            self.set_config([
                ("floppy_image_{0}".format(i), ""),
                ("x_floppy_image_{0}_sha1".format(i), ""),
            ])
    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 upload_prefix(self, prefix):
        self.stop_check()
        result = self.upload_check(prefix)
        print(len(result))
        for k in range(0, len(result), 20):
            self.stop_check()

            sha1 = result[k:k + 20]
            path = fsgs.file.find_by_sha1(bytes_to_hex(sha1))
            if not path:
                continue
            try:
                # this is done to properly handle encrypted ROMs
                archive = Archive(path)
                # FIXME: Use Archive.open to get support for filter functions
                # FIXME: Also use stream api, do not buffer entire file
                data = ROMManager.decrypt_archive_rom(archive, path)["data"]
            except Exception:
                traceback.print_exc()
                uri = "sha1://{0}".format(bytes_to_hex(sha1))
                print(uri)
                try:
                    input_stream = fsgs.file.open(uri)
                    data = input_stream.read()
                except Exception:
                    continue
                assert not input_stream.read()

            print("uploading file of size ", len(data))

            # self.progressed(gettext("Verifying {name}").format(
            #                 name=bytes_to_hex(sha1)))
            self.progressed(
                gettext("Uploading {name}").format(name=bytes_to_hex(sha1)))
            import hashlib

            new_hash = hashlib.sha1(data).hexdigest()
            print(new_hash, "vs", bytes_to_hex(sha1))
            if hashlib.sha1(data).hexdigest() != bytes_to_hex(sha1):
                print("hash mismatch, probably Cloanto ROM...")
                continue

            retry_seconds = 1
            while True:
                try:
                    self.client.post("/api/locker-upload-file", data=data)
                except OGDClient.NonRetryableHTTPError as e:
                    raise e
                except Exception:
                    traceback.print_exc()
                    self.progressed(
                        gettext("Re-trying in {0} seconds...").format(
                            retry_seconds))
                    for _ in range(retry_seconds):
                        self.stop_check()
                        time.sleep(1.0)
                    retry_seconds = min(retry_seconds * 2, 60 * 10)
                else:
                    break
 def checksum(self, path):
     print("[CHECKSUM]", repr(path))
     archive = Archive(path)
     if os.path.exists(path):
         size = os.path.getsize(path)
         if size == 0:
             # Either a real 0-byte file or a device node on a BSD
             # system (could be large). To reliably get the size we could
             # use ioctl, but we simply return the checksum for a 0-byte
             # file in either case.
             return ZERO_SHA1
     s = hashlib.sha1()
     f = archive.open(path)
     while True:
         data = f.read(65536)
         if not data:
             break
         s.update(data)
     return s.hexdigest()
 def download_game_file_archive(self, url):
     print("\ndownload_game_file_archive", url)
     archive_path = Downloader.cache_file_from_url(url)
     archive = Archive(archive_path)
     archive_files = archive.list_files()
     print(archive_files)
     for name in archive_files:
         print(name)
         ifs = archive.open(name)
         data = ifs.read()
         Downloader.cache_data(data)
     if len(archive_files) == 0:
         # might not be an archive then
         with open(archive_path, "rb") as f:
             data = f.read()
         Downloader.cache_data(data)
     # the downloaded archive is no longer needed, now that we have
     # extracted all the files
     os.remove(archive_path)
     print("\n")
 def convert_uri(cls, uri, prefer_path=False):
     if uri.startswith("sha1://"):
         return cls.open_sha1_uri(uri)
     elif uri.startswith("db://"):
         # old name for sha1://
         return cls.open_sha1_uri(uri)
     elif is_http_url(uri):
         return cls.open_url(uri)
     elif uri.startswith("locker://"):
         return open_locker_uri(uri)
     else:
         if uri.startswith("$"):
             uri = Paths.expand_path(uri)
         if prefer_path and os.path.exists(uri):
             # return helper object so isinstance does not match with str
             return File(uri)
         return Archive(uri).open(uri)
 def expand_default_path(src, default_dir):
     if "://" in src:
         return src, None
     src = Paths.expand_path(src, default_dir)
     archive = Archive(src)
     # if not archive.exists(src):
     #     dirs = [default_dir]
     #     for dir in dirs:
     #         path = os.path.join(dir, src)
     #         print("checking", repr(path))
     #         archive = Archive(path)
     #         if archive.exists(path):
     #         #if os.path.exists(path):
     #             src = path
     #             break
     #     else:
     #         raise Exception("Cannot find path for " + repr(src))
     return src, archive
def decrypt_amiromtype1_stream(stream, data):
    # If we open a stream starting with AMIROMTYPE1, we assume that the file
    # has been opened via Archive and stored in the database with the
    # decrypted checksum.
    # FIXME: Better to implement this properly with archive filters...
    path = stream.archive_path
    archive = Archive(path)
    key_path = archive.join(archive.dirname(path), "rom.key")
    key_archive = Archive(key_path)
    try:
        f2 = key_archive.open(key_path)
    except Exception:
        raise Exception("Did not find rom.key to decrypt ROM with")
    print("Using key file", key_path)
    key_data = f2.read()
    f2.close()

    out_data = []
    result = {}
    f = BytesIO(data[len("AMIROMTYPE1"):] + stream.read())
    sha1_obj = hashlib.sha1()

    while True:
        data = f.read(len(key_data))
        if not data:
            break
        dec = []
        for i in range(len(data)):
            dec.append(data[i] ^ key_data[i])
        dec_data = bytes(dec)
        # if file is not None:
        #     file.write(dec_data)
        out_data.append(dec_data)
        # if sha1 is not None:
        sha1_obj.update(dec_data)
    result["data"] = b"".join(out_data)
    result["sha1"] = sha1_obj.hexdigest()
    # print(result)
    ROMManager.patch_rom(result)
    # if file is not None:
    #     file.write(result["data"])
    # return result
    return BytesIO(result["data"])
    def add_rom_to_database(cls, path, database, log_function=None):
        if log_function is None:
            log_function = print
        try:
            archive = Archive(path)
            rom = ROMManager.decrypt_archive_rom(archive, path)
        except Exception:
            import traceback

            traceback.print_exc()
            return
        try:
            st = os.stat(path)
        except:
            log_function("Error stat-ing file {0}".format(repr(path)))
            return
        size = st.st_size
        mtime = int(st.st_mtime)
        log_function('Adding ROM "{0}" to database (SHA-1: {1})'.format(
            path, rom["sha1"]))
        database.delete_file(path=path)
        database.add_file(path=path, sha1=rom["sha1"], mtime=mtime, size=size)
    def unpack_archive(self, path, destination):
        print("unpack", path, "to", destination)
        archive = Archive(path)
        print(archive)
        print(archive.get_handler())
        for entry in archive.list_files():
            if self.stop_flag:
                return

            print(entry)
            n = entry[len(path) + 2 :]
            amiga_rel_path = amiga_path_to_host_path(n)

            out_path = os.path.join(destination, amiga_rel_path)
            print("out path", out_path)

            if entry.endswith("/"):
                os.makedirs(out_path)
            else:
                if not os.path.exists(os.path.dirname(out_path)):
                    os.makedirs(os.path.dirname(out_path))
                f = archive.open(entry)
                with open(out_path, "wb") as out_f:
                    while True:
                        data = f.read(65536)
                        if not data:
                            break
                        out_f.write(data)
                # FIXME: Extract real timestamps from archive
                # FIXME: Real metadata from archive
                # noinspection SpellCheckingInspection
                metadata = [
                    "----rwed",
                    " ",
                    "2000-01-01 00:00:00.00",
                    " ",
                    "",
                    "\n",
                ]
                info = archive.getinfo(entry)
                if info.comment:
                    # print(info.comment)
                    # raise Exception("gnit")
                    metadata[4] = encode_file_comment(info.comment)
                with open(out_path + ".uaem", "wb") as out_file:
                    out_file.write("".join(metadata).encode("UTF-8"))
 def lookup_rom_from_src(src):
     parts = src.split(":", 1)
     if len(parts) == 2 and len(parts[0]) > 1:
         # src has a scheme (not a Windows drive letter). Assume
         # we can find this file.
         return src
     archive = Archive(src)
     if archive.exists(src):
         return src
     dirs = [self.fsgs.amiga.get_kickstarts_dir()]
     for dir_ in dirs:
         path = os.path.join(dir_, src)
         print("[ROM] Checking", repr(path))
         archive = Archive(path)
         if archive.exists(path):
             return path
     return None
Beispiel #14
0
    def run_config_or_game(cls):
        gscontext = FSGameSystemContext()
        config_path = None
        archive_path = None
        floppy_image_paths = []
        cdrom_image_paths = []
        config_uuid = None
        floppy_extensions = (".adf", ".ipf", ".dms", ".adz")
        cdrom_extensions = (".cue", ".iso")
        archive_extensions = (".zip", ".lha")

        # FIXME: replace argument "parsing" with use of argparse module
        # at some point

        last_arg = sys.argv[-1]
        file_ext = os.path.splitext(last_arg)[-1].lower()
        if file_ext == ".fs-uae":
            config_path = last_arg
        elif file_ext in archive_extensions:
            archive_path = last_arg
        # elif file_ext in floppy_extensions:
        #     floppy_image_paths = [last_arg]
        elif is_uuid(last_arg):
            config_uuid = last_arg.lower()
        for arg in sys.argv[1:]:
            if not arg.startswith("--"):
                _, ext = os.path.splitext(arg)
                if ext in floppy_extensions:
                    floppy_image_paths.append(arg)
                elif ext in cdrom_extensions:
                    cdrom_image_paths.append(arg)

        if config_path:
            print("[STARTUP] Config path given:", config_path)
            if not os.path.exists(config_path):
                print("[STARTUP] Config path does not exist", file=sys.stderr)
                return True
            LauncherConfig.load_file(config_path)
            gscontext.config.add_from_argv()
            return cls.run_config_directly(gscontext=gscontext)

        if archive_path:
            print("[STARTUP] Archive path given:", archive_path)
            if not os.path.exists(archive_path):
                print("[STARTUP] Archive path does not exist", file=sys.stderr)
                return True
            archive = Archive(os.path.realpath(archive_path))
            archive_name = os.path.basename(archive_path)
            # We want to exclude pure directory entries when checking for
            # archives with only floppies.
            arc_files = [
                x for x in archive.list_files() if not x.endswith("/")
            ]
            if all(
                    map(lambda f: f.lower().endswith(floppy_extensions),
                        arc_files)):
                print("[STARTUP] Archive contains floppy disk images only")
                floppy_image_paths = arc_files
            else:
                if cls.auto_detect_game:
                    # FIXME: Could also do this for floppy file archives.
                    archive_util = ArchiveUtil(archive_path)
                    archive_uuid = archive_util.create_variant_uuid()
                    print(
                        "[STARTUP] Try auto-detecting variant, uuid =",
                        archive_uuid,
                    )
                    if gscontext.load_game_variant(archive_uuid):
                        print("[STARTUP] Auto-detected variant", archive_uuid)
                        print("[STARTUP] Adding archive files to file index")
                        for archive_file in archive.list_files():
                            stream = archive.open(archive_file)
                            data = stream.read()
                            size = len(data)
                            sha1 = hashlib.sha1(data).hexdigest()
                            FileDatabase.add_static_file(archive_file,
                                                         size=size,
                                                         sha1=sha1)
                        gscontext.config.add_from_argv()
                        gscontext.config.set("__config_name", archive_name)
                        LauncherConfig.post_load_values(gscontext.config)
                        return cls.run_config_directly(gscontext=gscontext)

                values = whdload.generate_config_for_archive(archive_path)
                values["hard_drive_0"] = archive_path
                values.update(gscontext.config.config_from_argv())
                # archive_name, archive_ext = os.path.splitext(archive_name)
                values["__config_name"] = archive_name
                return cls.run_config_directly_with_values(values,
                                                           gscontext=gscontext)

        if floppy_image_paths:
            enum_paths = tuple(enumerate(floppy_image_paths))
            values = {}
            values.update(gscontext.config.config_from_argv())
            max_drives = int(values.get("floppy_drive_count", "4"))
            values.update({
                "floppy_drive_{0}".format(k): v
                for k, v in enum_paths[:max_drives]
            })
            values.update(
                {"floppy_image_{0}".format(k): v
                 for k, v in enum_paths[:20]})
            # FIXME: Generate a better config name for save dir?
            values["__config_name"] = "Default"
            return cls.run_config_directly_with_values(values,
                                                       gscontext=gscontext)

        if cdrom_image_paths:
            enum_paths = tuple(enumerate(cdrom_image_paths))
            values = {"amiga_model": "CD32"}
            values.update(gscontext.config.config_from_argv())
            max_drives = int(values.get("cdrom_drive_count", "1"))
            values.update({
                "cdrom_drive_{0}".format(k): v
                for k, v in enum_paths[:max_drives]
            })
            values.update(
                {"cdrom_image_{0}".format(k): v
                 for k, v in enum_paths[:20]})
            # FIXME: Generate a better config name for save dir?
            values["__config_name"] = "Default"
            return cls.run_config_directly_with_values(values,
                                                       gscontext=gscontext)

        if config_uuid:
            print("[STARTUP] Config uuid given:", config_uuid)
            variant_uuid = config_uuid
            # values = gscontext.game.set_from_variant_uuid(variant_uuid)
            if gscontext.load_game_variant(variant_uuid):
                print("[STARTUP] Loaded variant")
            else:
                print("[STARTUP] Could not load variant, try to load game")
                game_uuid = config_uuid
                variant_uuid = gscontext.find_preferred_game_variant(game_uuid)
                print("[STARTUP] Preferred variant:", variant_uuid)
                gscontext.load_game_variant(variant_uuid)
            gscontext.config.add_from_argv()
            LauncherConfig.post_load_values(gscontext.config)
            return cls.run_config_directly(gscontext=gscontext)
 def checksum_rom(self, path):
     print("[CHECKSUM] ROM:", repr(path))
     archive = Archive(path)
     return ROMManager.decrypt_archive_rom(archive, path)["sha1"]
Beispiel #16
0
 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_roms(self):
        print("LaunchHandler.prepare_roms")
        current_task.set_progress(gettext("Preparing kickstart ROMs..."))
        amiga_model = self.config.get("amiga_model", "A500")
        model_config = Amiga.get_model_config(amiga_model)

        roms = [("kickstart_file", model_config["kickstarts"])]
        if self.config["kickstart_ext_file"] or model_config["ext_roms"]:
            # not all Amigas have extended ROMs
            roms.append(("kickstart_ext_file", model_config["ext_roms"]))
        if amiga_model.lower() == "cd32/fmv":
            roms.append(("fvm_rom", [CD32_FMV_ROM]))

        if self.config["graphics_card"].lower().startswith("picasso-iv"):
            roms.append(("graphics_card_rom", [PICASSO_IV_74_ROM]))

        if self.config["accelerator"].lower() == "cyberstorm-ppc":
            roms.append(("accelerator_rom", ["cyberstormppc.rom"]))

        if self.config["freezer_cartridge"] == "action-replay-2":
            # Ideally, we would want to recognize ROMs based on zeroing the
            # first four bytes, but right now we simply recognize a common
            # additional version. freezer_cartridge_rom isn't a real option,
            # we just want to copy the rom file and let FS-UAE find it
            roms.append(
                (
                    "[freezer_cartridge]",
                    [
                        ACTION_REPLAY_MK_II_2_14_ROM.sha1,
                        ACTION_REPLAY_MK_II_2_14_MOD_ROM.sha1,
                    ],
                )
            )
        elif self.config["freezer_cartridge"] == "action-replay-3":
            roms.append(
                (
                    "[freezer_cartridge]",
                    [
                        ACTION_REPLAY_MK_III_3_17_ROM.sha1,
                        ACTION_REPLAY_MK_III_3_17_MOD_ROM.sha1,
                    ],
                )
            )

        use_temp_kickstarts_dir = False

        for config_key, default_roms in roms:
            print("[ROM]", config_key, default_roms)
            src = self.config[config_key]
            print("[ROM]", src)
            if not src:
                for sha1 in default_roms:
                    print("[ROM] Trying", sha1)
                    if is_sha1(sha1):
                        rom_src = self.fsgs.file.find_by_sha1(sha1)
                        if rom_src:
                            src = rom_src
                            print("[ROM] Found", rom_src)
                            break
                    else:
                        # roms_dir = FSGSDirectories.get_kickstarts_dir()
                        # src = os.path.join(roms_dir, sha1)
                        # if os.path.exists(src):
                        #     break
                        # loop up file in roms dir instead
                        src = sha1
            elif src == "internal":
                continue
            elif src:
                src = Paths.expand_path(src)
            if not src:
                raise TaskFailure(
                    gettext(
                        "Did not find required Kickstart or "
                        "ROM for {}. Wanted one of these files: {}".format(
                            config_key, repr(default_roms)
                        )
                    )
                )

            dest = os.path.join(self.temp_dir, os.path.basename(src))

            def lookup_rom_from_src(src):
                parts = src.split(":", 1)
                if len(parts) == 2 and len(parts[0]) > 1:
                    # src has a scheme (not a Windows drive letter). Assume
                    # we can find this file.
                    return src
                archive = Archive(src)
                if archive.exists(src):
                    return src
                dirs = [self.fsgs.amiga.get_kickstarts_dir()]
                for dir_ in dirs:
                    path = os.path.join(dir_, src)
                    print("[ROM] Checking", repr(path))
                    archive = Archive(path)
                    if archive.exists(path):
                        return path
                return None

            org_src = src
            src = lookup_rom_from_src(src)
            if not src and org_src == "cyberstormppc.rom":
                src = lookup_rom_from_src(
                    "ralphschmidt-cyberstorm-ppc-4471.rom"
                )
                if not src:
                    for (
                        dir_
                    ) in FSGSDirectories.get_amiga_forever_directories():
                        path = os.path.join(
                            dir_,
                            "Shared",
                            "rom",
                            "ralphschmidt-cyberstorm-ppc-4471.rom",
                        )
                        if os.path.exists(path):
                            src = path
                            print("[ROM] Found", path)
                            break
                        else:
                            print("[ROM] Trying", path)
            stream = None
            # FIXME: prepare_roms should be rewritten, it's kind of crap.
            # Rom patching and decryption should be handled differently. Should
            # use file database filters, and decryption via rom.key should only
            # be supported when using uncompressed files directly on disk.
            if not src or not os.path.exists(src):
                try:
                    stream = self.fsgs.file.open(src)
                    if stream is None:
                        raise FileNotFoundError(src)
                except FileNotFoundError:
                    raise TaskFailure(
                        gettext(
                            "Cannot find required ROM "
                            "file: {name}".format(name=repr(org_src))
                        )
                    )
            with open(dest, "wb") as f:
                if stream:
                    print("[ROM] From stream => {}".format(dest))
                    rom = {}
                    rom["data"] = stream.read()
                    rom["sha1"] = hashlib.sha1(rom["data"]).hexdigest()
                    ROMManager.patch_rom(rom)
                    f.write(rom["data"])
                else:
                    archive = Archive(src)
                    ROMManager.decrypt_archive_rom(archive, src, file=f)
                if use_temp_kickstarts_dir:
                    self.config[config_key] = os.path.basename(src)
                else:
                    self.config[config_key] = dest
        if use_temp_kickstarts_dir:
            self.config["kickstarts_dir"] = self.temp_dir
    def multi_select(cls, parent=None, *, config):
        default_dir = FSGSDirectories.get_floppies_dir()
        dialog = LauncherFilePicker(
            parent,
            gettext("Select multiple floppies"),
            "floppy",
            multiple=True,
        )
        if not dialog.show_modal():
            return
        original_paths = dialog.get_paths()
        original_paths.sort()
        paths = []
        for path in original_paths:
            path = Paths.get_real_case(path)
            embedded_files = []
            if path.endswith(".zip"):
                archive = Archive(path)
                files = archive.list_files()
                for file in files:
                    _name, ext = os.path.splitext(file)
                    # FIXME: get list of floppy extensions from a central
                    # place
                    if ext in [".adf", ".ipf"]:
                        embedded_files.append(file)
            if len(embedded_files) > 0:
                embedded_files.sort()
                print("found embedded floppy images:")
                print(embedded_files)
                for file in embedded_files:
                    paths.append(file)
            else:
                paths.append(path)

        checksum_tool = ChecksumTool(parent)
        for i, path in enumerate(paths):
            sha1 = checksum_tool.checksum(path)
            path = Paths.contract_path(
                path, default_dir, force_real_case=False
            )

            if i < 4:
                config.set_multiple(
                    [
                        ("floppy_drive_{0}".format(i), path),
                        ("x_floppy_drive_{0}_sha1".format(i), sha1),
                    ]
                )
            config.set_multiple(
                [
                    ("floppy_image_{0}".format(i), path),
                    ("x_floppy_image_{0}_sha1".format(i), sha1),
                ]
            )

        # blank the rest of the drives
        for i in range(len(paths), 4):
            config.set_multiple(
                [
                    ("floppy_drive_{0}".format(i), ""),
                    ("x_floppy_drive_{0}_sha1".format(i), ""),
                ]
            )
        # blank the rest of the image list
        for i in range(len(paths), 20):
            config.set_multiple(
                [
                    ("floppy_image_{0}".format(i), ""),
                    ("x_floppy_image_{0}_sha1".format(i), ""),
                ]
            )