diff --git a/.gitignore b/.gitignore index 2241cd0aa..37cb57e2d 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ *.user* *~ *.swp +*.pyc diff --git a/CMakeLists.txt b/CMakeLists.txt index 8be589a23..d7ce8b389 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -28,6 +28,7 @@ set(BUILD_PERF OFF CACHE BOOL "Build performance profiling tool") set(BUILD_TEST OFF CACHE BOOL "Build testing harness") set(BUILD_SUITE OFF CACHE BOOL "Build test suite") set(BUILD_EXAMPLE OFF CACHE BOOL "Build example frontends") +set(BUILD_PYTHON OFF CACHE BOOL "Build Python bindings") set(BUILD_STATIC OFF CACHE BOOL "Build a static library") set(BUILD_SHARED ON CACHE BOOL "Build a shared library") set(SKIP_LIBRARY OFF CACHE BOOL "Skip building the library (useful for only building libretro or OpenEmu cores)") @@ -151,6 +152,9 @@ add_custom_target(version-info ALL include(${CMAKE_CURRENT_SOURCE_DIR}/version.cmake) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/src/core/version.c.in ${CMAKE_CURRENT_BINARY_DIR}/version.c) +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/src/core/flags.h.in ${CMAKE_CURRENT_BINARY_DIR}/flags.h) +install(FILES ${CMAKE_CURRENT_BINARY_DIR}/flags.h DESTINATION include/mgba COMPONENT lib${BINARY_NAME}) + list(APPEND UTIL_SRC ${CMAKE_CURRENT_BINARY_DIR}/version.c) source_group("Generated sources" FILES ${CMAKE_CURRENT_BINARY_DIR}/version.c) @@ -755,6 +759,11 @@ if(BUILD_SUITE) add_test(${BINARY_NAME}-suite ${BINARY_NAME}-suite) endif() +if(BUILD_PYTHON) + enable_testing() + add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/src/platform/python ${CMAKE_CURRENT_BINARY_DIR}/python) +endif() + if(BUILD_EXAMPLE) add_executable(${BINARY_NAME}-example-server ${CMAKE_CURRENT_SOURCE_DIR}/src/platform/example/client-server/server.c) target_link_libraries(${BINARY_NAME}-example-server ${BINARY_NAME}) @@ -838,6 +847,7 @@ if(NOT QUIET) message(STATUS " Profiling: ${BUILD_PERF}") message(STATUS " Test harness: ${BUILD_TEST}") message(STATUS " Test suite: ${BUILD_SUITE}") + message(STATUS " Python bindings: ${BUILD_PYTHON}") message(STATUS " Examples: ${BUILD_EXAMPLE}") message(STATUS "Cores:") message(STATUS " Libretro core: ${BUILD_LIBRETRO}") diff --git a/src/arm/arm.h b/src/arm/arm.h index b8ddad47a..c3c0eccfc 100644 --- a/src/arm/arm.h +++ b/src/arm/arm.h @@ -75,7 +75,7 @@ union PSR { unsigned z : 1; unsigned c : 1; unsigned v : 1; - unsigned : 20; + unsigned unused : 20; unsigned i : 1; unsigned f : 1; unsigned t : 1; @@ -85,7 +85,7 @@ union PSR { unsigned t : 1; unsigned f : 1; unsigned i : 1; - unsigned : 20; + unsigned unused : 20; unsigned v : 1; unsigned c : 1; unsigned z : 1; diff --git a/src/arm/debugger/debugger.h b/src/arm/debugger/debugger.h index 105702ccb..a12e30a58 100644 --- a/src/arm/debugger/debugger.h +++ b/src/arm/debugger/debugger.h @@ -12,6 +12,8 @@ CXX_GUARD_START #include "debugger/debugger.h" +#include "arm/arm.h" + struct ARMDebugBreakpoint { uint32_t address; bool isSw; diff --git a/src/core/flags.h.in b/src/core/flags.h.in new file mode 100644 index 000000000..aa2236dd1 --- /dev/null +++ b/src/core/flags.h.in @@ -0,0 +1,96 @@ +#ifndef FLAGS_H +#define FLAGS_H + +#ifndef MINIMAL_CORE +#cmakedefine MINIMAL_CORE @MINIMAL_CORE@ +#endif + +// BUILD flags + +#ifndef BUILD_GL +#cmakedefine BUILD_GL +#endif + +#ifndef BUILD_GLES2 +#cmakedefine BUILD_GLES2 +#endif + +// COLOR flags + +#ifndef COLOR_16_BIT +#cmakedefine COLOR_16_BIT +#endif + +#ifndef COLOR_5_6_5 +#cmakedefine COLOR_5_6_5 +#endif + +// M_CORE flags + +#ifndef M_CORE_GBA +#cmakedefine M_CORE_GBA +#endif + +#ifndef M_CORE_GB +#cmakedefine M_CORE_GB +#endif + +// USE flags + +#ifndef MINIMAL_CORE + +#ifndef USE_DEBUGGERS +#cmakedefine USE_DEBUGGERS +#endif + +#ifndef USE_EDITLINE +#cmakedefine USE_EDITLINE +#endif + +#ifndef USE_EPOXY +#cmakedefine USE_EPOXY +#endif + +#ifndef USE_FFMPEG +#cmakedefine USE_FFMPEG +#endif + +#ifndef USE_GDB_STUB +#cmakedefine USE_GDB_STUB +#endif + +#ifndef USE_LIBAV +#cmakedefine USE_LIBAV +#endif + +#ifndef USE_LIBZIP +#cmakedefine USE_LIBZIP +#endif + +#ifndef USE_LZMA +#cmakedefine USE_LZMA +#endif + +#ifndef USE_MAGICK +#cmakedefine USE_MAGICK +#endif + +#ifndef USE_MINIZIP +#cmakedefine USE_MINIZIP +#endif + +#ifndef USE_PNG +#cmakedefine USE_PNG +#endif + +#ifndef USE_PTHREADS +#cmakedefine USE_PTHREADS +#endif + +#ifndef USE_ZLIB +#cmakedefine USE_ZLIB +#endif + +#endif + +#endif diff --git a/src/core/log.c b/src/core/log.c index 880379fd9..125907e9a 100644 --- a/src/core/log.c +++ b/src/core/log.c @@ -44,4 +44,18 @@ const char* mLogCategoryName(int category) { return 0; } +void mLog(int category, enum mLogLevel level, const char* format, ...) { + struct mLogger* context = mLogGetContext(); + va_list args; + va_start(args, format); + if (context) { + context->log(context, category, level, format, args); + } else { + printf("%s: ", mLogCategoryName(category)); + vprintf(format, args); + printf("\n"); + } + va_end(args); +} + mLOG_DEFINE_CATEGORY(STATUS, "Status") diff --git a/src/core/log.h b/src/core/log.h index 680638324..9986fcbd7 100644 --- a/src/core/log.h +++ b/src/core/log.h @@ -32,19 +32,7 @@ int mLogGenerateCategory(const char*); const char* mLogCategoryName(int); ATTRIBUTE_FORMAT(printf, 3, 4) -static inline void mLog(int category, enum mLogLevel level, const char* format, ...) { - struct mLogger* context = mLogGetContext(); - va_list args; - va_start(args, format); - if (context) { - context->log(context, category, level, format, args); - } else { - printf("%s: ", mLogCategoryName(category)); - vprintf(format, args); - printf("\n"); - } - va_end(args); -} +void mLog(int category, enum mLogLevel level, const char* format, ...); #define mLOG(CATEGORY, LEVEL, ...) mLog(_mLOG_CAT_ ## CATEGORY (), mLOG_ ## LEVEL, __VA_ARGS__) diff --git a/src/debugger/debugger.h b/src/debugger/debugger.h index cc564499d..858d76554 100644 --- a/src/debugger/debugger.h +++ b/src/debugger/debugger.h @@ -10,7 +10,7 @@ CXX_GUARD_START -#include "arm/arm.h" +#include "core/cpu.h" #include "core/log.h" #include "util/vector.h" @@ -37,7 +37,7 @@ enum mDebuggerState { enum mWatchpointType { WATCHPOINT_WRITE = 1, WATCHPOINT_READ = 2, - WATCHPOINT_RW = WATCHPOINT_WRITE | WATCHPOINT_READ + WATCHPOINT_RW = 3 }; enum mBreakpointType { diff --git a/src/gb/gb.h b/src/gb/gb.h index f270e58bf..0ed6f99a0 100644 --- a/src/gb/gb.h +++ b/src/gb/gb.h @@ -122,7 +122,6 @@ void GBHalt(struct LR35902Core* cpu); struct VFile; bool GBLoadROM(struct GB* gb, struct VFile* vf); bool GBLoadSave(struct GB* gb, struct VFile* vf); -void GBYankROM(struct GB* gb); void GBUnloadROM(struct GB* gb); void GBSynthesizeROM(struct VFile* vf); diff --git a/src/gb/video.h b/src/gb/video.h index 0c7824478..e9ff5b956 100644 --- a/src/gb/video.h +++ b/src/gb/video.h @@ -19,17 +19,17 @@ enum { GB_VIDEO_HORIZONTAL_PIXELS = 160, GB_VIDEO_VERTICAL_PIXELS = 144, GB_VIDEO_VBLANK_PIXELS = 10, - GB_VIDEO_VERTICAL_TOTAL_PIXELS = GB_VIDEO_VERTICAL_PIXELS + GB_VIDEO_VBLANK_PIXELS, + GB_VIDEO_VERTICAL_TOTAL_PIXELS = 154, // TODO: Figure out exact lengths GB_VIDEO_MODE_2_LENGTH = 76, GB_VIDEO_MODE_3_LENGTH_BASE = 171, GB_VIDEO_MODE_0_LENGTH_BASE = 209, - GB_VIDEO_HORIZONTAL_LENGTH = GB_VIDEO_MODE_0_LENGTH_BASE + GB_VIDEO_MODE_2_LENGTH + GB_VIDEO_MODE_3_LENGTH_BASE, + GB_VIDEO_HORIZONTAL_LENGTH = 456, - GB_VIDEO_MODE_1_LENGTH = GB_VIDEO_HORIZONTAL_LENGTH * GB_VIDEO_VBLANK_PIXELS, - GB_VIDEO_TOTAL_LENGTH = GB_VIDEO_HORIZONTAL_LENGTH * GB_VIDEO_VERTICAL_TOTAL_PIXELS, + GB_VIDEO_MODE_1_LENGTH = 65664, + GB_VIDEO_TOTAL_LENGTH = 70224, GB_BASE_MAP = 0x1800, GB_SIZE_MAP = 0x0400 diff --git a/src/gba/memory.h b/src/gba/memory.h index c9a81f4f3..047771764 100644 --- a/src/gba/memory.h +++ b/src/gba/memory.h @@ -118,7 +118,7 @@ struct GBAMemory { uint32_t* wram; uint32_t* iwram; uint32_t* rom; - uint16_t io[SIZE_IO >> 1]; + uint16_t io[512]; struct GBACartridgeHardware hw; struct GBASavedata savedata; diff --git a/src/gba/rr/vbm.c b/src/gba/rr/vbm.c index 5a3f0d0b6..c9ebca2e4 100644 --- a/src/gba/rr/vbm.c +++ b/src/gba/rr/vbm.c @@ -245,7 +245,7 @@ bool GBAVBMSetStream(struct GBAVBMContext* vbm, struct VFile* vf) { uint8_t flags; vf->read(vf, &flags, sizeof(flags)); if (flags & 2) { -#if USE_ZLIB +#ifdef USE_ZLIB vbm->d.initFrom = INIT_FROM_SAVEGAME; #else // zlib is needed to parse the savegame diff --git a/src/gba/video.h b/src/gba/video.h index c25df5ef8..b5b1d810b 100644 --- a/src/gba/video.h +++ b/src/gba/video.h @@ -20,13 +20,13 @@ enum { VIDEO_HBLANK_PIXELS = 68, VIDEO_HDRAW_LENGTH = 1006, VIDEO_HBLANK_LENGTH = 226, - VIDEO_HORIZONTAL_LENGTH = VIDEO_HDRAW_LENGTH + VIDEO_HBLANK_LENGTH, + VIDEO_HORIZONTAL_LENGTH = 1232, VIDEO_VERTICAL_PIXELS = 160, VIDEO_VBLANK_PIXELS = 68, - VIDEO_VERTICAL_TOTAL_PIXELS = VIDEO_VERTICAL_PIXELS + VIDEO_VBLANK_PIXELS, + VIDEO_VERTICAL_TOTAL_PIXELS = 228, - VIDEO_TOTAL_LENGTH = VIDEO_HORIZONTAL_LENGTH * VIDEO_VERTICAL_TOTAL_PIXELS, + VIDEO_TOTAL_LENGTH = 280896, OBJ_HBLANK_FREE_LENGTH = 954, OBJ_LENGTH = 1210, @@ -178,7 +178,7 @@ struct GBAVideo { // VCOUNT int vcount; - uint16_t palette[SIZE_PALETTE_RAM >> 1]; + uint16_t palette[512]; uint16_t* vram; union GBAOAM oam; diff --git a/src/lr35902/isa-lr35902.c b/src/lr35902/isa-lr35902.c index c9298f57d..d8f10211f 100644 --- a/src/lr35902/isa-lr35902.c +++ b/src/lr35902/isa-lr35902.c @@ -8,6 +8,36 @@ #include "lr35902/emitter-lr35902.h" #include "lr35902/lr35902.h" +static inline uint16_t LR35902ReadHL(struct LR35902Core* cpu) { + uint16_t hl; + LOAD_16LE(hl, 0, &cpu->hl); + return hl; +} + +static inline void LR35902WriteHL(struct LR35902Core* cpu, uint16_t hl) { + STORE_16LE(hl, 0, &cpu->hl); +} + +static inline uint16_t LR35902ReadBC(struct LR35902Core* cpu) { + uint16_t bc; + LOAD_16LE(bc, 0, &cpu->bc); + return bc; +} + +static inline void LR35902WriteBC(struct LR35902Core* cpu, uint16_t bc) { + STORE_16LE(bc, 0, &cpu->bc); +} + +static inline uint16_t LR35902ReadDE(struct LR35902Core* cpu) { + uint16_t de; + LOAD_16LE(de, 0, &cpu->de); + return de; +} + +static inline void LR35902WriteDE(struct LR35902Core* cpu, uint16_t de) { + STORE_16LE(de, 0, &cpu->de); +} + #define DEFINE_INSTRUCTION_LR35902(NAME, BODY) \ static void _LR35902Instruction ## NAME (struct LR35902Core* cpu) { \ UNUSED(cpu); \ diff --git a/src/lr35902/lr35902.h b/src/lr35902/lr35902.h index 102f3941e..f2c0492c7 100644 --- a/src/lr35902/lr35902.h +++ b/src/lr35902/lr35902.h @@ -23,9 +23,9 @@ union FlagRegister { unsigned n : 1; unsigned h : 1; unsigned c : 1; - unsigned : 4; + unsigned unused : 4; #else - unsigned : 4; + unsigned unused : 4; unsigned c : 1; unsigned h : 1; unsigned n : 1; @@ -127,36 +127,6 @@ struct LR35902Core { struct mCPUComponent** components; }; -static inline uint16_t LR35902ReadHL(struct LR35902Core* cpu) { - uint16_t hl; - LOAD_16LE(hl, 0, &cpu->hl); - return hl; -} - -static inline void LR35902WriteHL(struct LR35902Core* cpu, uint16_t hl) { - STORE_16LE(hl, 0, &cpu->hl); -} - -static inline uint16_t LR35902ReadBC(struct LR35902Core* cpu) { - uint16_t bc; - LOAD_16LE(bc, 0, &cpu->bc); - return bc; -} - -static inline void LR35902WriteBC(struct LR35902Core* cpu, uint16_t bc) { - STORE_16LE(bc, 0, &cpu->bc); -} - -static inline uint16_t LR35902ReadDE(struct LR35902Core* cpu) { - uint16_t de; - LOAD_16LE(de, 0, &cpu->de); - return de; -} - -static inline void LR35902WriteDE(struct LR35902Core* cpu, uint16_t de) { - STORE_16LE(de, 0, &cpu->de); -} - void LR35902Init(struct LR35902Core* cpu); void LR35902Deinit(struct LR35902Core* cpu); void LR35902SetComponents(struct LR35902Core* cpu, struct mCPUComponent* master, int extra, struct mCPUComponent** extras); diff --git a/src/platform/python/CMakeLists.txt b/src/platform/python/CMakeLists.txt new file mode 100644 index 000000000..adda679bd --- /dev/null +++ b/src/platform/python/CMakeLists.txt @@ -0,0 +1,12 @@ +find_program(PYTHON python) + +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/setup.py.in ${CMAKE_CURRENT_BINARY_DIR}/setup.py) + +add_custom_command(OUTPUT build/lib/${BINARY_NAME}/__init__.py + COMMAND BINDIR=${CMAKE_CURRENT_BINARY_DIR}/.. ${PYTHON} ${CMAKE_CURRENT_BINARY_DIR}/setup.py build --build-base ${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}/_builder.py) + +add_custom_target(${BINARY_NAME}-py ALL DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/build/lib/${BINARY_NAME}/__init__.py) diff --git a/src/platform/python/_builder.h b/src/platform/python/_builder.h new file mode 100644 index 000000000..8d0cddbb3 --- /dev/null +++ b/src/platform/python/_builder.h @@ -0,0 +1,49 @@ +#define COMMON_H +#define PNG_H +#define _SYS_TIME_H +#define _SYS_TIME_H_ +#define _TIME_H +#define _TIME_H_ + +#define ATTRIBUTE_FORMAT(X, Y, Z) +#define DECL_BITFIELD(newtype, oldtype) typedef oldtype newtype +#define DECL_BIT(type, name, bit) +#define DECL_BITS(type, name, bit, nbits) + +#define CXX_GUARD_START +#define CXX_GUARD_END + +typedef int... time_t; +typedef int... off_t; +typedef ... va_list; +typedef ...* png_structp; +typedef ...* png_infop; +typedef ...* png_unknown_chunkp; + +void free(void*); + +#include +#undef const + +#include "flags.h" + +#include "core/core.h" +#include "core/tile-cache.h" +#include "platform/python/vfs-py.h" +#include "platform/python/log.h" + +#ifdef USE_PNG +#include "util/png-io.h" +#endif +#ifdef M_CORE_GBA +#include "arm/arm.h" +#include "gba/gba.h" +#include "gba/input.h" +#include "gba/renderers/tile-cache.h" +#endif +#ifdef M_CORE_GB +#include "lr35902/lr35902.h" +#include "gb/gb.h" +#include "gba/input.h" +#include "gb/renderers/tile-cache.h" +#endif diff --git a/src/platform/python/_builder.py b/src/platform/python/_builder.py new file mode 100644 index 000000000..4bf78ce53 --- /dev/null +++ b/src/platform/python/_builder.py @@ -0,0 +1,64 @@ +import cffi +import os, os.path +import shlex +import subprocess +import sys + +ffi = cffi.FFI() +pydir = os.path.dirname(os.path.abspath(__file__)) +srcdir = os.path.join(pydir, "..", "..") +bindir = os.environ.get("BINDIR", os.path.join(os.getcwd(), "..")) + +cpp = shlex.split(os.environ.get("CPP", "cc -E")) +cppflags = shlex.split(os.environ.get("CPPFLAGS", "")) +if __name__ == "__main__": + cppflags.extend(sys.argv[1:]) +cppflags.extend(["-I" + srcdir, "-I" + bindir]) + +ffi.set_source("mgba._pylib", """ +#include "flags.h" +#include "util/common.h" +#include "core/core.h" +#include "core/log.h" +#include "core/tile-cache.h" +#include "arm/arm.h" +#include "gba/gba.h" +#include "gba/input.h" +#include "gba/renderers/tile-cache.h" +#include "lr35902/lr35902.h" +#include "gb/gb.h" +#include "gb/renderers/tile-cache.h" +#include "util/png-io.h" +#include "util/vfs.h" + +struct VFile* VFileFromPython(void* fileobj); + +struct VFilePy { + struct VFile d; + void* fileobj; +}; + +struct mLogger* mLoggerPythonCreate(void* pyobj); + +struct mLoggerPy { + struct mLogger d; + void* pyobj; +}; +""", include_dirs=[srcdir], + extra_compile_args=cppflags, + libraries=["mgba"], + library_dirs=[bindir], + sources=[os.path.join(pydir, path) for path in ["vfs-py.c", "log.c"]]) + +preprocessed = subprocess.check_output(cpp + ["-fno-inline", "-P"] + cppflags + [os.path.join(pydir, "_builder.h")], universal_newlines=True) + +lines = [] +for line in preprocessed.splitlines(): + line = line.strip() + if line.startswith('#'): + continue + lines.append(line) +ffi.cdef('\n'.join(lines)) + +if __name__ == "__main__": + ffi.compile() diff --git a/src/platform/python/log.c b/src/platform/python/log.c new file mode 100644 index 000000000..aeb77f69f --- /dev/null +++ b/src/platform/python/log.c @@ -0,0 +1,27 @@ +/* Copyright (c) 2013-2016 Jeffrey Pfau + * + * 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/. */ +#include "core/log.h" + +struct mLoggerPy { + struct mLogger d; + void* pyobj; +}; + +void _pyLog(void* logger, int category, enum mLogLevel level, const char* message); + +static void _pyLogShim(struct mLogger* logger, int category, enum mLogLevel level, const char* format, va_list args) { + struct mLoggerPy* pylogger = (struct mLoggerPy*) logger; + char message[256] = {0}; + vsnprintf(message, sizeof(message) - 1, format, args); + _pyLog(pylogger, category, level, message); +} + +struct mLogger* mLoggerPythonCreate(void* pyobj) { + struct mLoggerPy* logger = malloc(sizeof(*logger)); + logger->d.log = _pyLogShim; + logger->pyobj = pyobj; + return &logger->d; +} \ No newline at end of file diff --git a/src/platform/python/log.h b/src/platform/python/log.h new file mode 100644 index 000000000..5ee1f7119 --- /dev/null +++ b/src/platform/python/log.h @@ -0,0 +1,15 @@ +/* Copyright (c) 2013-2016 Jeffrey Pfau + * + * 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/. */ +#include "core/log.h" + +struct mLoggerPy { + struct mLogger d; + void* pyobj; +}; + +struct mLogger* mLoggerPythonCreate(void* pyobj); + +extern "Python+C" void _pyLog(void* logger, int category, enum mLogLevel level, const char* message); diff --git a/src/platform/python/mgba/__init__.py b/src/platform/python/mgba/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/platform/python/mgba/arm.py b/src/platform/python/mgba/arm.py new file mode 100644 index 000000000..a594a6823 --- /dev/null +++ b/src/platform/python/mgba/arm.py @@ -0,0 +1,28 @@ +# Copyright (c) 2013-2016 Jeffrey Pfau +# +# 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 + +class _ARMRegisters: + def __init__(self, cpu): + self._cpu = cpu + + def __getitem__(self, r): + if r > lib.ARM_PC: + raise IndexError("Register out of range") + return self._cpu._native.gprs[r] + + def __setitem__(self, r, value): + if r >= lib.ARM_PC: + raise IndexError("Register out of range") + self._cpu._native.gprs[r] = value + +class ARMCore: + def __init__(self, native): + self._native = ffi.cast("struct ARMCore*", native) + self.gprs = _ARMRegisters(self) + self.cpsr = self._native.cpsr + self.spsr = self._native.spsr + diff --git a/src/platform/python/mgba/core.py b/src/platform/python/mgba/core.py new file mode 100644 index 000000000..8d86e3127 --- /dev/null +++ b/src/platform/python/mgba/core.py @@ -0,0 +1,162 @@ +# Copyright (c) 2013-2016 Jeffrey Pfau +# +# 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 +from cached_property import cached_property + +def find(path): + core = lib.mCoreFind(path.encode('UTF-8')) + if core == ffi.NULL: + return None + return Core._init(core) + +def findVF(vf): + core = lib.mCoreFindVF(vf.handle) + if core == ffi.NULL: + return None + return Core._init(core) + +def loadPath(path): + core = find(path) + if not core or not core.loadFile(path): + return None + return core + +def loadVF(vf): + core = findVF(vf) + if not core or not core.loadROM(vf): + return None + return core + +def needsReset(f): + def wrapper(self, *args, **kwargs): + if not self._wasReset: + raise RuntimeError("Core must be reset first") + return f(self, *args, **kwargs) + return wrapper + +class Core(object): + def __init__(self, native): + self._core = native + self._wasReset = False + + @cached_property + def tiles(self): + return tile.TileView(self) + + @classmethod + def _init(cls, native): + core = ffi.gc(native, native.deinit) + success = bool(core.init(core)) + if not success: + raise RuntimeError("Failed to initialize core") + if hasattr(cls, 'PLATFORM_GBA') and core.platform(core) == cls.PLATFORM_GBA: + return GBA(core) + if hasattr(cls, 'PLATFORM_GB') and core.platform(core) == cls.PLATFORM_GB: + return GB(core) + return Core(core) + + def _deinit(self): + self._core.deinit(self._core) + + def loadFile(self, path): + return bool(lib.mCoreLoadFile(self._core, path.encode('UTF-8'))) + + def isROM(self, vf): + return bool(self._core.isROM(vf.handle)) + + def loadROM(self, vf): + return bool(self._core.loadROM(self._core, vf.handle)) + + def loadSave(self, vf): + return bool(self._core.loadSave(self._core, vf.handle)) + + def loadTemporarySave(self, vf): + return bool(self._core.loadTemporarySave(self._core, vf.handle)) + + def loadPatch(self, vf): + return bool(self._core.loadPatch(self._core, vf.handle)) + + def autoloadSave(self): + return bool(lib.mCoreAutoloadSave(self._core)) + + def autoloadPatch(self): + return bool(lib.mCoreAutoloadPatch(self._core)) + + def platform(self): + return self._core.platform(self._core) + + def desiredVideoDimensions(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): + self._core.setVideoBuffer(self._core, image.buffer, image.stride) + + def reset(self): + self._core.reset(self._core) + self._wasReset = True + + @needsReset + def runFrame(self): + self._core.runFrame(self._core) + + @needsReset + def runLoop(self): + self._core.runLoop(self._core) + + @needsReset + def step(self): + self._core.step(self._core) + + @staticmethod + def _keysToInt(*args, **kwargs): + keys = 0 + if 'raw' in kwargs: + keys = kwargs['raw'] + for key in args: + keys |= 1 << key + return keys + + def setKeys(self, *args, **kwargs): + self._core.setKeys(self._core, self._keysToInt(*args, **kwargs)) + + def addKeys(self, *args, **kwargs): + self._core.addKeys(self._core, self._keysToInt(*args, **kwargs)) + + def clearKeys(self, *args, **kwargs): + self._core.clearKeys(self._core, self._keysToInt(*args, **kwargs)) + + @needsReset + def frameCounter(self): + return self._core.frameCounter(self._core) + + def frameCycles(self): + return self._core.frameCycles(self._core) + + def frequency(self): + return self._core.frequency(self._core) + + def getGameTitle(self): + title = ffi.new("char[16]") + self._core.getGameTitle(self._core, title) + return ffi.string(title, 16).decode("ascii") + + def getGameCode(self): + code = ffi.new("char[12]") + self._core.getGameCode(self._core, code) + return ffi.string(code, 12).decode("ascii") + +if hasattr(lib, 'PLATFORM_GBA'): + from .gba import GBA + Core.PLATFORM_GBA = lib.PLATFORM_GBA + +if hasattr(lib, 'PLATFORM_GB'): + from .gb import GB + from .lr35902 import LR35902Core + Core.PLATFORM_GB = lib.PLATFORM_GB diff --git a/src/platform/python/mgba/gb.py b/src/platform/python/mgba/gb.py new file mode 100644 index 000000000..0caf7ec99 --- /dev/null +++ b/src/platform/python/mgba/gb.py @@ -0,0 +1,81 @@ +# Copyright (c) 2013-2016 Jeffrey Pfau +# +# 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 .lr35902 import LR35902Core +from .core import Core, needsReset +from .memory import Memory +from .tile import Sprite + +class GB(Core): + KEY_A = lib.GBA_KEY_A + KEY_B = lib.GBA_KEY_B + KEY_SELECT = lib.GBA_KEY_SELECT + KEY_START = lib.GBA_KEY_START + KEY_DOWN = lib.GBA_KEY_DOWN + KEY_UP = lib.GBA_KEY_UP + KEY_LEFT = lib.GBA_KEY_LEFT + KEY_RIGHT = lib.GBA_KEY_RIGHT + + def __init__(self, native): + super(GB, self).__init__(native) + self._native = ffi.cast("struct GB*", native.board) + self.sprites = GBObjs(self) + self.cpu = LR35902Core(self._core.cpu) + + @needsReset + def _initTileCache(self, cache): + lib.GBVideoTileCacheInit(cache) + lib.GBVideoTileCacheAssociate(cache, ffi.addressof(self._native.video)) + + def _deinitTileCache(self, cache): + self._native.video.renderer.cache = ffi.NULL + lib.mTileCacheDeinit(cache) + +class GBMemory(Memory): + def __init__(self, core): + super(GBMemory, self).__init__(core, 0x10000) + + self.cart = Memory(core, lib.GB_SIZE_CART_BANK0 * 2, lib.GB_BASE_CART_BANK0) + 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.hram = Memory(core, lib.GB_SIZE_HRAM, lib.GB_BASE_HRAM) + +class GBSprite(Sprite): + PALETTE_BASE = 8, + + def __init__(self, obj, core): + self.x = obj.x + self.y = obj.y + self.tile = obj.tile + self._attr = obj.attr + self.width = 8 + lcdc = core._native.memory.io[0x40] + 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 + else: + self.paletteId = (self._attr >> 4) & 1 + + +class GBObjs: + def __init__(self, core): + self._core = core + self._obj = core._native.video.oam.obj + + def __len__(self): + return 40 + + def __getitem__(self, index): + if index >= len(self): + raise IndexError() + sprite = GBSprite(self._obj[index], self._core) + sprite.constitute(self._core.tiles, 0, 0) + return sprite diff --git a/src/platform/python/mgba/gba.py b/src/platform/python/mgba/gba.py new file mode 100644 index 000000000..0b8e12fee --- /dev/null +++ b/src/platform/python/mgba/gba.py @@ -0,0 +1,96 @@ +# Copyright (c) 2013-2016 Jeffrey Pfau +# +# 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 .arm import ARMCore +from .core import Core, needsReset +from .tile import Sprite +from .memory import Memory + +class GBA(Core): + KEY_A = lib.GBA_KEY_A + KEY_B = lib.GBA_KEY_B + KEY_SELECT = lib.GBA_KEY_SELECT + KEY_START = lib.GBA_KEY_START + KEY_DOWN = lib.GBA_KEY_DOWN + KEY_UP = lib.GBA_KEY_UP + KEY_LEFT = lib.GBA_KEY_LEFT + KEY_RIGHT = lib.GBA_KEY_RIGHT + KEY_L = lib.GBA_KEY_L + KEY_R = lib.GBA_KEY_R + + def __init__(self, native): + super(GBA, self).__init__(native) + self._native = ffi.cast("struct GBA*", native.board) + self.sprites = GBAObjs(self) + self.cpu = ARMCore(self._core.cpu) + + @needsReset + def _initTileCache(self, cache): + lib.GBAVideoTileCacheInit(cache) + lib.GBAVideoTileCacheAssociate(cache, ffi.addressof(self._native.video)) + + def _deinitTileCache(self, cache): + self._native.video.renderer.cache = ffi.NULL + lib.mTileCacheDeinit(cache) + + def reset(self): + super(GBA, self).reset() + self.memory = GBAMemory(self._core, self._native.memory.romSize) + +class GBAMemory(Memory): + def __init__(self, core, romSize=lib.SIZE_CART0): + super(GBAMemory, self).__init__(core, 0x100000000) + + 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.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) + self.cart0 = Memory(core, romSize, lib.BASE_CART0) + self.cart1 = Memory(core, romSize, lib.BASE_CART1) + 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 + PALETTE_BASE = 0x10, 1 + + 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._shape = self._a >> 14 + self._size = self._b >> 14 + self._256Color = 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 + self.tile >>= 1 + else: + self.paletteId = self._c >> 12 + +class GBAObjs: + def __init__(self, core): + self._core = core + self._obj = core._native.video.oam.obj + + def __len__(self): + return 128 + + def __getitem__(self, index): + if index >= len(self): + raise IndexError() + sprite = GBASprite(self._obj[index]) + map1D = bool(self._core._native.memory.io[0] & 0x40) + sprite.constitute(self._core.tiles, 0 if map1D else 0x20, int(sprite._256Color)) + return sprite diff --git a/src/platform/python/mgba/image.py b/src/platform/python/mgba/image.py new file mode 100644 index 000000000..f76945b32 --- /dev/null +++ b/src/platform/python/mgba/image.py @@ -0,0 +1,69 @@ +# Copyright (c) 2013-2016 Jeffrey Pfau +# +# 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 png + +class Image: + def __init__(self, width, height, stride=0): + self.width = width + self.height = height + self.stride = stride + self.constitute() + + def constitute(self): + 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) + success = p.writeHeader(self) + success = success and p.writePixels(self) + p.writeClose() + return success + +def u16ToU32(c): + r = c & 0x1F + g = (c >> 5) & 0x1F + b = (c >> 10) & 0x1F + a = (c >> 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 + abgr = r + abgr |= g << 5 + abgr |= b << 10 + abgr |= a << 15 + return abgr + +if ffi.sizeof("color_t") == 2: + def colorToU16(c): + return c + + colorToU32 = u16ToU32 + + def u16ToColor(c): + return c + + u32ToColor = u32ToU16 +else: + def colorToU32(c): + return c + + colorToU16 = u32ToU16 + + def u32ToColor(c): + return c + + u16ToColor = u16ToU32 diff --git a/src/platform/python/mgba/log.py b/src/platform/python/mgba/log.py new file mode 100644 index 000000000..a3412b186 --- /dev/null +++ b/src/platform/python/mgba/log.py @@ -0,0 +1,38 @@ +# Copyright (c) 2013-2016 Jeffrey Pfau +# +# 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 + +@ffi.def_extern() +def _pyLog(logger, category, level, message): + l = ffi.cast("struct mLoggerPy*", logger) + ffi.from_handle(l.pyobj).log(category, level, ffi.string(message).decode('UTF-8')) + +def installDefault(logger): + lib.mLogSetDefaultLogger(logger._native) + +class Logger(object): + FATAL = lib.mLOG_FATAL + DEBUG = lib.mLOG_DEBUG + INFO = lib.mLOG_INFO + WARN = lib.mLOG_WARN + ERROR = lib.mLOG_ERROR + STUB = lib.mLOG_STUB + GAME_ERROR = lib.mLOG_GAME_ERROR + + def __init__(self): + self._handle = ffi.new_handle(self) + self._native = ffi.gc(lib.mLoggerPythonCreate(self._handle), lib.free) + + @staticmethod + def categoryName(category): + return ffi.string(lib.mLogCategoryName(category)).decode('UTF-8') + + def log(self, category, level, message): + print("{}: {}".format(self.categoryName(category), message)) + +class NullLogger(Logger): + def log(self, category, level, message): + pass diff --git a/src/platform/python/mgba/lr35902.py b/src/platform/python/mgba/lr35902.py new file mode 100644 index 000000000..77400c0b0 --- /dev/null +++ b/src/platform/python/mgba/lr35902.py @@ -0,0 +1,123 @@ +# Copyright (c) 2013-2016 Jeffrey Pfau +# +# 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 + +class LR35902Core: + def __init__(self, native): + self._native = ffi.cast("struct LR35902Core*", native) + + @property + def a(self): + return self._native.a + + @property + def b(self): + return self._native.b + + @property + def c(self): + return self._native.c + + @property + def d(self): + return self._native.d + + @property + def e(self): + return self._native.e + + @property + def f(self): + return self._native.f + + @property + def h(self): + return self._native.h + + @property + def l(self): + return self._native.l + + @property + def sp(self): + return self._native.sp + + @property + def pc(self): + return self._native.pc + + @property + def af(self): + return (self.a << 8) | self.f + + @property + def bc(self): + return (self.b << 8) | self.c + + @property + def de(self): + return (self.d << 8) | self.e + + @property + def hl(self): + return (self.h << 8) | self.l + + @a.setter + def a(self, value): + self._native.a = value + + @b.setter + def b(self, value): + self._native.b = value + + @c.setter + def c(self, value): + self._native.c = value + + @d.setter + def d(self, value): + self._native.d = value + + @e.setter + def e(self, value): + self._native.e = value + + @f.setter + def f(self, value): + self._native.f.packed = value + self._native.f.unused = 0 + + @h.setter + def h(self, value): + self._native.h = value + + @l.setter + def l(self, value): + self._native.l = value + + @sp.setter + def sp(self, value): + self._native.sp = value + + @af.setter + def af(self, value): + self.a = value >> 8 + self.f = value & 0xFF + + @bc.setter + def bc(self, value): + self.b = value >> 8 + self.c = value & 0xFF + + @de.setter + def de(self, value): + self.d = value >> 8 + self.e = value & 0xFF + + @hl.setter + def hl(self, value): + self.h = value >> 8 + self.l = value & 0xFF diff --git a/src/platform/python/mgba/memory.py b/src/platform/python/mgba/memory.py new file mode 100644 index 000000000..a7f52e22e --- /dev/null +++ b/src/platform/python/mgba/memory.py @@ -0,0 +1,83 @@ +# Copyright (c) 2013-2016 Jeffrey Pfau +# +# 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 + +class MemoryView(object): + def __init__(self, core, width, size, base=0, sign="u"): + 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 + if sign == "u" or sign == "unsigned": + self._type = "uint{}_t".format(width * 8) + elif sign == "i" or sign == "s" or sign == "signed": + self._type = "int{}_t".format(width * 8) + else: + raise ValueError("Invalid sign type: '{}'".format(sign)) + + def _addrCheck(self, address): + if isinstance(address, slice): + start = address.start or 0 + stop = self._size - self._width if address.stop is None else address.stop + else: + start = address + stop = address + self._width + if start >= self._size or stop > self._size: + raise IndexError() + if start < 0 or stop < 0: + raise IndexError() + + def __len__(self): + return self._size + + def __getitem__(self, address): + self._addrCheck(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))) + + def __setitem__(self, address, value): + self._addrCheck(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) + else: + self._busWrite(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 rawWrite(self, address, value, segment=-1): + self._addrCheck(address) + self._rawWrite(self._core, self._base + address, segment, value & self._mask) + +class Memory(object): + def __init__(self, core, size, base=0): + self.size = size + self.base = base + + self.u8 = MemoryView(core, 1, size, base, "u") + self.u16 = MemoryView(core, 2, size, base, "u") + self.u32 = MemoryView(core, 4, size, base, "u") + self.s8 = MemoryView(core, 1, size, base, "s") + self.s16 = MemoryView(core, 2, size, base, "s") + self.s32 = MemoryView(core, 4, size, base, "s") + + def __len__(self): + return self._size diff --git a/src/platform/python/mgba/png.py b/src/platform/python/mgba/png.py new file mode 100644 index 000000000..bc1ff836c --- /dev/null +++ b/src/platform/python/mgba/png.py @@ -0,0 +1,24 @@ +# Copyright (c) 2013-2016 Jeffrey Pfau +# +# 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 vfs + +class PNG: + def __init__(self, f): + self.vf = vfs.open(f) + + def writeHeader(self, image): + self._png = lib.PNGWriteOpen(self.vf.handle) + self._info = lib.PNGWriteHeader(self._png, image.width, image.height) + return self._info != ffi.NULL + + def writePixels(self, image): + return lib.PNGWritePixels(self._png, image.width, image.height, image.stride, image.buffer) + + def writeClose(self): + lib.PNGWriteClose(self._png, self._info) + del self._png + del self._info diff --git a/src/platform/python/mgba/tile.py b/src/platform/python/mgba/tile.py new file mode 100644 index 000000000..4d89f8869 --- /dev/null +++ b/src/platform/python/mgba/tile.py @@ -0,0 +1,57 @@ +# Copyright (c) 2013-2016 Jeffrey Pfau +# +# 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 image + +class Tile: + def __init__(self, data): + self.buffer = data + + def toImage(self): + i = image.Image(8, 8) + self.composite(i, 0, 0) + return i + + def composite(self, i, x, y): + for iy in range(8): + for ix in range(8): + i.buffer[ix + x + (iy + y) * i.stride] = image.u16ToColor(self.buffer[ix + iy * 8]) + +class TileView: + def __init__(self, core): + self.core = core + self.cache = ffi.gc(ffi.new("struct mTileCache*"), core._deinitTileCache) + core._initTileCache(self.cache) + lib.mTileCacheSetPalette(self.cache, 0) + self.paletteSet = 0 + + def getTile(self, tile, palette): + return Tile(lib.mTileCacheGetTile(self.cache, tile, palette)) + + def setPalette(self, paletteSet): + if paletteSet > 1 or paletteSet < 0: + raise IndexError("Palette Set ID out of bounds") + lib.mTileCacheSetPalette(self.cache, paletteSet) + self.paletteSet = paletteSet + +class Sprite(object): + TILE_BASE = 0, 0 + PALETTE_BASE = 0, 0 + + def constitute(self, tileView, tilePitch, paletteSet): + oldPaletteSet = tileView.paletteSet + tileView.setPalette(paletteSet) + i = image.Image(self.width, self.height) + tileId = self.tile + self.TILE_BASE[paletteSet] + for y in range(self.height // 8): + for x in range(self.width // 8): + tile = tileView.getTile(tileId, self.paletteId + self.PALETTE_BASE[paletteSet]) + tile.composite(i, x * 8, y * 8) + tileId += 1 + if tilePitch: + tileId += tilePitch - self.width // 8 + self.image = i + tileView.setPalette(oldPaletteSet) diff --git a/src/platform/python/mgba/vfs.py b/src/platform/python/mgba/vfs.py new file mode 100644 index 000000000..158c07639 --- /dev/null +++ b/src/platform/python/mgba/vfs.py @@ -0,0 +1,130 @@ +# Copyright (c) 2013-2016 Jeffrey Pfau +# +# 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 +import os + +@ffi.def_extern() +def _vfpClose(vf): + vfp = ffi.cast("struct VFilePy*", vf) + ffi.from_handle(vfp.fileobj).close() + +@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() + +@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 + +@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 + +@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) + 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 + +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 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 + + return VFile(lib.VFileOpen(path.encode("UTF-8"), flags)) + +class VFile: + def __init__(self, vf): + self.handle = vf + + def close(self): + return self.handle.close(self.handle) + + 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 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 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 truncate(self, size): + self.handle.truncate(self.handle, size) + + def size(self): + return self.handle.size(self.handle) + + def sync(self, buffer, size): + return self.handle.sync(self.handle, buffer, size) diff --git a/src/platform/python/setup.py.in b/src/platform/python/setup.py.in new file mode 100644 index 000000000..31ce6cd52 --- /dev/null +++ b/src/platform/python/setup.py.in @@ -0,0 +1,24 @@ +from setuptools import setup +import re + +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" +] + +setup(name="${BINARY_NAME}", + version=re.sub("/", "-", "${VERSION_STRING}"), + author="Jeffrey Pfau", + author_email="jeffrey@endrift.com", + url="http://github.com/mgba-emu/mgba/", + packages=["mgba"], + setup_requires=['cffi>=1.6'], + install_requires=['cffi>=1.6', 'cached-property'], + cffi_modules=["_builder.py:ffi"], + license="MPL 2.0", + classifiers=classifiers + ) diff --git a/src/platform/python/vfs-py.c b/src/platform/python/vfs-py.c new file mode 100644 index 000000000..51b1f182b --- /dev/null +++ b/src/platform/python/vfs-py.c @@ -0,0 +1,46 @@ +/* Copyright (c) 2013-2016 Jeffrey Pfau + * + * 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/. */ +#include "util/vfs.h" + +struct VFilePy { + struct VFile d; + void* fileobj; +}; + +bool _vfpClose(struct VFile* vf); +off_t _vfpSeek(struct VFile* vf, off_t offset, int whence); +ssize_t _vfpRead(struct VFile* vf, void* buffer, size_t size); +ssize_t _vfpWrite(struct VFile* vf, const void* buffer, size_t size); +void* _vfpMap(struct VFile* vf, size_t size, int flags); +void _vfpUnmap(struct VFile* vf, void* memory, size_t size); +void _vfpTruncate(struct VFile* vf, size_t size); +ssize_t _vfpSize(struct VFile* vf); +bool _vfpSync(struct VFile* vf, const void* buffer, size_t size); + +struct VFile* VFileFromPython(void* fileobj) { + if (!fileobj) { + return 0; + } + + struct VFilePy* vfp = malloc(sizeof(struct VFilePy)); + if (!vfp) { + return 0; + } + + vfp->fileobj = fileobj; + vfp->d.close = _vfpClose; + vfp->d.seek = _vfpSeek; + vfp->d.read = _vfpRead; + vfp->d.readline = VFileReadline; + vfp->d.write = _vfpWrite; + vfp->d.map = _vfpMap; + vfp->d.unmap = _vfpUnmap; + vfp->d.truncate = _vfpTruncate; + vfp->d.size = _vfpSize; + vfp->d.sync = _vfpSync; + + return &vfp->d; +} diff --git a/src/platform/python/vfs-py.h b/src/platform/python/vfs-py.h new file mode 100644 index 000000000..82a5ac0cf --- /dev/null +++ b/src/platform/python/vfs-py.h @@ -0,0 +1,28 @@ +/* Copyright (c) 2013-2016 Jeffrey Pfau + * + * 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/. */ + +#include "util/vfs.h" + +struct VFilePy { + struct VFile d; + void* fileobj; +}; + +struct VFile* VFileFromPython(void* fileobj); + +extern "Python+C" { + +bool _vfpClose(struct VFile* vf); +off_t _vfpSeek(struct VFile* vf, off_t offset, int whence); +ssize_t _vfpRead(struct VFile* vf, void* buffer, size_t size); +ssize_t _vfpWrite(struct VFile* vf, const void* buffer, size_t size); +void* _vfpMap(struct VFile* vf, size_t size, int flags); +void _vfpUnmap(struct VFile* vf, void* memory, size_t size); +void _vfpTruncate(struct VFile* vf, size_t size); +ssize_t _vfpSize(struct VFile* vf); +bool _vfpSync(struct VFile* vf, const void* buffer, size_t size); + +} \ No newline at end of file diff --git a/src/platform/sdl/gles2-sdl.c b/src/platform/sdl/gles2-sdl.c index c45821ddf..7d55362c9 100644 --- a/src/platform/sdl/gles2-sdl.c +++ b/src/platform/sdl/gles2-sdl.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2013-2015 Jeffrey Pfau +/* Copyright (c) 2013-2016 Jeffrey Pfau * * 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 @@ -7,16 +7,20 @@ #include "gl-common.h" +#include "core/thread.h" + +#ifndef __APPLE__ #include +#endif static bool mSDLGLES2Init(struct mSDLRenderer* renderer); -static void mSDLGLES2RunloopGBA(struct mSDLRenderer* renderer, void* user); +static void mSDLGLES2Runloop(struct mSDLRenderer* renderer, void* user); static void mSDLGLES2Deinit(struct mSDLRenderer* renderer); void mSDLGLES2Create(struct mSDLRenderer* renderer) { renderer->init = mSDLGLES2Init; renderer->deinit = mSDLGLES2Deinit; - renderer->runloop = mSDLGLES2RunloopGBA; + renderer->runloop = mSDLGLES2Runloop; } bool mSDLGLES2Init(struct mSDLRenderer* renderer) { @@ -93,8 +97,13 @@ bool mSDLGLES2Init(struct mSDLRenderer* renderer) { mSDLGLCommonInit(renderer); #endif - renderer->d.outputBuffer = memalign(16, VIDEO_HORIZONTAL_PIXELS * VIDEO_VERTICAL_PIXELS * 4); - renderer->d.outputBufferStride = VIDEO_HORIZONTAL_PIXELS; +#ifndef __APPLE__ + renderer->outputBuffer = memalign(16, renderer->width * renderer->height * BYTES_PER_PIXEL); +#else + posix_memalign((void**) &renderer->outputBuffer, 16, renderer->width * renderer->height * BYTES_PER_PIXEL); +#endif + memset(renderer->outputBuffer, 0, renderer->width * renderer->height * BYTES_PER_PIXEL); + renderer->core->setVideoBuffer(renderer->core, renderer->outputBuffer, renderer->width); mGLES2ContextCreate(&renderer->gl2); renderer->gl2.d.user = renderer; @@ -107,17 +116,17 @@ bool mSDLGLES2Init(struct mSDLRenderer* renderer) { } void mSDLGLES2Runloop(struct mSDLRenderer* renderer, void* user) { - struct GBAThread* context = user; + struct mCoreThread* context = user; SDL_Event event; struct VideoBackend* v = &renderer->gl2.d; while (context->state < THREAD_EXITING) { while (SDL_PollEvent(&event)) { - mSDLHandleEventGBA(context, &renderer->player, &event); + mSDLHandleEvent(context, &renderer->player, &event); } if (mCoreSyncWaitFrameStart(&context->sync)) { - v->postFrame(v, renderer->d.outputBuffer); + v->postFrame(v, renderer->outputBuffer); } mCoreSyncWaitFrameEnd(&context->sync); v->drawFrame(v); @@ -142,5 +151,5 @@ void mSDLGLES2Deinit(struct mSDLRenderer* renderer) { #elif SDL_VERSION_ATLEAST(2, 0, 0) SDL_GL_DeleteContext(renderer->glCtx); #endif - free(renderer->d.outputBuffer); + free(renderer->outputBuffer); } diff --git a/src/third-party/blip_buf/blip_buf.h b/src/third-party/blip_buf/blip_buf.h index 52703643c..22b0a3ec8 100644 --- a/src/third-party/blip_buf/blip_buf.h +++ b/src/third-party/blip_buf/blip_buf.h @@ -24,7 +24,7 @@ void blip_set_rates( blip_t*, double clock_rate, double sample_rate ); enum { /** Maximum clock_rate/sample_rate ratio. For a given sample_rate, clock_rate must not be greater than sample_rate*blip_max_ratio. */ -blip_max_ratio = 1 << 20 }; +blip_max_ratio = 0x100000 }; /** Clears entire buffer. Afterwards, blip_samples_avail() == 0. */ void blip_clear( blip_t* ); diff --git a/src/util/table.c b/src/util/table.c index ebc756c29..3f4deb013 100644 --- a/src/util/table.c +++ b/src/util/table.c @@ -163,6 +163,14 @@ size_t TableSize(const struct Table* table) { return table->size; } +void HashTableInit(struct Table* table, size_t initialSize, void (deinitializer(void*))) { + TableInit(table, initialSize, deinitializer); +} + +void HashTableDeinit(struct Table* table) { + TableDeinit(table); +} + void* HashTableLookup(const struct Table* table, const char* key) { uint32_t hash = hash32(key, strlen(key), 0); const struct TableList* list; diff --git a/src/util/table.h b/src/util/table.h index 1305b995e..6e3979476 100644 --- a/src/util/table.h +++ b/src/util/table.h @@ -31,13 +31,8 @@ void TableClear(struct Table*); void TableEnumerate(const struct Table*, void (handler(uint32_t key, void* value, void* user)), void* user); size_t TableSize(const struct Table*); -static inline void HashTableInit(struct Table* table, size_t initialSize, void (deinitializer(void*))) { - TableInit(table, initialSize, deinitializer); -} - -static inline void HashTableDeinit(struct Table* table) { - TableDeinit(table); -} +void HashTableInit(struct Table* table, size_t initialSize, void (deinitializer(void*))); +void HashTableDeinit(struct Table* table); void* HashTableLookup(const struct Table*, const char* key); void HashTableInsert(struct Table*, const char* key, void* value); diff --git a/src/util/vfs.c b/src/util/vfs.c index 5f6533762..27932c717 100644 --- a/src/util/vfs.c +++ b/src/util/vfs.c @@ -104,7 +104,7 @@ struct VDir* VDirOpenArchive(const char* path) { dir = VDirOpenZip(path, 0); } #endif -#if USE_LZMA +#ifdef USE_LZMA if (!dir) { dir = VDirOpen7z(path, 0); } diff --git a/src/util/vfs.h b/src/util/vfs.h index d1ac0e69f..f05b7fc8a 100644 --- a/src/util/vfs.h +++ b/src/util/vfs.h @@ -67,12 +67,11 @@ struct VDir { struct VFile* VFileOpen(const char* path, int flags); struct VFile* VFileOpenFD(const char* path, int flags); -struct VFile* VFileFOpen(const char* path, const char* mode); struct VFile* VFileFromFD(int fd); + struct VFile* VFileFromMemory(void* mem, size_t size); struct VFile* VFileFromConstMemory(const void* mem, size_t size); struct VFile* VFileMemChunk(const void* mem, size_t size); -struct VFile* VFileFromFILE(FILE* file); struct VDir* VDirOpen(const char* path); struct VDir* VDirOpenArchive(const char* path); @@ -85,7 +84,11 @@ struct VDir* VDirOpenZip(const char* path, int flags); struct VDir* VDirOpen7z(const char* path, int flags); #endif +#if defined(WII) || defined(_3DS) +struct VFile* VFileFOpen(const char* path, const char* mode); +struct VFile* VFileFromFILE(FILE* file); struct VDir* VDeviceList(void); +#endif void separatePath(const char* path, char* dirname, char* basename, char* extension);