all repos — mgba @ d1ef27cff90eda02bde9f90cc526e901a4c81d83

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    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