1# FIXME: send fsgs as parameter to ValueConfigLoader instead
2from fsgs.context import fsgs
3import os
4import json
5from fsbc.paths import Paths
6from fsgs.amiga.amiga import Amiga
7from fsgs.filedatabase import FileDatabase
8from fsgs.network import openretro_url_prefix
9from fsgs.option import Option
10from fsgs.amiga import whdload
11
12
13class ValueConfigLoader(object):
14    DB_VERSION_MAX = 1
15
16    def __init__(self, uuid=""):
17        self.config = {}
18        self.options = {}
19        self.viewport = []
20        self.values = {}
21        self.uuid = uuid
22        self._file_list = None
23        self._cue_sheets = None
24        if uuid:
25            self.options["database_url"] = "{0}/game/{1}".format(
26                openretro_url_prefix(), uuid
27            )
28
29    def get_config(self):
30        return self.config.copy()
31
32    def load_values(self, values):
33        if values.get("db_version"):
34            try:
35                version = int(values.get("db_version"))
36            except ValueError:
37                version = 10000
38            if version > self.DB_VERSION_MAX:
39                self.config["__config_name"] = (
40                    "Unsupported Database "
41                    "Version (Please upgrade FS-UAE Launcher)"
42                )
43                return self.get_config()
44
45        self.values = values
46        # from pprint import pprint
47        # pprint(values)
48
49        cd_based = False
50        amiga_model = "A500"
51        platform = self.values.get("platform", "Amiga")
52        self.options["platform"] = platform.lower()
53
54        platform = platform.lower()
55        if platform == "amiga":
56            pass
57        elif platform == "cd32":
58            amiga_model = "CD32"
59            cd_based = True
60            self.options["joystick_port_1_mode"] = "cd32 gamepad"
61        elif platform == "cdtv":
62            amiga_model = "CDTV"
63            cd_based = True
64        else:
65            raise Exception("unknown platform")
66        self.config["amiga_model"] = amiga_model
67
68        # self.config["x_game_uuid"] = self.values["uuid"]
69
70        self.viewport = []
71        # game_node = self.root.find("game")
72        # if game_node is not None:
73        #     game_uuid = game_node.get("uuid", "")
74        #     self.config["x_game_uuid"] = game_uuid
75        #     self.load_game_info(game_uuid)
76
77        # self.load_options())
78
79        sort_list = []
80        for key in values:
81            if key == "chip_memory":
82                # chip memory is checked at the end because of support for
83                # the 1024+ value, and others keys can change amiga_model..
84                sort_list.append(("x_chip_memory", key))
85            else:
86                sort_list.append((key, key))
87        sort_list.sort()
88
89        for dummy, key in sort_list:
90            self.load_option(key, values[key])
91
92        if self.viewport:
93            self.options["viewport"] = ", ".join(self.viewport)
94
95        if cd_based:
96            if self.check_all_files():
97                self.load_cdroms()
98        else:
99            self.load_floppies()
100
101        self.load_hard_drives()
102
103        # if not self.check_all_files():
104        #     print(" -- some files are missing --")
105        #     self.options["x_missing_files"] = "1"
106
107        download_page = values.get("download_page", "")
108        if download_page:
109            self.options["download_page"] = download_page
110        download_file = values.get("download_file", "")
111        if download_file:
112            self.options["download_file"] = download_file
113        download_terms = values.get("download_terms", "")
114        if download_terms:
115            self.options["download_terms"] = download_terms
116        download_notice = values.get("download_notice", "")
117        if download_notice:
118            self.options["download_notice"] = download_notice
119
120        # for now, just copy all options to config without checking
121        for key, value in self.options.items():
122            self.config[key] = value
123
124        if self.config.get(Option.X_WHDLOAD_ARGS):
125            whdload.override_config(self.config)
126
127        if (
128            self.config.get("amiga_model", "") == "A500"
129            and self.config.get("slow_memory") == "0"
130        ):
131            self.config["amiga_model"] = "A500/512K"
132            self.config["slow_memory"] = ""
133
134        self.set_name_and_uuid()
135        self.contract_paths()
136        return self.get_config()
137
138    def check_all_files(self):
139        file_list_json = self.values.get("file_list", "[]")
140        file_list = json.loads(file_list_json)
141        for file in file_list:
142            if file["name"].endswith("/"):
143                # this is a folder
144                continue
145            # print(repr(file))
146            if not file["sha1"]:
147                return False
148            if not fsgs.file.find_by_sha1(file["sha1"]):
149                return False
150        return True
151
152    def contract_paths(self):
153        def fix(key):
154            if self.config.get(key):
155                self.config[key] = Paths.contract_path(
156                    self.config.get(key), default_dir, force_real_case=False
157                )
158
159        default_dir = fsgs.amiga.get_floppies_dir()
160        for i in range(Amiga.MAX_FLOPPY_DRIVES):
161            fix("floppy_drive_{0}".format(i))
162        for i in range(Amiga.MAX_FLOPPY_IMAGES):
163            fix("floppy_image_{0}".format(i))
164
165        default_dir = fsgs.amiga.get_cdroms_dir()
166        for i in range(Amiga.MAX_CDROM_DRIVES):
167            fix("cdrom_drive_{0}".format(i))
168        for i in range(Amiga.MAX_CDROM_IMAGES):
169            fix("cdrom_image_{0}".format(i))
170
171    def set_name_and_uuid(self):
172        # self.config["x_config_uuid"] = self.root.get("uuid", "")
173
174        self.config["game_uuid"] = self.values.get("game_uuid", "")
175        self.config["variant_uuid"] = self.values.get("variant_uuid", "")
176
177        game_name = self.values.get("game_name", "")
178        platform_name = self.values.get("platform", "")
179        variant_name = self.values.get("variant_name", "")
180        parts = []
181        if platform_name:
182            parts.append(platform_name)
183        if variant_name:
184            parts.append(variant_name)
185        if game_name and variant_name:
186            config_name = "{0} ({1})".format(game_name, ", ".join(parts))
187            self.config["__config_name"] = config_name
188        else:
189            self.config["__config_name"] = self.values.get("config_name", "")
190
191    def load_option(self, key, value):
192        model = self.options.get("amiga_model", "")
193        if key in ["variant_viewport", "viewport"]:
194            if "=" in value:
195                parts = value.split(",")
196                for i in range(len(parts)):
197                    parts[i] = parts[i].split("#", 1)[0].strip()
198                    # parts[i] = parts[i].replace("=", "=>")
199                    # parts[i] = parts[i].replace("==>", "=>")
200                value = ", ".join(parts)
201                while "  " in value:
202                    value = value.replace("  ", " ")
203            else:
204                value = "* * * * = " + value
205            self.viewport.append(value)
206        # if key.startswith("viewport_"):
207        #     parts = key.split("_")
208        #     if len(parts) == 5:
209        #         parts = parts[1:]
210        #         value = " ".join(parts) + " => " + value
211        #         value = value.replace("x", "*")
212        #         self.viewport.append(value)
213        elif key == "hd_startup":
214            self.options["hd_startup"] = value
215            if whdload.should_disable_drive_click():
216                self.options[Option.FLOPPY_DRIVE_VOLUME_EMPTY] = "0"
217        elif key == "whdload_args":
218            self.options[Option.X_WHDLOAD_ARGS] = value
219        elif key == "whdload_quit_key":
220            self.options[Option.WHDLOAD_QUIT_KEY] = value
221        elif key == "hdinst_args":
222            self.options["x_hdinst_args"] = value
223            if whdload.should_disable_drive_click():
224                self.options[Option.FLOPPY_DRIVE_VOLUME_EMPTY] = "0"
225        elif key == "hd_requirements":
226            self.options["hd_requirements"] = value
227        elif key == "save_disk":
228            self.options["save_disk"] = value
229        elif key == "whdload_version":
230            self.options["x_whdload_version"] = value
231        # elif key == "whdload_icon":
232        #     self.options["__whdload_icon"] = value
233        elif key == "kickstart":
234            if value == "1.2":
235                self.options["amiga_model"] = "A1000"
236            elif value == "2.0":
237                if model in ["A500+", "A600"]:
238                    pass
239                else:
240                    self.options["amiga_model"] = "A600"
241            elif value == "2.0+":
242                if model in [
243                    "A500+",
244                    "A600",
245                    "A1200",
246                    "A1200/020",
247                    "A3000",
248                    "A4000/040",
249                ]:
250                    pass
251                else:
252                    self.options["amiga_model"] = "A600"
253            elif value in ["3.0+", "3.1", "3.1+"]:
254                if model in ["A1200", "A1200/020", "A3000", "A4000/040"]:
255                    pass
256                else:
257                    self.options["amiga_model"] = "A1200"
258            elif value == "AROS":
259                self.options["kickstart_file"] = "internal"
260            else:
261                # FIXME: print warning
262                pass
263        elif key == "chipset":
264            if value == "ECS":
265                if model in ["A500+", "A600", "A3000"]:
266                    pass
267                else:
268                    self.options["amiga_model"] = "A600"
269            elif value == "AGA":
270                if model in ["A1200", "A1200/020", "A4000/040"]:
271                    pass
272                else:
273                    self.options["amiga_model"] = "A1200"
274        elif key == "cpu":
275            if value == "68020+" or value == "68020":
276                if model in ["A1200", "A1200/020", "A3000", "A4000/040"]:
277                    pass
278                else:
279                    self.options["amiga_model"] = "A1200"
280        elif key == "fast_memory":
281            ivalue = int(value)
282            if ivalue > 8192:
283                self.options["zorro_iii_memory"] = value
284                self.options["amiga_model"] = "A1200/020"
285            else:
286                self.options["fast_memory"] = value
287        elif key == "chip_memory":
288            model = self.options.get("amiga_model", "")
289            if value == "1024+":
290                if model in ["A1200", "A1200/020", "A4000/040"]:
291                    pass
292                else:
293                    self.options["chip_memory"] = "1024"
294            elif value == "2048+":
295                if model in ["A1200", "A1200/020", "A4000/040"]:
296                    pass
297                else:
298                    # self.options["amiga_model"] = "A1200"
299                    self.options["chip_memory"] = "2048"
300            else:
301                self.options["chip_memory"] = value
302        elif key == "video_standard":
303            if value == "NTSC":
304                self.options["ntsc_mode"] = "1"
305        elif key == "cracktro":
306            # FIXME: handle
307            pass
308        elif key in [
309            "joystick_port_0_mode",
310            "joystick_port_1_mode",
311            "joystick_port_2_mode",
312            "joystick_port_3_mode",
313            "joystick_port_4_mode",
314        ]:
315            self.load_joystick_port_x_mode_option(key, value)
316        elif key in [
317            "amiga_model",
318            "accuracy",
319            "cdrom_drive_0_delay",
320            "floppy_drive_count",
321            "slow_memory",
322            "front_sha1",
323            "screen1_sha1",
324            "screen2_sha1",
325            "screen3_sha1",
326            "screen4_sha1",
327            "screen5_sha1",
328            "title_sha1",
329            "year",
330            "publisher",
331            "developer",
332            "hol_url",
333            "lemon_url",
334            "wikipedia_url",
335            "mobygames_url",
336            "whdload_url",
337            "amigamemo_url",
338            "longplay_url",
339            "thelegacy_url",
340            "homepage_url",
341            "languages",
342            "dongle_type",
343        ]:
344            self.options[key] = value
345        elif key == "requirements":
346            if "wb" in value.lower():
347                self.options["hard_drive_0"] = "hd://template/workbench/DH0"
348                self.options["hard_drive_0_priority"] = "6"
349            elif "hd" in value.lower():
350                self.options["hard_drive_0"] = "hd://template/empty/DH0"
351        elif key == "players":
352            self.options["players"] = value
353        elif key == "protection":
354            self.options["protection"] = value
355        elif key == "game_notice":
356            self.options["x_game_notice"] = value
357        elif key == "variant_notice":
358            self.options["x_variant_notice"] = value
359        elif key == "variant_warning":
360            self.options["x_variant_warning"] = value
361        elif key == "variant_error":
362            self.options["x_variant_error"] = value
363        elif key == "joy_emu_conflict":
364            self.options["x_joy_emu_conflict"] = value
365            # elif key == "languages":
366            #     self.options["x_languages"] = value
367
368    def load_joystick_port_x_mode_option(self, key, value):
369        value = value.lower()
370        if "," not in value:
371            self.options[key] = value
372            return
373        parts = value.split(",")
374        assert "=" not in parts[0]
375        self.options[key] = parts[0]
376        port = ["0", "1", "2", "3", "4"].index(key[14])
377        for part in parts[1:]:
378            k, v = part.split("=")
379            k = k.strip()
380            v = v.strip()
381            if not v.startswith("action_"):
382                v = "action_" + v
383            k = "joystick_port_{0}_{1}".format(port, k)
384            self.options[k] = v
385
386    def get_file_list(self):
387        if self._file_list is None:
388            file_list_json = self.values.get("file_list", "[]")
389            self._file_list = json.loads(file_list_json)
390        return self._file_list
391
392    def get_cue_sheets(self):
393        if self._cue_sheets is None:
394            cue_sheets_json = self.values.get("cue_sheets", "[]")
395            self._cue_sheets = json.loads(cue_sheets_json)
396        return self._cue_sheets
397
398    def build_media_list(self, floppies=False, cds=False, hds=False):
399        media_list = []
400        added = set()
401        for file_item in self.get_file_list():
402            name = file_item["name"]
403            url = file_item.get("url", "")
404
405            if name.startswith("DH0/"):
406                if hds:
407                    # p = os.path.join(self.path, "HardDrive")
408                    p = "hd://game/" + self.uuid + "/DH0"
409                    if p in added:
410                        # already added
411                        continue
412                    added.add(p)
413                    # FIXME: hack for now
414                    sha1 = self.values.get("dh0_sha1", "")
415                    media_list.append((p, sha1))
416                else:
417                    continue
418
419            sha1 = file_item["sha1"]
420            base, ext = os.path.splitext(name)
421            ext = ext.lower()
422
423            if hds:
424                if ext not in [".zip"]:
425                    continue
426            elif cds:
427                if ext not in [".cue", ".iso"]:
428                    continue
429                if "(Track" in base:
430                    # probably part of a split multi-track cue
431                    continue
432            elif floppies:
433                if ext not in [".adf", ".adz", ".dms", ".ipf"]:
434                    continue
435
436            path = ""
437            found_sha1 = ""
438            if sha1:
439                print(sha1)
440                file = FileDatabase.get_instance().find_file(sha1=sha1)
441                if file:
442                    found_sha1 = sha1
443                    path = file["path"]
444            if url and not path:
445                path = url
446                found_sha1 = sha1
447
448            if path:
449                media_list.append((path, found_sha1))
450            else:
451                pass
452                # return False
453                #  FIXME: handle it with a visible error message
454                # raise Exception("could not find file " + repr(name))
455        return media_list
456
457    def load_floppies_from_floppy_list(self):
458        media_list = []
459        for item in self.values.get("floppy_list").split(","):
460            name, sha1 = item.split(":")
461            name = name.strip()
462            sha1 = sha1.strip()
463            path = "sha1://{0}/{1}/{2}".format(sha1, "", name)
464            media_list.append((path, sha1))
465        return media_list
466
467    def load_floppies(self):
468        floppy_drive_count = 4
469        if "floppy_drive_count" in self.options:
470            try:
471                floppy_drive_count = int(self.options["floppy_drive_count"])
472            except ValueError:
473                floppy_drive_count = 1
474            floppy_drive_count = max(0, min(4, floppy_drive_count))
475
476        if self.values.get("floppy_list", ""):
477            media_list = self.load_floppies_from_floppy_list()
478        else:
479            media_list = self.build_media_list(floppies=True)
480
481        for i, values in enumerate(media_list):
482            path, sha1 = values
483            if i < floppy_drive_count:
484                self.config["floppy_drive_{0}".format(i)] = path
485                self.config["x_floppy_drive_{0}_sha1".format(i)] = sha1
486            self.config["floppy_image_{0}".format(i)] = path
487            self.config["x_floppy_image_{0}_sha1".format(i)] = sha1
488        if floppy_drive_count < 4:
489            self.config["floppy_drive_count"] = floppy_drive_count
490
491    def cdrom_sha1_to_uri(self, sha1):
492        for file_item in self.get_file_list():
493            if file_item["sha1"] == sha1:
494                return "game://{}/{}".format(self.uuid, file_item["name"])
495        raise Exception("cdrom_sha1_to_uri: could not find " + sha1)
496
497    def load_cdroms(self):
498        # media_list = self.build_media_list(cds=True)
499        cue_sheets = self.get_cue_sheets()
500        if cue_sheets:
501            return self.load_cdroms_from_cue_sheets(cue_sheets)
502        cue = False
503        ccd = False
504        # iso = False
505        for file_item in self.get_file_list():
506            if file_item["name"].endswith(".cue"):
507                cue = True
508            if file_item["name"].endswith(".ccd"):
509                ccd = True
510                # if file_item["name"].endswith(".iso"):
511                #     iso = True
512        media_list = []
513        if ccd:
514            ext = ".ccd"
515        elif cue:
516            ext = ".cue"
517        else:
518            ext = ".iso"
519        for file_item in self.get_file_list():
520            if file_item["name"].endswith(ext):
521                media_list.append((file_item["name"], file_item["sha1"]))
522        print("load_cdroms media_list =", media_list)
523        for i, values in enumerate(media_list):
524            path, sha1 = values
525            path = self.cdrom_sha1_to_uri(sha1)
526            if i < 1:
527                self.config["cdrom_drive_{0}".format(i)] = path
528                self.config["x_cdrom_drive_{0}_sha1".format(i)] = sha1
529            self.config["cdrom_image_{0}".format(i)] = path
530            self.config["x_cdrom_image_{0}_sha1".format(i)] = sha1
531
532    def load_cdroms_from_cue_sheets(self, cue_sheets):
533        for i, item in enumerate(cue_sheets):
534            path = "game://{}/{}".format(self.uuid, item["name"])
535            if i < 1:
536                self.config["cdrom_drive_{0}".format(i)] = path
537            self.config["cdrom_image_{0}".format(i)] = path
538
539    def load_hard_drives(self):
540        media_list = self.build_media_list(hds=True)
541        print(media_list)
542        for i, values in enumerate(media_list):
543            path, sha1 = values
544            self.config["hard_drive_{0}".format(i)] = path
545            self.config["x_hard_drive_{0}_sha1".format(i)] = sha1
546