all repos — mgba @ a04cb97653efc918e4879771c6ae06000200b43c

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  # pylint: disable=no-name-in-module
  7from . import tile, audio
  8from cached_property import cached_property
  9from functools import wraps
 10
 11
 12def find(path):
 13    core = lib.mCoreFind(path.encode('UTF-8'))
 14    if core == ffi.NULL:
 15        return None
 16    return Core._init(core)
 17
 18
 19def find_vf(vfile):
 20    core = lib.mCoreFindVF(vfile.handle)
 21    if core == ffi.NULL:
 22        return None
 23    return Core._init(core)
 24
 25
 26def load_path(path):
 27    core = find(path)
 28    if not core or not core.load_file(path):
 29        return None
 30    return core
 31
 32
 33def load_vf(vfile):
 34    core = find_vf(vfile)
 35    if not core or not core.load_rom(vfile):
 36        return None
 37    return core
 38
 39
 40def needs_reset(func):
 41    @wraps(func)
 42    def wrapper(self, *args, **kwargs):
 43        if not self._was_reset:
 44            raise RuntimeError("Core must be reset first")
 45        return func(self, *args, **kwargs)
 46    return wrapper
 47
 48
 49def protected(func):
 50    @wraps(func)
 51    def wrapper(self, *args, **kwargs):
 52        if self._protected:
 53            raise RuntimeError("Core is protected")
 54        return func(self, *args, **kwargs)
 55    return wrapper
 56
 57
 58@ffi.def_extern()
 59def _mCorePythonCallbacksVideoFrameStarted(user):  # pylint: disable=invalid-name
 60    context = ffi.from_handle(user)
 61    context._video_frame_started()
 62
 63
 64@ffi.def_extern()
 65def _mCorePythonCallbacksVideoFrameEnded(user):  # pylint: disable=invalid-name
 66    context = ffi.from_handle(user)
 67    context._video_frame_ended()
 68
 69
 70@ffi.def_extern()
 71def _mCorePythonCallbacksCoreCrashed(user):  # pylint: disable=invalid-name
 72    context = ffi.from_handle(user)
 73    context._core_crashed()
 74
 75
 76@ffi.def_extern()
 77def _mCorePythonCallbacksSleep(user):  # pylint: disable=invalid-name
 78    context = ffi.from_handle(user)
 79    context._sleep()
 80
 81
 82class CoreCallbacks(object):
 83    def __init__(self):
 84        self._handle = ffi.new_handle(self)
 85        self.video_frame_started = []
 86        self.video_frame_ended = []
 87        self.core_crashed = []
 88        self.sleep = []
 89        self.context = lib.mCorePythonCallbackCreate(self._handle)
 90
 91    def _video_frame_started(self):
 92        for callback in self.video_frame_started:
 93            callback()
 94
 95    def _video_frame_ended(self):
 96        for callback in self.video_frame_ended:
 97            callback()
 98
 99    def _core_crashed(self):
