all repos — mgba @ 7fa8de1f0d3a9b10f346bce0bdb81d1ad543e322

mGBA Game Boy Advance Emulator

Python: Revamp a bunch of stuff
Vicki Pfau vi@endrift.com
Mon, 25 Jun 2018 15:38:31 -0700
commit

7fa8de1f0d3a9b10f346bce0bdb81d1ad543e322

parent

3f05b12bc11d152c51516470b763939e05a842cb

A src/platform/python/.pylintrc

@@ -0,0 +1,2 @@

+[MESSAGES CONTROL] +disable=line-too-long,missing-docstring,too-few-public-methods,too-many-instance-attributes,too-many-public-methods,wrong-import-order
M src/platform/python/CMakeLists.txtsrc/platform/python/CMakeLists.txt

@@ -11,15 +11,6 @@ endforeach()

file(GLOB PYTHON_HEADERS ${CMAKE_CURRENT_SOURCE_DIR}/*.h) -if(NOT GIT_TAG) - if(GIT_BRANCH STREQUAL "master" OR NOT GIT_BRANCH) - set(PYLIB_VERSION -b .dev${GIT_REV}+g${GIT_COMMIT_SHORT}) - else() - set(PYLIB_VERSION -b .dev${GIT_REV}+${GIT_BRANCH}.g${GIT_COMMIT_SHORT}) - endif() -endif() -configure_file(${CMAKE_CURRENT_SOURCE_DIR}/setup.py.in ${CMAKE_CURRENT_BINARY_DIR}/setup.py) - add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/lib.c COMMAND BINDIR=${CMAKE_CURRENT_BINARY_DIR}/.. CPPFLAGS="${INCLUDE_FLAGS}" ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/_builder.py COMMAND ${CMAKE_COMMAND} -E touch ${CMAKE_CURRENT_BINARY_DIR}/lib.c

@@ -35,15 +26,29 @@ set_target_properties(${BINARY_NAME}-pylib PROPERTIES INCLUDE_DIRECTORIES "${CMAKE_BINARY_DIR};${INCLUDE_DIRECTORIES}")

set_target_properties(${BINARY_NAME}-pylib PROPERTIES COMPILE_DEFINITIONS "${OS_DEFINES};${FEATURE_DEFINES};${FUNCTION_DEFINES}") add_custom_target(${BINARY_NAME}-py ALL - COMMAND ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/setup.py egg_info -e ${CMAKE_CURRENT_BINARY_DIR} ${PYLIB_VERSION} - COMMAND BINDIR=${CMAKE_CURRENT_BINARY_DIR}/.. CPPFLAGS="${INCLUDE_FLAGS}" ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/setup.py build -b ${CMAKE_CURRENT_BINARY_DIR} + COMMAND BINDIR=${CMAKE_CURRENT_BINARY_DIR}/.. LIBDIR=${CMAKE_CURRENT_BINARY_DIR}/.. CPPFLAGS="${INCLUDE_FLAGS}" ${PYTHON_EXECUTABLE} setup.py build -b ${CMAKE_CURRENT_BINARY_DIR} WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} DEPENDS ${BINARY_NAME} - DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/setup.py + DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/setup.py DEPENDS ${PYTHON_HEADERS} DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/_builder.py DEPENDS ${BINARY_NAME}-pylib) +add_custom_target(${BINARY_NAME}-py-install + COMMAND BINDIR=${CMAKE_CURRENT_BINARY_DIR}/.. LIBDIR=${CMAKE_CURRENT_BINARY_DIR}/.. CPPFLAGS="${INCLUDE_FLAGS}" ${PYTHON_EXECUTABLE} setup.py install -b ${CMAKE_CURRENT_BINARY_DIR} + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + DEPENDS ${BINARY_NAME}-py) + +add_custom_target(${BINARY_NAME}-py-develop + COMMAND BINDIR=${CMAKE_CURRENT_BINARY_DIR}/.. LIBDIR=${CMAKE_CURRENT_BINARY_DIR}/.. CPPFLAGS="${INCLUDE_FLAGS}" ${PYTHON_EXECUTABLE} setup.py develop -b ${CMAKE_CURRENT_BINARY_DIR} + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + DEPENDS ${BINARY_NAME}-py) + +add_custom_target(${BINARY_NAME}-py-bdist + COMMAND BINDIR=${CMAKE_CURRENT_BINARY_DIR}/.. LIBDIR=${CMAKE_CURRENT_BINARY_DIR}/.. CPPFLAGS="${INCLUDE_FLAGS}" ${PYTHON_EXECUTABLE} setup.py bdist_wheel -b ${CMAKE_CURRENT_BINARY_DIR} + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + DEPENDS ${BINARY_NAME}-py) + file(GLOB BASE_TESTS ${CMAKE_CURRENT_SOURCE_DIR}/test_*.py) file(GLOB SUBTESTS ${CMAKE_CURRENT_SOURCE_DIR}/tests/*/test_*.py) foreach(TEST IN LISTS BASE_TESTS SUBTESTS)

@@ -56,6 +61,8 @@ set(PATH LD_LIBRARY_PATH)

endif() string(REGEX REPLACE "${CMAKE_CURRENT_SOURCE_DIR}/(tests/.*/)?test_" "" TEST_NAME "${TEST}") string(REPLACE ".py" "" TEST_NAME "${TEST_NAME}") - add_test(python-${TEST_NAME} ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/setup.py build -b ${CMAKE_CURRENT_BINARY_DIR} pytest --extras --addopts ${TEST}) - set_tests_properties(python-${TEST_NAME} PROPERTIES ENVIRONMENT "${PATH}=${CMAKE_CURRENT_BINARY_DIR}/..") + add_test(NAME python-${TEST_NAME} + COMMAND ${PYTHON_EXECUTABLE} setup.py build -b ${CMAKE_CURRENT_BINARY_DIR} pytest --extras --addopts ${TEST} + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) + set_tests_properties(python-${TEST_NAME} PROPERTIES ENVIRONMENT "${PATH}=${CMAKE_CURRENT_BINARY_DIR}/..;BINDIR=${CMAKE_CURRENT_BINARY_DIR}/..;LIBDIR=${CMAKE_CURRENT_BINARY_DIR}/..;CPPFLAGS=${INCLUDE_FLAGS}") endforeach()
M src/platform/python/_builder.pysrc/platform/python/_builder.py

@@ -78,10 +78,6 @@ ffi.embedding_api('\n'.join(lines))

