1import hashlib
2
3import os
4
5from fsgs import Option
6from fsgs.drivers.mednafendriver import MednafenDriver
7from fsgs.platform import Platform
8from fsgs.platforms.loader import SimpleLoader
9
10
11class GameBoyColorPlatform(Platform):
12    PLATFORM_NAME = "Game Boy Color"
13
14    def driver(self, fsgs):
15        return MednafenGbcDriver(fsgs)
16
17    def loader(self, fsgc):
18        return GameBoyColorLoader(fsgc)
19
20
21class GameBoyColorLoader(SimpleLoader):
22    pass
23
24
25class MednafenGbcDriver(MednafenDriver):
26    CONTROLLER = {
27        "type": "gamepad",
28        "description": "Built-in Controller",
29        "mapping_name": "gameboycolor",
30    }
31
32    PORTS = [{"description": "Controller", "types": [CONTROLLER]}]
33
34    def __init__(self, fsgc):
35        super().__init__(fsgc)
36        self.helper = GameBoyColorHelper(self.options)
37
38    def prepare(self):
39        super().prepare()
40        self.set_mednafen_aspect(47, 43)
41        # We do aspect calculation separately. Must not be done twice.
42        # self.emulator.args.extend(["-snes.correct_aspect", "0"])
43        rom_path = self.helper.prepare_rom(self)
44        self.emulator.args.append(rom_path)
45
46    def get_game_refresh_rate(self):
47        return 59.73
48
49    def mednafen_input_mapping(self, _):
50        return {
51            "A": "gb.input.builtin.gamepad.a",
52            "B": "gb.input.builtin.gamepad.b",
53            "UP": "gb.input.builtin.gamepad.up",
54            "DOWN": "gb.input.builtin.gamepad.down",
55            "LEFT": "gb.input.builtin.gamepad.left",
56            "RIGHT": "gb.input.builtin.gamepad.right",
57            "SELECT": "gb.input.builtin.gamepad.select",
58            "START": "gb.input.builtin.gamepad.start",
59        }
60
61    def mednafen_rom_extensions(self):
62        return [".gbc"]
63
64    def mednafen_scanlines_setting(self):
65        return 33
66
67    def mednafen_special_filter(self):
68        return "nn2x"
69
70    def mednafen_system_prefix(self):
71        return "gb"
72
73    def game_video_par(self):
74        # return (4.4 / 4.0) / (160 / 144)
75        # Close enough to 1.0, might just as well go with that.
76        return 1.0
77
78    def game_video_size(self):
79        return 160, 144
80
81    def get_game_file(self, config_key="cartridge_slot"):
82        return None
83
84
85class GameBoyColorHelper:
86    def __init__(self, options):
87        self.options = options
88
89    def prepare_rom(self, driver):
90        file_uri = self.options[Option.CARTRIDGE_SLOT]
91        input_stream = driver.fsgc.file.open(file_uri)
92        _, ext = os.path.splitext(file_uri)
93        return self.prepare_rom_with_stream(driver, input_stream, ext)
94
95    def prepare_rom_with_stream(self, driver, input_stream, ext):
96        sha1_obj = hashlib.sha1()
97        path = driver.temp_file("rom" + ext).path
98        with open(path, "wb") as f:
99            while True:
100                data = input_stream.read(65536)
101                if not data:
102                    break
103                f.write(data)
104                sha1_obj.update(data)
105        new_path = os.path.join(
106            os.path.dirname(path), sha1_obj.hexdigest()[:8].upper() + ext
107        )
108        os.rename(path, new_path)
109        return new_path
110