1# Copyright (c) 2013-2017 Jeffrey Pfau
2#
3# This Source Code Form is subject to the terms of the Mozilla Public
4# License, v. 2.0. If a copy of the MPL was not distributed with this
5# file, You can obtain one at http://mozilla.org/MPL/2.0/.
6from ._pylib import ffi, lib  # pylint: disable=no-name-in-module
7from .sm83 import SM83Core
8from .core import Core, needs_reset
9from .memory import Memory
10from .tile import Sprite
11from . import create_callback
12
13
14class GB(Core):
15    KEY_A = lib.GBA_KEY_A
16    KEY_B = lib.GBA_KEY_B
17    KEY_SELECT = lib.GBA_KEY_SELECT
18    KEY_START = lib.GBA_KEY_START
19    KEY_DOWN = lib.GBA_KEY_DOWN
20    KEY_UP = lib.GBA_KEY_UP
21    KEY_LEFT = lib.GBA_KEY_LEFT
22    KEY_RIGHT = lib.GBA_KEY_RIGHT
23
24    def __init__(self, native):
25        super(GB, self).__init__(native)
26        self._native = ffi.cast("struct GB*", native.board)
27        self.sprites = GBObjs(self)
28        self.cpu = SM83Core(self._core.cpu)
29        self.memory = None
30        self._link = None
31
32    @needs_reset
33    def _init_cache(self, cache):
34        lib.GBVideoCacheInit(cache)
35        lib.GBVideoCacheAssociate(cache, ffi.addressof(self._native.video))
36
37    def _deinit_cache(self, cache):
38        lib.mCacheSetDeinit(cache)
39        if self._was_reset:
40            self._native.video.renderer.cache = ffi.NULL
41
42    def _load(self):
43        super(GB, self)._load()
44        self.memory = GBMemory(self._core)
45
46    def attach_sio(self, link):
47        self._link = link
48        lib.GBSIOSetDriver(ffi.addressof(self._native.sio), link._native)
49
50    def __del__(self):
51        if self._link:
52            lib.GBSIOSetDriver(ffi.addressof(self._native.sio), ffi.NULL)
53            self._link = None
54
55
56create_callback("GBSIOPythonDriver", "init")
57create_callback("GBSIOPythonDriver", "deinit")
58create_callback("GBSIOPythonDriver", "writeSB")
59create_callback("GBSIOPythonDriver", "writeSC")
60
61
62class GBSIODriver(object):
63    def __init__(self):
64        self._handle = ffi.new_handle(self)
65        self._native = ffi.gc(lib.GBSIOPythonDriverCreate(self._handle), lib.free)
66
67    def init(self):
68        return True
69
70    def deinit(self):
71        pass
72
73    def write_sb(self, value):
74        pass
75
76    def write_sc(self, value):
77        return value
78
79
80class GBSIOSimpleDriver(GBSIODriver):
81    def __init__(self, period=0x100):
82        super(GBSIOSimpleDriver, self).__init__()
83        self.rx = 0x00  # pylint: disable=invalid-name
84        self._period = period
85
86    def init(self):
87        self._native.p.period = self._period
88        return True
89
90    def write_sb(self, value):
91        self.rx = value  # pylint: disable=invalid-name
92
93    def write_sc(self, value):
94        self._native.p.period = self._period
95        if value & 0x80:
96            lib.mTimingDeschedule(ffi.addressof(self._native.p.p.timing), ffi.addressof(self._native.p.event))
97            lib.mTimingSchedule(ffi.addressof(self._native.p.p.timing), ffi.addressof(self._native.p.event), self._native.p.period)
98        return value
99
100    def is_ready(self):
101        return not self._native.p.remainingBits
102
103    @property
104    def tx(self):  # pylint: disable=invalid-name
105        return self._native.p.pendingSB
106
107    @property
108    def period(self):
109        return self._native.p.period
110
111    @tx.setter
112    def tx(self, newTx):  # pylint: disable=invalid-name
113        self._native.p.pendingSB = newTx
114        self._native.p.remainingBits = 8
115
116    @period.setter
117    def period(self, new_period):
118        self._period = new_period
119        if self._native.p:
120            self._native.p.period = new_period
121
122
123class GBMemory(Memory):
124    def __init__(self, core):
125        super(GBMemory, self).__init__(core, 0x10000)
126
127        self.cart = Memory(core, lib.GB_SIZE_CART_BANK0 * 2, lib.GB_BASE_CART_BANK0)
128        self.vram = Memory(core, lib.GB_SIZE_VRAM, lib.GB_BASE_VRAM)
129        self.sram = Memory(core, lib.GB_SIZE_EXTERNAL_RAM, lib.GB_REGION_EXTERNAL_RAM)
130        self.iwram = Memory(core, lib.GB_SIZE_WORKING_RAM_BANK0, lib.GB_BASE_WORKING_RAM_BANK0)
131        self.oam = Memory(core, lib.GB_SIZE_OAM, lib.GB_BASE_OAM)
132        self.io = Memory(core, lib.GB_SIZE_IO, lib.GB_BASE_IO)  # pylint: disable=invalid-name
133        self.hram = Memory(core, lib.GB_SIZE_HRAM, lib.GB_BASE_HRAM)
134
135
136class GBSprite(Sprite):
137    PALETTE_BASE = (8,)
138
139    def __init__(self, obj, core):
140        self.x = obj.x  # pylint: disable=invalid-name
141        self.y = obj.y  # pylint: disable=invalid-name
142        self.tile = obj.tile
143        self._attr = obj.attr
144        self.width = 8
145        lcdc = core._native.memory.io[0x40]
146        self.height = 16 if lcdc & 4 else 8
147        if core._native.model >= lib.GB_MODEL_CGB:
148            if self._attr & 8:
149                self.tile += 512
150            self.palette_id = self._attr & 7
151        else:
152            self.palette_id = (self._attr >> 4) & 1
153        self.palette_id += 8
154
155
156class GBObjs:
157    def __init__(self, core):
158        self._core = core
159        self._obj = core._native.video.oam.obj
160
161    def __len__(self):
162        return 40
163
164    def __getitem__(self, index):
165        if index >= len(self):
166            raise IndexError()
167        sprite = GBSprite(self._obj[index], self._core)
168        sprite.constitute(self._core.tiles[0], 0)
169        return sprite
170