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 if hasattr(lib, 'PLATFORM_GBA'):
95 PLATFORM_GBA = lib.PLATFORM_GBA
96
97 if hasattr(lib, 'PLATFORM_GB'):
98 PLATFORM_GB = lib.PLATFORM_GB
99
100 if hasattr(lib, 'PLATFORM_DS'):
101 PLATFORM_GB = lib.PLATFORM_DS
102
103 def __init__(self, native):
104 self._core = native
105 self._wasReset = False
106 self._protected = False
107 self._callbacks = CoreCallbacks()
108 self._core.addCoreCallbacks(self._core, self._callbacks.context)
109 self.config = Config(ffi.addressof(native.config))
110
111 def __del__(self):
112 self._wasReset = False
113
114 @cached_property
115 def graphicsCache(self):
116 if not self._wasReset:
117 raise RuntimeError("Core must be reset first")
118 return tile.CacheSet(self)
119
120 @cached_property
121 def tiles(self):
122 t = []
123 ts = ffi.addressof(self.graphicsCache.cache.tiles)
124 for i in range(lib.mTileCacheSetSize(ts)):
125 t.append(tile.TileView(lib.mTileCacheSetGetPointer(ts, i)))
126 return t
127
128 @cached_property
129 def maps(self):
130 m = []
131 ms = ffi.addressof(self.graphicsCache.cache.maps)
132 for i in range(lib.mMapCacheSetSize(ms)):
133 m.append(tile.MapView(lib.mMapCacheSetGetPointer(ms, i)))
134 return m
135
136 @classmethod
137 def _init(cls, native):
138 core = ffi.gc(native, native.deinit)
139 success = bool(core.init(core))
140 lib.mCoreInitConfig(core, ffi.NULL)
141 if not success:
142 raise RuntimeError("Failed to initialize core")
143 return cls._detect(core)
144
145 @classmethod
146 def _detect(cls, core):
147 if hasattr(cls, 'PLATFORM_GBA') and core.platform(core) == cls.PLATFORM_GBA:
148 from .gba import GBA
149 return GBA(core)
150 if hasattr(cls, 'PLATFORM_GB') and core.platform(core) == cls.PLATFORM_GB:
151 from .gb import GB
152 return GB(core)
153 if hasattr(cls, 'PLATFORM_DS') and core.platform(core) == cls.PLATFORM_DS:
154 from .ds import DS
155 return DS(core)
156 return Core(core)
157
158 def _load(self):
159 self._wasReset = True
160
161 def loadFile(self, path):
162 return bool(lib.mCoreLoadFile(self._core, path.encode('UTF-8')))
163
164 def isROM(self, vf):
165 return bool(self._core.isROM(vf.handle))
166
167 def loadROM(self, vf):
168 return bool(self._core.loadROM(self._core, vf.handle))
169
170 def loadBIOS(self, vf, id=0):
171 return bool(self._core.loadBIOS(self._core, vf.handle, id))
172
173 def loadSave(self, vf):
174 return bool(self._core.loadSave(self._core, vf.handle))
175
176 def loadTemporarySave(self, vf):
177 return bool(self._core.loadTemporarySave(self._core, vf.handle))
178
179 def loadPatch(self, vf):
180 return bool(self._core.loadPatch(self._core, vf.handle))
181
182 def loadConfig(self, config):
183 lib.mCoreLoadForeignConfig(self._core, config._native)
184
185 def autoloadSave(self):
186 return bool(lib.mCoreAutoloadSave(self._core))
187
188 def autoloadPatch(self):
189 return bool(lib.mCoreAutoloadPatch(self._core))
190
191 def autoloadCheats(self):
192 return bool(lib.mCoreAutoloadCheats(self._core))
193
194 def platform(self):
195 return self._core.platform(self._core)
196
197 def desiredVideoDimensions(self):
198 width = ffi.new("unsigned*")
199 height = ffi.new("unsigned*")
200 self._core.desiredVideoDimensions(self._core, width, height)
201 return width[0], height[0]
202
203 def setVideoBuffer(self, image):
204 self._core.setVideoBuffer(self._core, image.buffer, image.stride)
205
206 def reset(self):
207 self._core.reset(self._core)
208 self._load()
209
210 @needsReset
211 @protected
212 def runFrame(self):
213 self._core.runFrame(self._core)
214
215 @needsReset
216 @protected
217 def runLoop(self):
218 self._core.runLoop(self._core)
219
220 @needsReset
221 def step(self):
222 self._core.step(self._core)
223
224 @staticmethod
225 def _keysToInt(*args, **kwargs):
226 keys = 0
227 if 'raw' in kwargs:
228 keys = kwargs['raw']
229 for key in args:
230 keys |= 1 << key
231 return keys
232
233 def setKeys(self, *args, **kwargs):
234 self._core.setKeys(self._core, self._keysToInt(*args, **kwargs))
235
236 def addKeys(self, *args, **kwargs):
237 self._core.addKeys(self._core, self._keysToInt(*args, **kwargs))
238
239 def clearKeys(self, *args, **kwargs):
240 self._core.clearKeys(self._core, self._keysToInt(*args, **kwargs))
241
242 @property
243 @needsReset
244 def frameCounter(self):
245 return self._core.frameCounter(self._core)
246
247 @property
248 def frameCycles(self):
249 return self._core.frameCycles(self._core)
250
251 @property
252 def frequency(self):
253 return self._core.frequency(self._core)
254
255 @property
256 def gameTitle(self):
257 title = ffi.new("char[16]")
258 self._core.getGameTitle(self._core, title)
259 return ffi.string(title, 16).decode("ascii")
260
261 @property
262 def gameCode(self):
263 code = ffi.new("char[12]")
264 self._core.getGameCode(self._core, code)
265 return ffi.string(code, 12).decode("ascii")
266
267 def addFrameCallback(self, cb):
268 self._callbacks.videoFrameEnded.append(cb)
269
270 @property
271 def crc32(self):
272 return self._native.romCrc32
273
274class ICoreOwner(object):
275 def claim(self):
276 raise NotImplementedError
277
278 def release(self):
279 raise NotImplementedError
280
281 def __enter__(self):
282 self.core = self.claim()
283 self.core._protected = True
284 return self.core
285
286 def __exit__(self, type, value, traceback):
287 self.core._protected = False
288 self.release()
289
290class IRunner(object):
291 def pause(self):
292 raise NotImplementedError
293
294 def unpause(self):
295 raise NotImplementedError
296
297 def useCore(self):
298 raise NotImplementedError
299
300 def isRunning(self):
301 raise NotImplementedError
302
303 def isPaused(self):
304 raise NotImplementedError
305
306class Config(object):
307 def __init__(self, native=None, port=None, defaults={}):
308 if not native:
309 self._port = ffi.NULL
310 if port:
311 self._port = ffi.new("char[]", port.encode("UTF-8"))
312 native = ffi.gc(ffi.new("struct mCoreConfig*"), lib.mCoreConfigDeinit)
313 lib.mCoreConfigInit(native, self._port)
314 self._native = native
315 for key, value in defaults.items():
316 if isinstance(value, bool):
317 value = int(value)
318 lib.mCoreConfigSetDefaultValue(self._native, ffi.new("char[]", key.encode("UTF-8")), ffi.new("char[]", str(value).encode("UTF-8")))
319
320 def __getitem__(self, key):
321 string = lib.mCoreConfigGetValue(self._native, ffi.new("char[]", key.encode("UTF-8")))
322 if not string:
323 return None
324 return ffi.string(string)
325
326 def __setitem__(self, key, value):
327 if isinstance(value, bool):
328 value = int(value)
329 lib.mCoreConfigSetValue(self._native, ffi.new("char[]", key.encode("UTF-8")), ffi.new("char[]", str(value).encode("UTF-8")))