all repos — mgba @ 5d72a2be9d5e7e74bc9fc94ac0130442ae91dc86

mGBA Game Boy Advance Emulator

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