ffi.embedding_init_code(""" import os, os.path - venv = os.getenv('VIRTUAL_ENV') - if venv: - activate = os.path.join(venv, 'bin', 'activate_this.py') - exec(compile(open(activate, "rb").read(), activate, 'exec'), dict(__file__=activate)) from mgba._pylib import ffi, lib symbols = {} globalSyms = {

@@ -109,7 +105,7 @@ def mPythonLoadScript(name, vf):

from mgba.vfs import VFile vf = VFile(vf) name = ffi.string(name) - source = vf.readAll().decode('utf-8') + source = vf.read_all().decode('utf-8') try: code = compile(source, name, 'exec') pendingCode.append(code)
M src/platform/python/cinema/__init__.pysrc/platform/python/cinema/__init__.py

@@ -2,15 +2,16 @@ from PIL.ImageChops import difference

from PIL.ImageOps import autocontrast from PIL.Image import open as PIOpen + class VideoFrame(object): - def __init__(self, pilImage): - self.image = pilImage.convert('RGB') + def __init__(self, pil_image): + self.image = pil_image.convert('RGB') @staticmethod def diff(a, b): diff = difference(a.image, b.image) - diffNormalized = autocontrast(diff) - return (VideoFrame(diff), VideoFrame(diffNormalized)) + diff_normalized = autocontrast(diff) + return (VideoFrame(diff), VideoFrame(diff_normalized)) @staticmethod def load(path):
M src/platform/python/cinema/movie.pysrc/platform/python/cinema/movie.py

@@ -4,44 +4,38 @@ from . import VideoFrame

Output = namedtuple('Output', ['video']) + class Tracer(object): def __init__(self, core): self.core = core - self.fb = Image(*core.desiredVideoDimensions()) - self.core.setVideoBuffer(self.fb) - self._videoFifo = [] + self.framebuffer = Image(*core.desired_video_dimensions()) + self.core.set_video_buffer(self.framebuffer) + self._video_fifo = [] - def yieldFrames(self, skip=0, limit=None): + def yield_frames(self, skip=0, limit=None): self.core.reset() skip = (skip or 0) + 1 while skip > 0: - frame = self.core.frameCounter - self.core.runFrame() + frame = self.core.frame_counter + self.core.run_frame() skip -= 1 - while frame <= self.core.frameCounter and limit != 0: - self._videoFifo.append(VideoFrame(self.fb.toPIL())) + while frame <= self.core.frame_counter and limit != 0: + self._video_fifo.append(VideoFrame(self.framebuffer.to_pil())) yield frame - frame = self.core.frameCounter - self.core.runFrame() + frame = self.core.frame_counter + self.core.run_frame() if limit is not None: assert limit >= 0 limit -= 1 def video(self, generator=None, **kwargs): if not generator: - generator = self.yieldFrames(**kwargs) + generator = self.yield_frames(**kwargs) try: while True: - if self._videoFifo: - result = self._videoFifo[0] - self._videoFifo = self._videoFifo[1:] - yield result + if self._video_fifo: + yield self._video_fifo.pop(0) else: next(generator) except StopIteration: return - - def output(self, **kwargs): - generator = self.yieldFrames(**kwargs) - - return mCoreOutput(video=self.video(generator=generator, **kwargs))
M src/platform/python/cinema/test.pysrc/platform/python/cinema/test.py

@@ -1,5 +1,7 @@

-import os, os.path -import mgba.core, mgba.image +import os +import os.path +import mgba.core +import mgba.image import cinema.movie import itertools import glob

@@ -7,20 +9,21 @@ import re

import yaml from copy import deepcopy from cinema import VideoFrame -from cinema.util import dictMerge +from cinema.util import dict_merge + class CinemaTest(object): TEST = 'test.(mvl|gb|gba|nds)' def __init__(self, path, root, settings={}): - self.fullPath = path or [] - self.path = os.path.abspath(os.path.join(root, *self.fullPath)) + self.full_path = path or [] + self.path = os.path.abspath(os.path.join(root, *self.full_path)) self.root = root self.name = '.'.join(path) self.settings = settings try: with open(os.path.join(self.path, 'manifest.yml'), 'r') as f: - dictMerge(self.settings, yaml.safe_load(f)) + dict_merge(self.settings, yaml.safe_load(f)) except IOError: pass self.tests = {}

@@ -28,41 +31,42 @@

def __repr__(self): return '<%s %s>' % (self.__class__.__name__, self.name) - def setUp(self): + def setup(self): results = [f for f in glob.glob(os.path.join(self.path, 'test.*')) if re.search(self.TEST, f)] - self.core = mgba.core.loadPath(results[0]) + self.core = mgba.core.load_path(results[0]) if 'config' in self.settings: self.config = mgba.core.Config(defaults=self.settings['config']) - self.core.loadConfig(self.config) + self.core.load_config(self.config) self.core.reset() - def addTest(self, name, cls=None, settings={}): + def add_test(self, name, cls=None, settings={}): cls = cls or self.__class__ - newSettings = deepcopy(self.settings) - dictMerge(newSettings, settings) - self.tests[name] = cls(self.fullPath + [name], self.root, newSettings) + new_settings = deepcopy(self.settings) + dict_merge(new_settings, settings) + self.tests[name] = cls(self.full_path + [name], self.root, new_settings) return self.tests[name] - def outputSettings(self): - outputSettings = {} + def output_settings(self): + output_settings = {} if 'frames' in self.settings: - outputSettings['limit'] = self.settings['frames'] + output_settings['limit'] = self.settings['frames'] if 'skip' in self.settings: - outputSettings['skip'] = self.settings['skip'] - return outputSettings + output_settings['skip'] = self.settings['skip'] + return output_settings def __lt__(self, other): return self.path < other.path + class VideoTest(CinemaTest): BASELINE = 'baseline_%04u.png' - def setUp(self): - super(VideoTest, self).setUp(); + def setup(self): + super(VideoTest, self).setup() self.tracer = cinema.movie.Tracer(self.core) - def generateFrames(self): - for i, frame in zip(itertools.count(), self.tracer.video(**self.outputSettings())): + def generate_frames(self): + for i, frame in zip(itertools.count(), self.tracer.video(**self.output_settings())): try: baseline = VideoFrame.load(os.path.join(self.path, self.BASELINE % i)) yield baseline, frame, VideoFrame.diff(baseline, frame)

@@ -70,14 +74,15 @@ except IOError:

yield None, frame, (None, None) def test(self): - self.baseline, self.frames, self.diffs = zip(*self.generateFrames()) + self.baseline, self.frames, self.diffs = zip(*self.generate_frames()) assert not any(any(diffs[0].image.convert("L").point(bool).getdata()) for diffs in self.diffs) - def generateBaseline(self): - for i, frame in zip(itertools.count(), self.tracer.video(**self.outputSettings())): + def generate_baseline(self): + for i, frame in zip(itertools.count(), self.tracer.video(**self.output_settings())): frame.save(os.path.join(self.path, self.BASELINE % i)) -def gatherTests(root=os.getcwd()): + +def gather_tests(root=os.getcwd()): tests = CinemaTest([], root) for path, _, files in os.walk(root): test = [f for f in files if re.match(CinemaTest.TEST, f)]

@@ -85,12 +90,12 @@ if not test:

continue prefix = os.path.commonprefix([path, root]) suffix = path[len(prefix)+1:] - testPath = suffix.split(os.sep) - testRoot = tests - for component in testPath[:-1]: - newTest = testRoot.tests.get(component) - if not newTest: - newTest = testRoot.addTest(component) - testRoot = newTest - testRoot.addTest(testPath[-1], VideoTest) + test_path = suffix.split(os.sep) + test_root = tests + for component in test_path[:-1]: + new_test = test_root.tests.get(component) + if not new_test: + new_test = test_root.add_test(component) + test_root = new_test + test_root.add_test(test_path[-1], VideoTest) return tests
M src/platform/python/cinema/util.pysrc/platform/python/cinema/util.py

@@ -1,8 +1,8 @@

-def dictMerge(a, b): +def dict_merge(a, b): for key, value in b.items(): if isinstance(value, dict): if key in a: - dictMerge(a[key], value) + dict_merge(a[key], value) else: a[key] = dict(value) else:
M src/platform/python/mgba/__init__.pysrc/platform/python/mgba/__init__.py

@@ -3,31 +3,34 @@ #

# This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. -from ._pylib import ffi, lib +from ._pylib import ffi, lib # pylint: disable=no-name-in-module from collections import namedtuple -def createCallback(structName, cbName, funcName=None): - funcName = funcName or "_py{}{}".format(structName, cbName[0].upper() + cbName[1:]) - fullStruct = "struct {}*".format(structName) - def cb(handle, *args): - h = ffi.cast(fullStruct, handle) - return getattr(ffi.from_handle(h.pyobj), cbName)(*args) + +def create_callback(struct_name, cb_name, func_name=None): + func_name = func_name or "_py{}{}".format(struct_name, cb_name[0].upper() + cb_name[1:]) + full_struct = "struct {}*".format(struct_name) + + def callback(handle, *args): + handle = ffi.cast(full_struct, handle) + return getattr(ffi.from_handle(handle.pyobj), cb_name)(*args) + + return ffi.def_extern(name=func_name)(callback) - return ffi.def_extern(name=funcName)(cb) -version = ffi.string(lib.projectVersion).decode('utf-8') +__version__ = ffi.string(lib.projectVersion).decode('utf-8') GitInfo = namedtuple("GitInfo", "commit commitShort branch revision") -git = {} +GIT = {} if lib.gitCommit and lib.gitCommit != "(unknown)": - git['commit'] = ffi.string(lib.gitCommit).decode('utf-8') + GIT['commit'] = ffi.string(lib.gitCommit).decode('utf-8') if lib.gitCommitShort and lib.gitCommitShort != "(unknown)": - git['commitShort'] = ffi.string(lib.gitCommitShort).decode('utf-8') + GIT['commitShort'] = ffi.string(lib.gitCommitShort).decode('utf-8') if lib.gitBranch and lib.gitBranch != "(unknown)": - git['branch'] = ffi.string(lib.gitBranch).decode('utf-8') + GIT['branch'] = ffi.string(lib.gitBranch).decode('utf-8') if lib.gitRevision > 0: - git['revision'] = lib.gitRevision + GIT['revision'] = lib.gitRevision -git = GitInfo(**git) +GIT = GitInfo(**GIT)
M src/platform/python/mgba/arm.pysrc/platform/python/mgba/arm.py

@@ -3,21 +3,23 @@ #

# This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. -from ._pylib import ffi, lib +from ._pylib import ffi, lib # pylint: disable=no-name-in-module + class _ARMRegisters: def __init__(self, cpu): self._cpu = cpu - def __getitem__(self, r): - if r > lib.ARM_PC: + def __getitem__(self, reg): + if reg > lib.ARM_PC: raise IndexError("Register out of range") - return self._cpu._native.gprs[r] + return self._cpu._native.gprs[reg] - def __setitem__(self, r, value): - if r >= lib.ARM_PC: + def __setitem__(self, reg, value): + if reg >= lib.ARM_PC: raise IndexError("Register out of range") - self._cpu._native.gprs[r] = value + self._cpu._native.gprs[reg] = value + class ARMCore: def __init__(self, native):

@@ -25,4 +27,3 @@ self._native = ffi.cast("struct ARMCore*", native)

self.gprs = _ARMRegisters(self) self.cpsr = self._native.cpsr self.spsr = self._native.spsr -
M src/platform/python/mgba/core.pysrc/platform/python/mgba/core.py

@@ -3,9 +3,11 @@ #

# This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. -from ._pylib import ffi, lib -from . import tile, createCallback +from ._pylib import ffi, lib # pylint: disable=no-name-in-module +from . import tile from cached_property import cached_property +from functools import wraps + def find(path): core = lib.mCoreFind(path.encode('UTF-8'))

@@ -13,82 +15,95 @@ if core == ffi.NULL:

return None return Core._init(core) -def findVF(vf): - core = lib.mCoreFindVF(vf.handle) + +def find_vf(vfile): + core = lib.mCoreFindVF(vfile.handle) if core == ffi.NULL: return None return Core._init(core) -def loadPath(path): + +def load_path(path): core = find(path) - if not core or not core.loadFile(path): + if not core or not core.load_file(path): return None return core -def loadVF(vf): - core = findVF(vf) - if not core or not core.loadROM(vf): + +def load_vf(vfile): + core = find_vf(vfile) + if not core or not core.load_rom(vfile): return None return core -def needsReset(f): + +def needs_reset(func): + @wraps(func) def wrapper(self, *args, **kwargs): - if not self._wasReset: + if not self._was_reset: raise RuntimeError("Core must be reset first") - return f(self, *args, **kwargs) + return func(self, *args, **kwargs) return wrapper -def protected(f): + +def protected(func): + @wraps(func) def wrapper(self, *args, **kwargs): if self._protected: raise RuntimeError("Core is protected") - return f(self, *args, **kwargs) + return func(self, *args, **kwargs) return wrapper + @ffi.def_extern() -def _mCorePythonCallbacksVideoFrameStarted(user): +def _mCorePythonCallbacksVideoFrameStarted(user): # pylint: disable=invalid-name context = ffi.from_handle(user) - context._videoFrameStarted() + context._video_frame_started() + @ffi.def_extern() -def _mCorePythonCallbacksVideoFrameEnded(user): +def _mCorePythonCallbacksVideoFrameEnded(user): # pylint: disable=invalid-name context = ffi.from_handle(user) - context._videoFrameEnded() + context._video_frame_ended() + @ffi.def_extern() -def _mCorePythonCallbacksCoreCrashed(user): +def _mCorePythonCallbacksCoreCrashed(user): # pylint: disable=invalid-name context = ffi.from_handle(user) - context._coreCrashed() + context._core_crashed() + @ffi.def_extern() -def _mCorePythonCallbacksSleep(user): +def _mCorePythonCallbacksSleep(user): # pylint: disable=invalid-name context = ffi.from_handle(user) context._sleep() + class CoreCallbacks(object): def __init__(self): self._handle = ffi.new_handle(self) - self.videoFrameStarted = [] - self.videoFrameEnded = [] - self.coreCrashed = [] + self.video_frame_started = [] + self.video_frame_ended = [] + self.core_crashed = [] self.sleep = [] self.context = lib.mCorePythonCallbackCreate(self._handle) - def _videoFrameStarted(self): - for cb in self.videoFrameStarted: - cb() + def _video_frame_started(self): + for callback in self.video_frame_started: + callback() - def _videoFrameEnded(self): - for cb in self.videoFrameEnded: - cb() + def _video_frame_ended(self): + for callback in self.video_frame_ended: + callback() - def _coreCrashed(self): - for cb in self.coreCrashed: - cb() + def _core_crashed(self): + for callback in self.core_crashed: + callback() def _sleep(self): - for cb in self.sleep: - cb() + for callback in self.sleep: + callback() + class Core(object): if hasattr(lib, 'PLATFORM_GBA'):

@@ -99,36 +114,36 @@ PLATFORM_GB = lib.PLATFORM_GB

def __init__(self, native): self._core = native - self._wasReset = False + self._was_reset = False self._protected = False self._callbacks = CoreCallbacks() self._core.addCoreCallbacks(self._core, self._callbacks.context) self.config = Config(ffi.addressof(native.config)) def __del__(self): - self._wasReset = False + self._was_reset = False @cached_property - def graphicsCache(self): - if not self._wasReset: + def graphics_cache(self): + if not self._was_reset: raise RuntimeError("Core must be reset first") return tile.CacheSet(self) @cached_property def tiles(self): - t = [] - ts = ffi.addressof(self.graphicsCache.cache.tiles) - for i in range(lib.mTileCacheSetSize(ts)): - t.append(tile.TileView(lib.mTileCacheSetGetPointer(ts, i))) - return t + tiles = [] + native_tiles = ffi.addressof(self.graphics_cache.cache.tiles) + for i in range(lib.mTileCacheSetSize(native_tiles)): + tiles.append(tile.TileView(lib.mTileCacheSetGetPointer(native_tiles, i))) + return tiles @cached_property def maps(self): - m = [] - ms = ffi.addressof(self.graphicsCache.cache.maps) - for i in range(lib.mMapCacheSetSize(ms)): - m.append(tile.MapView(lib.mMapCacheSetGetPointer(ms, i))) - return m + maps = [] + native_maps = ffi.addressof(self.graphics_cache.cache.maps) + for i in range(lib.mMapCacheSetSize(native_maps)): + maps.append(tile.MapView(lib.mMapCacheSetGetPointer(native_maps, i))) + return maps @classmethod def _init(cls, native):

@@ -150,73 +165,73 @@ return GB(core)

return Core(core) def _load(self): - self._wasReset = True + self._was_reset = True - def loadFile(self, path): + def load_file(self, path): return bool(lib.mCoreLoadFile(self._core, path.encode('UTF-8'))) - def isROM(self, vf): - return bool(self._core.isROM(vf.handle)) + def is_rom(self, vfile): + return bool(self._core.isROM(vfile.handle)) - def loadROM(self, vf): - return bool(self._core.loadROM(self._core, vf.handle)) + def load_rom(self, vfile): + return bool(self._core.loadROM(self._core, vfile.handle)) - def loadBIOS(self, vf, id=0): - return bool(self._core.loadBIOS(self._core, vf.handle, id)) + def load_bios(self, vfile, id=0): + return bool(self._core.loadBIOS(self._core, vfile.handle, id)) - def loadSave(self, vf): - return bool(self._core.loadSave(self._core, vf.handle)) + def load_save(self, vfile): + return bool(self._core.loadSave(self._core, vfile.handle)) - def loadTemporarySave(self, vf): - return bool(self._core.loadTemporarySave(self._core, vf.handle)) + def load_temporary_save(self, vfile): + return bool(self._core.loadTemporarySave(self._core, vfile.handle)) - def loadPatch(self, vf): - return bool(self._core.loadPatch(self._core, vf.handle)) + def load_patch(self, vfile): + return bool(self._core.loadPatch(self._core, vfile.handle)) - def loadConfig(self, config): + def load_config(self, config): lib.mCoreLoadForeignConfig(self._core, config._native) - def autoloadSave(self): + def autoload_save(self): return bool(lib.mCoreAutoloadSave(self._core)) - def autoloadPatch(self): + def autoload_patch(self): return bool(lib.mCoreAutoloadPatch(self._core)) - def autoloadCheats(self): + def autoload_cheats(self): return bool(lib.mCoreAutoloadCheats(self._core)) def platform(self): return self._core.platform(self._core) - def desiredVideoDimensions(self): + def desired_video_dimensions(self): width = ffi.new("unsigned*") height = ffi.new("unsigned*") self._core.desiredVideoDimensions(self._core, width, height) return width[0], height[0] - def setVideoBuffer(self, image): + def set_video_buffer(self, image): self._core.setVideoBuffer(self._core, image.buffer, image.stride) def reset(self): self._core.reset(self._core) self._load() - @needsReset + @needs_reset @protected - def runFrame(self): + def run_frame(self): self._core.runFrame(self._core) - @needsReset + @needs_reset @protected - def runLoop(self): + def run_loop(self): self._core.runLoop(self._core) - @needsReset + @needs_reset def step(self): self._core.step(self._core) @staticmethod - def _keysToInt(*args, **kwargs): + def _keys_to_int(*args, **kwargs): keys = 0 if 'raw' in kwargs: keys = kwargs['raw']

@@ -224,22 +239,25 @@ for key in args:

keys |= 1 << key return keys - def setKeys(self, *args, **kwargs): - self._core.setKeys(self._core, self._keysToInt(*args, **kwargs)) + @protected + def set_keys(self, *args, **kwargs): + self._core.setKeys(self._core, self._keys_to_int(*args, **kwargs)) - def addKeys(self, *args, **kwargs): - self._core.addKeys(self._core, self._keysToInt(*args, **kwargs)) + @protected + def add_keys(self, *args, **kwargs): + self._core.addKeys(self._core, self._keys_to_int(*args, **kwargs)) - def clearKeys(self, *args, **kwargs): - self._core.clearKeys(self._core, self._keysToInt(*args, **kwargs)) + @protected + def clear_keys(self, *args, **kwargs): + self._core.clearKeys(self._core, self._keys_to_int(*args, **kwargs)) @property - @needsReset - def frameCounter(self): + @needs_reset + def frame_counter(self): return self._core.frameCounter(self._core) @property - def frameCycles(self): + def frame_cycles(self): return self._core.frameCycles(self._core) @property

@@ -247,23 +265,24 @@ def frequency(self):

return self._core.frequency(self._core) @property - def gameTitle(self): + def game_title(self): title = ffi.new("char[16]") self._core.getGameTitle(self._core, title) return ffi.string(title, 16).decode("ascii") @property - def gameCode(self): + def game_code(self): code = ffi.new("char[12]") self._core.getGameCode(self._core, code) return ffi.string(code, 12).decode("ascii") - def addFrameCallback(self, cb): - self._callbacks.videoFrameEnded.append(cb) + def add_frame_callback(self, callback): + self._callbacks.video_frame_ended.append(callback) @property def crc32(self): return self._native.romCrc32 + class ICoreOwner(object): def claim(self):

@@ -281,6 +300,7 @@ def __exit__(self, type, value, traceback):

self.core._protected = False self.release() + class IRunner(object): def pause(self): raise NotImplementedError

@@ -288,14 +308,17 @@

def unpause(self): raise NotImplementedError - def useCore(self): + def use_core(self): raise NotImplementedError - def isRunning(self): + @property + def running(self): raise NotImplementedError - def isPaused(self): + @property + def paused(self): raise NotImplementedError + class Config(object): def __init__(self, native=None, port=None, defaults={}):
M src/platform/python/mgba/debugger.pysrc/platform/python/mgba/debugger.py

@@ -3,25 +3,26 @@ #

# This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. -from ._pylib import ffi, lib +from ._pylib import ffi, lib # pylint: disable=no-name-in-module from .core import IRunner, ICoreOwner, Core -import io import sys + class DebuggerCoreOwner(ICoreOwner): def __init__(self, debugger): self.debugger = debugger - self.wasPaused = False + self.was_paused = False def claim(self): if self.debugger.isRunning(): - self.wasPaused = True + self.was_paused = True self.debugger.pause() return self.debugger._core def release(self): - if self.wasPaused: + if self.was_paused: self.debugger.unpause() + class NativeDebugger(IRunner): WATCHPOINT_WRITE = lib.WATCHPOINT_WRITE

@@ -49,37 +50,40 @@

def unpause(self): self._native.state = lib.DEBUGGER_RUNNING - def isRunning(self): + @property + def running(self): return self._native.state == lib.DEBUGGER_RUNNING - def isPaused(self): + @property + def paused(self): return self._native.state in (lib.DEBUGGER_PAUSED, lib.DEBUGGER_CUSTOM) - def useCore(self): + def use_core(self): return DebuggerCoreOwner(self) - def setBreakpoint(self, address): + def set_breakpoint(self, address): if not self._native.platform.setBreakpoint: raise RuntimeError("Platform does not support breakpoints") self._native.platform.setBreakpoint(self._native.platform, address) - def clearBreakpoint(self, address): + def clear_breakpoint(self, address): if not self._native.platform.setBreakpoint: raise RuntimeError("Platform does not support breakpoints") self._native.platform.clearBreakpoint(self._native.platform, address) - def setWatchpoint(self, address): + def set_watchpoint(self, address): if not self._native.platform.setWatchpoint: raise RuntimeError("Platform does not support watchpoints") self._native.platform.setWatchpoint(self._native.platform, address) - def clearWatchpoint(self, address): + def clear_watchpoint(self, address): if not self._native.platform.clearWatchpoint: raise RuntimeError("Platform does not support watchpoints") self._native.platform.clearWatchpoint(self._native.platform, address) - def addCallback(self, cb): - self._cbs.append(cb) + def add_callback(self, callback): + self._cbs.append(callback) + class CLIBackend(object): def __init__(self, backend):

@@ -87,6 +91,7 @@ self.backend = backend

def write(self, string): self.backend.printf(string) + class CLIDebugger(NativeDebugger): def __init__(self, native):

@@ -97,5 +102,5 @@ def printf(self, message, *args, **kwargs):

message = message.format(*args, **kwargs) self._cli.backend.printf(ffi.new("char []", b"%s"), ffi.new("char []", message.encode('utf-8'))) - def installPrint(self): + def install_print(self): sys.stdout = CLIBackend(self)
M src/platform/python/mgba/gamedata.pysrc/platform/python/mgba/gamedata.py

@@ -8,6 +8,7 @@ import mgba_gamedata

except ImportError: pass + def search(core): crc32 = None if hasattr(core, 'PLATFORM_GBA') and core.platform() == core.PLATFORM_GBA:
M src/platform/python/mgba/gb.pysrc/platform/python/mgba/gb.py

@@ -3,12 +3,13 @@ #

# This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. -from ._pylib import ffi, lib +from ._pylib import ffi, lib # pylint: disable=no-name-in-module from .lr35902 import LR35902Core -from .core import Core, needsReset +from .core import Core, needs_reset from .memory import Memory from .tile import Sprite -from . import createCallback +from . import create_callback + class GB(Core): KEY_A = lib.GBA_KEY_A

@@ -25,31 +26,34 @@ super(GB, self).__init__(native)

self._native = ffi.cast("struct GB*", native.board) self.sprites = GBObjs(self) self.cpu = LR35902Core(self._core.cpu) + self.memory = None - @needsReset - def _initCache(self, cache): + @needs_reset + def _init_cache(self, cache): lib.GBVideoCacheInit(cache) lib.GBVideoCacheAssociate(cache, ffi.addressof(self._native.video)) - def _deinitCache(self, cache): + def _deinit_cache(self, cache): lib.mCacheSetDeinit(cache) - if self._wasReset: + if self._was_reset: self._native.video.renderer.cache = ffi.NULL def _load(self): super(GB, self)._load() self.memory = GBMemory(self._core) - def attachSIO(self, link): + def attach_sio(self, link): lib.GBSIOSetDriver(ffi.addressof(self._native.sio), link._native) def __del__(self): lib.GBSIOSetDriver(ffi.addressof(self._native.sio), ffi.NULL) -createCallback("GBSIOPythonDriver", "init") -createCallback("GBSIOPythonDriver", "deinit") -createCallback("GBSIOPythonDriver", "writeSB") -createCallback("GBSIOPythonDriver", "writeSC") + +create_callback("GBSIOPythonDriver", "init") +create_callback("GBSIOPythonDriver", "deinit") +create_callback("GBSIOPythonDriver", "writeSB") +create_callback("GBSIOPythonDriver", "writeSC") + class GBSIODriver(object): def __init__(self):

@@ -62,53 +66,55 @@

def deinit(self): pass - def writeSB(self, value): + def write_sb(self, value): pass - def writeSC(self, value): + def write_sc(self, value): return value + class GBSIOSimpleDriver(GBSIODriver): def __init__(self, period=0x100): super(GBSIOSimpleDriver, self).__init__() - self.rx = 0x00 + self.rx = 0x00 # pylint: disable=invalid-name self._period = period def init(self): self._native.p.period = self._period return True - def writeSB(self, value): - self.rx = value + def write_sb(self, value): + self.rx = value # pylint: disable=invalid-name - def writeSC(self, value): + def write_sc(self, value): self._native.p.period = self._period if value & 0x80: lib.mTimingDeschedule(ffi.addressof(self._native.p.p.timing), ffi.addressof(self._native.p.event)) lib.mTimingSchedule(ffi.addressof(self._native.p.p.timing), ffi.addressof(self._native.p.event), self._native.p.period) return value - def isReady(self): + def is_ready(self): return not self._native.p.remainingBits @property - def tx(self): - self._native.p.pendingSB + def tx(self): # pylint: disable=invalid-name + return self._native.p.pendingSB @property def period(self): return self._native.p.period @tx.setter - def tx(self, newTx): + def tx(self, newTx): # pylint: disable=invalid-name self._native.p.pendingSB = newTx self._native.p.remainingBits = 8 @period.setter - def period(self, newPeriod): - self._period = newPeriod + def period(self, new_period): + self._period = new_period if self._native.p: - self._native.p.period = newPeriod + self._native.p.period = new_period + class GBMemory(Memory): def __init__(self, core):

@@ -119,15 +125,16 @@ self.vram = Memory(core, lib.GB_SIZE_VRAM, lib.GB_BASE_VRAM)

self.sram = Memory(core, lib.GB_SIZE_EXTERNAL_RAM, lib.GB_REGION_EXTERNAL_RAM) self.iwram = Memory(core, lib.GB_SIZE_WORKING_RAM_BANK0, lib.GB_BASE_WORKING_RAM_BANK0) self.oam = Memory(core, lib.GB_SIZE_OAM, lib.GB_BASE_OAM) - self.io = Memory(core, lib.GB_SIZE_IO, lib.GB_BASE_IO) + self.io = Memory(core, lib.GB_SIZE_IO, lib.GB_BASE_IO) # pylint: disable=invalid-name self.hram = Memory(core, lib.GB_SIZE_HRAM, lib.GB_BASE_HRAM) + class GBSprite(Sprite): - PALETTE_BASE = 8, + PALETTE_BASE = (8,) def __init__(self, obj, core): - self.x = obj.x - self.y = obj.y + self.x = obj.x # pylint: disable=invalid-name + self.y = obj.y # pylint: disable=invalid-name self.tile = obj.tile self._attr = obj.attr self.width = 8

@@ -136,10 +143,10 @@ self.height = 16 if lcdc & 4 else 8

if core._native.model >= lib.GB_MODEL_CGB: if self._attr & 8: self.tile += 512 - self.paletteId = self._attr & 7 + self.palette_id = self._attr & 7 else: - self.paletteId = (self._attr >> 4) & 1 - self.paletteId += 8 + self.palette_id = (self._attr >> 4) & 1 + self.palette_id += 8 class GBObjs:
M src/platform/python/mgba/gba.pysrc/platform/python/mgba/gba.py

@@ -3,12 +3,13 @@ #

# This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. -from ._pylib import ffi, lib +from ._pylib import ffi, lib # pylint: disable=no-name-in-module from .arm import ARMCore -from .core import Core, needsReset +from .core import Core, needs_reset from .tile import Sprite from .memory import Memory -from . import createCallback +from . import create_callback + class GBA(Core): KEY_A = lib.GBA_KEY_A

@@ -34,23 +35,24 @@ super(GBA, self).__init__(native)

self._native = ffi.cast("struct GBA*", native.board) self.sprites = GBAObjs(self) self.cpu = ARMCore(self._core.cpu) + self.memory = None self._sio = set() - @needsReset - def _initCache(self, cache): + @needs_reset + def _init_cache(self, cache): lib.GBAVideoCacheInit(cache) lib.GBAVideoCacheAssociate(cache, ffi.addressof(self._native.video)) - def _deinitCache(self, cache): + def _deinit_cache(self, cache): lib.mCacheSetDeinit(cache) - if self._wasReset: + if self._was_reset: self._native.video.renderer.cache = ffi.NULL def _load(self): super(GBA, self)._load() self.memory = GBAMemory(self._core, self._native.memory.romSize) - def attachSIO(self, link, mode=lib.SIO_MULTI): + def attach_sio(self, link, mode=lib.SIO_MULTI): self._sio.add(mode) lib.GBASIOSetDriver(ffi.addressof(self._native.sio), link._native, mode)

@@ -58,14 +60,18 @@ def __del__(self):

for mode in self._sio: lib.GBASIOSetDriver(ffi.addressof(self._native.sio), ffi.NULL, mode) -createCallback("GBASIOPythonDriver", "init") -createCallback("GBASIOPythonDriver", "deinit") -createCallback("GBASIOPythonDriver", "load") -createCallback("GBASIOPythonDriver", "unload") -createCallback("GBASIOPythonDriver", "writeRegister") + +create_callback("GBASIOPythonDriver", "init") +create_callback("GBASIOPythonDriver", "deinit") +create_callback("GBASIOPythonDriver", "load") +create_callback("GBASIOPythonDriver", "unload") +create_callback("GBASIOPythonDriver", "writeRegister") + class GBASIODriver(object): def __init__(self): + super(GBASIODriver, self).__init__() + self._handle = ffi.new_handle(self) self._native = ffi.gc(lib.GBASIOPythonDriverCreate(self._handle), lib.free)

@@ -81,8 +87,9 @@

def unload(self): return True - def writeRegister(self, address, value): + def write_register(self, address, value): return value + class GBASIOJOYDriver(GBASIODriver): RESET = lib.JOY_RESET

@@ -91,10 +98,11 @@ TRANS = lib.JOY_TRANS

RECV = lib.JOY_RECV def __init__(self): - self._handle = ffi.new_handle(self) + super(GBASIOJOYDriver, self).__init__() + self._native = ffi.gc(lib.GBASIOJOYPythonDriverCreate(self._handle), lib.free) - def sendCommand(self, cmd, data): + def send_command(self, cmd, data): buffer = ffi.new('uint8_t[5]') try: buffer[0] = data[0]

@@ -110,6 +118,7 @@ if outlen > 0 and outlen <= 5:

return bytes(buffer[0:outlen]) return None + class GBAMemory(Memory): def __init__(self, core, romSize=lib.SIZE_CART0): super(GBAMemory, self).__init__(core, 0x100000000)

@@ -117,7 +126,7 @@

self.bios = Memory(core, lib.SIZE_BIOS, lib.BASE_BIOS) self.wram = Memory(core, lib.SIZE_WORKING_RAM, lib.BASE_WORKING_RAM) self.iwram = Memory(core, lib.SIZE_WORKING_IRAM, lib.BASE_WORKING_IRAM) - self.io = Memory(core, lib.SIZE_IO, lib.BASE_IO) + self.io = Memory(core, lib.SIZE_IO, lib.BASE_IO) # pylint: disable=invalid-name self.palette = Memory(core, lib.SIZE_PALETTE_RAM, lib.BASE_PALETTE_RAM) self.vram = Memory(core, lib.SIZE_VRAM, lib.BASE_VRAM) self.oam = Memory(core, lib.SIZE_OAM, lib.BASE_OAM)

@@ -127,6 +136,7 @@ self.cart2 = Memory(core, romSize, lib.BASE_CART2)

self.cart = self.cart0 self.rom = self.cart0 self.sram = Memory(core, lib.SIZE_CART_SRAM, lib.BASE_CART_SRAM) + class GBASprite(Sprite): TILE_BASE = 0x800, 0x400

@@ -136,18 +146,19 @@ def __init__(self, obj):

self._a = obj.a self._b = obj.b self._c = obj.c - self.x = self._b & 0x1FF - self.y = self._a & 0xFF + self.x = self._b & 0x1FF # pylint: disable=invalid-name + self.y = self._a & 0xFF # pylint: disable=invalid-name self._shape = self._a >> 14 self._size = self._b >> 14 - self._256Color = bool(self._a & 0x2000) + self._256_color = bool(self._a & 0x2000) self.width, self.height = lib.GBAVideoObjSizes[self._shape * 4 + self._size] self.tile = self._c & 0x3FF - if self._256Color: - self.paletteId = 0 + if self._256_color: + self.palette_id = 0 self.tile >>= 1 else: - self.paletteId = self._c >> 12 + self.palette_id = self._c >> 12 + class GBAObjs: def __init__(self, core):

@@ -161,7 +172,7 @@ def __getitem__(self, index):

if index >= len(self): raise IndexError() sprite = GBASprite(self._obj[index]) - tiles = self._core.tiles[3 if sprite._256Color else 2] - map1D = bool(self._core._native.memory.io[0] & 0x40) - sprite.constitute(tiles, 0 if map1D else 0x20) + tiles = self._core.tiles[3 if sprite._256_color else 2] + map_1d = bool(self._core._native.memory.io[0] & 0x40) + sprite.constitute(tiles, 0 if map_1d else 0x20) return sprite
M src/platform/python/mgba/image.pysrc/platform/python/mgba/image.py

@@ -3,13 +3,14 @@ #

# This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. -from ._pylib import ffi, lib +from ._pylib import ffi # pylint: disable=no-name-in-module from . import png try: import PIL.Image as PImage except ImportError: pass + class Image: def __init__(self, width, height, stride=0, alpha=False):

@@ -24,58 +25,63 @@ if self.stride <= 0:

self.stride = self.width self.buffer = ffi.new("color_t[{}]".format(self.stride * self.height)) - def savePNG(self, f): - p = png.PNG(f, mode=png.MODE_RGBA if self.alpha else png.MODE_RGB) - success = p.writeHeader(self) - success = success and p.writePixels(self) - p.writeClose() + def save_png(self, fileobj): + png_file = png.PNG(fileobj, mode=png.MODE_RGBA if self.alpha else png.MODE_RGB) + success = png_file.write_header(self) + success = success and png_file.write_pixels(self) + png_file.write_close() return success if 'PImage' in globals(): - def toPIL(self): - type = "RGBA" if self.alpha else "RGBX" - return PImage.frombytes(type, (self.width, self.height), ffi.buffer(self.buffer), "raw", - type, self.stride * 4) + def to_pil(self): + colorspace = "RGBA" if self.alpha else "RGBX" + return PImage.frombytes(colorspace, (self.width, self.height), ffi.buffer(self.buffer), "raw", + colorspace, self.stride * 4) + -def u16ToU32(c): - r = c & 0x1F - g = (c >> 5) & 0x1F - b = (c >> 10) & 0x1F - a = (c >> 15) & 1 +def u16_to_u32(color): + # pylint: disable=invalid-name + r = color & 0x1F + g = (color >> 5) & 0x1F + b = (color >> 10) & 0x1F + a = (color >> 15) & 1 abgr = r << 3 abgr |= g << 11 abgr |= b << 19 abgr |= (a * 0xFF) << 24 return abgr -def u32ToU16(c): - r = (c >> 3) & 0x1F - g = (c >> 11) & 0x1F - b = (c >> 19) & 0x1F - a = c >> 31 + +def u32_to_u16(color): + # pylint: disable=invalid-name + r = (color >> 3) & 0x1F + g = (color >> 11) & 0x1F + b = (color >> 19) & 0x1F + a = color >> 31 abgr = r abgr |= g << 5 abgr |= b << 10 abgr |= a << 15 return abgr + if ffi.sizeof("color_t") == 2: - def colorToU16(c): - return c + def color_to_u16(color): + return color - colorToU32 = u16ToU32 + color_to_u32 = u16_to_u32 # pylint: disable=invalid-name - def u16ToColor(c): - return c + def u16_to_color(color): + return color - u32ToColor = u32ToU16 + u32_to_color = u32_to_u16 # pylint: disable=invalid-name else: - def colorToU32(c): - return c + def color_to_u32(color): + return color - colorToU16 = u32ToU16 + color_to_u16 = u32_to_u16 # pylint: disable=invalid-name - def u32ToColor(c): - return c + def u32_to_color(color): + return color - u16ToColor = u16ToU32 + u16_to_color = u16_to_u32 # pylint: disable=invalid-name
M src/platform/python/mgba/log.pysrc/platform/python/mgba/log.py

@@ -3,17 +3,15 @@ #

# This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. -from ._pylib import ffi, lib -from . import createCallback +from ._pylib import ffi, lib # pylint: disable=no-name-in-module +from . import create_callback -createCallback("mLoggerPy", "log", "_pyLog") +create_callback("mLoggerPy", "log", "_pyLog") -defaultLogger = None -def installDefault(logger): - global defaultLogger - defaultLogger = logger - lib.mLogSetDefaultLogger(logger._native) +def install_default(logger): + Logger.install_default(logger) + class Logger(object): FATAL = lib.mLOG_FATAL

@@ -24,16 +22,24 @@ ERROR = lib.mLOG_ERROR

STUB = lib.mLOG_STUB GAME_ERROR = lib.mLOG_GAME_ERROR + _DEFAULT_LOGGER = None + def __init__(self): self._handle = ffi.new_handle(self) self._native = ffi.gc(lib.mLoggerPythonCreate(self._handle), lib.free) @staticmethod - def categoryName(category): + def category_name(category): return ffi.string(lib.mLogCategoryName(category)).decode('UTF-8') + @classmethod + def install_default(cls, logger): + cls._DEFAULT_LOGGER = logger + lib.mLogSetDefaultLogger(logger._native) + def log(self, category, level, message): - print("{}: {}".format(self.categoryName(category), message)) + print("{}: {}".format(self.category_name(category), message)) + class NullLogger(Logger): def log(self, category, level, message):
M src/platform/python/mgba/lr35902.pysrc/platform/python/mgba/lr35902.py

@@ -3,9 +3,11 @@ #

# This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. -from ._pylib import ffi, lib +from ._pylib import ffi # pylint: disable=no-name-in-module + class LR35902Core: + # pylint: disable=invalid-name def __init__(self, native): self._native = ffi.cast("struct LR35902Core*", native)
M src/platform/python/mgba/memory.pysrc/platform/python/mgba/memory.py

@@ -3,7 +3,8 @@ #

# This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. -from ._pylib import ffi, lib +from ._pylib import ffi, lib # pylint: disable=no-name-in-module + class MemoryView(object): def __init__(self, core, width, size, base=0, sign="u"):

@@ -11,11 +12,11 @@ self._core = core

self._width = width self._size = size self._base = base - self._busRead = getattr(self._core, "busRead" + str(width * 8)) - self._busWrite = getattr(self._core, "busWrite" + str(width * 8)) - self._rawRead = getattr(self._core, "rawRead" + str(width * 8)) - self._rawWrite = getattr(self._core, "rawWrite" + str(width * 8)) - self._mask = (1 << (width * 8)) - 1 # Used to force values to fit within range so that negative values work + self._bus_read = getattr(self._core, "busRead" + str(width * 8)) + self._bus_write = getattr(self._core, "busWrite" + str(width * 8)) + self._raw_read = getattr(self._core, "rawRead" + str(width * 8)) + self._raw_write = getattr(self._core, "rawWrite" + str(width * 8)) + self._mask = (1 << (width * 8)) - 1 # Used to force values to fit within range so that negative values work if sign == "u" or sign == "unsigned": self._type = "uint{}_t".format(width * 8) elif sign == "i" or sign == "s" or sign == "signed":

@@ -23,7 +24,7 @@ self._type = "int{}_t".format(width * 8)

else: raise ValueError("Invalid sign type: '{}'".format(sign)) - def _addrCheck(self, address): + def _addr_check(self, address): if isinstance(address, slice): start = address.start or 0 stop = self._size - self._width if address.stop is None else address.stop

@@ -39,33 +40,32 @@ def __len__(self):

return self._size def __getitem__(self, address): - self._addrCheck(address) + self._addr_check(address) if isinstance(address, slice): start = address.start or 0 stop = self._size - self._width if address.stop is None else address.stop step = address.step or self._width - return [int(ffi.cast(self._type, self._busRead(self._core, self._base + a))) for a in range(start, stop, step)] - else: - return int(ffi.cast(self._type, self._busRead(self._core, self._base + address))) + return [int(ffi.cast(self._type, self._bus_read(self._core, self._base + a))) for a in range(start, stop, step)] + return int(ffi.cast(self._type, self._bus_read(self._core, self._base + address))) def __setitem__(self, address, value): - self._addrCheck(address) + self._addr_check(address) if isinstance(address, slice): start = address.start or 0 stop = self._size - self._width if address.stop is None else address.stop step = address.step or self._width - for a in range(start, stop, step): - self._busWrite(self._core, self._base + a, value[a] & self._mask) + for addr in range(start, stop, step): + self._bus_write(self._core, self._base + addr, value[addr] & self._mask) else: - self._busWrite(self._core, self._base + address, value & self._mask) + self._bus_write(self._core, self._base + address, value & self._mask) - def rawRead(self, address, segment=-1): - self._addrCheck(address) - return int(ffi.cast(self._type, self._rawRead(self._core, self._base + address, segment))) + def raw_read(self, address, segment=-1): + self._addr_check(address) + return int(ffi.cast(self._type, self._raw_read(self._core, self._base + address, segment))) - def rawWrite(self, address, value, segment=-1): - self._addrCheck(address) - self._rawWrite(self._core, self._base + address, segment, value & self._mask) + def raw_write(self, address, value, segment=-1): + self._addr_check(address) + self._raw_write(self._core, self._base + address, segment, value & self._mask) class MemorySearchResult(object):

@@ -75,12 +75,13 @@ self.segment = result.segment

self.guessDivisor = result.guessDivisor self.type = result.type - if result.type == Memory.SEARCH_8: - self._memory = memory.u8 - elif result.type == Memory.SEARCH_16: - self._memory = memory.u16 - elif result.type == Memory.SEARCH_32: - self._memory = memory.u32 + if result.type == Memory.SEARCH_INT: + if result.width == 1: + self._memory = memory.u8 + elif result.width == 2: + self._memory = memory.u16 + elif result.width == 4: + self._memory = memory.u32 elif result.type == Memory.SEARCH_STRING: self._memory = memory.u8 else:

@@ -123,7 +124,7 @@ self.s16 = MemoryView(core, 2, size, base, "s")

self.s32 = MemoryView(core, 4, size, base, "s") def __len__(self): - return self._size + return self.size def search(self, value, type=SEARCH_GUESS, flags=RW, limit=10000, old_results=[]): results = ffi.new("struct mCoreMemorySearchResults*")

@@ -138,11 +139,11 @@ else:

params.valueStr = ffi.new("char[]", str(value).encode("ascii")) for result in old_results: - r = lib.mCoreMemorySearchResultsAppend(results) - r.address = result.address - r.segment = result.segment - r.guessDivisor = result.guessDivisor - r.type = result.type + native_result = lib.mCoreMemorySearchResultsAppend(results) + native_result.address = result.address + native_result.segment = result.segment + native_result.guessDivisor = result.guessDivisor + native_result.type = result.type if old_results: lib.mCoreMemorySearchRepeat(self._core, params, results) else:

@@ -154,5 +155,4 @@

def __getitem__(self, address): if isinstance(address, slice): return bytearray(self.u8[address]) - else: - return self.u8[address] + return self.u8[address]
M src/platform/python/mgba/png.pysrc/platform/python/mgba/png.py

@@ -3,37 +3,41 @@ #

# This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. -from ._pylib import ffi, lib +from ._pylib import ffi, lib # pylint: disable=no-name-in-module from . import vfs MODE_RGB = 0 MODE_RGBA = 1 MODE_INDEX = 2 + class PNG: - def __init__(self, f, mode=MODE_RGB): - self.vf = vfs.open(f) - self.mode = mode + def __init__(self, f, mode=MODE_RGB): + self._vfile = vfs.open(f) + self._png = None + self._info = None + self.mode = mode - def writeHeader(self, image): - self._png = lib.PNGWriteOpen(self.vf.handle) - if self.mode == MODE_RGB: - self._info = lib.PNGWriteHeader(self._png, image.width, image.height) - if self.mode == MODE_RGBA: - self._info = lib.PNGWriteHeaderA(self._png, image.width, image.height) - if self.mode == MODE_INDEX: - self._info = lib.PNGWriteHeader8(self._png, image.width, image.height) - return self._info != ffi.NULL + def write_header(self, image): + self._png = lib.PNGWriteOpen(self._vfile.handle) + if self.mode == MODE_RGB: + self._info = lib.PNGWriteHeader(self._png, image.width, image.height) + if self.mode == MODE_RGBA: + self._info = lib.PNGWriteHeaderA(self._png, image.width, image.height) + if self.mode == MODE_INDEX: + self._info = lib.PNGWriteHeader8(self._png, image.width, image.height) + return self._info != ffi.NULL - def writePixels(self, image): - if self.mode == MODE_RGB: - return lib.PNGWritePixels(self._png, image.width, image.height, image.stride, image.buffer) - if self.mode == MODE_RGBA: - return lib.PNGWritePixelsA(self._png, image.width, image.height, image.stride, image.buffer) - if self.mode == MODE_INDEX: - return lib.PNGWritePixels8(self._png, image.width, image.height, image.stride, image.buffer) + def write_pixels(self, image): + if self.mode == MODE_RGB: + return lib.PNGWritePixels(self._png, image.width, image.height, image.stride, image.buffer) + if self.mode == MODE_RGBA: + return lib.PNGWritePixelsA(self._png, image.width, image.height, image.stride, image.buffer) + if self.mode == MODE_INDEX: + return lib.PNGWritePixels8(self._png, image.width, image.height, image.stride, image.buffer) + return False - def writeClose(self): - lib.PNGWriteClose(self._png, self._info) - del self._png - del self._info + def write_close(self): + lib.PNGWriteClose(self._png, self._info) + self._png = None + self._info = None
M src/platform/python/mgba/thread.pysrc/platform/python/mgba/thread.py

@@ -3,8 +3,9 @@ #

# This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. -from ._pylib import ffi, lib +from ._pylib import ffi, lib # pylint: disable=no-name-in-module from .core import IRunner, ICoreOwner, Core + class ThreadCoreOwner(ICoreOwner): def __init__(self, thread):

@@ -18,13 +19,14 @@ return self.thread._core

def release(self): lib.mCoreThreadContinue(self.thread._native) + class Thread(IRunner): def __init__(self, native=None): if native: self._native = native self._core = Core(native.core) - self._core._wasReset = lib.mCoreThreadHasStarted(self._native) + self._core._was_reset = lib.mCoreThreadHasStarted(self._native) else: self._native = ffi.new("struct mCoreThread*")

@@ -34,7 +36,7 @@ raise ValueError

self._core = core self._native.core = core._core lib.mCoreThreadStart(self._native) - self._core._wasReset = lib.mCoreThreadHasStarted(self._native) + self._core._was_reset = lib.mCoreThreadHasStarted(self._native) def end(self): if not lib.mCoreThreadHasStarted(self._native):

@@ -48,11 +50,13 @@

def unpause(self): lib.mCoreThreadUnpause(self._native) - def isRunning(self): + @property + def running(self): return bool(lib.mCoreThreadIsActive(self._native)) - def isPaused(self): + @property + def paused(self): return bool(lib.mCoreThreadIsPaused(self._native)) - def useCore(self): + def use_core(self): return ThreadCoreOwner(self)
M src/platform/python/mgba/tile.pysrc/platform/python/mgba/tile.py

@@ -3,14 +3,15 @@ #

# This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. -from ._pylib import ffi, lib +from ._pylib import ffi, lib # pylint: disable=no-name-in-module from . import image + class Tile: def __init__(self, data): self.buffer = data - def toImage(self): + def to_image(self): i = image.Image(8, 8) self.composite(i, 0, 0) return i

@@ -19,18 +20,21 @@ def composite(self, i, x, y):

for iy in range(8): ffi.memmove(ffi.addressof(i.buffer, x + (iy + y) * i.stride), ffi.addressof(self.buffer, iy * 8), 8 * ffi.sizeof("color_t")) + class CacheSet: def __init__(self, core): self.core = core - self.cache = ffi.gc(ffi.new("struct mCacheSet*"), core._deinitCache) - core._initCache(self.cache) + self.cache = ffi.gc(ffi.new("struct mCacheSet*"), core._deinit_cache) + core._init_cache(self.cache) + class TileView: def __init__(self, cache): self.cache = cache - def getTile(self, tile, palette): + def get_tile(self, tile, palette): return Tile(lib.mTileCacheGetTile(self.cache, tile, palette)) + class MapView: def __init__(self, cache):

@@ -53,6 +57,7 @@ lib.mMapCacheCleanRow(self.cache, y >> 3)

row = lib.mMapCacheGetRow(self.cache, y) ffi.memmove(ffi.addressof(i.buffer, i.stride * y), row, self.width * 8 * ffi.sizeof("color_t")) return i + class Sprite(object): def constitute(self, tileView, tilePitch):
M src/platform/python/mgba/vfs.pysrc/platform/python/mgba/vfs.py

@@ -3,139 +3,152 @@ #

# This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. -from ._pylib import ffi, lib -import mmap +# pylint: disable=invalid-name,unused-argument +from ._pylib import ffi, lib # pylint: disable=no-name-in-module import os + @ffi.def_extern() def _vfpClose(vf): - vfp = ffi.cast("struct VFilePy*", vf) - ffi.from_handle(vfp.fileobj).close() - return True + vfp = ffi.cast("struct VFilePy*", vf) + ffi.from_handle(vfp.fileobj).close() + return True + @ffi.def_extern() def _vfpSeek(vf, offset, whence): - vfp = ffi.cast("struct VFilePy*", vf) - f = ffi.from_handle(vfp.fileobj) - f.seek(offset, whence) - return f.tell() + vfp = ffi.cast("struct VFilePy*", vf) + f = ffi.from_handle(vfp.fileobj) + f.seek(offset, whence) + return f.tell() + @ffi.def_extern() def _vfpRead(vf, buffer, size): - vfp = ffi.cast("struct VFilePy*", vf) - pybuf = ffi.buffer(buffer, size) - ffi.from_handle(vfp.fileobj).readinto(pybuf) - return size + vfp = ffi.cast("struct VFilePy*", vf) + pybuf = ffi.buffer(buffer, size) + ffi.from_handle(vfp.fileobj).readinto(pybuf) + return size + @ffi.def_extern() def _vfpWrite(vf, buffer, size): - vfp = ffi.cast("struct VFilePy*", vf) - pybuf = ffi.buffer(buffer, size) - ffi.from_handle(vfp.fileobj).write(pybuf) - return size + vfp = ffi.cast("struct VFilePy*", vf) + pybuf = ffi.buffer(buffer, size) + ffi.from_handle(vfp.fileobj).write(pybuf) + return size + @ffi.def_extern() def _vfpMap(vf, size, flags): - pass + pass + @ffi.def_extern() def _vfpUnmap(vf, memory, size): - pass + pass + @ffi.def_extern() def _vfpTruncate(vf, size): - vfp = ffi.cast("struct VFilePy*", vf) - ffi.from_handle(vfp.fileobj).truncate(size) + vfp = ffi.cast("struct VFilePy*", vf) + ffi.from_handle(vfp.fileobj).truncate(size) + @ffi.def_extern() def _vfpSize(vf): - vfp = ffi.cast("struct VFilePy*", vf) - f = ffi.from_handle(vfp.fileobj) - pos = f.tell() - f.seek(0, os.SEEK_END) - size = f.tell() - f.seek(pos, os.SEEK_SET) - return size + vfp = ffi.cast("struct VFilePy*", vf) + f = ffi.from_handle(vfp.fileobj) + pos = f.tell() + f.seek(0, os.SEEK_END) + size = f.tell() + f.seek(pos, os.SEEK_SET) + return size + @ffi.def_extern() def _vfpSync(vf, buffer, size): - vfp = ffi.cast("struct VFilePy*", vf) - f = ffi.from_handle(vfp.fileobj) - if buffer and size: - pos = f.tell() - f.seek(0, os.SEEK_SET) - _vfpWrite(vf, buffer, size) - f.seek(pos, os.SEEK_SET) - f.flush() - os.fsync() - return True + vfp = ffi.cast("struct VFilePy*", vf) + f = ffi.from_handle(vfp.fileobj) + if buffer and size: + pos = f.tell() + f.seek(0, os.SEEK_SET) + _vfpWrite(vf, buffer, size) + f.seek(pos, os.SEEK_SET) + f.flush() + os.fsync() + return True + -def open(f): - handle = ffi.new_handle(f) - vf = VFile(lib.VFileFromPython(handle)) - # Prevent garbage collection - vf._fileobj = f - vf._handle = handle - return vf +def open(f): # pylint: disable=redefined-builtin + handle = ffi.new_handle(f) + vf = VFile(lib.VFileFromPython(handle), _no_gc=(f, handle)) + return vf + + +def open_path(path, mode="r"): + flags = 0 + if mode.startswith("r"): + flags |= os.O_RDONLY + elif mode.startswith("w"): + flags |= os.O_WRONLY | os.O_CREAT | os.O_TRUNC + elif mode.startswith("a"): + flags |= os.O_WRONLY | os.O_CREAT | os.O_APPEND + else: + return None -def openPath(path, mode="r"): - flags = 0 - if mode.startswith("r"): - flags |= os.O_RDONLY - elif mode.startswith("w"): - flags |= os.O_WRONLY | os.O_CREAT | os.O_TRUNC - elif mode.startswith("a"): - flags |= os.O_WRONLY | os.O_CREAT | os.O_APPEND - else: - return None + if "+" in mode[1:]: + flags |= os.O_RDWR + if "x" in mode[1:]: + flags |= os.O_EXCL - if "+" in mode[1:]: - flags |= os.O_RDWR - if "x" in mode[1:]: - flags |= os.O_EXCL + vf = lib.VFileOpen(path.encode("UTF-8"), flags) + if vf == ffi.NULL: + return None + return VFile(vf) - vf = lib.VFileOpen(path.encode("UTF-8"), flags); - if vf == ffi.NULL: - return None - return VFile(vf) class VFile: - def __init__(self, vf): - self.handle = vf + def __init__(self, vf, _no_gc=None): + self.handle = vf + self._no_gc = _no_gc - def close(self): - return bool(self.handle.close(self.handle)) + def __del__(self): + self.close() + + def close(self): + return bool(self.handle.close(self.handle)) - def seek(self, offset, whence): - return self.handle.seek(self.handle, offset, whence) + def seek(self, offset, whence): + return self.handle.seek(self.handle, offset, whence) - def read(self, buffer, size): - return self.handle.read(self.handle, buffer, size) + def read(self, buffer, size): + return self.handle.read(self.handle, buffer, size) - def readAll(self, size=0): - if not size: - size = self.size() - buffer = ffi.new("char[%i]" % size) - size = self.handle.read(self.handle, buffer, size) - return ffi.unpack(buffer, size) + def read_all(self, size=0): + if not size: + size = self.size() + buffer = ffi.new("char[%i]" % size) + size = self.handle.read(self.handle, buffer, size) + return ffi.unpack(buffer, size) - def readline(self, buffer, size): - return self.handle.readline(self.handle, buffer, size) + def readline(self, buffer, size): + return self.handle.readline(self.handle, buffer, size) - def write(self, buffer, size): - return self.handle.write(self.handle, buffer, size) + def write(self, buffer, size): + return self.handle.write(self.handle, buffer, size) - def map(self, size, flags): - return self.handle.map(self.handle, size, flags) + def map(self, size, flags): + return self.handle.map(self.handle, size, flags) - def unmap(self, memory, size): - self.handle.unmap(self.handle, memory, size) + def unmap(self, memory, size): + self.handle.unmap(self.handle, memory, size) - def truncate(self, size): - self.handle.truncate(self.handle, size) + def truncate(self, size): + self.handle.truncate(self.handle, size) - def size(self): - return self.handle.size(self.handle) + def size(self): + return self.handle.size(self.handle) - def sync(self, buffer, size): - return self.handle.sync(self.handle, buffer, size) + def sync(self, buffer, size): + return self.handle.sync(self.handle, buffer, size)
M src/platform/python/setup.cfgsrc/platform/python/setup.cfg

@@ -1,2 +1,6 @@

[aliases] test=pytest + +[pycodestyle] +exclude = .eggs +ignore = E501,E741,E743
A src/platform/python/setup.py

@@ -0,0 +1,38 @@

+from setuptools import setup +import re +import os +import os.path +import sys +import subprocess + + +def get_version_component(piece): + return subprocess.check_output(['cmake', '-DPRINT_STRING={}'.format(piece), '-P', '../../../version.cmake']).decode('utf-8').strip() + + +version = '{}.{}.{}'.format(*(get_version_component(p) for p in ('LIB_VERSION_MAJOR', 'LIB_VERSION_MINOR', 'LIB_VERSION_PATCH'))) +if not get_version_component('GIT_TAG'): + version += '.{}+g{}'.format(*(get_version_component(p) for p in ('GIT_REV', 'GIT_COMMIT_SHORT'))) + +setup( + name="mgba", + version=version, + author="Jeffrey Pfau", + author_email="jeffrey@endrift.com", + url="http://github.com/mgba-emu/mgba/", + packages=["mgba"], + setup_requires=['cffi>=1.6', 'pytest-runner'], + install_requires=['cffi>=1.6', 'cached-property'], + extras_require={'pil': ['Pillow>=2.3'], 'cinema': ['pyyaml', 'pytest']}, + tests_require=['pytest'], + cffi_modules=["_builder.py:ffi"], + license="MPL 2.0", + classifiers=[ + "Programming Language :: C", + "Programming Language :: Python :: 2", + "Programming Language :: Python :: 3", + "License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0)", + "Topic :: Games/Entertainment", + "Topic :: System :: Emulators" + ] +)
M src/platform/python/test_cinema.pysrc/platform/python/test_cinema.py

@@ -4,7 +4,7 @@ import mgba.log

import os.path import yaml -mgba.log.installDefault(mgba.log.NullLogger()) +mgba.log.install_default(mgba.log.NullLogger()) def flatten(d): l = []

@@ -18,7 +18,7 @@ return l

def pytest_generate_tests(metafunc): if 'vtest' in metafunc.fixturenames: - tests = cinema.test.gatherTests(os.path.join(os.path.dirname(__file__), '..', '..', '..', 'cinema')) + tests = cinema.test.gather_tests(os.path.join(os.path.dirname(__file__), '..', '..', '..', 'cinema')) testList = flatten(tests) params = [] for test in testList:

@@ -34,9 +34,9 @@ def vtest(request):

return request.param def test_video(vtest, pytestconfig): - vtest.setUp() + vtest.setup() if pytestconfig.getoption('--rebaseline'): - vtest.generateBaseline() + vtest.generate_baseline() else: try: vtest.test()
M src/platform/python/tests/mgba/test_vfs.pysrc/platform/python/tests/mgba/test_vfs.py

@@ -4,26 +4,30 @@

import mgba.vfs as vfs from mgba._pylib import ffi + def test_vfs_open(): with open(__file__) as f: vf = vfs.open(f) assert vf assert vf.close() -def test_vfs_openPath(): - vf = vfs.openPath(__file__) + +def test_vfs_open_path(): + vf = vfs.open_path(__file__) assert vf assert vf.close() + def test_vfs_read(): - vf = vfs.openPath(__file__) + vf = vfs.open_path(__file__) buffer = ffi.new('char[13]') assert vf.read(buffer, 13) == 13 assert ffi.string(buffer) == b'import pytest' vf.close() + def test_vfs_readline(): - vf = vfs.openPath(__file__) + vf = vfs.open_path(__file__) buffer = ffi.new('char[16]') linelen = vf.readline(buffer, 16) assert linelen in (14, 15)

@@ -33,16 +37,18 @@ elif linelen == 15:

assert ffi.string(buffer) == b'import pytest\r\n' vf.close() -def test_vfs_readAllSize(): - vf = vfs.openPath(__file__) - buffer = vf.readAll() + +def test_vfs_read_all_size(): + vf = vfs.open_path(__file__) + buffer = vf.read_all() assert buffer assert len(buffer) assert len(buffer) == vf.size() vf.close() + def test_vfs_seek(): - vf = vfs.openPath(__file__) + vf = vfs.open_path(__file__) assert vf.seek(0, os.SEEK_SET) == 0 assert vf.seek(1, os.SEEK_SET) == 1 assert vf.seek(1, os.SEEK_CUR) == 2

@@ -52,6 +58,7 @@ assert vf.seek(0, os.SEEK_END) == vf.size()

assert vf.seek(-1, os.SEEK_END) == vf.size() -1 vf.close() -def test_vfs_openPath_invalid(): - vf = vfs.openPath('.invalid') + +def test_vfs_open_path_invalid(): + vf = vfs.open_path('.invalid') assert not vf
M version.cmakeversion.cmake

@@ -47,7 +47,7 @@ set(GIT_BRANCH "(unknown)")

endif() if(DEFINED PRINT_STRING) - message("${${PRINT_STRING}}") + execute_process(COMMAND ${CMAKE_COMMAND} -E echo "${${PRINT_STRING}}") elseif(NOT VERSION_STRING_CACHE OR NOT VERSION_STRING STREQUAL VERSION_STRING_CACHE) set(VERSION_STRING_CACHE ${VERSION_STRING} CACHE STRING "" FORCE)