src/platform/python/mgba/core.py (view raw)
1# Copyright (c) 2013-2016 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
7from . import tile, createCallback
8from cached_property import cached_property
9
10def find(path):
11 core = lib.mCoreFind(path.encode('UTF-8'))
12 if core == ffi.NULL:
13 return None
14 return Core._init(core)
15
16def findVF(vf):
17 core = lib.mCoreFindVF(vf.handle)
18 if core == ffi.NULL:
19 return None
20 return Core._init(core)
21
22def loadPath(path):
23 core = find(path)
24 if not core or not core.loadFile(path):
25 return None
26 return core
27
28def loadVF(vf):
29 core = findVF(vf)
30 if not core or not core.loadROM(vf):
31 return None
32 return core
33
34def needsReset(f):
35 def wrapper(self, *args, **kwargs):
36 if not self._wasReset:
37 raise RuntimeError("Core must be reset first")
38 return f(self, *args, **kwargs)
39 return wrapper
40
41def protected(f):
42 def wrapper(self, *args, **kwargs):
43 if self._protected:
44 raise RuntimeError("Core is protected")
45 return f(self, *args, **kwargs)
46 return wrapper
47
48@ffi.def_extern()
49def _mCorePythonCallbacksVideoFrameStarted(user):
50 context = ffi.from_handle(user)
51 context._videoFrameStarted()
52
53@ffi.def_extern()
54def _mCorePythonCallbacksVideoFrameEnded(user):
55 context = ffi.from_handle(user)
56 context._videoFrameEnded()
57
58@ffi.def_extern()
59def _mCorePythonCallbacksCoreCrashed(user):
60 context = ffi.from_handle(user)
61 context._coreCrashed()
62
63@ffi.def_extern()
64def _mCorePythonCallbacksSleep(user):
65 context = ffi.from_handle(user)
66 context._sleep()
67
68class CoreCallbacks(object):
69 def __init__(self):
70 self._handle = ffi.new_handle(self)
71 self.videoFrameStarted = []
72 self.videoFrameEnded = []
73 self.coreCrashed = []
74 self.sleep = []
75 self.context = lib.mCorePythonCallbackCreate(self._handle)
76
77 def _videoFrameStarted(self):
78 for cb in self.videoFrameStarted:
79 cb()
80
81 def _videoFrameEnded(self):
82 for cb in self.videoFrameEnded:
83 cb()
84
85 def _coreCrashed(self):
86 for cb in self.coreCrashed:
87 cb()
88
89 def _sleep(self):
90 for cb in self.sleep:
91 cb()
92
93class Core(object):
94 def __init__(self, native):
95 self._core = native
96 self._wasReset = False
97 self._protected = False
98 self._callbacks = CoreCallbacks()
99 self._core.addCoreCallbacks(self._core, self._callbacks.context)
100
101 @cached_property
102 def tiles(self):
103 return tile.TileView(self)
104
105 @classmethod
106 def _init(cls, native):
107 core = ffi.gc(native, native.deinit)
108 success = bool(core.init(core))
109 lib.mCoreInitConfig(core, ffi.NULL)
110 if not success:
111 raise RuntimeError("Failed to initialize core")
112 return cls._detect(core)
113
114 def _deinit(self):
115 self._core.deinit(self._core)
116
117 @classmethod
118 def _detect(cls, core):
119 if hasattr(cls, 'PLATFORM_GBA') and core.platform(core) == cls.PLATFORM_GBA:
120 return GBA(core)
121 if hasattr(cls, 'PLATFORM_GB') and core.platform(core) == cls.PLATFORM_GB:
122 return GB(core)
123 return Core(core)
124
125 @protected
126 def loadFile(self, path):
127 return bool(lib.mCoreLoadFile(self._core, path.encode('UTF-8')))
128
129 def isROM(self, vf):
130 return bool(self._core.isROM(vf.handle))
131
132 def loadROM(self, vf):
133 return bool(self._core.loadROM(self._core, vf.handle))
134
135 def loadSave(self, vf):
136 return bool(self._core.loadSave(self._core, vf.handle))
137
138 def loadTemporarySave(self, vf):
139 return bool(self._core.loadTemporarySave(self._core, vf.handle))
140
141 @protected
142 def loadPatch(self, vf):
143 return bool(self._core.loadPatch(self._core, vf.handle))
144
145 def autoloadSave(self):
146 return bool(lib.mCoreAutoloadSave(self._core))
147
148 def autoloadPatch(self):
149 return bool(lib.mCoreAutoloadPatch(self._core))
150
151 def platform(self):
152 return self._core.platform(self._core)
153
154 def desiredVideoDimensions(self):
155 width = ffi.new("unsigned*")
156 height = ffi.new("unsigned*")
157 self._core.desiredVideoDimensions(self._core, width, height)
158 return width[0], height[0]
159
160 def setVideoBuffer(self, image):
161 self._core.setVideoBuffer(self._core, image.buffer, image.stride)
162
163 @protected
164 def reset(self):
165 self._core.reset(self._core)
166 self._wasReset = True
167
168 @needsReset
169 @protected
170 def runFrame(self):
171 self._core.runFrame(self._core)
172
173 @needsReset
174 @protected
175 def runLoop(self):
176 self._core.runLoop(self._core)
177
178 @needsReset
179 def step(self):
180 self._core.step(self._core)
181
182 @staticmethod
183 def _keysToInt(*args, **kwargs):
184 keys = 0
185 if 'raw' in kwargs:
186 keys = kwargs['raw']
187 for key in args:
188 keys |= 1 << key
189 return keys
190
191 def setKeys(self, *args, **kwargs):
192 self._core.setKeys(self._core, self._keysToInt(*args, **kwargs))
193
194 def addKeys(self, *args, **kwargs):
195 self._core.addKeys(self._core, self._keysToInt(*args, **kwargs))
196
197 def clearKeys(self, *args, **kwargs):
198 self._core.clearKeys(self._core, self._keysToInt(*args, **kwargs))
199
200 @property
201 @needsReset
202 def frameCounter(self):
203 return self._core.frameCounter(self._core)
204
205 @property
206 def frameCycles(self):
207 return self._core.frameCycles(self._core)
208
209 @property
210 def frequency(self):
211 return self._core.frequency(self._core)
212
213 @property
214 def gameTitle(self):
215 title = ffi.new("char[16]")
216 self._core.getGameTitle(self._core, title)
217 return ffi.string(title, 16).decode("ascii")
218
219 @property
220 def gameCode(self):
221 code = ffi.new("char[12]")
222 self._core.getGameCode(self._core, code)
223 return ffi.string(code, 12).decode("ascii")
224
225 def addFrameCallback(self, cb):
226 self._callbacks.videoFrameEnded.append(cb)
227
228class ICoreOwner(object):
229 def claim(self):
230 raise NotImplementedError
231
232 def release(self):
233 raise NotImplementedError
234
235 def __enter__(self):
236 self.core = self.claim()
237 self.core._protected = True
238 return self.core
239
240 def __exit__(self, type, value, traceback):
241 self.core._protected = False
242 self.release()
243
244class IRunner(object):
245 def pause(self):
246 raise NotImplementedError
247
248 def unpause(self):
249 raise NotImplementedError
250
251 def useCore(self):
252 raise NotImplementedError
253
254 def isRunning(self):
255 raise NotImplementedError
256
257 def isPaused(self):
258 raise NotImplementedError
259
260if hasattr(lib, 'PLATFORM_GBA'):
261 from .gba import GBA
262 Core.PLATFORM_GBA = lib.PLATFORM_GBA
263
264if hasattr(lib, 'PLATFORM_GB'):
265 from .gb import GB
266 from .lr35902 import LR35902Core
267 Core.PLATFORM_GB = lib.PLATFORM_GB