mirror of https://github.com/mgba-emu/mgba.git
Python: Revamp a bunch of stuff
This commit is contained in:
parent
3f05b12bc1
commit
7fa8de1f0d
|
@ -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
|
|
@ -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 "${CMA
|
|||
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 @@ foreach(TEST IN LISTS BASE_TESTS SUBTESTS)
|
|||
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()
|
||||
|
|
|
@ -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 @@ ffi.embedding_init_code("""
|
|||
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)
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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 @@ class CinemaTest(object):
|
|||
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 @@ class VideoTest(CinemaTest):
|
|||
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 @@ def gatherTests(root=os.getcwd()):
|
|||
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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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)
|
||||
|
||||
return ffi.def_extern(name=funcName)(cb)
|
||||
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)
|
||||
|
||||
version = ffi.string(lib.projectVersion).decode('utf-8')
|
||||
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)
|
||||
|
||||
|
||||
__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)
|
||||
|
|
|
@ -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 @@ class ARMCore:
|
|||
self.gprs = _ARMRegisters(self)
|
||||
self.cpsr = self._native.cpsr
|
||||
self.spsr = self._native.spsr
|
||||
|
||||
|
|
|
@ -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 @@ def find(path):
|
|||
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):
|
||||
context = ffi.from_handle(user)
|
||||
context._videoFrameStarted()
|
||||
|
||||
@ffi.def_extern()
|
||||
def _mCorePythonCallbacksVideoFrameEnded(user):
|
||||
def _mCorePythonCallbacksVideoFrameStarted(user): # pylint: disable=invalid-name
|
||||
context = ffi.from_handle(user)
|
||||
context._videoFrameEnded()
|
||||
context._video_frame_started()
|
||||
|
||||
|
||||
@ffi.def_extern()
|
||||
def _mCorePythonCallbacksCoreCrashed(user):
|
||||
def _mCorePythonCallbacksVideoFrameEnded(user): # pylint: disable=invalid-name
|
||||
context = ffi.from_handle(user)
|
||||
context._coreCrashed()
|
||||
context._video_frame_ended()
|
||||
|
||||
|
||||
@ffi.def_extern()
|
||||
def _mCorePythonCallbacksSleep(user):
|
||||
def _mCorePythonCallbacksCoreCrashed(user): # pylint: disable=invalid-name
|
||||
context = ffi.from_handle(user)
|
||||
context._core_crashed()
|
||||
|
||||
|
||||
@ffi.def_extern()
|
||||
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 @@ class Core(object):
|
|||
|
||||
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 @@ class Core(object):
|
|||
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 @@ class Core(object):
|
|||
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,24 +265,25 @@ class Core(object):
|
|||
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):
|
||||
raise NotImplementedError
|
||||
|
@ -281,6 +300,7 @@ class ICoreOwner(object):
|
|||
self.core._protected = False
|
||||
self.release()
|
||||
|
||||
|
||||
class IRunner(object):
|
||||
def pause(self):
|
||||
raise NotImplementedError
|
||||
|
@ -288,15 +308,18 @@ class IRunner(object):
|
|||
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={}):
|
||||
if not native:
|
||||
|
|
|
@ -3,26 +3,27 @@
|
|||
# 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
|
||||
WATCHPOINT_READ = lib.WATCHPOINT_READ
|
||||
|
@ -49,37 +50,40 @@ class NativeDebugger(IRunner):
|
|||
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):
|
||||
|
@ -88,6 +92,7 @@ class CLIBackend(object):
|
|||
def write(self, string):
|
||||
self.backend.printf(string)
|
||||
|
||||
|
||||
class CLIDebugger(NativeDebugger):
|
||||
def __init__(self, native):
|
||||
super(CLIDebugger, self).__init__(native)
|
||||
|
@ -97,5 +102,5 @@ class CLIDebugger(NativeDebugger):
|
|||
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)
|
||||
|
|
|
@ -8,6 +8,7 @@ try:
|
|||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
def search(core):
|
||||
crc32 = None
|
||||
if hasattr(core, 'PLATFORM_GBA') and core.platform() == core.PLATFORM_GBA:
|
||||
|
|
|
@ -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 @@ class GB(Core):
|
|||
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 @@ class GBSIODriver(object):
|
|||
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 @@ class GBMemory(Memory):
|
|||
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 @@ class GBSprite(Sprite):
|
|||
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:
|
||||
|
|
|
@ -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 @@ class GBA(Core):
|
|||
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 @@ class GBA(Core):
|
|||
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,9 +87,10 @@ class GBASIODriver(object):
|
|||
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
|
||||
POLL = lib.JOY_POLL
|
||||
|
@ -91,10 +98,11 @@ class GBASIOJOYDriver(GBASIODriver):
|
|||
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 @@ class GBASIOJOYDriver(GBASIODriver):
|
|||
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 @@ class GBAMemory(Memory):
|
|||
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)
|
||||
|
@ -128,6 +137,7 @@ class GBAMemory(Memory):
|
|||
self.rom = self.cart0
|
||||
self.sram = Memory(core, lib.SIZE_CART_SRAM, lib.BASE_CART_SRAM)
|
||||
|
||||
|
||||
class GBASprite(Sprite):
|
||||
TILE_BASE = 0x800, 0x400
|
||||
PALETTE_BASE = 0x10, 1
|
||||
|
@ -136,18 +146,19 @@ class GBASprite(Sprite):
|
|||
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 @@ class GBAObjs:
|
|||
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
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
# 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:
|
||||
|
@ -11,6 +11,7 @@ try:
|
|||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
class Image:
|
||||
def __init__(self, width, height, stride=0, alpha=False):
|
||||
self.width = width
|
||||
|
@ -24,58 +25,63 @@ class Image:
|
|||
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
|
||||
|
|
|
@ -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 @@ class Logger(object):
|
|||
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):
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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,10 +12,10 @@ class MemoryView(object):
|
|||
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._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)
|
||||
|
@ -23,7 +24,7 @@ class MemoryView(object):
|
|||
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 @@ class MemoryView(object):
|
|||
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,11 +75,12 @@ class MemorySearchResult(object):
|
|||
self.guessDivisor = result.guessDivisor
|
||||
self.type = result.type
|
||||
|
||||
if result.type == Memory.SEARCH_8:
|
||||
if result.type == Memory.SEARCH_INT:
|
||||
if result.width == 1:
|
||||
self._memory = memory.u8
|
||||
elif result.type == Memory.SEARCH_16:
|
||||
elif result.width == 2:
|
||||
self._memory = memory.u16
|
||||
elif result.type == Memory.SEARCH_32:
|
||||
elif result.width == 4:
|
||||
self._memory = memory.u32
|
||||
elif result.type == Memory.SEARCH_STRING:
|
||||
self._memory = memory.u8
|
||||
|
@ -123,7 +124,7 @@ class Memory(object):
|
|||
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 @@ class Memory(object):
|
|||
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 @@ class Memory(object):
|
|||
def __getitem__(self, address):
|
||||
if isinstance(address, slice):
|
||||
return bytearray(self.u8[address])
|
||||
else:
|
||||
return self.u8[address]
|
||||
|
|
|
@ -3,20 +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
|
||||
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._vfile = vfs.open(f)
|
||||
self._png = None
|
||||
self._info = None
|
||||
self.mode = mode
|
||||
|
||||
def writeHeader(self, image):
|
||||
self._png = lib.PNGWriteOpen(self.vf.handle)
|
||||
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:
|
||||
|
@ -25,15 +28,16 @@ class PNG:
|
|||
self._info = lib.PNGWriteHeader8(self._png, image.width, image.height)
|
||||
return self._info != ffi.NULL
|
||||
|
||||
def writePixels(self, image):
|
||||
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):
|
||||
def write_close(self):
|
||||
lib.PNGWriteClose(self._png, self._info)
|
||||
del self._png
|
||||
del self._info
|
||||
self._png = None
|
||||
self._info = None
|
||||
|
|
|
@ -3,9 +3,10 @@
|
|||
# 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):
|
||||
self.thread = thread
|
||||
|
@ -19,12 +20,13 @@ class ThreadCoreOwner(ICoreOwner):
|
|||
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 @@ class Thread(IRunner):
|
|||
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 @@ class Thread(IRunner):
|
|||
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)
|
||||
|
|
|
@ -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,19 +20,22 @@ class Tile:
|
|||
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):
|
||||
self.cache = cache
|
||||
|
@ -54,6 +58,7 @@ class MapView:
|
|||
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):
|
||||
i = image.Image(self.width, self.height, alpha=True)
|
||||
|
|
|
@ -3,16 +3,18 @@
|
|||
# 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
|
||||
|
||||
|
||||
@ffi.def_extern()
|
||||
def _vfpSeek(vf, offset, whence):
|
||||
vfp = ffi.cast("struct VFilePy*", vf)
|
||||
|
@ -20,6 +22,7 @@ def _vfpSeek(vf, offset, whence):
|
|||
f.seek(offset, whence)
|
||||
return f.tell()
|
||||
|
||||
|
||||
@ffi.def_extern()
|
||||
def _vfpRead(vf, buffer, size):
|
||||
vfp = ffi.cast("struct VFilePy*", vf)
|
||||
|
@ -27,6 +30,7 @@ def _vfpRead(vf, 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)
|
||||
|
@ -34,19 +38,23 @@ def _vfpWrite(vf, buffer, size):
|
|||
ffi.from_handle(vfp.fileobj).write(pybuf)
|
||||
return size
|
||||
|
||||
|
||||
@ffi.def_extern()
|
||||
def _vfpMap(vf, size, flags):
|
||||
pass
|
||||
|
||||
|
||||
@ffi.def_extern()
|
||||
def _vfpUnmap(vf, memory, size):
|
||||
pass
|
||||
|
||||
|
||||
@ffi.def_extern()
|
||||
def _vfpTruncate(vf, 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)
|
||||
|
@ -57,6 +65,7 @@ def _vfpSize(vf):
|
|||
f.seek(pos, os.SEEK_SET)
|
||||
return size
|
||||
|
||||
|
||||
@ffi.def_extern()
|
||||
def _vfpSync(vf, buffer, size):
|
||||
vfp = ffi.cast("struct VFilePy*", vf)
|
||||
|
@ -70,15 +79,14 @@ def _vfpSync(vf, buffer, size):
|
|||
os.fsync()
|
||||
return True
|
||||
|
||||
def open(f):
|
||||
|
||||
def open(f): # pylint: disable=redefined-builtin
|
||||
handle = ffi.new_handle(f)
|
||||
vf = VFile(lib.VFileFromPython(handle))
|
||||
# Prevent garbage collection
|
||||
vf._fileobj = f
|
||||
vf._handle = handle
|
||||
vf = VFile(lib.VFileFromPython(handle), _no_gc=(f, handle))
|
||||
return vf
|
||||
|
||||
def openPath(path, mode="r"):
|
||||
|
||||
def open_path(path, mode="r"):
|
||||
flags = 0
|
||||
if mode.startswith("r"):
|
||||
flags |= os.O_RDONLY
|
||||
|
@ -94,14 +102,19 @@ def openPath(path, mode="r"):
|
|||
if "x" in mode[1:]:
|
||||
flags |= os.O_EXCL
|
||||
|
||||
vf = lib.VFileOpen(path.encode("UTF-8"), flags);
|
||||
vf = lib.VFileOpen(path.encode("UTF-8"), flags)
|
||||
if vf == ffi.NULL:
|
||||
return None
|
||||
return VFile(vf)
|
||||
|
||||
|
||||
class VFile:
|
||||
def __init__(self, vf):
|
||||
def __init__(self, vf, _no_gc=None):
|
||||
self.handle = vf
|
||||
self._no_gc = _no_gc
|
||||
|
||||
def __del__(self):
|
||||
self.close()
|
||||
|
||||
def close(self):
|
||||
return bool(self.handle.close(self.handle))
|
||||
|
@ -112,7 +125,7 @@ class VFile:
|
|||
def read(self, buffer, size):
|
||||
return self.handle.read(self.handle, buffer, size)
|
||||
|
||||
def readAll(self, size=0):
|
||||
def read_all(self, size=0):
|
||||
if not size:
|
||||
size = self.size()
|
||||
buffer = ffi.new("char[%i]" % size)
|
||||
|
|
|
@ -1,2 +1,6 @@
|
|||
[aliases]
|
||||
test=pytest
|
||||
|
||||
[pycodestyle]
|
||||
exclude = .eggs
|
||||
ignore = E501,E741,E743
|
||||
|
|
|
@ -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"
|
||||
]
|
||||
)
|
|
@ -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 @@ def flatten(d):
|
|||
|
||||
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()
|
||||
|
|
|
@ -4,26 +4,30 @@ import os
|
|||
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 @@ def test_vfs_readline():
|
|||
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 @@ def test_vfs_seek():
|
|||
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
|
||||
|
|
|
@ -47,7 +47,7 @@ if(NOT GIT_BRANCH)
|
|||
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)
|
||||
|
||||
|
|
Loading…
Reference in New Issue