100        for callback in self.core_crashed:
101            callback()
102
103    def _sleep(self):
104        for callback in self.sleep:
105            callback()
106
107
108class Core(object):
109    if hasattr(lib, 'PLATFORM_GBA'):
110        PLATFORM_GBA = lib.PLATFORM_GBA
111
112    if hasattr(lib, 'PLATFORM_GB'):
113        PLATFORM_GB = lib.PLATFORM_GB
114
115    def __init__(self, native):
116        self._core = native
117        self._was_reset = False
118        self._protected = False
119        self._callbacks = CoreCallbacks()
120        self._core.addCoreCallbacks(self._core, self._callbacks.context)
121        self.config = Config(ffi.addressof(native.config))
122
123    def __del__(self):
124        self._was_reset = False
125
126    @cached_property
127    def graphics_cache(self):
128        if not self._was_reset:
129            raise RuntimeError("Core must be reset first")
130        return tile.CacheSet(self)
131
132    @cached_property
133    def tiles(self):
134        tiles = []
135        native_tiles = ffi.addressof(self.graphics_cache.cache.tiles)
136        for i in range(lib.mTileCacheSetSize(native_tiles)):
137            tiles.append(tile.TileView(lib.mTileCacheSetGetPointer(native_tiles, i)))
138        return tiles
139
140    @cached_property
141    def maps(self):
142        maps = []
143        native_maps = ffi.addressof(self.graphics_cache.cache.maps)
144        for i in range(lib.mMapCacheSetSize(native_maps)):
145            maps.append(tile.MapView(lib.mMapCacheSetGetPointer(native_maps, i)))
146        return maps
147
148    @classmethod
149    def _init(cls, native):
150        core = ffi.gc(native, native.deinit)
151        success = bool(core.init(core))
152        lib.mCoreInitConfig(core, ffi.NULL)
153        if not success:
154            raise RuntimeError("Failed to initialize core")
155        return cls._detect(core)
156
157    @classmethod
158    def _detect(cls, core):
159        if hasattr(cls, 'PLATFORM_GBA') and core.platform(core) == cls.PLATFORM_GBA:
160            from .gba import GBA
161            return GBA(core)
162        if hasattr(cls, 'PLATFORM_GB') and core.platform(core) == cls.PLATFORM_GB:
163            from .gb import GB
164            return GB(core)
165        return Core(core)
166
167    def _load(self):
168        self._was_reset = True
169
170    @protected
171    def load_file(self, path):
172        return bool(lib.mCoreLoadFile(self._core, path.encode('UTF-8')))
173
174    def is_rom(self, vfile):
175        return bool(self._core.isROM(vfile.handle))
176
177    @protected
178    def load_rom(self, vfile):
179        return bool(self._core.loadROM(self._core, vfile.handle))
180
181    @protected
182    def load_bios(self, vfile, id=0):
183        res = bool(self._core.loadBIOS(self._core, vfile.handle, id))
184        if res:
185            vfile._claimed = True
186        return res
187
188    @protected
189    def load_save(self, vfile):
190        res = bool(self._core.loadSave(self._core, vfile.handle))
191        if res:
192            vfile._claimed = True
193        return res
194
195    @protected
196    def load_temporary_save(self, vfile):
197        return bool(self._core.loadTemporarySave(self._core, vfile.handle))
198
199    @protected
200    def load_patch(self, vfile):
201        return bool(self._core.loadPatch(self._core, vfile.handle))
202
203    @protected
204    def load_config(self, config):
205        lib.mCoreLoadForeignConfig(self._core, config._native)
206
207    @protected
208    def autoload_save(self):
209        return bool(lib.mCoreAutoloadSave(self._core))
210
211    @protected
212    def autoload_patch(self):
213        return bool(lib.mCoreAutoloadPatch(self._core))
214
215    @protected
216    def autoload_cheats(self):
217        return bool(lib.mCoreAutoloadCheats(self._core))
218
219    @property
220    def platform(self):
221        return self._core.platform(self._core)
222
223    @protected
224    def desired_video_dimensions(self):
225        width = ffi.new("unsigned*")
226        height = ffi.new("unsigned*")
227        self._core.desiredVideoDimensions(self._core, width, height)
228        return width[0], height[0]
229
230    @protected
231    def set_video_buffer(self, image):
232        self._core.setVideoBuffer(self._core, image.buffer, image.stride)
233
234    @protected
235    def set_audio_buffer_size(self, size):
236        self._core.setAudioBufferSize(self._core, size)
237
238    @property
239    def audio_buffer_size(self):
240        return self._core.getAudioBufferSize(self._core)
241
242    @protected
243    def get_audio_channels(self):
244        return audio.StereoBuffer(self.get_audio_channel(0), self.get_audio_channel(1));
245
246    @protected
247    def get_audio_channel(self, channel):
248        return audio.Buffer(self._core.getAudioChannel(self._core, channel), self.frequency)
249
250    @protected
251    def reset(self):
252        self._core.reset(self._core)
253        self._load()
254
255    @needs_reset
256    @protected
257    def run_frame(self):
258        self._core.runFrame(self._core)
259
260    @needs_reset
261    @protected
262    def run_loop(self):
263        self._core.runLoop(self._core)
264
265    @needs_reset
266    @protected
267    def step(self):
268        self._core.step(self._core)
269
270    @needs_reset
271    @protected
272    def load_raw_state(self, state):
273        if len(state) < self._core.stateSize(self._core):
274            return False
275        return self._core.loadState(self._core, state)
276
277    @needs_reset
278    @protected
279    def save_raw_state(self):
280        state = ffi.new('unsigned char[%i]' % self._core.stateSize(self._core))
281        if self._core.saveState(self._core, state):
282            return state
283        return None
284
285    @staticmethod
286    def _keys_to_int(*args, **kwargs):
287        keys = 0
288        if 'raw' in kwargs:
289            keys = kwargs['raw']
290        for key in args:
291            keys |= 1 << key
292        return keys
293
294    @protected
295    def set_keys(self, *args, **kwargs):
296        self._core.setKeys(self._core, self._keys_to_int(*args, **kwargs))
297
298    @protected
299    def add_keys(self, *args, **kwargs):
300        self._core.addKeys(self._core, self._keys_to_int(*args, **kwargs))
301
302    @protected
303    def clear_keys(self, *args, **kwargs):
304        self._core.clearKeys(self._core, self._keys_to_int(*args, **kwargs))
305
306    @property
307    @needs_reset
308    def frame_counter(self):
309        return self._core.frameCounter(self._core)
310
311    @property
312    def frame_cycles(self):
313        return self._core.frameCycles(self._core)
314
315    @property
316    def frequency(self):
317        return self._core.frequency(self._core)
318
319    @property
320    def game_title(self):
321        title = ffi.new("char[16]")
322        self._core.getGameTitle(self._core, title)
323        return ffi.string(title, 16).decode("ascii")
324
325    @property
326    def game_code(self):
327        code = ffi.new("char[12]")
328        self._core.getGameCode(self._core, code)
329        return ffi.string(code, 12).decode("ascii")
330
331    def add_frame_callback(self, callback):
332        self._callbacks.video_frame_ended.append(callback)
333
334    @property
335    def crc32(self):
336        return self._native.romCrc32
337
338
339class ICoreOwner(object):
340    def claim(self):
341        raise NotImplementedError
342
343    def release(self):
344        raise NotImplementedError
345
346    def __enter__(self):
347        self.core = self.claim()
348        self.core._protected = True
349        return self.core
350
351    def __exit__(self, type, value, traceback):
352        self.core._protected = False
353        self.release()
354
355
356class IRunner(object):
357    def pause(self):
358        raise NotImplementedError
359
360    def unpause(self):
361        raise NotImplementedError
362
363    def use_core(self):
364        raise NotImplementedError
365
366    @property
367    def running(self):
368        raise NotImplementedError
369
370    @property
371    def paused(self):
372        raise NotImplementedError
373
374
375class Config(object):
376    def __init__(self, native=None, port=None, defaults={}):
377        if not native:
378            self._port = ffi.NULL
379            if port:
380                self._port = ffi.new("char[]", port.encode("UTF-8"))
381            native = ffi.gc(ffi.new("struct mCoreConfig*"), lib.mCoreConfigDeinit)
382            lib.mCoreConfigInit(native, self._port)
383        self._native = native
384        for key, value in defaults.items():
385            if isinstance(value, bool):
386                value = int(value)
387            lib.mCoreConfigSetDefaultValue(self._native, ffi.new("char[]", key.encode("UTF-8")), ffi.new("char[]", str(value).encode("UTF-8")))
388
389    def __getitem__(self, key):
390        string = lib.mCoreConfigGetValue(self._native, ffi.new("char[]", key.encode("UTF-8")))
391        if not string:
392            return None
393        return ffi.string(string)
394
395    def __setitem__(self, key, value):
396        if isinstance(value, bool):
397            value = int(value)
398        lib.mCoreConfigSetValue(self._native, ffi.new("char[]", key.encode("UTF-8")), ffi.new("char[]", str(value).encode("UTF-8")))